DBI::DBDモジュール v10.8 (DBI v1.14)(日本語チョー訳)

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を使って新しいドライバを作成する

 

純粋なPerlドライバを書くことは驚くほど簡単です。しかし気をつけなければならない問題があります。もちろん一番よい選択子は既にあるドライバをピックアップして、注意深く1つ1つののメソッドを変更していくことです。

例としてDBD::Fileドライバについて見ていきます。これはプレーンなファイルをテーブルとしてアクセスするものでDBD::CSV の一部です。以下の説明では新しいパッケージの名前をDriverとします:最低限、実装しなければいけないのはファイルMakefile.PLとDriver.pmです。


Makefile.PL

まず通常は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ファイルは名のためのドライバか、ビルド処理のために事前に必要なことは何か、実際のビルド処理、そしてエラー報告の方法について記述しなければなりません。あなたが悪夢のなかですら:-)思っても見なかったような方法で、利用者はドライバのビルドやテスト処理を失敗させる方法を見つけます。このためこのドキュメントは守りを固めて、正確に書く必要があります。またあなたのテストが可能な限り広く機能することを保証するということにも関心があるでしょう。既にあるドライバのREADMEを、あなたのものの基本として使いましょう。


MANIFEST

MANIFESTはMakefileされるdistターゲットによって、CPANにアップロードされる配布するtarファイルを構築するために使われます。配布に含めたいすべてのファイルを1行ずつリストにします。


lib/Bundle/DBD/Driver.pm

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

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);

以下の引数を取ります:

$class
通常ドライバ・クラスになります。たとえば"DBD::File::dr"が最初の引数としてdriverメソッドに渡されます。
$attr1
これはName, Version, Err, Errstr State そしてAttributrion.といった属性のハッシュ・リファレンスです。これらはDBIによって処理され利用されます。これらについて何か想定しないようがよいですし、ここに独自の属性を追加してはいけません。
$attr2
これがもう1つの(オプションの)独自の属性を持ったハッシュ・リファレンスです。DBI はこれには手を出しません。
DBI::new_drh メソッドとdriver メソッドは失敗すると両方ともundefを返します。(この場合、ドライバ・ハンドルがないので$DBI::err と $DBI::errstrを見る必要があります)
 
データベース・ハンドル コンストラクタ
次の行は以下のコードのようになります:
  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)によって設定されなければなりません。疑問があれば、これらのメソッドを使ってください。

 
エラーの取り扱い
connectメソッドでなんらかの失敗があるというのはよくあることです。例えばDBD::Fileでは、f_dir属性を使って存在しないディレクトリにカレント・ディレクトリを設定するとエラーとなります。

エラーを報告するためには、DBI::set_err関数/メソッドを使います:

 
    $h->DBI::set_err($errcode, $errmsg);

これはエラーが正しく記録されること、RaiseErrorやPrintErrorなどが正しく扱われることを保証します。典型的には常にこのメソッド・インスタンスを、いわゆるメソッドの最初の引数として使います。

set_err は常にundefを返すので、エラーを扱うコードは通常、以下のように単純にすることができます:

  return $h->DBI::set_err($errcode, $errmsg) if ...;
他のドライバ・ハンドル メソッド
他のドライバ・ハンドル・メソッドを、この後に続けることができます。特にdata_sources メソッドと(おそらく空である) disconnect_all メソッドについて考慮しなければなりません。DBI(3)をご覧下さい。
 
ステートメント・ハンドル コンストラクタ
 
ステートメント・ハンドル・コンストラクでは新しいことはあまりありません。
  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を呼び出すことが出来ません。

 
 
トランザクションの扱い
純粋なPerlドライバではほとんどトランザクションをサポートしていません。このためcommit とrollback メソッドは通常とても単純です:
  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;
  }
STORE と FETCH メソッド
これらのメソッド(既に使っています。上記をご覧下さい)は、利用者が以下のようにしたり
    $dbh->{$attr} = $val;

またはこの反対に

    $val = $dbh->{$attr};

するときに使われます。

なぜtieされたハッシュ・リファレンスにはこれらのメソッドが必要なのかを理解するための詳細についてはperltie(1) をご覧下さい。

DBIは大抵の属性、特にRaiseErrorPrintErrorといった属性を、あなたに代わって取り扱います。ドライバ独自の属性や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メソッドに、ドライバ特有のコードは何も実装する必要はありません。

他のデータベース・ハンドルメソッド
この後に他のデータベース・ハンドルメソッドをいれることができます。(DBIのデフォルトが十分でなければ)特に(ひっとしたら空かもしれない)disconnect メソッド、quote メソッドについてよく検討してください。
 
executeメソッド
ここでパラメータ・バインディングを考慮しなければならないために、これが多分最も難しいメソッドです。上記から driver_params 属性を使うことにより簡単になった実装を示します:
  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 }

