For Development HEAD DRAFTSearch (procedure/syntax/module):

12.27 dbi - データベース非依存アクセス層

Module: dbi

このモジュールはさまざまなリレーショナルデータベースシステム(RDBMS)に アクセスするための統一されたインタフェースを提供します。個々のデータベー スシステムに特有の操作についてはデータベースドライバ(DBD)モジュールに パッケージされています。DBDのモジュールは通常暗黙裏にDBIの層からロード されます。

このモジュールは Perl の DBI/DBD アーキテクチャに強く影響を受けていま す。Perl DBIを使った経験があるなら、このモジュールを使うのはたやすいで しょう。

まず例を見るほうがよいでしょう。以下はdbiモジュールを使ったデー タベースアクセス例の概要です。

(use dbi)
(use gauche.collection) ; to make 'map' work on the query result

(guard (e ((<dbi-error> e)
           ;; handle error
           ))
  (let* ((conn   (dbi-connect "dbi:mysql:test;host=dbhost"))
         (query  (dbi-prepare conn
                   "SELECT id, name FROM users WHERE department = ?"))
         (result (dbi-execute query "R&D"))
         (getter (relation-accessor result)))
    (map (lambda (row)
           (list (getter row "id")
                 (getter row "name")))
         result)))

dbi-connectにわたす、"dbi:mysql:test;host=dbhost"引数以 外は使用するデータベースシステムに依存する部分はありません。この引数に より、dbiモジュールはこのアクセスがmysqlデータベースに対 するものであると判断します。そして、mysql-特有の手続を扱うようにします。 別のデータベースシステムwhateverを使いたいのであれば、単に "dbi:whatever:parameter"dbi-connectに渡せ ばよく、dbd.whateverがシステムにインストールされていれば同じよ うにできます。

データベースに対するクエリはdbi-prepareを使って作成します。 クエリの発行はdbi-executeで行います。このような2つのフェーズを 使うことで、パラメータ化されたSQL文の一種であるプリペアドクエリを 作ることができます。上の例ではクエリはSQL文の中で'?'で表現され ている部分に、ひとつの引数をわりあてます。実引数の値は dbi-executeで設定されます。類似のクエリを大量に発行するような場 合にはプリペアドクエリをひとつ生成し、それにさまざまなパラメータを渡し て実行するとパフォーマンスがかせげます。このパラメータは自動的にクォー トされます。

クエリがSELECT文の場合、その結果は関係プロトコルを実装するコレ クションとして返されます。詳細はgauche.collection - コレクションフレームワークおよび util.relation - リレーションフレームワークを見てください。

いちばん外側にあるguardはエラーを捕捉するためのものです。 dbiに関連したエラーは<dbi-error>コンディションを継承して いるものと見なされます。いくつかの特有のエラーはdbiモジュールで 定義されています。特定のdbd層はさらに固有のエラーを定義していま す。

次節ではユーザレベルのAPIについて説明します。すなわち、dbiを使 う際に必要となる手続に関する説明です。そのあとのセクションではドライバ APIを説明をします。すなわち特定のdbdドライバをdbiフレー ムワークで使えるようにするために使うAPIの説明です。


12.27.1 DBIのユーザAPI

DBIのコンディション

dbi API が投げる可能性のあるコンディションがいくつか定義されて います。コンディションの詳細については例外を見てください。

Condition Type: <dbi-error>

{dbi} dbi-関連のコンディションのベースクラス。<error>を継承し ています。

Condition Type: <dbi-nonexistent-driver-error>

{dbi} dbi-connectは指定されたドライバが見つからない場合にこのコンディ ションを投げます。<dbi-error>を継承しています。

Instance Variable of <dbi-nonexistent-driver-error>: driver-name

要求されたドライバの名前を文字列として保持している。

Condition Type: <dbi-unsupported-error>

{dbi} 呼び出されたメソッドが基盤となるドライバでサポートされていない場合、こ のコンディションが投げられます。<dbi-error>を継承しています。

Condition Type: <dbi-parameter-error>

