by Hippo2000(2000/6/22)
DBIに入っているDBD作成者ガイドなのです。
なおこのドキュメントはDBIモジュールをインストールしたときに一緒にはいるDBD.pmのpodをhtml化し日本語に訳そうとしたものです。わかりにくい部分は本物を見てください。(^^;;
原本の著作権はJonathan Leffler 、Jochen Wiedmann 、そしてTim Bunce氏がお持ちです。
Jonathan Lefflerさんにメールで了解をいただきました。なお内容等が間違っていたら修正します。ご連絡ください。
DBI::DBD - DBD ドライバ作成者ガイド
perldoc DBI::DBD
$Revision: 10.8 $
$Date: 2000/06/11 00:06:02 $
このドキュメントはさらに作業を進めるために必要最小限のドラフトです。
DBIの仕様変更やその結果DBDドライバに要求されることが変更されること、そしてこのドキュメントを読んだ人たちからフィードバックが改善を提案することの2つの理由により変更が発生します。
まずDBI FAQを含めてすべてのDBIドキュメントをお読みください。これを読むときにはDBI仕様を読み返します。これは助けとなるでしょう。
このドキュメントは多くの作者からよせられた原稿を集めたものです。さらなる寄稿(どちらかといえばパッチとして)は大歓迎です。
このドキュメントは第一にPerl データベースインターフェース(Perl DBI)のための新しいデータベース・ドライバを作成する人達を助けることを目指しています。DBDドライバの内部がどうしてそのように書かれているのかを理解したい他の人たちにも助けになるでしょう。
これはガイドです。すべての可能な環境下で完全に権威のあるステートメントは(もしあったとしても)あまりありません。つまりこのドキュメントでのガイドラインを適用するときには判断が必要になります。
もし少しでも疑問な点があれば、どうかTim Bunceや他のドライバの作者が助けてくれるdbi-devメーリング・リスト(詳細は以下にあります)にコンタクトしてください。
DBIソフトウェアと情報が置いてある第一のWEBサイトは
http://www.symbolstone.org/technology/perl/DBI
DBIで作業している人々のために主には2つ、そして補助的な1つのメーリングリストがあります。主なリストは、DBIとDBDドライバの一般的な利用者のためのdbi-users@fugue.com、主にDBDドライバの作者のためのdbi-dev@fugue.comです(たいした理由も無くdbi-devリストには参加しないで下さい)。補助的なリストはDBIまたはDBDドライバの新しいリリースをアナウンスするためのdbi-announce@fugue.comです。
これらのリストにはWebサイトhttp: から参加することができます。そのリストは閉じているので、まずリストに参加しなければリストのメールを送信することが出来ません。
(訳者注:メーリングリストには上記http://www.symbolstone.org/technology/perl/DBIから登録サイトへのリンクがはられています。)
また comp.lang.perl.*ニュースグループもモニタすることも考慮してください。
Perl DBIについての決定版は'Programming the Perl DBI: Database programming with Perl' by Alligator Descartes and Tim Bunce, published by O'Reilly Associates, February 2000, ISBN 1-56592-699-4 です。まだ買っていなければ、今すぐ買いましょう。
(訳者注:日本版も作成中だそうです)
新しいドライバを書く前に、あなたのデータベースのためのドライバがあるかどうかが気になるでしょう。そのようなドライバがあれば、自分自身で作成するよりももっと簡単です!
Perlソフトウェアを置いておくための一番のWEBサイトはhttp://www.perl.org/CPANです。たくさんのモジュールのリストがあります。主に以下の2つを見てください。
http://www.perl.org/CPAN/modules/by-category/07_Database_Interfaces/DBI
http://www.perl.org/CPAN/modules/by-category/07_Database_Interfaces/DBD
情報についてはDBI WebサイトのDBIドキュメントやメーリングリストをご覧下さい。
公式の登録処理を始める前に、すでに作業に入っているドライバが無いことを確認する必要があります。DBIメーリングリストに利用できるそのようなドライバがあるか、だれかがそれについて作業していないかを尋ねることによって、確認することができます。
純粋なPerlドライバを書くことは驚くほど簡単です。しかし気をつけなければならない問題があります。もちろん一番よい選択子は既にあるドライバをピックアップして、注意深く1つ1つののメソッドを変更していくことです。
例としてDBD::Fileドライバについて見ていきます。これはプレーンなファイルをテーブルとしてアクセスするものでDBD::CSV の一部です。以下の説明では新しいパッケージの名前をDriverとします:最低限、実装しなければいけないのはファイルMakefile.PLとDriver.pmです。
まず通常はMakefileジェネレータであるMakefile.PLを書くことから始めます。このファイルの内容はMakeMakerのマニュアル・ページに詳しく記述されています。まずこれを読むことはとてもよいことです。最低限ExtUtils::MakeMakerマニュアルページからCONFIGURE, DEFINED, DIR, EXE_FILES, INC, LIBS, LINKTYPE, NAME, OPTIMIZE, PL_FILES, VERSION, VERSION_FROM, clean, depend, realclean変数について知っておいてください:これらはほとんどすべてのMakefile.PLで使われます。さらにOverriding MakeMaker Methods(MakeMakerメソッドの上書き)のセクションやstcheck, disttest そして dist ターゲットの説明についても読んでください。それらは間違い無く有用です。
DBIドライバにとって特に重要なのはExtUtils::MM_Unixマニュアル・ページでのpostamblemeメソッドです。そしてEamcsユーザにはlibscanメソッドをお勧めします。
さて例です。Driverという単語を使っているところにはあなたのドライバの名前を入れてください:
# -*- perl -*-
use DBI 1.03;
use DBI::DBD;
use ExtUtils::MakeMaker;
ExtUtils::MakeMaker::WriteMakefile(
'NAME' => 'DBD::Driver',
'VERSION_FROM' => 'Driver.pm',
'INC' => $DBI_INC_DIR,
'dist' => { 'SUFFIX' => '.gz',
'COMPRESS' => 'gzip -9f' },
'realclean' => '*.xsi'
);
package MY;
sub postamble { dbd_postamble(@_); }
sub libscan {
my($self, $path) = @_;
($path =~ /\~$/) ? undef : $path;
}
ExtUtils::MakeMaker(3). ExtUtils::MM_Unix(3). も読んでください。
READMEファイルは名のためのドライバか、ビルド処理のために事前に必要なことは何か、実際のビルド処理、そしてエラー報告の方法について記述しなければなりません。あなたが悪夢のなかですら:-)思っても見なかったような方法で、利用者はドライバのビルドやテスト処理を失敗させる方法を見つけます。このためこのドキュメントは守りを固めて、正確に書く必要があります。またあなたのテストが可能な限り広く機能することを保証するということにも関心があるでしょう。既にあるドライバのREADMEを、あなたのものの基本として使いましょう。
MANIFESTはMakefileされるdistターゲットによって、CPANにアップロードされる配布するtarファイルを構築するために使われます。配布に含めたいすべてのファイルを1行ずつリストにします。
CPANモジュールは、あなたのドライバにあらかじめ必要なものを指定することを可能とするとても強力なバンドル機構を提供します。まずあらかじめ必要なものはBundle::DBIです;あなたは他のものも欲しかったり、必要だったりするかもしれません。バンドル設定を正しく行えば、ユーザが以下のようにタイプすると:
perl -MCPAN -e 'install Bundle::DBD::Driver'
Perlはあなたのドライバを構築するために必要とされるすべてのPerlモジュールをダウンロード、コンパイル、テストそしてインストールしてくれます。
このファイルの適切な骨格を以下に示します。あらかじめ必要なモジュールはCONTENTSセクションに、公式の名前とダッシュを付けて非公式な名前または説明を一覧にします。Bundle::DBIを主要なあらかじめ必要なものとしてリストに載せることは、ものごとを簡単にさせます。あなたのドライバをリストに載せるのを忘れないで下さい。DBMSそのものはPerlモジュールでなければ、このファイルでのあらかじめ必要なもののリストに載せることが出来ないことに注意してください。あならのドライバのバージョンとバンドルのバージョンの同期を取るようにすることは強く勧められます。構成管理、著作権、ライセンス情報をを加えたいかもしれません。
package Bundle::DBD::Driver;
$VERSION = '0.01';
1;
__END__
=head1 NAME
Bundle::DBD::Driver - A bundle to install all DBD::Driver related modules
=head1 SYNOPSIS
C<perl -MCPAN -e 'install Bundle::DBD::Driver'>
=head1 CONTENTS
Bundle::DBI - Bundle for DBI by TIMB (Tim Bunce)
DBD::Driver - DBD::Driver by YOU (Your Name)
=head1 DESCRIPTION
This bundle includes all the modules used by the Perl Database
Interface (DBI) driver for Driver (DBD::Driver), assuming the
use of DBI version 1.13 or later, created by Tim Bunce.
If you've not previously used the CPAN module to install any
bundles, you will be interrogated during its setup phase.
But when you've done it once, it remembers what you told it.
You could start by running:
C<perl -MCPAN -e 'install Bundle::CPAN'>
=head1 SEE ALSO
Bundle::DBI
=head1 AUTHOR
Your Name E<lt>F<you@yourdomain.com>E<gt>
=head1 THANKS
This bundle was created by ripping off Bundle::libnet created by
Graham Barr E<lt>F<gbarr@ti.com>E<gt>, and radically simplified
with some information from Jochen Wiedmann E<lt>F<joe@ispsoft.de>E<gt>.
The template was then included in the DBI::DBD documentation by
Jonathan Leffler E<lt>F<jleffler@informix.com>E<gt>.
=cut
Driver.pmファイルはPerlモジュールDBD::Driverをあなたのドライバ用として定義します。いくつかのバージョン情報、いくつかの変数定義、そして関数driverとともに、標準よりも多くあるいは少ない構造を持つパッケージDBD::Driver を定義します。
また(メソッド connect()、data_sources()そしてdisconnect_all()を持つ)パッケージDBD::Driver::dr、パッケージDBD::Driver::db(これは関数prepareなどを定義します)、execute()、fetch()などのメソッドを持つパッケージDBD::Driver::stも同時に定義します。
Driver.pm ファイルにはperldocによって使われるフォーマットでDBD::Driverについての仕様のドキュメントも入れます。
さあ例としてFile.pmの引用について詳しく見てみましょう。どのモジュールにも共通となる(DBI、DBD以外のモジュールでさえ)こと、あるいは本当にDBD::Fileパッケージに特有のものについては無視します。
package DBD::File;
use strict; use vars qw($err $errstr $state $drh);
$err = 0; # DBI::errのためのエラーコード $errstr = ""; # DBI::errstrのためのエラー文字列 $sqlstate = ""; # DBI::stateのためのSQL状態
これらの変数はエラー状態とメッセージを格納するために使われます。しかし、直接変更してはならない;その代わりに以下に示すようeventメソッドを使うということは理解しにくいかもしれません。
$drh = undef; # 初期化されたらドライバ・ハンドルを保持する
作成されたら、ここにドライバ・ハンドルが格納されます。ドライバにはたった1つのハンドルしかないと想定できるということに注意してください。
sub driver {
return $drh if $drh; # すでに作られていれば同じものを返します
my($class, $attr) = @_;
$class .= "::dr";
# 複数のドライバを防ぐために上記のように使うので'my'ではありません
$drh = DBI::_new_drh($class, {
'Name' => 'File',
'Version' => $VERSION,
'Err' => \$DBD::File::err,
'Errstr' => \$DBD::File::errstr,
'State' => \$DBD::File::state,
'Attribution' => 'DBD::File by Jochen Wiedmann',
});
return $drh; }
driver メソッドがドライバ・ハンドルのコンストラクタです。DBIがどのようにそのハンドルを実装するかというよい例です。ハンドルは3種類あります:ドライバ・ハンドル(通常は$drhに格納されます、以降drhとします)、データベース・ハンドル(以降dbhまたは$dbhとします)、そしてステートメント・ハンドル(以降sthまたは$sthとします)です。
DBI::_new_drhのプロトタイプは以下のようになります
$drh = DBI::_new_drh($class, $attr1, $attr2);
以下の引数を取ります:
package DBD::Driver::dr; # ====== DRIVER ======
$DBD::Driver::dr::imp_data_size = 0;
ドライバがロードされるとき、DBIがあなたに代わって面倒を見てくれるので、ここや他のDBD::Driver::*のために@ISAが必要ないことに注意してください。
データベースハンドル・コンストラクはdriverメソッドです、そこで名前空間を変更する必要があります。
sub connect {
my($drh, $dbname, $user, $auth, $attr)= @_;
# いくつかのドライバ特有の認証、デフォルト設定などを行います
# ここではエラーの場合'die'することが適切な
# 文法チェックやそれと同様の処理だけをいれるようにしてください。
#
# '空(= blank)'のdbhを生成します(スーパークラスのコンストラクタを呼び出す)
my $dbh = DBI::_new_dbh($drh, {
'Name' => $dbname,
'USER' => $user,
'CURRENT_USER' => $user,
});
# DSNからの属性を処理します:ここではODBC文法を想定しています。
# つまりDSNはvar1=val1;...;varN=valNのようになります
my $var;
foreach $var (split(/;/, $dbname)) {
if ($var =~ /(.*?)=(,*)/) {
# Not !!! $dbh->{$var} = $val;
$dbh->STORE($var, $val);
}
}
$dbh;
}
これは上記のドライバ・ハンドル・コンストラクタとほとんど同じです。属性はDBIマニュアル・ページに記述されています。DBI(3)をご覧下さい。コンストラクタが呼ばれるとデータベース・ハンドルを返します。コンストラクタのプロトタイプは以下の通りです:
$dbh = DBI::_new_dbh($drh, $attr1, $attr2);
$classが$drhに変わる以外は、ドライバ・ハンドル・コンストラクタと同じ引数を取ります。
dbh属性を設定するためにSTOREメソッドを使うことに注意してください。これはドライバ・コードでは、あなたが持っているハンドル・オブジェクトはtieされたハッシュの「内部用('inner')」ハンドルであり、ドライバの利用者が持っている「外部用(outer)」ハンドルではないためです。
内部用ハンドルを持っているので、ハッシュから値を取得したり、ハッシュに設定するときtieの仕掛けは呼び出されません。シンプルで特定でないドライバ特有の属性を取得したり、設定したいときにスピードの面からとても重宝します。
しかしいくつかの属性値、DBIによって扱われるPrintErrorのようなは、ハッシュには実際には存在しません。そして$h->FETCH($attrib)によって読みこまれ、$h->STORE($attrib, $value)によって設定されなければなりません。疑問があれば、これらのメソッドを使ってください。
エラーを報告するためには、DBI::set_err関数/メソッドを使います:
$h->DBI::set_err($errcode, $errmsg);
これはエラーが正しく記録されること、RaiseErrorやPrintErrorなどが正しく扱われることを保証します。典型的には常にこのメソッド・インスタンスを、いわゆるメソッドの最初の引数として使います。
set_err は常にundefを返すので、エラーを扱うコードは通常、以下のように単純にすることができます:
return $h->DBI::set_err($errcode, $errmsg) if ...;
package DBD::Driver::db; # ====== DATABASE ======
$DBD::Driver::db::imp_data_size = 0;
sub prepare {
my($dbh, $statement, @attribs)= @_;
# '空の=blank'のsthを作成します
my $sth = DBI::_new_sth($dbh, {
'Statement' => $statement,
});
# モジュールに特定のデータを設定
$sth->STORE('driver_params', []);
$sth->STORE('NUM_OF_PARAMS', ($statement =~ tr/?//));
$sth; }
ここまではまだ同じです:引数をチェックし、上位クラス・コンストラクタDBI::_new_sthを呼び出します。属性名での接頭辞driver_ に注意してください:独自の属性はすべて小文字のそのような接頭辞を使わなければなりません。DBIマニュアルをご覧下さい。
ここで属性NUM_OF_PARAMSを設定するためにステートメントを解析することに注意してください。下記のexecuteメソッドで同じ事をすることができますし、DBIの仕様は明確に後に遅らせることを許しています。しかしながら、その場合はbind_paramを呼び出すことが出来ません。
sub commit {
my($dbh) = @_;
if ($dbh->FETCH('Warn')) {
warn("Commit ineffective while AutoCommit is on");
}
1;
}
sub rollback {
my($dbh) = @_;
if ($dbh->FETCH('Warn')) {
warn("Rollback ineffective while AutoCommit is on");
}
0;
}
$dbh->{$attr} = $val;
またはこの反対に
$val = $dbh->{$attr};
するときに使われます。
なぜtieされたハッシュ・リファレンスにはこれらのメソッドが必要なのかを理解するための詳細についてはperltie(1) をご覧下さい。
DBIは大抵の属性、特にRaiseError や PrintErrorといった属性を、あなたに代わって取り扱います。ドライバ独自の属性やDBIが扱うことが出来ないAutoCommitといった属性だけを取り扱わなければなりません。よい例は以下のようになります:
sub STORE {
my($dbh, $attr, $val) = @_;
if ($attr eq 'AutoCommit') {
# AutoCommitは現在、考慮しなくてはならないけない唯一の
# 標準属性です
if (!$val) { die "Can't disable AutoCommit"; }
return 1;
}
if ($attr =~ /^driver_/) {
# ここでは独自の属性だけを扱います。
# 任意の処理を引き起こすことも出来るということに注意してください。
# 理想としては未知の属性も受取らなければなりません。
$dbh->{$attr} = $val; # はい、こうすることができます。
return 1; # しかし独自の属性についてのみです。
}
# そうでなければ我々の代りに扱ってもらうようにDBIに渡します。
$dbh->SUPER::STORE($attr, $val);
}
sub FETCH {
my($dbh, $attr) = @_;
if ($attr eq 'AutoCommit') { return 1; }
if ($attr =~ /^driver_/) {
# ここでは独自の属性だけを扱います。
# 任意の処理を引き起こすことも出来るということに注意してください。
return $dbh->{$attr}; # はい、こうすることができます。
# しかし独自の属性についてのみです。
}
# そうでなければDBIに取り扱ってもらうように渡します。
$dbh->SUPER::FETCH($attr);
}
DBIは実際には警告もエラーもなしに、ドライバ特有の属性(すべて小文字の名前で)を格納し、取り出します。そのため値の取得や設定で特別なロジック/チェックを必要としないのであれば、実際にはFETCHやSTOREメソッドに、ドライバ特有のコードは何も実装する必要はありません。
package DBD::Driver::st;
$DBD::Driver::st::imp_data_size = 0;
sub bind_param {
my($sth, $pNum, $val, $attr) = @_;
my $type = (ref $attr) ? $attr->{TYPE} : $attr;
if ($type) {
my $dbh = $sth->{Database};
$val = $dbh->quote($sth, $type);
}
my $params = $sth->FETCH('driver_params');
$params->[$pNum-1] = $val;
1;
}
sub execute {
my($sth, @bind_values) = @_;
my $params = (@bind_values) ?
\@bind_values : $sth->FETCH('driver_params');
my $numParam = $sth->FETCH('NUM_OF_PARAMS');
if (@$params != $numParam) { ... }
my $statement = $sth->{'Statement'};
for (my $i = 0; $i < $numParam; $i++) {
$statement =~ s/?/$params->[$i]/e;
}
# 何もしません... 行の配列のリファレンスが
# 生成され格納することを想定しています:
$sth->{'driver_data'} = $data;
$sth->{'driver_rows'} = @$data; # number of rows
$sth->STORE('NUM_OF_FIELDS') = $numFields;
@$data || '0E0';
}
ここで注意しなければならないことは: ここでNUM_OF_FIELDS 属性を設定することです。というのもbind_columns属性が機能するためには必須だからです。そして属性$sth->{'Statement'} を使います。これは prepare.の中で生成しています。 属性$sth->{'Database'}、これはdth 以外のなにものでもないのですが、自動的にDBIによって作成されます。
最後に数値0ではなく、文字列'0E0'を返すことに注意してください。そのため
if (!$sth->execute()) { die $sth->errstr }
がうまく動きます。
sub fetchrow_arrayref {
my($sth) = @_;
my $data = $sth->FETCH('driver_data');
my $row = shift @$data;
if (!$row) { return undef; }
if ($sth->FETCH('ChopBlanks')) {
map { $_ =~ s/\s+$//; } @$row;
}
return $sth->_set_fbav($row);
}
*fetch = \&fetchrow_arrayref; # fetchrow_arrayrefのために必要なエリアス
sub rows { my($sth) = @_; $sth->FETCH('driver_rows'); }
メソッド_set_fbavを使っていることに注意してください。これはbind_col や bind_columns が機能するために必要です。
クォートされたクエスチョンマークの扱いがうまく実装されていないという問題を修正することを、読者のみなさんの練習として残してあります。:-)
STORE と FETCH のほかのメソッドは概ね上記のdbhのものと同じです。
table_info メソッドは利用できるテーブルについての詳細を返します。
type_info_all メソッドはサポートされているデータ型の詳細を返します。
そしておそらくDBI仕様にはないいくつかの他のメソッド、特にメタデータの作成が利用可能です。Timの最後の記事を考慮して、がんばってODBCドライバに従うようにしてください。
テストプロセスはPerl標準規約(Perl standard test harness)にできるだけ従うべきです。
特にテストのほとんどはサブディレクトリで実行されなければなりません。そして'make test'で実行されたときには単純に'ok'だけを生成するべきです。これがどのようにするかについての詳細についてはラクダ本の第7章"標準Perlライブラリ"のTest::Harness のセクションをごらんください。
テストはテストするために使われるデータベースのタイプやドライバをテストするユーザの権限に合わせる必要があるかもしれません。
DBD::Informix
のテスト・コードは多くの場所で、異なった
Informix データベースが異なった能力を持っているのに合わせて、接続されているデータベースのタイプに順応しなければなりません。
[...More info TBS...]
ありあわせからC/XSドライバを作成することは、気の重たくなる作業です。参照となるドライバ実装をピックアップし、それをドライバを書こうとしているデータベース製品にあうように改造することによってかなり簡単にすることができますし、するべきです。
事実上のリファレンス・ドライバはDBIパッケージの作者でもあるTim Bunceによって書かれたDBD::Oracleでした。DBD::OracleモジュールはCレベルAPIによって実装されている、よい例です。
今日ではTimとJeff UrlwinによってメンテナンスされているDBD::ODBCという別のドライバを元にしたほうがよいようです。というのも数多くのメタデータを提供していますし、将来の開発のためのガイドラインとなるようにも思われます(DBD::OracleもOracle 8 OCIインターフェースに深く掘り下げているので、いまではほんのわずかの差しかないでしょう)
DBD::Informix ドライバは'埋め込みSQL(embedded SQL)'を使ったドライバのよいリファレンスですし、DBD::Ingressも一見の価値があります。
[...More info TBS...]
T.B.S.
最小限のドライバは典型的には7つのファイルといくつかのテストで構成されます。あなたのドライバの名前をDBD::Driverだとすると、以下のファイルになります:
Driver.pm ファイルは上記にある純粋なPerlモジュールのものと同じです。しかし細かい点でいくつか違いがあります:
Driver.pmの一部です、しかしXSコードにコールバックを持っています。さあ例としてOracle.pm(Oracle8をサポートする前のバージョン0.54から)の一部をさらに詳しく見てみましょう。既に純粋なPerlドライバで説明したことやOracleだけに特有なことは無視します。
sub connect {
my($drh, $dbname, $user, $auth)= @_;
# いくつかのデータベース特有の確認や、デフォルト設定のようなこと
# を入れます。ここには文法チェックやそれに類似するエラー
# 場合には'die'するべきようなものだけを入れるべきです。
#
# 空('blank')のdbhを生成します(スーパークラスのコンストラクタを呼び出す)
my $dbh = DBI::_new_dbh($drh, {
'Name' => $dbname,
'USER' => $user, 'CURRENT_USER' => $user,
});
# Oracle.xsファイルにあるOracle OCI orlon 関数を呼び出し、
# 内部ハンドル・データを移します。
DBD::Oracle::db::_login($dbh, $dbname, $user, $auth)
or return undef;
$dbh;
}
これは純粋なPerlの場合とほとんど同じです。違いはプライベートな_login コールバックを使っていることです:これは実際にデータベースに接続します。これはDriver.xstで実装されます(あなたはこれを実装するべきではありません)そしてdbdimp.c から dbd_db_login を呼び出します。詳細は下記をご覧下さい。
DBI::_new_xxh メソッドは通常の状態に落ちることがないので、_loginを呼び出す前に$dbhをチェックしません。
package DBD::Oracle::db; # ====== DATABASE ====== use strict;
sub prepare {
my($dbh, $statement, @attribs)= @_;
# 空の('blank') sthを生成
my $sth = DBI::_new_sth($dbh, {
'Statement' => $statement,
});
# Oracle.xs ファイルの中のOracle OCI oparse 関数を呼び出す。
# (これは実際にはoopenも呼び出します)
# そして内部ハンドルデータに移します。
DBD::Oracle::st::_prepare($sth, $statement, @attribs)
or return undef;
$sth; }
Driver.xs は以下のような感じになります:
#include "Driver.h"
DBISTATE_DECLARE;
INCLUDE: Driver.xsi
MODULE = DBD::Driver PACKAGE = DBD::Driver::db
/* もしあれば、標準で無い dbh XS メソッドをここに入れます。 */ /* 現在は、DBD::mSQLとDBD::mysql からの _list_tables */ /* のようなものが含まれます。 */
MODULE = DBD::Driver PACKAGE = DBD::Driver::st
/* もしあれば標準で無い sth XS メソッドをここに入れます。 */ /* 特にDBD::mSQLとDBD::mysqlからの _list_fields のように */ /* メタデータにアクセスするためのものが含まれます。 */
特にここでDriver.xsi のインクルードに注意してください: DBIは、ほとんどのプライベートなメソッドのためのスタブ関数をここに入れており、それらは通常はあなたに代っていろんな機能をします。なにかを本当に実装する必要があるときには、dbdimp.cでプライベートな関数を呼びます。これがあなたは実装しなけばならないものです。
Driver.h は以下のようになります:
#define NEED_DBIXS_VERSION 93
#include <DBIXS.h> /* DBIモジュールによってインストールされます。 */
#include "dbdimp.h"
#include <dbd_xsh.h> /* DBIモジュールによってインストールされます */
このヘッダファイルは2つの役割があります:
第一にハンドルのプライベートな部分のためのデータ構造を定義します。
第二にdbd_db_loginのような一般的な名前をora_db_loginのようなデータベース特有の名前に変更するマクロを定義します。これにより名前の衝突をさけ、静的にリンクされたPerlで動かしたときに異なるドライバを使うことを可能になります。
実装したくないXSメソッドを使えなくするという重要な働きも持っています。
最後にいくつかの関数の代りの実装を選ぶためにもマクロが使われます。例えば、現在定義されているdbd_db_login関数には属性ハッシュは渡されません。将来、もしdbd_db_login6マクロが(6つの引数をもつ関数のために)定義されたならば、それは6番目の引数に属性ハッシュに渡されるもののかわりに使われるでしょう。
Oracleのdbdimp.cだけをピックアップし、いくつかの名前、構造体、データ型をを使うのが好まれていますが、私はこれには強く反対の勧告をします。一見、これは時間を節約するように見えますが、あなたの実装を読みにくくしてしまいます。DBI仕様とOracle特有の部分、mSQL特有の部分、mysql特有の部分をDBD::mysqlのdbdimp.hとdbdimp.c(DBD::mysqlはDBD::Oracleを基本としたDBD::mSQLのポートです)を分けるというのは地獄です。ドライバのこの部分はあなたの独占部分です。寄せ集めから書きなおしましょう。そうすることによりきれいで短くなります。いいかえればよりよいコードの部分になります(もちろん他の人たちの作品も見てください)
struct imp_drh_st {
dbih_drc_t com; /* 構造体の最初の要素でなければなりません */
/* あなたのドライバ・ハンドル属性をここにいれます */ };
struct imp_dbh_st {
dbih_dbc_t com; /* 構造体の最初の要素でなければなりません */
/* あなたのデータベース・ハンドル属性をここにいれます */ };
struct imp_sth_st {
dbih_stc_t com; /* 構造体の最初の要素でなければなりません */
/* あなたのステートメント・ハンドル属性をここにいれます */ };
/* 名前の衝突を避けるための名前変更関数;プロトタイプは */ /* dbd_xst.hにあります */ #define dbd_init ora_init #define dbd_db_login ora_db_login #define dbd_db_do ora_db_do ... many more here ...
この構造体はハンドルのプライベートな部分を実装します。imp_dbh_dr|db|stという名前を使い、最初のフィールドの型はdbih_drc|dbc|stc_t.でなければなりません。以下に示すDBIc_xxxを使う以外に、このフィールドに直接アクセスしてはいけません。
これが実装のための主たるファイルです。私はすべての関数に簡単な注意書きをおいてあり、それは Driver.xsi で使われており、こうして実装されなければなりません。もちろんプライベートやよりよい静的関数を追加することができます。
ほとんどの人がまだ Kernighan & Ritchie 書式を使っていることに注意してください。個人的はこれは好きではありませんし、このドキュメントでは害にはなりません。そこでANSIを使いましょう。最後にTim BunceはDBIソースもANSIに移行することに関心があると言っています。
#include "Driver.h"
DBISTATE_DECLARE;
void dbd_init(dbistate_t* dbistate) {
DBIS = dbistate; /* DBIマクロの初期化 */
}
dbd_init はドライバが初めてロードされたときに呼ばれます。これらのステートメントはDBIマクロを使用するために必要です。それらはプライベートなヘッダファイルdbdimp.hに代りにインクルードされます。
void do_error(SV* h, int rc, char* what) {
h が汎用的なハンドルであること、それはドライバ・ハンドル、データベースまたはステートメント・ハンドルでありうることに注意してください。
D_imp_xxh(h);
このマクロは変数imp_xxh を宣言し、プライベートなハンドル・ポンンタへのポインタで初期化します。これを imp_drh_t, imp_dbh_t または imp_sth_tにキャストすることもできます。
SV *errstr = DBIc_ERRSTR(imp_xxh);
sv_setiv(DBIc_ERR(imp_xxh), (IV)rc); /* あらかじめエラーを設定 */
sv_setpv(errstr, what);
DBIh_EVENT2(h, ERROR_event, DBIc_ERR(imp_xxh), errstr);
ハンドル・エラー文字列とエラーコードにアクセスするために、マクロDBIc_ERRSTR と DBIc_ERR を使うことに注意してください。
マクロDBIh_EVENT2が属性RaiseError と PrintError が機能することを確定させます。それこそが、あなたのやらなければならないことです:-) 。
if (dbis->debug >= 2)
fprintf(DBILOGFP, "%s error %d recorded: %s\n",
what, rc, SvPV(errstr,na));
DBIドライバの中ででデバッグ/トレース・ログがどのように動くのかをみるのは、これが初めてです。できるだけ多くこれを使ってください!
int dbd_db_login(SV* dbh, imp_dbh_t* imp_dbh, char* dbname,
char* user, char* auth) {
この関数は実際にデータベースに接続します。引数 dbh はデータベース・ハンドル、imp_dbh は上記do_errorでのimp_xxx のように、プライベート・データへのポインタです。引数dsn, user そして auth はドライバ・ハンドルのconnect メソッドに対応します。
ここでは、たくさんのデータベース特有の属性を使うでしょう、それらはDSNで指定されます。DSNをconnectメソッドで解析し、dbd_db_loginにハンドル属性として渡すことをお勧めします。それらをどのように取り出すか、hostnameとportを使う例を示します:
/* このプログラムはDBI::_new_dbhへの*2番目の*属性パラメータが * login属性をもったハッシュを格納するために使われることを想定しています */ SV* imp_data = DBIc_IMP_DATA(dbh); HV* hv; SV** svp; char* hostname; char* port;
if (!SvTRUE(imp_data) || !SvROK(imp_data) ||
SvTYPE(hv = (HV*) SvRV(imp_data)) != SVt_PVHV) {
croak("Implementation dependent data invalid: Not a hash ref.\n");
}
if ((svp = hv_fetch(hv, "hostname", strlen("hostname"), FALSE)) &&
SvTRUE(*svp)) {
hostname = SvPV(*svp, na);
} else {
hostname = "localhost";
}
if ((svp = hv_fetch(hv, "port", strlen("port"), FALSE)) &&
SvTRUE(*svp)) {
port = SvPV(*svp, na); /* おそらくはサービス名 */
} else {
port = DEFAULT_PORT;
}
ついに実際にデータベースに接続しなければなりません。もし成功すれば(あるいは失敗した場合であっても、リソースを占有していれば)、以下のマクロを使わなければなりません:
DBIc_IMPSET_on(imp_dbh);
これはそのドライバ(実装)がimp_dbh構造体のなかでリソースを占有していること、そしてそのハンドルが破壊されるとき、実装のプライベートなdbd_db_destroy関数が呼ばれなければならないことを示します。
DBIc_ACTIVE_on(imp_dbh);
これはそのハンドラがサーバへのアクティブな接続を持っていること、そしてそのハンドルが破壊される前にそのdbd_db_disconnect関数が呼ばれなければならないことを示します。
dbd_db_login関数は成功すればTRUE、そうでなければFALSEを返さなければなりません。
int dbd_db_commit( SV* dbh, imp_dbh_t* imp_dbh );
int dbd_db_rollback( SV* dbh, imp_dbh_t* imp_dbh );
これらはコミットとロールバックのために使われます。成功すればTRUE、そうでなければFALSEを返さなければなりません。
引数 dbh とimp_dbh は上記と同じです、これ以降、現れたときには説明を省略します。
int dbd_db_disconnect(SV* dbh, imp_dbh_t* imp_dbh);
データベース・ハンドルは成功すればTRUE、そうでなければFALSEを返さなければなりません。
リターンする前にはDBIにdbd_db_disconnectが実行されたことを知らせるために、いかなる場合も、
DBIc_ACTIVE_off(imp_dbh);
をしなければなりません。
int dbd_discon_all (SV *drh, imp_drh_t *imp_drh) {
この関数はシャットダウンの時に呼ばれるかもしれません。もし可能であれば−すべてのデータベース・ハンドルを切断するように全力を尽くさなければなりません。いくつかのデータベースはそれをサポートしていません。その場合には'success'を返すことしか出来ません。
リターン・コードが何かわかりますか?(ヒント:すぐ上の関数にあります :-))
void dbd_db_destroy(SV* dbh, imp_dbh_t* imp_dbh) {
DBIc_IMPSET_off(imp_dbh);
}
ハンドラがまだアクティブであればdbd_db_destroyを呼ぶ前に、DBI Driver.xst コードが代りにdbd_db_disconnectを呼ばせるでしょう。
リターンの前に、DBIにデストラクタが既に呼ばれたことを知らせるために、関数はIMPSETをオフに切り替えなければなりません。
$dbh->{$key} = $value;
そのプロトタイプは以下のものです。
int dbd_db_STORE_attrib(SV* dbh, imp_dbh_t* imp_dbh, SV* keysv,
SV* valuesv);
あなたがすべてのDBI属性をここで扱わなってはいけないのとは反対に、あなたはすべての属性を扱いません:これをDBIにまかせてください(唯一の例外は、AutoCommitです。これは注意してください)
戻り値は、もしその属性を扱ったのであればTRUEそうでなければFALSEです。属性を扱って何か失敗すれば、必要であればDBIが例外を起こせるように、do_errorを呼ばなければなりません。もしdo_errorが戻っても、問題があります:通常は$dbh->errstrをチェックしないので、ユーザはそのエラーについて分からないでしょう。
もし do_error が返ってきたら、処理を続けるという一般的な方法は推奨できません。しかしDBI仕様でcroak()することを期待しているところでさえ、そうしているという例もあります。(DBI(3)のAutoCommitメソッドをご覧ください。)
属性を格納する必要があれば、プライベートなデータ構造im_xxxを使い、((HV*)SvRV(dbh)を通して)ハッシュを扱うか、プライベートなimp_dataを使ってください。
前者の方がintegerやポインタといった内部Cの値に向いていますし、ドライバでスピードが重要なところに向いています。ハンドル・ハッシュはドライバ特有の属性を取得/設定したいかもしらない値にと向いています。プライベートなimp_dataはハンドルに付与された追加的なSVです。名前のないハンドル属性と考えることもができます。通常は使われません。
$value = $dbh->{$key};
そのプロトタイプは以下のようになります:
SV* dbd_db_FETCH_attrib(SV* dbh, imp_dbh_t* imp_dbh, SV* keysv) {
これまでのすべてのメソッドと違って、これは値をもったSVを返します。もし定数値でない値を返すのであれば、通常はsv_2mortalを実行しなければならないことに注意してください。(定数値は&sv_undef、&sv_noそして&sv_yesです)
DBIは属性値のためのキャッシュするアルゴリズムを実装していることに注意してください。属性がフェッチされていると思うのであれば、dbhそれ自身に格納します。
if (cacheit) /* 後のDBIが'速く'取り出す値のためのキャッシュ? */
hv_store((HV*)SvRV(dbh), key, kl, cachesv, 0);
int dbd_st_prepare(SV* sth, imp_sth_t* imp_sth, char* statement,
SV* attribs);
典型的な、単純な可能性はimp_dataハッシュ・リファレンスに単にステートメントを格納し、それをdbd_st_executeで使用することです。できることなら、NUM_OF_FIELDS、NAMEといった属性をここで設定するべきですが、DBIはそれを要求はしません。しかし、そうしたのであれば、ドキュメントに残しましょう。
どんな場合であっても、上記dbd_db_connectでやったのと同じようにIMPSETフラグを設定しなければなりません:
DBIc_IMPSET_on(imp_sth);
int dbd_st_execute(SV* sth, imp_sth_t* imp_sth);
ステートメントが繰り返し実行されることもあることに気がつかなければならないことに注意してください。また2つの実行の間にfinishが呼ばれることも期待してはいけません。
あなたのドライバがパラメータのバインドをサポートしている(そうすべきですが)のに、データベースがそうでなければ、おそらくここでそれをしなければならないでしょう。それは以下のようにすることができます:
char* statement = dbd_st_get_statement(sth, imp_sth);
/* この関数を実装するのはドライバの仕事です。 これは */
/* prepareに渡されたステートメントを保存しなければなりません。 */
/* これをどうするかについての例については上記 imp_data の */
/* 使い方をご覧ください。 */
int numParam = DBIc_NUM_PARAMS(imp_sth);
int i;
for (i = 0; i < numParam; i++) {
char* value = dbd_db_get_param(sth, imp_sth, i);
/* dbd_db_get_paramを実装するのはドライバの仕事です。 */
/* dbd_bind_phの反対の部分として設定されなければなりません。 */
/* '?'を探して、それを値で置き換えてください。簡単ではありません。 */
/* クォートの中に入ったクエスチョン・マークもあることや他にも... */
/* にも注意してください。 :-( */
/* 例として DBD::mysql をご覧ください。(例をあまり深く見ないで下さい。 */
/* どこでサボったかわかってしまう...) */
}
if (isSelectStatement) {
DBIc_NUM_FIELDS(imp_sth) = numFields;
DBIc_ACTIVE_on(imp_sth);
}
ACTIVEフラグをselectステートメントのためだけに設定するのは重要なことです。さらに詳しい説明は上記のdbd_st_preparse と dbd_db_connect をご覧ください。
やらなければならないことは以下の通りです:
AV* av;
int numFields = DBIc_NUM_FIELDS(imp_sth); /* もしNUM_FIELDSがこのステートメントに
ついて決まっていれば、正しいことです。ドライバによってはこうでそうでない場合も
あります! */
int chopBlanks = DBIc_is(imp_sth, DBIcf_ChopBlanks);
int i;
if (!fetch_new_row_of_data(...)) {
... /* エラーまたはデータの終わりでないかをチェックします */
DBIc_ACTIVE_off(imp_sth); /* Activeフラグを自動的にオフにします */
return Nullav;
}
/* この行のためにfbav(フィールド・バッファ配列値)を取得します */
/* 返ってきたデータ行を持っているということがわかっているとき */
/* にだけ、これを呼ぶのはとても重要です */
av = DBIS->get_fbav(imp_sth);
for (i = 0; i < numFields; i++) {
SV* sv = fetch_a_field(..., i);
if (chopBlanks && SvOK(sv) && type_is_blank_padded(field_type[i])) {
/* svの末尾(だけ)の空白を削除します */
}
sv_setsv(AvARRAY(av)[i], sv); /* 注意:(再)利用! */
}
return av;
SV*を返すfetch_a_field関数を使う必要はありません。文字列としてデータを取り出すデータベースAPI関数を使い、以下のようにする方がもっと一般的です:
sv_setpvn(AvARRAY(av)[i], char_ptr, char_count);
NULL 値はundefで返さなければなりません:以下のようにすることができます:
SvOK_off(AvARRAY(av)[i]);
この関数は成功すればDBIによって用意されたAVを、そうでなければNullavを返します。
必要なのはsthのためのActiveフラグをオフに切りかえることだけです。ドライバがsthのためにACTIVEを設定しているならば、Driver.xstコードによって呼ばれるだけです。
最小限の例(DBIのデフォルトのメソッドは単にこうしています):
int dbd_st_finish(SV* sth, imp_sth_t* imp_sth) {
DBIc_ACTIVE_off(imp_sth);
return 1;
}
この関数は成功すればTRUE、そうでなければFALSEを返します。
void dbd_st_destroy(SV* sth, imp_sth_t* imp_sth);
... /* 必要な後片付けをします */
DBIc_IMPSET_off(imp_sth); /* DBIにそれがすんだことを知らせます */
}
dbd_st_destroyを呼び出す前にsthがACTIVEフラグを設定していれば、DBI Driver.xst コードはあなたに代わってdbd_st_finish を呼び出します。
int dbd_st_STORE_attrib(SV* sth, imp_sth_t* imp_sth, SV* keysv,
SV* valuesv);
SV* dbd_st_FETCH_attrib(SV* sth, imp_sth_t* imp_sth, SV* keysv);
int dbd_bind_ph (SV *sth, imp_sth_t *imp_sth, SV *param,
SV *value, IV sql_type, SV *attribs,
int is_inout, IV maxlen);
param 引数はパラメータ番号(1,2,....)を持ったIVを保持します。value引数はパラメータの値、sql_typeはそのデータ型です。
もしあなたのドライバがbind_param_inoutをサポートしなければmaxlenを無視し、is_inoutがTRUEであれば警告(croak)しなければなりません。
もしあなたのドライバがbind_param_inoutをサポートするのであれば、bind_param_inoutに渡されるリファレンスをデリファレンスした後、valueはSVであることに注意しなければなりません。
簡単なデータベースのドライバでは、関数は例えばパラメータ配列に格納し、後のdbd_st_execute の中で使うでしょう。例としてDBD::mysql ドライバをご覧下さい。
これは厳密に純粋なPerlの場合と同じです。正直に言うと、上記のMakefile.PL には純粋なPerlには余分なものも入っています。 :-)
DBIコードはDBI->functin()といった書き方を使ってアクセスされるメソッドの多くを実装しています。唯一の例外はDBI->connect() と DBI->data_sources() で、これはドライバからのサポートを必要とします。
DBI コードは以下に書かれているドライバ、データベース、そしてステートメント関数を実装しています。これらはDBDドライバの作者は書く必要がありません。
DBIコードはデフォルトの実装がそのドライバにとって正しくないのでなければ、DBDドライバの作者がそれを書かなくて済むように、以下の関数のデフォルトを実装しています。
2つのクォートの形式の引数のために、クォートが必要とする情報を提供するためのtype_infoメソッドを実装する必要があります。
sub ping {
my $dbh = shift;
$sth = $dbh->prepare_cached(q{
select * from A_TABLE_NAME where 1=0
}) or return 0;
$sth->execute or return 0;
$sth->finish;
return 1;
}
A_TABLE_NAME は(データベースのシステム・カタログのような)常に存在するテーブルの名前です
考え方については、(DBD::Oracleと一緒に提供される)Oraperl.pm、(DBD::Ingresと一緒に提供される)Ingperl.pmそして対応するdbdimp.cファイルを勉強してください。
エミュレーション・コードは各接続について$dbh->{CompatMode} = 1;を設定していることに注意してください。これはドライバの内部でそれらを扱うときに、古いインターフェースと互換性をもった動きを実装できるようにするためです。
例えばingperlは変数$sql_rowcount
を持っています。これをIngperl.pmのなかで手動で更新するよりは、Cコードでやってしまう方がむしろ速いでしょう。dbd_init()では:
sql_rowcount = perl_get_sv("Ingperl::sql_rowcount", GV_ADDMULTI);
対応する場所は以下のようにします:
if (DBIc_COMPAT(imp_sth)) /* これは互換性を保つモードのハンドルでのみ行う */
sv_setiv(sql_rowcount, the_row_count);
すべてのハンドルはプライベートがいっぱい入った対応するC構造体を持ちます。このデータの一部はDBIのために予約されています(以下のDBIcマクロを使うのを除いて)、一部はあなたのためです。例として上記のdbdimp.h の説明をご覧下さい。dbdimp.cのほとんどの関数はハンドルxyzとimp_xyzへのポインタを渡されます。しかしながら、まれに以下のマクロを使うこともできます。
DBIc_TYPE(imp_xxx)
== DBIt_DBであれば、imp_xxxをimp_dbh_t*にキャストするのは安全です・(sv_derived_from(h,
"DBI::db")を呼ぶこともできますが、それはもっと時間がかかります)ハンドルを初期化するドライバ・コードは、後片付けコードが呼ばれなければならないような状態になったらすぐに、DBIc_IMPSET_on()を使わなければなりません。これが発生するときはドライバ・コードによって判定されます。
これを呼び出すのに失敗するとデータ構造がおかしくなるかもしれません。例えばDBD::Informixはドライバで、リンクされたデータベース・ハンドルのリストを、そして各ハンドルでは状態のリンクされたリスト管理します。一度リンクされたリストにステートメントが追加されると、片付けられるのは(リストから削除される)のはとても重要です。DBIc_IMPSET_on()が呼ばれるのが遅すぎると、あらゆる種類の障害を起こす可能性があります。
その昔、以下のようなマクロを通すのが内部DBIブール値のフラグ/属性を扱う唯一の方法でした:
DBIc_WARN DBIc_WARN_on DBIc_WARN_off
DBIc_COMPAT DBIc_COMPAT_on DBIc_COMPAT_off
これらのそれぞれはimp_xxhポインタを引数として取ります。
その後、ChopBlanks, RaiseError and PrintErrorといった新しく加えられた属性は、マクロのフルセットを持ちません。これらを扱うために認められている方法はいまや4つのマクロです:
DBIc_is(imp, flag)
DBIc_has(imp, flag) an alias for DBIc_is
DBIc_on(imp, flag)
DBIc_off(imp, flag)
したがってマクロのDBIc_XXXXXの種類はいまやほとんど止めるようにいわれており、古いドライバはおそらくしばらくの間はそうするでしょうけれども、新しいドライバはそれらを使うことを避けるべきです。しかしながら...
重要な例外があります。ACTIVEとIMPSETフラグはDBIc_ACTIVE_onとDBIc_IMPSET_onマクロによって設定され、DBIc_ACTIVE_offとDBIc_IMPSET_offによって解除なければなりません、
DBIの仕様でドキュメント化されている$sth->bind_col() と $sth->bind_columns() はドライバの作者によって実行される必要はありません。というのもDBIが代りに面倒を見てくれるからです。しかしカラムのバインドが機能することを保証する鍵は、データの行を取り出すDBIS->get_fbav() 関数を呼び出すことです。これはAVを返し、そしてAVの各要素は返されるデータを持つように設定されるべきSVを持っています。
上記はCドライバのみです。同等のPerlは純粋なPerlドライバの部分で記述される$sth->_set_fbav($data)メソッドです。
これは完全にオープンな議題です。DBD::File ドライバが証明しているように、それは可能です。しかし思っているほど簡単ではないかもしれません。
(このトピックはDBIのサブクラス化とは違うことに注意してください。その例については、DBIと一緒に提供されるt/subclass.tファイルをご覧下さい)
主な問題は、connectやprepareメソッドが返すdbhのそしてsthはDBD::Driver::db や DBD::Driver::st のインスタンスではありません。そこから派生したものですらありません。その代わりにDBD::db or DBD::st クラスまたは派生したサブクラスのインスタンスです。このため、もしmymethodを書いて、以下のようにすると
$dbh->mymethod()
autoloaderはDBI::dbパッケージでそのメソッドを探します。もちろん代りに以下のようにすることもできます。
$dbh->func('mymethod')
そして、もしmymethodが継承されていたとしても、これは本当に機能しますが、追加的な機能はありません。@ISAを設定することだけでは不充分です。
最初の問題は、connect メソッドにはサブクラスの考えがないことです。例えば同じファイルにベースとなるクラスとサブクラスを実行することはできません:install_driverメソッドが以下のようにしたい。
require DBD::Driver;
特に、あなたのサブクラスはDBIの視点からは別のドライバになるべきです。そしてドライバ・ハンドルを共有することはできません。
それはもちろん大した問題ではありません。ベース・クラスのconnectメソッドを継承することできるはずです。しかし以下のようなことをしないかぎり、簡単にメソッドを上書きすることはできません。DBD::CSV:から引用すると
sub connect ($$;$$$) {
my($drh, $dbname, $user, $auth, $attr) = @_;
my $this = $drh->DBD::File::dr::connect($dbname, $user, $auth, $attr);
if (!exists($this->{csv_tables})) {
$this->{csv_tables} = {};
}
$this; }
通常にOO環境でおこなうように、以下のようにすることができないことに注意してください
$srh->SUPER::connect($dbname, $user, $auth, $attr);
というのも$drhはDBI::drのインスタンスだからです。そしてDBD::Fileのconnectメソッドはサブクラスの属性を扱うことが出来ることも注意してください。上記の純粋Perlドライバの説明をご覧下さい。
上記のマナーで常にスーパークラス・メソッドを呼ぶことは非常に重要です。しかしながら、それはそうすべきです。
幸いなことにDBI仕様は簡単でありながら、パフォーマンスのよい属性を取り扱う方法を可能にしています。この考え方は、すべてのドライバはプライベートはメソッドのためには接頭語driver_を使うという約束を基本にしています。このため属性をスーパー・クラスに渡すのか、そうでないのかは常に明確です。例えばDBD::CSV クラスからのSTOREメソッドについて考えてみてください:
sub STORE {
my($dbh, $attr, $val) = @_;
if ($attr !~ /^driver_/) {
return $dbh->DBD::File::db::STORE($attr, $val);
}
if ($attr eq 'driver_foo') {
...
}
Tim Bunce - DBIの作成、DBIの仕様およびDBD::Oracleドライバの管理に
Jonathan Leffler 、Jochen Wiedmann 、そしてTim Bunce.
ご意見、ご質問はこちらの掲示板で受け付けています。
またメールは河馬屋(Nifty)にお願いします。