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-state
とserversession-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がHaskellName
やDBName
の命名を変えたり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]
に変更されている。
となると、
EntityDef
をUnboundEntityDef
を変換できればなんとかなりそうではあります。
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が互換性のないアップデートをするのに伴って、バグを作ってしまっている可能性を引きました。
とりあえず色々修正を試みてみます。
いや、これEntityDef
のHaskellName
入れてるだけじゃないですか?
entityDef
のentityHaskell
を雑に変えてみるとそのように変わった。
なるほどそういう仕組みだったんですね…
修正方法
汎用的な所に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をある程度展開して引数を書き換えた方が良いかもしれません。