{dbi} プリペアドクエリへ渡されたパラメータの数がプリペアドステートメントの中 のものと一致しないとき、このコンディションが投げられます。

上の3つのエラー以外に、dbiがプリペアドSQL文を構文解析するのにド ライバを利用す場合、不正なSQL文がdbi-prepareに渡されると、 <sql-parse-error>が投げられます (text.sql - SQLのパーズと構築参照)。

データベースへの接続

Function: dbi-connect dsn :key username password

{dbi} dsn(データソース名)で指定されたデータソースを使ってデータベース に接続します。dsnは以下の構文をもつ文字列です。

dbi:driver:options

driverは特定のドライバ名です。対応するドライバモジュールがなけれ ばなりません。すなわち、dbd.driverがシステムにインストー ルされていなければなりません。たとえば、dsn"dbi:mysql:" ではじまるとすると、dbi-connectdbd.mysqlをロードしよう とします。

options部分の解釈はドライバに依存します。通常この部分のフォーマッ トはkey1=value1;key2=value2;...のようになっていますが、ドライバ によっては別の解釈になります。たとえば、mysqlドライバでは、 optionsの最初の部分でデータベース名を指定することができます。 optionsの正確な仕様については各ドライバのドキュメントをチェック してください。

接続のために必要な追加情報はキーワード引数であたえます。 usernameおよびpasswordは共通でサポートされている引数です。 ドライバは他にもキーワード引数を認識します。

データベースへの接続が成功したら、コネクションオブジェクト (<dbi-connection>のサブクラスのインスタンス)が返ります。さもな ければ、エラーがあがります。

Class: <dbi-connection>

{dbi} データベースシステムへの接続のベースクラス。各ドライバはこのクラスのサ ブクラスを定義し、これにデータベース特有のコネクションに関する情報を持 たせます。

Method: dbi-open? (c <dbi-connection>)

{dbi} データベースへの接続がオープン状態(アクティブ状態)にあるかどうかを確か めます。

Method: dbi-close (c <dbi-connection>)

{dbi} データベースへの接続を閉じます。これによりこの接続に関連付けられたリソー スが解放されます。いったん閉じたcに対してはどのようなdbi操作もで きません。(dbi-open?だけは例外)。すでに閉じられたコネクションに 対してdbi-closeを呼んでもなにも起りません。

ドライバは通常<dbi-connection>がガベージコレクションされたとき にコネクションを閉じますが、このことを期待したコードを書くのはいただけ ません。GCのタイミングというのは予測不可能だからです。ユーザプログラム は適切なタイミングでdbi-closeを呼ぶようにすべきです。

Function: dbi-list-drivers

{dbi} 解っているドライバのモジュール名のリストを返します。

Class: <dbi-driver>

{dbi} ドライバのベースクラス。高レベルのdbi APIを使うかぎり、これが必 要になることはありません。

Function: dbi-make-driver driver-name

{dbi} dbi-connectから呼ばれる低レベル関数、通常この関数を呼ぶ必要はあ りません。

driver-nameで指定されたドライバモジュールをロードし、当該のドラ イバクラスのインスタンスを生成してそれを返します。

クエリの準備と発行

Method: dbi-prepare conn sql :key pass-through …

{dbi} SQL文の文字列表現sqlからデータベースコネクションconn用の クエリオブジェクト(<dbi-query>のインスタンスもしくはそのサブク ラスのインスタンス)を生成してそれを返します。

sql?であらわされているパラメータスロットを持ちます。

(dbi-prepare conn "insert into tab (col1, col2) values (?, ?)")

(dbi-prepare conn "select * from tab where col1 = ?")

これらのスロットはdbi-executeを使って実際にクエリを発行したとき に埋められます。パラメータスロットを使うのは以下の利点があるからです。 (1) クォートが自動的にほどこされます。不適切なクォートによるセキュリティ ホールを気にする必要はありません。 (2) いくつかのドライバでは準備の段階でサーバへテンプレートSQL文を送る 機能がサポートされていて、実行段階ではパラメータを送るだけで済みます。 これは似たようなクエリを大量に一度に発行するときには効率のよいやりかた です。