がうまく動きます。

データの取り出し
fetchrow_array, fetchall_arrayref, ... といったメソッドを実装する必要はありません。というのもこれらは既にDBIの一部だからです。必要なのはfetchrow_arrayrefメソッドだけです。
  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_colbind_columns が機能するために必要です。

クォートされたクエスチョンマークの扱いがうまく実装されていないという問題を修正することを、読者のみなさんの練習として残してあります。:-)

 
ステートメント属性
dbhとsth属性の主な違いは、ここではDBIによって要求されているたくさんの属性を実装しなければならないことです。例えば: NAME, NULLABLE, TYPE, ... など。

STORE と FETCH のほかのメソッドは概ね上記のdbhのものと同じです。

他のステートメントメソッド
平凡なfinish メソッドは格納されたデータを捨て、$sth->SUPER::finishをします。

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を使った新しいドライバを作成する

ありあわせから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.


CODE TO BE WRITTEN

最小限のドライバは典型的には7つのファイルといくつかのテストで構成されます。あなたのドライバの名前をDBD::Driverだとすると、以下のファイルになります:

Driver.pm
 
Driver.xs
 
Driver.h
 
dbdimp.h
 
dbdimp.c
 
Makefile.PL
 
README
 
MANIFEST
 
lib/Bundle/DBD/Driver.pm
 

Driver.pm

Driver.pm ファイルは上記にある純粋なPerlモジュールのものと同じです。しかし細かい点でいくつか違いがあります:

さあ例として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をチェックしません。

 
ステートメント・ハンドル・コンストラクタ
ステートメント・ハンドル・コンストラクタではあまり新しいことはありません。 connect メソッドと同様に、Cコールバックを持ちます:
  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

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

Driver.h は以下のようになります:

  #define NEED_DBIXS_VERSION 93
  #include <DBIXS.h>      /* DBIモジュールによってインストールされます。  */
  #include "dbdimp.h"
  #include <dbd_xsh.h>     /* DBIモジュールによってインストールされます  */

ヘッダdbdimp.hの実装

このヘッダファイルは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.hdbdimp.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を使う以外に、このフィールドに直接アクセスしてはいけません。


ソースdbdimp.cの実装

これが実装のための主たるファイルです。私はすべての関数に簡単な注意書きをおいてあり、それは 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に代りにインクルードされます。

 
do_error
エラーの記録を取り扱う関数が必要です。好きな名前をつけることができますが、ここではdo_errorと呼びます。
    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が属性RaiseErrorPrintError が機能することを確定させます。それこそが、あなたのやらなければならないことです:-) 。

        if (dbis->debug >= 2)
              fprintf(DBILOGFP, "%s error %d recorded: %s\n",
                    what, rc, SvPV(errstr,na));

DBIドライバの中ででデバッグ/トレース・ログがどのように動くのかをみるのは、これが初めてです。できるだけ多くこれを使ってください!

 
dbd_db_login
    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にハンドル属性として渡すことをお勧めします。それらをどのように取り出すか、hostnameportを使う例を示します:

 
  /* このプログラムは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を返さなければなりません。

dbd_db_commit
 
dbd_db_rollback
    int dbd_db_commit(   SV* dbh, imp_dbh_t* imp_dbh );
    int dbd_db_rollback( SV* dbh, imp_dbh_t* imp_dbh );

これらはコミットとロールバックのために使われます。成功すればTRUE、そうでなければFALSEを返さなければなりません。

引数 dbhimp_dbh は上記と同じです、これ以降、現れたときには説明を省略します。

dbd_db_disconnect
これはdisconnectメソッドのプライベートな部分です。ACTIVEフラグがオンになっている、すべてのdbhは切断されなければなりません。(上記dbd_db_connectでそれを設定しなければならないことに注意)
    int dbd_db_disconnect(SV* dbh, imp_dbh_t* imp_dbh);

データベース・ハンドルは成功すればTRUE、そうでなければFALSEを返さなければなりません。

リターンする前にはDBIにdbd_db_disconnectが実行されたことを知らせるために、いかなる場合も、

    DBIc_ACTIVE_off(imp_dbh);

をしなければなりません。

 
dbd_db_discon_all
    int dbd_discon_all (SV *drh, imp_drh_t *imp_drh) {

この関数はシャットダウンの時に呼ばれるかもしれません。もし可能であれば−すべてのデータベース・ハンドルを切断するように全力を尽くさなければなりません。いくつかのデータベースはそれをサポートしていません。その場合には'success'を返すことしか出来ません。

リターン・コードが何かわかりますか?(ヒント:すぐ上の関数にあります :-))

dbd_db_destroy
これはデータベース・ハンドラのデストラクタのプライベートな部分です。安全にリソースを解放できるように、IMPSETフラグがオンであるすべてのdbhは破壊されなければなりません。(上記dbd_db_connectでそれを設定しなければならないことに注意)
    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をオフに切り替えなければなりません。

dbd_db_STORE_attrib
この関数は以下のものを扱います
     $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です。名前のないハンドル属性と考えることもができます。通常は使われません。

 
dbd_db_FETCH_attrib
これはdbd_db_STORE_attribの逆で、以下のために必要です:
    $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);
dbd_st_prepare
これはprepareメソッドのプライベートな部分です。実際にここでステートメントを実行してはいけないことに注意してください。たとえばステートメントを準備して適正チェックや同じようなことをすることができます。
    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);
dbd_st_execute
ここでステートメントが本当に実行されます:
   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 をご覧ください。(例をあまり深く見ないで下さい。 */
          /*  どこでサボったかわかってしまう...)       */
      }
