Bisql
2-way-SQL のデータアクセスライブラリを Clojure で作っています。
2-way なので bi- で bisql 。 読み方は自転車と同じ「バイスクル」
2-way-SQL に限らず SQL テンプレート系ライブラリの弱点は、全ての SQL を SQL テンプレートとして書くとなると手間が大変というものだと思います。 この解決ために多くの SQL テンプレート系ライブラリではクエリビルダをサポートしています。 テンプレートでも書けるし、クエリビルダでも書ける。 しかし、これは中途半端です。 すべてのクエリ関数を SQL (SQL テンプレート)として維持すると、レビューや把握のコストが大幅に下がります。 すべてのデータベースアクセスが SQL ファイルとしての実体をもつわけなので。 それなのに、クエリビルダが混ざるとそうではなくなってしまいます。 また、単純なクエリだけをクエリビルダで書くつもりがいつの間にか複雑なクエリまでクエリビルダで構築するようになり、発行される SQL が意図したものではなくなっていることも多々あります。
bisql はこの解決のために、全てのデータベースアクセスは SQL を書かなけれいけないというルールです。
しかし、単純な CRUD 操作まで全テーブルについて手書きするのは億劫だと思うので、典型的な CRUD クエリは網羅的に膨大に自動生成する、と言うアプローチを取ります。
実DBを参照し、インデックスを考慮しインデックスを効かせられる様々なパターンのSQLをテンプレートとして作成します。
(defquery) マクロはこれら SQLテンプレートの .sql ファイルを一括で全て Clojure 関数に変換します。
面倒はありません。
Malli サポート
今回、自動生成する SQL テンプレートに :malli/in と :malli/out という metadata が埋め込まれるようにしました。
ここにはクエリ関数にわたすパラメータとレスポンスデータのスキーマがそれぞれ自動で埋め込まれます(Bisql のSQL テンプレートは生成されるクエリ関数に反映される任意の metadata を設定可能)。
クエリ関数の metadata に :malli/in や :malli/out が入っていると自動で malli で validation を実行します(切り替えは可能)。
また、各テーブルでベースとなる malli スキーマが schema.clj として生成されます。
:malli/in と :malli/out ではそれらを参照するかたちで スキーマを定義しています。
生成されるクエリの例:
/*:name crud.get-by-id */ /*:cardinality :one */ /*:malli/in [:map {:closed true} [:id int?]] */ /*:malli/out [:maybe sql.postgresql.public.users.schema/row] */ SELECT * FROM users WHERE id = /*$id*/1
生成される schema の例:
(ns sql.postgresql.public.users.schema (:refer-clojure :exclude [update]) (:require [bisql.schema :as bisql.schema])) #_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]} (def insert [:map {:closed true} [:id [:or int? bisql.schema/malli-default-sentinel]] [:email string?] [:display-name string?] [:status [:or string? bisql.schema/malli-default-sentinel]] [:created-at [:or [:fn bisql.schema/offset-date-time?] bisql.schema/malli-default-sentinel]]]) #_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]} (def update (bisql.schema/malli-map-all-entries-optional insert)) #_{:clojure-lsp/ignore [:clojure-lsp/unused-public-var]} (def row (bisql.schema/malli-map-all-entries-strip-default-sentinel insert))
つまり、典型的な CRUD クエリは SQL テンプレートとそのスキーマが自動で生成され、自動で透過的に validation を実行できるようになりました。