• 作成:

serversessionのアップデートで新しいpersistentに対応した時の格闘記録

前提

これをやった日は3時間ぐらいしか寝てなくて朦朧としてました。なので相当グダグダやっていて、普段ならすぐに解決できるタイプの問題の解決にも手間取っていました。

やる作業

yesodweb/serversession: Secure, modular server-side sessions. を新しいLTSに対応させる。これも昔私が対応させたのですが、今はLTS 15になっています。

persistentのライブラリ的には2.10.5.2から2.13.3.5へのアップデート。多分。

何故必要か

我々がserversessionを使っていて、これのせいで新しいLTSにアップデート出来ないため。そろそろなんとかしないとHLSがGHCのサポート切ってくるから早急になんとかしたい。

デフォルトのファイルベースのセッションに戻るとかの選択も良い気はして来ましたが、アプリケーション側で柔軟にセッションを切り替えようとすると難しいんですよね。

前ちょっとやったけど難しかった

2月あたりにもやろうかと思ったのですが、あまりにも書き換えるポイントが多すぎるのと、 persistentの内部構造のアップデートに追随するのが難しくて後回しにしてしまいました。

前の奮闘内容はstashに置いておきましょう。文章でどうなってるか書いてないので何を目的にした差分か分からなくなっている。

前回の撤退で割と気合のいる作業だと分かっているので、今回は文章を書いて自分が何をしているのか把握できるようにしています。

対象のLTS

最初は18あたりにしようかと思っていましたが、まあ今の最新のLTSは19.5なのでそちらの方が寿命が長いでしょう。

LTS 19.5にする時にパッケージを切り捨てたい

serversession-backend-acid-stateserversession-frontend-snapは大本のパッケージが更新されてないと思うので、対応させるのは困難なのでひとまず切り捨てることを検討します。

後でどうにか出来るならどうにかするかもしれませんが、とりあえずは考える対象を少なくしたい。

と思ってなんの気なしに削除してみましたが、 18の昔と違って19の今はStackageにも登録されているらしい?

それならやっても良いかもしれません。

やってみましょう。

heistがビルドできない

heist-1.1.0.1@sha256:121288965f6c77b0d06a09c5d8a3b80f9a083830d06857555e99f10868b18dcb,9311 がビルドできません。 aeson-2に非対応だからですね。

これsnapのパッケージ郡の一部かあ、一応対処のPRは開いてるみたいなんですけど、 Bump aeson by soiamsoNG · Pull Request #132 · snapframework/heist 現状これに依存するわけにもいかないのでsnapは放棄確定ですね。

依存ライブラリのバージョンを揃える

グローバルでallow-newer: trueしてて気が付かなかったけど、ライブラリのバージョン制約が厳しいので新しいLTSに揃える。

色々と非互換性があるのかもしれないが、 LTSに載せるのにはどうせライブラリのLTS向けの依存が必要なので仕方がない。

非互換性は後で修正していく。

NoStarIsTypeがデフォルトになったのに対応する

NoStarIsType 言語拡張が必要になるときを参考に、 NoStarIsTypeがデフォルトになったGHCに合わせる。

persistentがHaskellNameDBNameの命名を変えたりFieldDefのシグネチャを変えたのに対応

単純にpersistentが求める内容を書くしか無かった。

mkMigrateの型変更に対応

mkMigrateの型が変更されたのに追随する。

前のpersistentに関数が無いことに困惑したけど、 persistent-template: Type-safe, non-relational, multi-backend persistence. が新しいpersistentに吸収されたことが分かって納得。これまではpersistent-templateを呼び出していたことが分かりました。

mkMigrate :: String -> [EntityDef] -> Q [Dec]

が、

mkMigrate :: String -> [UnboundEntityDef] -> Q [Dec]

に変更されている。

となると、 EntityDefUnboundEntityDefを変換できればなんとかなりそうではあります。