次にやることはステートメントを本当に実行することです。まだやっていなければ、ステートメントが正常に実行されたら、NUM_OF_FIELDS、NAME、...といった属性を準備しなければならないことに注意してください:それらは潜在的なfetchrowの前にでも使われるかもしれません。DBI内部で使われるため、特にDBIにはそのステートメントが持っているフィールドの数を伝えなければなりません。こうしてこの関数は通常以下のようにして終了します:
 
    if (isSelectStatement) {
        DBIc_NUM_FIELDS(imp_sth) = numFields;
        DBIc_ACTIVE_on(imp_sth);
    }

ACTIVEフラグをselectステートメントのためだけに設定するのは重要なことです。さらに詳しい説明は上記のdbd_st_preparsedbd_db_connect をご覧ください。

dbd_st_fetch
この関数はデータの行を取り出します。行はDBIがあなたのために用意したSVの配列に格納されます。これには2つの利点があります:それは速く(最初のfetchrowの後に作成する必要がないので、SVを再利用することもできます)、DBIがbind_colsを扱うことを保証します。

やらなければならないことは以下の通りです:

    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を返します。

dbd_st_finish
この関数はサーバが提供する行をまだもっていたとしても、ユーザがもう行をとりださないということを示したいという場合に呼ぶことができます。背景についての詳細はDBIドキュメントをご覧下さい。

必要なのは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を返します。

dbd_st_destroy
この関数はステートメント・ハンドルのデストラクタのプライベートな部分です。
    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 を呼び出します。

dbd_st_STORE_attrib
 
dbd_st_FETCH_attrib
これらの関数はステートメント・ハンドル用であることを除けば、上記のdbd_db_STORE|FETCH に対応します。上記をご覧下さい。
    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);
dbd_bind_ph
この関数はbind_param、bind_param_inout、そしてexecuteがバインド変数を伴って呼ばれるならばDBI Driver.xst コードによって内部的に使用されます。
    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 ドライバをご覧下さい。


Makefile.PL

これは厳密に純粋なPerlの場合と同じです。正直に言うと、上記のMakefile.PL には純粋なPerlには余分なものも入っています。 :-)


書く必要のないメソッド

DBIコードはDBI->functin()といった書き方を使ってアクセスされるメソッドの多くを実装しています。唯一の例外はDBI->connect() と DBI->data_sources() で、これはドライバからのサポートを必要とします。

DBI コードは以下に書かれているドライバ、データベース、そしてステートメント関数を実装しています。これらはDBDドライバの作者は書く必要がありません。

$dbh->do()
この関数のデフォルトの実装はそのステートメントを準備(prepare)し、実行(execute)し、破壊(destroy)します。もしパラメータがなければ場合によっては使うことが出来るEXECUTE IMMEDIATEのように、実装するのによりよい方法があれば、これを置きかえることができます。
$h->errstr()
 
$h->err()
 
$h->state()
 
$h->trace()
これらのルーチンについて、DBD ドライバはまったく心配する必要がありません。
$h->{ChopBlanks}
この属性はfetch操作の間は気にしなければなりませんが、属性を取り扱う部分で扱われる必要はありません。
$h->{RaiseError}
DBD ドライバはこの属性についてまったく心配する必要がありません。
$h->{PrintError}
DBD ドライバはこの属性についてまったく心配する必要がありません。
$sth->bind_col()
DBIS->get_fbav() 関数(Cドライバ、以下を参照)、または$sth->_set_fbav($data) メソッド(Perlドライバ)を使うドライバであれば、ドライバはこのルーチンについて何もする必要がありません。
$sth->bind_columns()
ドライバがDBIS->get_fbav()を使っているかどうかに関らず、それが単に内部的に$sth->bind_col()を呼ぶので、ドライバはこのルーチンについて何もする必要がありません。

DBIコードはデフォルトの実装がそのドライバにとって正しくないのでなければ、DBDドライバの作者がそれを書かなくて済むように、以下の関数のデフォルトを実装しています。