バックエンドでプリペアド文がサポートされていない場合(でSQLテンプレー トが?パラメータを持つ場合)、ドライバはsqlを解析するのに text.sqlモジュールを使います。与えられたSQL文が正しい構文でなけ れば、<sql-parse-error>コンディションが発生します。

キーワード引数pass-throughに真の値を渡して、SQLの解釈を抑制し、 sqlをそのままバックエンドのデータベースシステムに渡すことができ ます。text.sqlで理解できないようなSQLの拡張をバックエンドがサポー トしている場合に役立ちます。

ドライバがプリペアド文をtext.sql抜きでバックエンドに処理させた 場合、pass-through引数は無視されます。ドライバは他のキーワード 引数を取ることもあります。詳細はそれぞれのドライバのドキュメントを参照 してください。

注意:SQL文のケース畳み込みは実装依存です。DBMSのなかにはテーブ ル名やカラム名は大文字小文字の区別をしないものもあり、一方で区別するも のもあります。ポータブルなSQL文を書きたいのなら、識別子をクォートしま しょう。すなわち常に名前をダブルクォートで囲むようにします。

Class: <dbi-query>

{dbi} dbi-prepareによって作成されたプリペアドクエリに関する情報を保持 します。以下のスロットが定義されています。

Instance Variable of <dbi-query>: connection

<dbi-connection>オブジェクトを含みます。

Instance Variable of <dbi-query>: prepared

ドライバがクエリを準備する場合、このスロットがプリペアド文を保持します。 このスロットをどのように使うかはおのおののドライバによります。したがっ て、クライアントはこの値に依存してはいけません。

Method: dbi-open? (q <dbi-query>)

{dbi} クエリがdbi-executeに渡せる状態になっているときにのみ、 #tを返します。

Method: dbi-close (q <dbi-query>)

{dbi} クエリを破棄し、当該クエリに関連づけられたリソースを解放します。この操 作を実行後は、dbi-open?qに対して#fを返します。 そして、当該クエリは他の用途にはつかえません。qがガベージコレク ションにより回収された場合、リソースは解放されますが、アプリケーション が明示的にクエリを閉じるようにすることを強く勧めます。

Method: dbi-execute (q <dbi-query>) parameter …

{dbi} dbi-prepareによって作成されたクエリを実行します。当該クエリが期 待するのと同じ数のパラメータを渡す必要があります。

発行されたクエリがselect文の場合dbi-executeリレーションを表わすオブジェクトを返します。リレーションは 行とカラムの値をカプセル化したもので、カラム名のようなメタ情報も同様で す。結果へアクセスの方法については後述の「クエリの結果を見る」を見てく ださい。

クエリがSELECT以外のcreateinsertdeleteなどの 場合、クエリクロージャー返り値は不定です。

Method: dbi-do conn sql :optional options parameter-value …

{dbi} この手続はクエリを作成し、すぐに実行したいときに便利です。これは次の式 と同じですが、この場合はドライバはオーバーヘッドを避けるため、中間のク エリを作らないようにこのメソッドをオーバーロードします。

(dbi-execute (apply dbi-prepare conn sql options)
             parameter-value ...)
Method: dbi-escape-sql conn str

{dbi} str中の特殊文字をエスケープした文字列を返します。

