blog.anqou.net
rss
author
tags

ppx_rapper を改造しファイルから SQL を読む

ppx_rapper は OCaml で SQL を扱うときの便利ライブラリで、SQL を書くと PPX によってそれを Caqti を使って RDB にアクセスする OCaml コードに変換してくれます。

このライブラリは便利なのですが、以下のように SQL を文字列リテラルとして直書きすることしか想定しておらず、長大な SQL を扱いたいときには不便です:

let my_query =
  [%rapper
    get_opt
      {sql|
      SELECT @int{id}, @string{username}, @bool{following}, @string?{bio}
      FROM users
      WHERE username <> %string{wrong_user} AND id > %int{min_id}
      |sql}]

別ファイルに SQL のみを記述し、それを埋め込めるといいなと思い調べていたのですが、似たようなことができそうな janestreet/ppx_embed_file などを使っても PPX の式の中に PPX の式を書くことになりうまく動きませんでした。また cppo を使ってプリプロセスで対応できないかと思いましたが、これも cppo 側のサポートが足らず無理でした。

しばらく困っていたのですが、ふと ppx_rapper を改造してファイル読み込みできるようにすれば良いということに気づいたので、 GLM Coding Plan で使い放題(使い放題ではない)になっている GLM-5.2 をしばいて作ってもらいました:

https://github.com/ushitora-anqou/ppx_rapper/commit/bc7ca705ee74759a225027bb24ee500f82a1277e

使う場合は例によって waq-external-repo が便利です。ppx_rapper.3.1.0+waq.4 以降で使えます。

以下のように [%ppx_rapper_file ...] で SQL ファイル名を指定します:

let my_query =
  [%rapper_file
    get_opt
      "queries/users.sql"]

このとき、SQL ファイルが Dune に補足されている必要があるのに注意して下さい。 GLM-5.2 が書いた README には deps を使うと良いとされていますが、 PPX でしか使わないので、多分以下のように preprocessor_deps を使うほうが良いでしょう:

(library (
  # ...
  (preprocessor_deps (file queries/users.sql))
  (preprocess (pps (ppx_rapper)))))