$dbh->quote()
データベースがクォートされた文字列のための、シングル・クォートで囲まれ、埋め込まれたシングル・クォートは1つのシングル・クォートにつき2つのシングル・クォートで置き換えるという、ANSI SQL標準を受け取らない場合にのみ、書かれる必要があります。

2つのクォートの形式の引数のために、クォートが必要とする情報を提供するためのtype_infoメソッドを実装する必要があります。

$dbh->ping()
データベースへの接続がまだ生きているかどうかを判定するための単純な効率的な方法として実装されなければなりません。典型的には以下のようになります:
  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 は(データベースのシステム・カタログのような)常に存在するテーブルの名前です


昔のPerlインターフェースのためのエミュレーション・レイアの書き方

考え方については、(DBD::Oracleと一緒に提供される)Oraperl.pm、(DBD::Ingresと一緒に提供される)Ingperl.pmそして対応するdbdimp.cファイルを勉強してください。

エミュレーション・コードは各接続について$dbh->{CompatMode} = 1;を設定していることに注意してください。これはドライバの内部でそれらを扱うときに、古いインターフェースと互換性をもった動きを実装できるようにするためです。


エミュレーションPerl変数を設定する

例えば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);

その他の情報


imp_xyz_tデータ型

すべてのハンドルはプライベートがいっぱい入った対応するC構造体を持ちます。このデータの一部はDBIのために予約されています(以下のDBIcマクロを使うのを除いて)、一部はあなたのためです。例として上記のdbdimp.h の説明をご覧下さい。dbdimp.cのほとんどの関数はハンドルxyzとimp_xyzへのポインタを渡されます。しかしながら、まれに以下のマクロを使うこともできます。

D_imp_dbh(dbh)
関数引数 dbh変数を与えられ、imp_dbhを宣言し、それをハンドルへのプライベート・データへのポインタで初期化します。
注意:これは関数ヘッダの一部でなければなりません、というのも変数を宣言するからです。
D_imp_sth(sth)
ステートメント・ハンドルと同様
D_imp_xxx(h)
すべてのハンドルを与えられ、変数imp_xxx を宣言し、それをハンドルのプライベート・データへのポインタで初期化します。例えば、DBIc_TYPE(imp_xxx) == DBIt_DBであれば、imp_xxximp_dbh_t*にキャストするのは安全です・(sv_derived_from(h, "DBI::db")を呼ぶこともできますが、それはもっと時間がかかります)
D_imp_sth_from_dbh
imp_sthを与えられ、変数imp_dbhを宣言し、それを親データベース・ハンドルの実装構造へのポインタで初期化します。

DBIc_IMPSET_onの使い方

ハンドルを初期化するドライバ・コードは、後片付けコードが呼ばれなければならないような状態になったらすぐに、DBIc_IMPSET_on()を使わなければなりません。これが発生するときはドライバ・コードによって判定されます。

これを呼び出すのに失敗するとデータ構造がおかしくなるかもしれません。例えばDBD::Informixはドライバで、リンクされたデータベース・ハンドルのリストを、そして各ハンドルでは状態のリンクされたリスト管理します。一度リンクされたリストにステートメントが追加されると、片付けられるのは(リストから削除される)のはとても重要です。DBIc_IMPSET_on()が呼ばれるのが遅すぎると、あらゆる種類の障害を起こす可能性があります。


DBIc_is(), DBIc_has(), DBIc_on() and DBIc_off()の使い方

その昔、以下のようなマクロを通すのが内部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によって解除なければなりません、


DBIS->get_fbav()の使い方

DBIの仕様でドキュメント化されている$sth->bind_col() と $sth->bind_columns() はドライバの作者によって実行される必要はありません。というのもDBIが代りに面倒を見てくれるからです。しかしカラムのバインドが機能することを保証する鍵は、データの行を取り出すDBIS->get_fbav() 関数を呼び出すことです。これはAVを返し、そしてAVの各要素は返されるデータを持つように設定されるべきSVを持っています。

上記はCドライバのみです。同等のPerlは純粋なPerlドライバの部分で記述される$sth->_set_fbav($data)メソッドです。


DBIドライバのサブクラス化

これは完全にオープンな議題です。DBD::File ドライバが証明しているように、それは可能です。しかし思っているほど簡単ではないかもしれません。

(このトピックはDBIのサブクラス化とは違うことに注意してください。その例については、DBIと一緒に提供されるt/subclass.tファイルをご覧下さい)

主な問題は、connectprepareメソッドが返すdbhのそしてsthはDBD::Driver::dbDBD::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::Fileconnectメソッドはサブクラスの属性を扱うことが出来ることも注意してください。上記の純粋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.


ホーム Perlの小技

ご意見、ご質問はこちらの掲示板で受け付けています。
またメールは河馬屋(Nifty)にお願いします。