2.13.0.0から公開されたと書いてある。 2月に見たときはInternalで苦しんだ記憶があるので、これはちゃんとやるチャンスでは。

Database.Persist.EntityDefも前はInternalを使ってたけど今は公開されていると思ったけど、しかしコンストラクタを使わないといけないので結局Internalのimportは必要。それは余裕のあるうちになんとかすることを考えよう。

unbindEntityDefを使えばとりあえず変更出来そうなので、とりあえずInternalということを気にせずに実装。

そうすると以下のようなエラー。

serversession-backend-persistent      > /home/ncaq/Desktop/serversession/serversession-backend-persistent/tests/Main.hs:18:1: error:
serversession-backend-persistent      >      Couldn't match kind ‘* -> *’ with ‘*’
serversession-backend-persistent      >       When matching types
serversession-backend-persistent      >         proxy0 :: * -> *
serversession-backend-persistent      >         Proxy :: (* -> *) -> *
serversession-backend-persistent      >       Expected: proxy0 record0
serversession-backend-persistent      >         Actual: Proxy PersistentSession
serversession-backend-persistent      >     • In the first argument of ‘P.entityDef’, namely
serversession-backend-persistent      >         ‘(Proxy :: Proxy PersistentSession)’
serversession-backend-persistent      >       In the expression: P.entityDef (Proxy :: Proxy PersistentSession)
serversession-backend-persistent      >       In the expression: [P.entityDef (Proxy :: Proxy PersistentSession)]
serversession-backend-persistent      >    |
serversession-backend-persistent      > 18 | P.mkMigrate "migrateAll" (serverSessionDefs (Proxy :: Proxy SessionMap))
serversession-backend-persistent      >    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

StarをData.Kind.Typeに全部書き換えたから非互換になっているのでは?

一度全部Starに戻してみたけど変わらず。

どのへんでエラーになっているのか

バージョン変えても、

serverSessionDefs :: forall sess. PersistEntity (PersistentSession sess) => Proxy sess -> [P.EntityDef]
serverSessionDefs _ = [entityDef (Proxy :: Proxy (PersistentSession sess))]

EntityDefが生成出来ている。 unbindEntityDefで変換も出来ている。

mkMigrateで実行するコード内部で問題が起きているのかな? となるとやるべきことは一つで展開をするべきだけどHLSは一回コンパイルが通らないと動かないし厄介。と思ったけど、そもそもTHでエラーになる場合は展開した構文木でのエラーを出すので、 THでの構築コードでエラーになっているわけではない?

問題の切り分けをしよう。

s :: [P.UnboundEntityDef]
s = serverSessionDefs (Proxy :: Proxy SessionMap)

はエラーにならない。なので変換過程でエラーになっているわけではない。

この問題置いておくか… 置いて先行っても解決する気はしないけど。

次の問題もEntityDef関係だし。

Exampleの方のコンパイルエラーはEntityDef関係は諦めてmkSaveだけ修正した。

serverSessionDefs :: forall sess. PersistEntity (PersistentSession sess) => Proxy sess -> [P.EntityDef]
serverSessionDefs _ = [entityDef (Proxy :: Proxy (PersistentSession sess))]

これがエラーにならないとしてもProxyを直接扱ってる関数がこれしか無いから、これがエラーの元になっている可能性が非常に高い。

issueとか探ってみたけど誰もこれ関係で質問していないし、 persistentのソースコードを地道に読んでみるか… それで解決するとはあまり思えないけれど。

結局Template Haskellは展開するべきでした

TH展開して修正することでコンパイルに成功しました。

展開すると以下のようになるので、

entityDefListFormigrateAll :: [P.EntityDef]
entityDefListFormigrateAll
  = [P.entityDef (Proxy :: Proxy PersistentSession)]
migrateAll :: P.Migration
migrateAll = P.migrateModels entityDefListFormigrateAll

これを修正すると問題なくなる。

