モダンPerlの世界へようこそ

第35回 DBI:生のSQLが散らばると言う前に

この記事を読むのに必要な時間:およそ 5 分

Perlとdbm

いまでは省みられることも少なくなりましたが,Perlには1989年にリリースされたバージョン3.0以降,dbmと呼ばれるシンプルなデータベースにアクセスする機構が標準で組み込まれています。このdbmは,いわゆるリレーショナルデータベースとは違ってキーと値の組み合わせをディスクに保存できるだけのものですが,ハッシュ(当時はまだ連想配列と呼んでいました)と結びつけることでタブ区切りファイルなどを読んでいくより高速に検索ができたため,ユーザ環境に永続的なデータを保存する手段のひとつとして重宝されていました。Perl 3/4の時代にはdbmopenというコマンドが使われていましたが,この機構はPerl 5になって一新され,いまではより汎用的なtieというコマンドを使うことになっています。この仲間としては古くからあるBerkeley DBやGDBMなどのほか,最近では平林幹雄氏のTokyoCabinetKyotoCabinetも同様に扱えるようになっているので,そちらでおなじみの方もいるかもしれません。

use strict;
use warnings;
use DB_File;

tie my(%hash), "DB_File", "test" or die $!;
$hash{test} = "hello world";
untie %hash;

tie my(%new_hash), "DB_File", "test" or die $!;
print $new_hash{test}; # hello world
untie %new_hash;

DBperlとDBI

dbmは,使いどころによっては便利なものですが,本格的なデータベースのかわりになるものではありません。そのため,バージョン3.0のリリースから半年ほどたった1990年8月には,より本格的なデータベースにも対応できるよう,Perlをコンパイルする際にユーザが自分の好きなライブラリを組み込んで使えるようにする仕掛けが用意されました。この仕掛けを利用することで,Oracleのクライアントライブラリを埋め込んだOraperlや,Sybaseを扱えるようにするSybperl,Interbase対応がなされたInterperlといった派生品が生まれ,Perl本体とは別に維持・公開されていくようになったのですが,以前にも紹介したように,これらは個々のデータベースの特徴に強く依存していたため,さまざまな問題を引き起こすことにもなりました。

たとえば,諸般の事情でOracleのデータベースとSybaseのデータベースに同時にアクセスしなければならなくなったとしましょう。当時,特定のデータベースクライアントに紐づけられたPerlではほかのデータベースにアクセスできないのがふつうでしたから,両方にアクセスしたいと思ったら,まずはそれぞれに専用のPerlを用意する必要がありました。

また,Oraperlではデータベースの接続に「&ora_login($system_id, $username, $password))」というコマンドを利用しますが,Sybperlでは同じ用途に引数の順序が異なる「&dblogin($user, $pwd, $server)」というコマンドを使うとか,SQL文を用意する際も,Oraperlの場合はふつう「&ora_open($lda, $statement)」から「&ora_bind($csr, $var, ...)」と続けるのが,Sybperlでは「&dbcmd($dbproc, $str)」になるといった具合に,種々のインタフェースがデータベースごとに異なるため,開発や保守,学習にかかるコストが高くなるという問題もありました。

そのような状況を改善するため,1992年にはMicrosoft社がODBC(Open Database Connectivity)と呼ばれるリレーショナルデータベースにアクセスするための共通インタフェースをリリースし,SQL業界でもメジャーバージョンアップとなるSQL92の制定が行われたのですが,Perlの世界でもバズ・モシェッティ(Buzz Moschetti)氏らを中心にデータベースまわりの問題を議論するユーザグループが生まれ,インタフェースを統一するための話し合いが始まりました。

その成果は,当初はDBperlの名でまとめられていましたが,Perl 5のアルファ版が登場すると,それまでの関数群は新しいオブジェクト指向プログラミング風の書き方にあらためられ,Perl 5.000誕生前夜の1994年10月12日には最初のPerl 5用DBIモジュールが関係者向けのメーリングリストに公開されました。これはすぐにPerl 5のコアにも取り入れられ,その直後にリリースされたPerl 5.000gammaにはDBIも同梱されていたそうですが,プロジェクトの全体的な進捗状況も考慮した結果,作者のティム・バンス(Tim Bunce)氏は最後の最後でDBIをPerl 5.000のコアから外してもらうことを決断。惜しまれながらもコアから外れたDBIは,ラリー・ウォール氏には「あとから追加してもいいんだし」と言ってもらっていたものの,かれこれ15年がたったいまもPerlのコアモジュールには戻っていないのですが,コアに入っていようがいまいが,DBIがPerl界のデファクトスタンダードとして確固たる地位を築いていることはみなさんよくご存じの通り。いまではどんな環境であれ,Perlが入っていてデータベースが使えることになっているならDBIも入っていると期待してもよいでしょう。

