Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
2
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

[pending]Common LispでどうやってMySQLと接続するのか調べてみる。

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を読んでいこうかな。

また気が向いたら書きます。
それでは。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
2
Help us understand the problem. What are the problem?