SQLの公式標準ではこのような文字としてはシングルクォート(')につ いてだけ規定しています。しかし、印字可能文字ではない文字については規定 がありません。また、データベースシステムによっては他のエスケープ文字を 使うものもあります。それゆえ、自分でエスケープしようとせずに、このメソッ ドを使う必要があります。

;; c を利用可能なDBIコネクションとする
(dbi-escape-sql c "don't know")
  ⇒ "don''t know"

クエリの結果を見る

クエリがselect文である場合、<collection><relation>の両方のオブジェクトが返ります。行のコレクション (すなわち、<collection> APIの実装)ですから、行にアクセスするに はmapfor-each、その他のジェネリック関数が使えます。 また、カラム名やアクセサを取り出すにはリレーションAPIが使えます。 リレーションAPIについてはutil.relation - リレーションフレームワークをコレクションAPIにつ いてはgauche.collection - コレクションフレームワークを見てください。

クエリから戻ったオブジェクトの実際のクラスはドライバによりますが、 以下のメソッドを使うことができます。

Method: dbi-open? result

{dbi} クエリの結果がまだアクティブであるかどうかをチェックします。 結果はdbi-closeによって明示的に閉じられるかデータベースへのコネ クションが閉じられると非アクティブになります。

Method: dbi-close result

{dbi} クエリの結果を閉じます。結果に関連付けられていたリソースが解放されます。 resultは、いったん閉じると使えなくなります。ただし、 dbi-open?にだけは渡せます。

ドライバは通常、結果がガベージコレクタによって回収される時にリソースを 解放しますが、アプリケーションはこれに依存してはいけません。結果を使い おわったら明示的にdbi-closeを呼ぶことをおすすめします。


12.27.2 DBI用のドライバを書く

特定のデータベースシステムのドライバを書くということは、 dbd.fooモジュールを実装することです。ここでfooはド ライバの名前になります。

このモジュールは以下に説明するいくつかのクラスとメソッドを実装しなけれ ばなりません。

実装するDBIクラス

以下のクラスを定義しなければなりません。

実装するDBIメソッド

ドライバは以下のメソッドを実装しなければなりません。

Method: dbi-make-connection (d <foo-driver>) (options <string>) (options-alist <list>) :key username password …

{dbi} このメソッドはdbi-connectから呼ばれ、データベースへの接続を担い、 コネクションオブジェクトを作成します。コネクションオブジェクトを返さな ければなりません。コネクションが確立できない場合には、 <dbi-error>をあげなければなりません。

optionsdbi-connectに与えられるデータソースネーム(DSN)の オプションパートです。options-alistoptionsを解析した結果 の連想リストです。両方ともに用意して、ドライバが自明ではない方法で options文字列を解釈できるようにします。

たとえば、DSNとして "dbi:foo:myaddressbook;host=dbhost;port=8998"が与えられたとする と、fooのdbi-make-connectionoptionsとして "myaddressbook;host=dbhost;port=8998"を受け取り、 options-alistとして (("myaddressbook" . #t) ("host" . "dbhost") ("port" . "8998")) を受け取ります。

options-alistの後ろならどのようなキーワード引数でも dbi-connectに渡せます。DBIプロトコルは現在のところは usernameおよびpasswordのみを指定します。 ドライバはその他のキーワード引数を定義できます。 ドライバ特有のキーワード引数にはドライバ名を接頭辞として付けることをお 勧めします。たとえば、dbd.fooなら、:foo-whateverのように です。

どのようなオプションを使えるようにするか、あるいはオプションの構文をど うするかはドライバを書く人しだいです。基本的な考え方は、DSN はデータの ソースを識別するためのものであり、その役割りはWWWにおけるURLのようなも のだということです。それゆえ、データベースのホスト名、ポート番号、それ にデータベース名などが含まれることになるでしょう。しかし、ユーザ名やパ スワードのような認証に関する情報を含めてはいけません。というわけで、そ の手の情報はキーワード引数で渡すのです。

Method: dbi-prepare (c <foo-connection>) (sql <string>) :key pass-through …

{dbi} このメソッドは<dbi-query>あるいはそのサブクラスのインスタンスで あるプリペアドクエリオブジェクトを生成し、それを返すものでなくてはなり ません。sqlによるクエリがデータベースに発行されるのは、プリペア ドクエリオブジェクトがdbi-executeに渡されたときです。

このメソッドは返されるクエリオブジェクトのconnectionスロットに cを設定しなけばなりません。

sqlはSQL文です。これには'?'で表現されたプレイスホルダが含 まれることがあります。クエリクロージャはこのプレイスホルダと同じ数の引 数をとらなければなりません。内部的にsqlをどのようにパーズするか、 クエリクロージャが呼ばれたとき完全なSQL文を構築するか、sqlをバッ クエンドのサーバに送って文を準備し、クエリクロージャはパラメータだけを 送るようにするかなどはドライバに依存します。

ドライバがSQL文を内部的にわたす場合、キーワード引数pass-through を認識しなければいけません。もし、真の値が与えられたら、ドライバは sqlを不透明なものとして扱い、これをそのままクエリクロージャが呼 ばれた際に渡さなければなりません。

ドライバがその他のキーワード引数を定義することもできます。 その場合、ドライバ特有のキーワード引数にはドライバ名を接頭辞として付け ることをお勧めします。たとえば、dbd.fooなら、 :foo-whateverのようにです。

Method: dbi-execute-using-connection (c <foo-connection>) (q <dbi-query>) (params <list>)

{dbi} このメソッドはdbi-executeから呼ばれます。qが保持するクエ リを発行しなければなりません。クエリがパラメータ化されている場合、 dbi-executeに与えられた実際のパラメータはparams引数に渡さ れます。

qselect-型のクエリの場合は、このメソッドは適切なリレー ションオブジェクトを返さなければなりません。

Method: dbi-escape-sql (c <foo-connection>) str

{dbi} デフォルトのエスケープメソッドでは十分でないとき、ドライバは特別のエス ケープを行うためにこのメソッドをオーバーロードすることができます。たと えば、MySQLではバックスラッシュ文字はシングルクォートと同様に特別あつ かいしますので、dbi-escape-sqlメソッドを持っています。

Method: dbi-open? (c <foo-connection>)
Method: dbi-open? (q <foo-query>)
Method: dbi-open? (r <foo-result>)
Method: dbi-close (c <foo-connection>)
Method: dbi-close (q <foo-query>)
Method: dbi-close (r <foo-result>)

{dbi} これらのメソッドでコネクションおよび結果の状態を調べ、コネクションおよ び結果を閉じます。closeメソッドはコネクションや結果が利用しているリソー スを解放しなければなりません。ドライバはdbi-closeがすでに閉じら れたコネクションや結果に対しても適用できるようにしておかなければなりま せん。

Method: dbi-do (c <foo-connection>) (sql <string>) :optional options parameter-value …

{dbi} この機能を実装するのにデフォルトメソッドはdbi-prepareおよび dbi-executeを使っています。これだけでも動きますが、 ドライバは効率のために中間のクエリオブジェクトの生成をスキップするため にこのメソッドをオーバーロードできます。

DBIのユーティリティ関数

以下の関数は上述のメソッドを実装するための低レベルのユーティリティです。

Function: dbi-parse-dsn data-source-name

{dbi} dbi-connectに与えられたデータソースネーム(DSN)文字列を解析し、 以下の3つの値を返す。(1) ドライバ名(文字列) (2) DSNのオプション部分(文 字列) (3) 解析済オプション(連想リスト)。与えられた文字列がDSN構文に準 拠していない場合には<dbi-error>があがります。

典型的なドライバを書く場合には必要ありません。構文解析は dbi-make-connectionを呼ぶ前にすんでいるからです。このメソッドは プロキシのようなメタドライバという類のものを書くときに便利です。

Function: dbi-prepare-sql connection sql

{dbi} プレイスホルダを含むSQL文 sql をパーズし、実際の値をパラメータと して渡されたときに完全なSQLを生成するクロージャを作成します。 バックエンドがプリペアド文をサポートしていない場合は、ドライバ中でクエ リを準備するのにこの関数を使うことになります。

connectionはデータベースへのDBIコネクションです。SQL中の値は適切 にエスケープされている必要があります(上述のdbi-escape-sqlをみて ください)。

;; c は正しいdbiコネクションを持っているとする
((dbi-prepare-sql c "select * from table where id=?") "foo'bar")
 => "select * from table where id='foo''bar'"


For Development HEAD DRAFTSearch (procedure/syntax/module):
DRAFT