DBIはPerl界標準のインタフェース

DBIという名前はDatabase Interfaceの略語とされていますが,この名前からもわかるように,DBIは個々のデータベースと直接やりとりするためのモジュールではありません。さまざまなデータベースに対応することを念頭において書かれた汎用的なインタフェースを定義・実装するためのモジュールです。

個々のデータベースエンジンに固有の部分は背後のデータベースドライバ(DBD::で始まる一連のモジュール群)がよしなに計らうように定められているため,一般的なプログラマは裏で何が起こっているかを気にする必要はありません。DBIが定義する共通インタフェース(API)さえ押さえておけば,OracleだろうがSybaseだろうが,あるいはMySQLだろうがPostgreSQLだろうが,原則的には(呼び出すドライバを切り替えるだけで)同じアプリケーションをそのまま使い回せることになっています。そのため,プロトタイプの開発にはフリーのデータベースを利用し,本番に近い環境での検証やサービス展開時にはより高価な製品に切り替えるといったことも建前上は簡単にできますし,実際,多くのデータベース関連モジュールはこの性質を利用して,基本的なテストは取り扱いの簡単なSQLiteなどで済ませ,より複雑な,あるいは特定のデータベースでしか動かない部分については,専用のテスト環境にアクセスするための情報を要求する(なければテストをスキップする)といった手法を採用しています。

ODBCとDBIの違い

もちろん同じようなことはODBCなどでもできます。

たとえば,データベースからidが1の行を取ってくる例を見てみましょう。生のODBCに近い例としてWin32::ODBCというモジュールを使うと,システムにsample_dbという名前で登録したデータソースから,idが1の行を取得できます。対応するドライバがインストールされていれば,データソースはAccessのデータベースであろうとExcelのシートであろうとタブ区切りのテキストであろうと,アプリケーション側で気にする必要はありません。

use strict;
use warnings;
use Win32::ODBC;

my $db = Win32::ODBC->new('sample_db');
$db->Sql('SELECT * FROM foo WHERE id = 1');
while($db->FetchRow) {
    my %row = $db->DataHash;
    print $row{id}, "\n";
}
$db->Close;

DBIの場合も,基本的には同じようなAPIで記述できます。ここでは比較のためにODBCのドライバを利用してみましたが,DBI->connect()の引数を変えれば特定のデータベース用に調整されたドライバを利用することもできます。

use strict;
use warnings;
use DBI;

my $dbh = DBI->connect('dbi:ODBC:sample_db');

my $sth = $dbh->prepare('SELECT * FROM foo WHERE id = ?');
$sth->execute(1);
while (my $row = $sth->fetchrow_hashref) {
    print $row->{id}, "\n";
}
$sth->finish;
$dbh->disconnect;

ただし,PerlのDBIができることはこれだけではありません。

たとえば,細かな制御が必要ないのであれば,SQLの準備と取得をひとまとめにしたAPIを使うこともできます。

my $row = $dbh->selectrow_hashref('SELECT * FROM foo WHERE id = ?', undef, 1);
print $row->{id}, "\n";

このコードは先頭1行のみを取る例ですが,該当するすべての行を一度に取得することもできます(この場合は一般的にselectall_hashrefではなくselectall_arrayrefを使うほうが扱いが簡単になることも覚えておいて損はないでしょう)⁠

my $rows = $dbh->selectall_arrayref('SELECT * FROM foo WHERE id = ?', { Slice => {} }, 1);
for my $row (@$rows) {
    print $row->{id}, "\n";
}

速さが気になる場合は,作成に時間のかかるハッシュリファレンスではなく,カラムに変数をバインドすることで速度を稼ぐこともできます。

my $sth = $dbh->prepare('SELECT * FROM foo WHERE id = ?');
$sth->bind_columns(\my $id, \my $name, \my $address);
$sth->execute(1);
while ($sth->fetch) {
    print $id, "\n";
}

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/

コメント

コメントの記入