entityDefListFormigrateAll :: [P.EntityDef]
entityDefListFormigrateAll
  = [P.entityDef (Proxy :: Proxy (PersistentSession SessionMap))]
migrateAll :: P.Migration
migrateAll = P.migrateModels entityDefListFormigrateAll

手動での修正作業をせずに、元々の呼び出し方でこれが展開されるようにするべきですね。

ここまで突き止めて疲労困憊になりました。

Template Haskellが絡むデバッグはHLSが仕事拒否しても頑張って動くようにして、ちゃんと展開をするべきですね。

問題はProxyの型引数を消してしまうことにある?

persistentが互換性のないアップデートをするのに伴って、バグを作ってしまっている可能性を引きました。

とりあえず色々修正を試みてみます。

いや、これEntityDefHaskellName入れてるだけじゃないですか? entityDefentityHaskellを雑に変えてみるとそのように変わった。

なるほどそういう仕組みだったんですね…

修正方法

汎用的な所にSessionMapを入れるのを諦めてデータ型を一般的にしない

即座に思いつくタイプの解決策。 serversessionの互換性も壊れるが、仕方がないのかもしれない。

entityHaskellでProxyのデータも入れるようにする

一般的な解決策。

ですが、 Proxyというか、型引数sessのHaskell文字列を手に入れる方法が思いつかない。 entityDefはモナドの文脈ではないので。

引数のrecordからデータを取得とかも考えましたが、 Illegal type constructor or class name: ‘PersistentSession SessionMap’ というエラーの解消方法を思いつかないので、どちらにしてもだめですね。

要所で部分適用が出来れば自動で解決する以外にも文字列を引数で渡してやって埋め込むことも出来たのですが。

呼び出し側で気をつける

entityHaskellを無視しているようで非常に心苦しいのですが、呼び出し側である程度展開して設定するしか無いでしょう。

mkServerSessionDefs :: forall sess. PersistEntity sess => Proxy sess -> T.Text -> [P.UnboundEntityDef]
mkServerSessionDefs _ name =
  [P.unbindEntityDef $ (entityDef (Proxy :: Proxy sess)) { P.entityHaskell = P.EntityNameHS name }]

type PersistentSessionBySessionMap = PersistentSession SessionMap
P.mkMigrate "migrateAll" (mkServerSessionDefs (Proxy :: Proxy PersistentSessionBySessionMap) "PersistentSessionBySessionMap")

これでいけると思ったんですが何故かテストコードの方はコンパイル通るっぽいですがExampleの方は通らない、なんで?

もうTH展開したのをそのまま書かせるほうがマシかもしれません。 type aliasを使うよりはマシかも?

でもとりあえずこれを採用することにしました。

connEscapeNameが削除されてる

YesodのExampleのテストコードが動かない。

最新版では使ってないのでは? 見てみます。 stack newしても16.31が最新版なので普通に使われてますね。

せめてどこかに消した理由と代替手段を書いておいてほしい。

とりあえずこれは放置で。流石に無理。 PR先に出してどうすれば良いのか聞きます。

今日で独力で終わらせることは無理と判断しました

流石に無理っぽい。ここまでを成果として、とりあえずPRを作っておきます。

updated: LTS: 19 by ncaq · Pull Request #27 · yesodweb/serversession

無責任かもしれませんが、何も残さないよりは他の人のヒントになるかもしれません。

やり残したこと

Exampleを整備出来ませんでした。何故かtestの方では新しいmkServerSessionDefsは動くのですが、 Exampleのプロジェクトの方も同じように書き換えるとKindの型不一致エラーが出ます。

ExampleのTestImportからconnEscapeNameを取り除けませんでした。 persistentからconnEscapeNameが削除されたコミットの形跡を見つけられなかったため、どう書き換えれば良いのか手掛かりが掴めません。

方針の悩み

新しくtype aliasを作ってHaskellNameを新しく指定させる方針でセットアップするようにしましたが、 Template Haskellをある程度展開して引数を書き換えた方が良いかもしれません。