Database ecosystems in common lisp.

TLDR.

cl-yesqlを使え。

対象読者

Introduction.

結論は上に記した通り。

以下では何故上記の結論に至ったかを備忘録として記しておきます。

Issues about database ecosystems.

データベースは難しい。

何故難しいかというと、関連する問題領域が多岐に渡るからです。

ざっくりリストアップすると以下のようになります。

一つづつ見ていきましょう。

Choice issue.

データベースは複数の実装があります。 どのデータベースがあなたのアプリに適しているかはあなたが作りたいアプリケーションによります。 自分で判断してください。

Design issue.

ちゃんと設計しておかないと後々大変なことになりえます。 しっかり本を買ってお勉強して置きましょう。

Portability issue.

SQL自体は仕様が定められていますが、各データベースは独自拡張を追加したりしているものです。 筆者が探した限りでは「write once, run on any databases.」に相当する解決手段は見当たりませんでした。

開き直って独自機能にがっつり依存するのも有力な選択肢だとも思います。

Connection issue.

データベースはLispの外のものです。

Lispとデータベースを接続するライブラリは多数あります。 あなたが使いたいデータベースとの接続を確立するライブラリを探してください。

もし見つからなかったら? おめでとうございます。 第一人者になれますね:)

これら接続ライブラリの設計ポリシーには二種類あります。 「一対一設計」と「一対多設計」です。

基本的に一対多設計のライブラリはおすすめしません。 例外は、すでにデータベースに慣れ親しんでおり、あのプロジェクトではあのデータベース、そのプロジェクトではそのデータベース、と複数のデータベースに渡って開発をしているような人です。

筆者のようにデータベースにまだまだ明るくない人間は使うべきではありません。 一対多設計のライブラリは、えてして、一対一設計のライブラリの上にラッパとして作られているものです。 うまくいっている間は何も問題ないのですが、データベースに明るくない人間は、無知だからこそちょっとしたエラーを頻繁に引き起こします。 そのような場合、調べるべき場所がデータベースと一対一設計ライブラリとラッパライブラリの計三ヶ所に渡るのは素人には辛いものがあります。 充分な経験を積んだあとなら、たとえエラーが起きても、勘所がわかるようになるものでしょうが、初心者はそうはいきません。

充分データベースに慣れ親しむまでは、調べるべき箇所を減らす努力はしておいて損はないと判断します。

Lisp side syntax issue.

Lispソースコード内にSQL文をどのような形で持つかも問題です。

これは主に二種類あります。 文字列で持つか、S式で持つか、です。

この二択ならS式一択でしょう。

設計方法にもよりますが、S式が関数なりマクロなりであるなら、ちょっとしたタイポはコンパイル時にundefined-functionの形で判明します。 文字列で持つ場合、実行時にデータベースにSQL文を送って初めてシンタックスエラーと判明します。

S式なら、関数やマクロ等で動的に構築するのも文字列操作より容易です。

また、SQLインジェクション対策もS式で設計されたもののほうが、余計なことができない分、相対的に安全でしょう。

しかしながら、この結論は「文字列とS式の二択なら」という前提に基づくものです。

筆者のおすすめはcl-yesqlの使用です。

cl-yesqlはclojureのライブラリであるyesqlにインスパイアされたものです。 yesqlREADMEにも書いてあることですが、開発者がSQLに明るくない場合、やりたいことを実現するためにインターネットの検索エンジンで検索をかけ、それっぽいSQL文を探してコピペ/改変を行うものですが、そこにS式に変換するという手間が一つ増えます。

このS式への変換コストが案外馬鹿にならない。

最悪の場合、そのS式SQLジェネレータでは実現できず、文字列で持つしかないという場合も考えられます。

すでにSQLに充分明るいのであれば、余技としてS式で持つ方法を学ぶのも悪くはないと思いますが、未だSQLに明るくない身分であるなら素のSQLに近いかたちで持てるcl-yesqlがもっとも適正であると考えます。

ORM issue.

最後に、データベースから取ってきた値をLispランタイム内でどのようなデータ形式で持つか、も重要な問題です。

筆者個人は、構造体やクラスオブジェクトで持つ手段に懐疑的です。 というのもマッピングテーブルを経由して複数のテーブルからデータをつまみ食いしたいことは割と多いと考えるからです。 常にテーブルの全カラムを取得するのが必須であるなら、オブジェクトで持つのもアリかとは思います。

理想はPLISTで持つことです。(軽量なので。) (次点でハッシュテーブル。) 理由は、取ってくるべきカラムが増減したとき、ソースコードに手を入れるべき箇所が少なくて済むからです。

cl-yesqlは値をリストにくくって返す仕様で、そこは気に食わない点ですが、いざとなればフォークして改良もアリだと思っています。