Common Lispで
どうやってデータベース(MySQL)とのやり取りをするんだろうかわからなかったので、
のソースコードから学んでいきます。
注意
Lispに関してはまだまだ初心者ですので、間違いがあればぜひご指摘ください。すごく喜びます。
読み進めると、どんどん雑になっているのが分かると思います。
補足などのリクエストもぜひお願いします。
cl-dbiとは
CL-DBI - Database independent interface for Common Lisp
つまりデータベースと簡単にやりとりできるすばらしいライブラリ。
(#追記 いろんなデータベースとやりとりできることがすごく便利でした。)
では、さっそくとりかかっていきます。
読むファイルを決める
Lispではsourceをsrcに、
テストをtにいれる(はず)なので
srcを読み込んでいきます。
すべて読むと効率が悪いので、
メインのファイルを読んでいきます。(dbi.lisp、driver.lisp、mysql.lisp)
src/
dbd/
mysql/
error.lisp
ココ→mysql.lisp
postgres.lisp
sqlite3.lisp
ココ→dbi.lisp
ココ→driver.lisp
error.lisp
test.lisp
dbi.lisp
dbi.driverからimportしてるメソッドが
prepare、execute、commit、rollebackがあるので、
driverの方ではクエリを実行したりするのかなと予想をつけます。
さらにimportしたものも、exportしているので
このファイルがメインのファイルだろうなと推測します。
メイン処理を読み始めて最初に目に入るのが、
@export
(defun connect (driver-name &rest params &key database-name &allow-other-keys)
"Open a connection to the database which corresponds to `driver-name`."
...
この@export。
LispではS式を採用しておりカッコに囲まれているため、
この@exportはいびつです。
調べてみると、これはアノテーションと呼ばれる技法であり
マクロが呼ばれる前の任意のタイミングで評価するリードマクロであることが分かります。
http://blog.8arrow.org/entry/20120419/1334852535
つまり記事を参考にすると、上の記述は
(progn
(export #'connect)
(defun connect ...)
のように解釈されるということです。
connect関数
こちらでは適切なdriveを探し、
そのdriveからmake-instanceし、
make-connectionを行ってます。
make-instance
とは、CLOSにおいてdefclassで定義した
クラスのオブジェクトを作成するメソッドで、driverのオブジェクトを作成しています。
make-connection関数
こちらはdriverファイルに定義されているので、ファイルを移動します。
(defgeneric make-connection (driver &key)
(:documentation "Create a instance of `<dbi-connection>` for the `driver`.
This method must be implemented in each drivers.")
defgenericは確か総称関数とばれるもののはずですが、忘れたので調べます。
こちらを参考に調べてみると、
(defgeneric who-are-u()
)
(defmethod who-are-u ((a person-class))
(format nil "your person")
)
(defmethod who-are-u ((a animal-class))
(format nil "your animal")
)
のように、引数のクラス?によって
あらかじめdefgenericで定義していた
関数のふるまいを変更することができるようです。引数のクラスが何を継承しているかで
また更にいろいろな処理を事前、事後に走らせることができて便利ですね。
ぱっと思いつくものだと、
コールバックなどにつかえるのかな、なんて思いました。
さて、コードに戻ります。
make-connectionの中では
:documentationと
:methodが続きメイン処理が
ないように見えますが、
どうやら:methodとはdefmethodと
同義で、デフォルトの定義となるようです。
つまりこの総称関数のメイン処理は
@ignore driver
になります。
けど、driverとは先ほど
make-instanceなはずなので、??となります。
ちなみに<dbi-driver>
は
(defclass <dbi-driver> () ()
(:documentation "Base class for DB driver."))
クラスみたいですね。
どうやらクラスはとなってるみたいで凄くわかりやすいです。
僕もまねしようと思います。
さて行き詰りましたが、
先ほど定義されていた
make-connection の実態が
なんであるのかが気になります。
ただインスタンスを返すメソッドであれば、接続は確立されないはず。。
いったん、make-connectionが
どのように使われているのか調べてみます。ソースコードを検索してみると。。。
src/dbd/mysql.lisp
にも定義されていました。
(defmethod make-connection ((driver <dbd-mysql>) &key host database-name username password port socket client-flag)
(make-instance '<dbd-mysql-connection>
なるほど、driverとはMySqlなどのRDMSであり、
それによってふるまい方を変えているのですね。(それが総称関数を使うことのメリットですね。忘れていました。すごくわかりやすい。)
どのようにこのファイルを読み込んでいるのか、僕の知識不足で分かっていないですが、
driver.lispが各RDMSの抽象層であることがわかりました。
mysql.lisp内で定義されている
make-connectionを見ると、
(defmethod make-connection ((driver <dbd-mysql>) &key host database-name username password port socket client-flag)
(make-instance '<dbd-mysql-connection>
:database-name database-name
:handle (connect :host host
どうやら、ここでは<dbd-mysql-connection>
のオブジェクトを作成しています。
ここのhandleで使われているconnect関数が実は
cl-mysqlの関数であり、MySQLとのコネクションを作成してる
みたいなので、cl-mysqlに移動します。
...
cl-mysql
Common Lisp MySQL library
10年前のコードみたいですね。
(そんな昔のコードを読むことができて、おぉっとなりました。)
さてconnect関数を探していくと、
にありました。
connect
(defun connect (&key host user password database port socket
...
(setf *last-database* (make-instance 'connection-pool
...
ここではconnection-poolのオブジェクトを作成されていますね。
connection-poolクラスには、定義しか書かれていないので
実際にどのような処理が走ってるのかよくわかりません。
少しソースコードを探索すると、
(defmethod initialize-instance :after ((self connection-pool) &rest initargs)
"The connection arrays need to be set-up after the pool is created."
どうやらconnection-poolのオブジェクトを作成する前に
initialize-instance関数が実行されるようです。
initialize-instance
http://clhs.lisp.se/Body/f_init_i.htm
http://www.nct9.ne.jp/m_hiroi/clisp/clisp07.html#chap0702
この関数の中でも総称関数を使った技法があります。
(defmethod initialize-instance :after ((self connection-pool) &rest initargs)
"The connection arrays need to be set-up after the pool is created."
(declare (ignore initargs))
(setf (connections self) (make-array (max-connections self)
...
最後に書かれているsetfですが、実はこれは
普通のsetfではありません。
(defmethod (setf max-connections) ((max-connect number) (pool connection-pool))
...
(with-lock (pool-lock pool)
(setf (slot-value pool 'max-connections) max-connect))
(pool-notify pool)
この定義によりmax-connectionsが引数に渡った時の
setfを再定義しており、この書き方をすることによって
setfに渡すクラスによって、どの変数に定義するするのか
事前に決めることができます。
そして最大接続数と残りの可能な接続数を
設定した後に、
connect-upto-minimumというメイン処理に進みます。
connect-upto-minimum
(loop for i from 0 to (1- (- min n))
do (connect-to-server self))
シンプルですね。
connect-to-serverを見に行きます。
connect-to-server
(defmethod connect-to-server ((self connection-pool))
"Create a new single connection and add it to the pool."
(let* ((mysql (mysql-init (null-pointer)))
(connection (mysql-real-connect mysql
...
(error-if-null mysql connection)
(add-connection self (make-instance 'connection
:pointer connection
:owner-pool self
:in-use nil))))
ここでは、connection変数をmysql-real-connectを使って
作成し、その変数を使ってconnectionのオブジェクトを作成して
既存のコネクションらの可変長配列に追加しているようです。
ここででてくるmysql-real-connectが僕が探していた関数です。
(長かった。。)
mysql-real-connect
system.lispの中にあるので、
定義を見てみると、、、
(defmysqlfun ("mysql_real_connect" mysql-real-connect) :pointer
...
(client-flag :unsigned-long))
どうやらマクロでさらに定義されているみたいです。
defmysqlfun
(defmacro defmysqlfun ((name internal-name) return-type &body args)
...
(let* ((n name)
(int-name internal-name)
(int-libname (intern (string-upcase
...
`(defcfun (,n ,int-name) ,return-type
,mysql-doc-ref
,@args))))
渡した"mysql-real-connect"を,
defcfunを使って定義しています。
このdefcfunの定義はソースにのっていないので、
調べてみると、
defcfun
C言語のライブラリ(限定しないかもしれない)を使うために
cffiとライブラリがあるようで、
それを使ってC言語のmysql-real-connect関数を呼び出しているのですね。
つまり、実際にどのようにコネクションを作っているのかは、
さらにこのC言語のmysql-real-connect関数を読み解かなければ
ならない。。。orz
残念ながら僕はCをサクサク読めないので、
今回は一旦ここまでとして、また気が向いたら↑を
読んでいきたいと思います。
今回ではCLOSを使った書き方をいっぱい学べました。
OSSを読むといっぱい勉強できるので楽しいです。
次はlackを読んでいこうかな。
また気が向いたら書きます。
それでは。