| Tripletail documentation | Contained in the Tripletail distribution. |
Tripletail::DB - DBI のラッパ
$TL->startCgi(
-DB => 'DB',
-main => \&main,
);
sub main {
my $DB = $TL->getDB('DB');
$DB->setDefaultSet('R_Trans');
$DB->tx(sub{
my $sth = $DB->execute(q{SELECT a, b FROM foo WHERE a = ?}, 999);
while (my $hash = $sth->fetchHash) {
$TL->print($hash->{a});
}
# commit is done implicitly.
});
$DB->tx('W_Trans' => sub{
$DB->execute(q{UPDATE counter SET counter = counter + 1 WHERE id = ?}, 1);
$DB->commit; # can commit explicitly.
}
}
手動で接続/切断する場合は、connect/disconnectを使うこともできるが、なるべく使用しないことを推奨。
$db->execute(q{select * from a where mode in (??)}, ['a', 'b'])
と記述すると、
$db->execute(q{select * from a where mode in (?, ?)}, 'a', 'b')
のように解釈される。
$db->execute(q{select * from a limit ??}, [10, \'SQL_INTEGER'])
型指定ができるのは拡張プレースホルダのみです.
通常の ? によるプレースホルダではエラーとなります.
Tripletail::DBでは、レプリケーションを利用してロードバランスすることを支援するため、 1つのDBグループの中に、複数のDBセットを定義することが可能となっている。 DBセットの中には、複数のDBコネクションを定義できる。
更新用DBセット、参照用DBセット、などの形で定義しておき、プログラム中で トランザクション単位でどのDBセットを使用するか指定することで、 更新用クエリはマスタDB、参照用クエリはスレーブDB、といった 使い分けをすることが可能となる。
DBセットには複数のDBコネクションを定義でき、複数定義した場合は プロセス単位でプロセスIDを元に1つのコネクションが選択される。 (プロセスIDを定義数で割り、その余りを使用して決定する。)
同じDBグループの中の複数のDBセットで同じDBコネクション名が使用された場合は、 実際にDBに接続されるコネクション数は1つとなる。 このため、縮退運転時に参照用DBセットのDBコネクションを更新用の ものに差し替えたり、予め将来を想定して多くのDBセットに分散 させておくことが可能となっている。
DBセットの名称はSET_XXXX(XXXXは任意の文字列)でなければならない。 DBコネクションの名称はCON_XXXX(XXXXは任意の文字列)でなければならない。
いずれのDBコネクションも利用可能である必要があり、 接続できなかった場合はエラーとなる。
DBのフェイルオーバーには(現時点では)対応していない。
Tripletail の DB クラスは DBI に対するラッパの形となっており、多くのインタフェースは DBI のものとは異なる。
ただし、いつでも $DB->getDbh() メソッドにより元の DBI オブジェクトを取得できるので、 DBI のインタフェースで利用することも可能となっている。
DBI のインタフェースは以下のようなケースで利用できる。 ただし、 DBI を直接利用する場合は、TLの拡張プレースホルダやデバッグ機能、トランザクション整合性の管理などの機能は利用できない。
DBI に対するラッパであるため、大量の SQL を実行する場合などはパフォーマンス上のデメリットがある。
DBI での SELECT は、以下のように置き換えられる。
# DBI
my $sth = $DB->prepare(q{SELECT * FROM test WHERE id = ?});
$sth->execute($id);
while(my $data = $sth->fetchrow_hashref) {
}
# TL
my $sth = $DB->execute(q{SELECT * FROM test WHERE id = ?}, $id);
while(my $data = $sth->fetchHash) {
}
TL では prepare/execute は一括で行い、 prepared statement は利用できない。
INSERT・UPDATEは、以下のように置き換えられる。
# DBI
my $sth = $DB->prepare(q{INSERT INTO test VALUES (?, ?)});
my $ret = $sth->execute($id, $data);
# TL
my $sth = $DB->execute(q{INSERT INTO test VALUES (?, ?)}, $id, $data);
my $ret = $sth->ret;
prepare/execute を一括で行うのは同様であるが、 execute の戻り値は$sthオブジェクトであり、影響した行数を取得するためには $sth->ret メソッドを呼ぶ必要がある。
プレースホルダの型指定は以下のように行う。
# DBI
my $sth = $DB->prepare(q{SELECT * FROM test LIMIT ?});
$sth->bind_param(1, $limit, { TYPE => SQL_INTEGER });
$sth->execute;
# TL
my $sth = $DB->execute(q{SELECT * FROM test LIMIT ??}, [$limit, \'SQL_INTEGER']);
TLの拡張プレースホルダ(??で表記される)を利用し、配列のリファレンスの最後に型をスカラのリファレンスの形で渡す。 拡張プレースホルダでは、複数の値を渡すことも可能である。
# DBI
my $sth = $DB->prepare(q{SELECT * FROM test LIMIT ?, ?});
$sth->bind_param(1, $limit, { TYPE => SQL_INTEGER });
$sth->bind_param(2, $offset, { TYPE => SQL_INTEGER });
$sth->execute;
# TL
my $sth = $DB->execute(q{SELECT * FROM test LIMIT ??}, [$limit, $offset, \'SQL_INTEGER']);
INSERTした行のAUTO_INCREMENT値の取得は、getLastInsertId で行える。
# DBI
my $id = $DB->{mysql_insertid};
# TL
my $id = $DB->getLastInsertId;
拡張ラッパでは制御できない機能にアクセスする場合などは、 DBI のハンドラを直接利用する。
# DBI
my $id = $DB->{RowCacheSize};
# TL
my $id = $DB->getDbh()->{RowCacheSize};
トランザクションには $DB->tx(sub{...}) メソッドを用いる。
DBセットを指定する時には $DB->tx(dbset_name=>sub{...}) となる。
渡したコードをトランザクション内で実行する。
die なしにコードを抜けた時に自動的にコミットされる。
途中で die した場合にはトランザクションはロールバックされる。
# DBI
$DB->do(q{BEGIN WORK});
# do something.
$DB->commit;
# TL
$DB->tx(sub{
# do something.
});
begin() メソッドも実装はされているがその使用は非推奨である。
また、 $DB->execute(q{BEGIN WORK}); として利用することはできない。
"execute" に渡される SQL 文には、通常のプレースホルダの他に、 拡張プレースホルダ "??" を埋め込む事が出来る。 拡張プレースホルダの置かれた場所には、パラメータとして通常のスカラー値でなく、 配列へのリファレンスを与えなければならない。配列が複数の値を持っている場合には、 それらが通常のプレースホルダをカンマで繋げたものに展開される。
例: 以下の二文は等価
$DB->execute(
q{SELECT * FROM a WHERE a IN (??) AND b = ?},
['AAA', 'BBB', 'CCC'], 800);
$DB->execute(
q{SELECT * FROM a WHERE a IN (?, ?, ?) AND b = ?},
'AAA', 'BBB', 'CCC', 800);
パラメータとしての配列の最後の項目が文字列へのリファレンスである時、その文字列は SQL 型名として扱われる。配列が複数の値を持つ時には、その全ての要素に対して 型指定が適用される。型名はDBI.pmで定義される。
例:
$DB->execute(q{SELECT * FROM a LIMIT ??}, [20, \'SQL_INTEGER']);
==> SELECT * FROM a LIMIT 20
$DB->execute(q{SELECT * FROM a LIMIT ??}, [20, 5, \'SQL_INTEGER']);
==> SELECT * FROM a LIMIT 20, 5
配列内の要素を更に2要素の配列とし、二番目の要素を文字列へのリファレンスと する事で、要素の型を個別に指定出来る。
例:
$DB->execute(
q{SELECT * FROM a WHERE a IN (??) AND b = ?},
[[100, \'SQL_INTEGER'], 'foo', \'SQL_VARCHAR'], 800);
==> SELECT * FROM a WHERE a IN (100, 'foo') AND b = '800'
Tripletail::DB メソッド$TL->getDB$DB = $TL->getDB $DB = $TL->getDB($inigroup)
Tripletail::DB オブジェクトを取得。 引数には Ini で設定したグループ名を渡す。 引数省略時は 'DB' グループが使用される。
$TL->startCgi / $TL->trapError の関数内でDBオブジェクトを取得する場合に使用する。
$TL->newDB$DB = $TL->newDB $DB = $TL->newDB($inigroup)
新しく Tripletail::DB オブジェクト作成。 引数には Ini で設定したグループ名を渡す。 引数省略時は 'DB' グループが使用される。
動的にコネクションを作成したい場合などに使用する。 この方法で Tripletail::DB オブジェクトを取得した場合、"connect" / "disconnect" を呼び出し、接続の制御を行う必要がある。
connectDBに接続する。
$TL->startCgi / $TL->trapError の関数内でDBオブジェクトを取得する場合には自動的に接続が管理されるため、このメソッドを呼び出してはならない。
$TL->newDB|"$TL->newDB" で作成した Tripletail::DB オブジェクトに関しては、このメソッドを呼び出し、DBへ接続する必要がある。
connect時には、AutoCommit 及び RaiseError オプションは 1 が指定され、PrintError オプションは 0 が指定される。
disconnectDBから切断する。
$TL->startCgi / $TL->trapError の関数内でDBオブジェクトを取得する場合には自動的に接続が管理されるため、このメソッドを呼び出してはならない。
$TL->newDB|"$TL->newDB" で作成した Tripletail::DB オブジェクトに関しては、このメソッドを呼び出し、DBへの接続を切断する必要がある。
tx $DB->tx(sub{...})
$DB->tx('SET_W_Trans' => sub{...})
指定されたDBセット名でトランザクションを開始し、その中でコードを 実行する。トランザクション名(DBセット名) は ini で定義されていな ければならない。名前を省略した場合は、デフォルトのDBセットが使われるが、 setDefaultSetによってデフォルトが選ばれていない場合には例外を発生させる。
コードを die なしに終了した時にトランザクションは暗黙にコミットされる。
die した場合にはロールバックされる。
コードの中で明示的にコミット若しくはロールバックを行うこともできる。
明示的にコミット若しくはロールバックをした後は、 tx を抜けるまで
DB 操作は禁止される。 この間の DB 操作は例外を発生させる。
rollback$DB->rollback
現在実行中のトランザクションを取り消す。
commit$DB->commit
現在実行中のトランザクションを確定する。
inTx $DB->inTx() and die "double transaction";
$DB->inTx('SET_W_Trans') or die "transaction required";
既にトランザクション中であるかを確認する。 既にトランザクション中であれば真を、 他にトランザクションが走っていなければ偽を返す。 トランザクションの指定も可能。 異なるDBセット名のトランザクションが実行中だった場合には 例外を発生させる。
begin $DB->begin
$DB->begin('SET_W_Trans')
非推奨。tx を使用のこと。
指定されたDBセット名でトランザクションを開始する。トランザクション名 (DBセット名) は ini で定義されていなければならない。 名前を省略した場合は、デフォルトのDBセットが使われるが、 setDefaultSetによってデフォルトが選ばれていない場合には例外を発生させる。
CGIの中でトランザクションを開始し、終了せずに Main 関数を抜けた場合は、自動的に
rollbackされる。
トランザクション実行中にこのメソッドを呼んだ場合には、例外を発生させる。 1度に開始出来るトランザクションは、1つのDBグループにつき1つだけとなる。
setDefaultSet $DB->setDefaultSet('SET_W_Trans')
デフォルトのDBセットを選択する。ここで設定されたDBセットは、引数無しのbegin() や、beginせずに行ったexecuteの際に使われる。このメソッドは Main 関数 の先頭で呼ばれる事を想定している。
execute$DB->execute($sql, $param...) $DB->execute(\'SET_W_Trans' => $sql, $param...)
SELECT/UPDATE/DELETEなどの SQL 文を実行する。
第1引数に SQL 、第2引数以降にプレースホルダの引数を渡す。
ただし、第1引数にリファレンスでDBセットを渡すことにより、
トランザクション外での実行時にDBセットを指定することが可能。
第2引数以降の引数では、拡張プレースホルダが使用できる。 "拡張プレースホルダ詳細" を参照。
既にトランザクションが実行されていれば、そのトランザクションの DBセットで SQL が実行される。
トランザクションが開始されておらず、かつ "lock" により テーブルがロックされていれば、ロックをかけているDBセットで SQL が実行される。
いずれの場合でもない場合は、"setDefaultSet" で指定された トランザクションが使用される。 "setDefaultSet" による設定がされていない場合は、例外を発生させる。
このメソッドを使用して、LOCK/UNLOCK/BEGIN/COMMITといった SQL 文を
実行してはならない。実行しようとした場合は例外を発生させる。
代わりに専用のメソッドを使用する事。
selectAllHash$DB->selectAllHash($sql, $param...) $DB->selectAllHash(\'SET_W_Trans' => $sql, $param...)
SELECT結果をハッシュの配列へのリファレンスで返す。 データがない場合は [] が返る。
my $arrayofhash = $DB->selectAllHash($sql, $param...);
foreach my $hash (@$arrayofhash){
$TL->log(DBDATA => "name of id $hash->{id} is $hash->{name}");
}
selectAllArray$DB->selectAllArray($sql, $param...) $DB->selectAllArray(\'SET_W_Trans' => $sql, $param...)
SELECT結果を配列の配列へのリファレンスで返す。 データがない場合は [] が返る。
my $arrayofarray = $DB->selectAllArray($sql, $param...);
foreach my $array (@$arrayofarray){
$TL->log(DBDATA => $array->[0]);
}
selectRowHash$DB->selectRowHash($sql, $param...) $DB->selectRowHash(\'SET_W_Trans' => $sql, $param...)
SELECT結果の最初の1行をハッシュへのリファレンスで返す。
実行後、内部でfinishする。
データがない場合は undef が返る。
my $hash = $DB->selectRowHash($sql, $param...);
$TL->log(DBDATA => "name of id $hash->{id} is $hash->{name}");
selectRowArray$DB->selectRowArray($sql, $param...) $DB->selectRowArray(\'SET_W_Trans' => $sql, $param...)
SELECT結果の最初の1行を配列へのリファレンスで返す。
実行後、内部でfinishする。
データがない場合は undef が返る。
my $array = $DB->selectRowArray($sql, $param...); $TL->log(DBDATA => $array->[0]);
lock$DB->lock(set => 'SET_W_Trans', read => ['A', 'B'], write => 'C')
指定されたDBセットに対してLOCK TABLESを実行する。setが省略された場合はデフォルト
のDBセットが選ばれる。 CGI の中でロックした場合は、 Main 関数
を抜けた時点で自動的に unlock される。
ロック実行中にこのメソッドを呼んだ場合には、例外を発生させる。 1度に開始出来るロックは、1つのDBグループにつき1つだけとなる。
現在 mysql でのみ使用可能.
mysql ではロック中にテーブルのエイリアスを使用する場合、エイリアスに対してもロックを指定する必要がある。これを行うには、テーブル名の文字列の替わりにハッシュのリファレンス {'テーブル名' => 'エイリアス'} を指定する。次に、テーブル sample とそのエイリアス A, B をロックする例を示す。
$DB->lock(read => ['sample', {'sample' => 'A'}, {'sample' => 'B'}]);
$DB->execute(q{
SELECT sample.nval, A.nval as A, B.nval as B
FROM sample, sample AS A, sample AS B
WHERE sample.nval + 1 = A.nval AND A.nval + 1 = B.nval
});
$DB->unlock;
unlock$DB->unlock
UNLOCK TABLES を実行する。
ロックがかかっていない場合は例外を発生させる。
現在 mysql でのみ使用可能.
setBufferSize$DB->setBufferSize($kbytes)
バッファサイズをKB単位でセットする。行を1行読み込んだ結果
このサイズを上回る場合、dieする。
0 または undef をセットすると、制限が解除される。
symquote$DB->symquote($sym)
文字列を識別子としてクォートする。
mysql の場合は `a b c` となり、それ以外の場合は "a b c" となる。
getType$DB->getType;
DBのタイプを返す。(mysql, pgsql, ...)
getDbh $dbh = $DB->getDbh
$dbh = $DB->getDbh('SET_W_Trans')
DBセット内のDBハンドルを返す。
返されるオブジェクトは DBI ネイティブのdbhである。
ネイティブのDBハンドルを使用してクエリを発行した場合、デバッグ機能(プロファイリング等)の機能は使用できません。 また、トランザクションやロック状態の管理もフレームワークで行えなくなるため、注意して使用する必要があります。
getLastInsertId$id = $DB->getLastInsertId()
セッション内の最後の自動採番の値を取得.
Tripletail::DB::STH メソッドfetchHash$sth->fetchHash
ハッシュへのリファレンスで1行取り出す。
fetchArray$sth->fetchArray
配列へのリファレンスで1行取り出す。
ret$sth->ret
最後に実行した execute の戻り値を返す。
rows$sth->rows
DBI と同様。
finish$sth->finish
DBI と同様。
nameArray$sth->nameArray
$sth->{NAME_lc} を返す。
nameHash$sth->nameHash
$sth->{NAME_lc_hash} を返す。
DBグループのパラメータのうち、半角小文字英数字のみで構成された パラメータは予約済みで、DBグループの動作設定に使用する。 DBセットは、予約済みではない名前であれば任意の名称が使用でき、 値としてDBコネクションのINIグループ名をカンマ区切りで指定する。
例:
[DB] namequery=1 type=mysql defaultset=SET_R_Trans SET_W_Trans=CON_DBW1 SET_R_Trans=CON_DBR1,CON_DBR2 [CON_DBW1] dbname=test user=daemon host=192.168.0.100 [CON_DBR1] dbname=test user=daemon host=192.168.0.110 [CON_DBR2] dbname=test user=daemon host=192.168.0.111
以下は特別なパラメータ:
namequerynamequery = 1
これを1にすると、実行しようとしたクエリのコマンド名の直後に
/* foo.pl:111 [DB.R_Transaction1.DBR1] */ のようなコメントを挿入する。
デフォルトは0。
typetype = mysql
DBの種類を選択する。 mysql, pgsql, oracle, sqlite, mssql が使用可能。 必須項目。
defaultsetdefaultset = SET_W_Trans
デフォルトのDBセットを設定する。 ここで設定されたDBセットは、引数無しのbegin()や、beginせずに行ったexecuteの際に使われる。
dbnamedbname = test
DB名を設定する。
hosthost = localhost
DBのアドレスを設定する。
デフォルトはlocalhost。
useruser = www
DBに接続する際のユーザー名を設定する。
passwordpassword = PASS
DBに接続する際のパスワードを設定する。 省略可能。
mysql_read_default_filemysql_read_default_file = .../tl_mysql.cnf
mysql クライアントライブラリが使用する設定ファイル my.cnf のパスを指定する。 パスの指定を .../ で始めることで、 ini ファイルからの相対パスとして指定する事も可能。 設定ファイルを使用する事で、 default-character-set 等の Tripletail::DB や DBD::mysql からは設定できない項目が設定できる。 また、設定ファイルで user, password, host 等の値を指定する場合は、 Ini パラメータ のDBコネクションの値を省略する事ができる。(dbname だけは省略できない)
mysql_read_default_groupmysql_read_default_group = tripletail
mysql_read_default_file 指定時に、設定ファイル中のどのグループを使用するかを指定する。 グループを指定した場合は、 [client] グループの設定と指定したグループの設定の両方が有効になる。 グループを指定しない場合、 [client] グループの設定のみが有効となる。
試験的に SQL Server との接続が実装されています. DBD::ODBC と, Linux であれば unixODBC + freetds で, Windows であれば 組み込みの ODBC マネージャで動作します.
設定例:
# <tl.ini> [DB] type=mssql defaultset=SET_W_Trans SET_W_Trans=CON_RW [CON_RW] # dbname に ODBC-dsn を設定. dbname=test user=test password=test # freetds経由の時は, そちらのServernameも指定. tdsname=tds_test
freetds での接続文字コードの設定は freetds.conf で 設定します.
;; <freetds.conf> [tds_test] host = 10.0.0.1 ;;port = 1433 tds version = 7.0 client charset = UTF-8
Copyright 2006 YMIRLINK Inc.
This framework is free software; you can redistribute it and/or modify it under the same terms as Perl itself
このフレームワークはフリーソフトウェアです。あなたは Perl と同じライセンスの 元で再配布及び変更を行うことが出来ます。
Address bug reports and comments to: tl@tripletail.jp
HP : http://tripletail.jp/
| Tripletail documentation | Contained in the Tripletail distribution. |
# ----------------------------------------------------------------------------- # Tripletail::DB - DBIã®ã©ãã # ----------------------------------------------------------------------------- package Tripletail::DB; use strict; use warnings; use Tripletail; require Time::HiRes; use DBI qw(:sql_types); sub _INIT_REQUEST_HOOK_PRIORITY() { -1_000_000 } # é åºã¯åããªã sub _POST_REQUEST_HOOK_PRIORITY() { -1_000_000 } # ã»ãã·ã§ã³ããã¯ã®å¾ sub _TERM_HOOK_PRIORITY() { -1_000_000 } # ã»ãã·ã§ã³ããã¯ã®å¾ our $INSTANCES = {}; # ã°ã«ã¼ãå => ã¤ã³ã¹ã¿ã³ã¹ sub _TX_STATE_NONE() { 0 } sub _TX_STATE_ACTIVE() { 1 } sub _TX_STATE_CLOSEWAIT() { 2 } our @TX_STATE_NAME = qw(NONE ACTIVE CLOSEWAIT); 1; sub _getInstance { my $class = shift; my $group = shift; if(!defined($group)) { $group = 'DB'; } elsif(ref($group)) { die "TL#getDB: arg[1] is a reference. (第1弿°ããªãã¡ã¬ã³ã¹ã§ã)\n"; } my $obj = $INSTANCES->{$group}; if(!$obj) { die "TL#getDB: DB group [$group] was not passed to the startCgi() / trapError(). (startCgi/trapErrorã®DBã«æå®ããã¦ããªãDBã°ã«ã¼ã[${group}]ãæå®ããã¾ãã)\n"; } $obj; } sub _reconnectSilentlyAll { # fork ãããå¾ãåããã»ã¹å´ã§å¼ã°ãããç¾å¨ $INSTANCES ã«ä¿åããã¦ãã # DBI-dbh ã¯å ¨ã¦è¦ªã¨å ±æããã¦ããã®ã§ããããå ¨ã¦ã® InactiveDestroy ãã©ã°ã # ç«ã¦ã¦ããæ¥ç¶ãç´ããªããã°ãªããªãã foreach my $db (values %$INSTANCES) { $db->_reconnectSilently; } return; } sub _reconnectSilently { my $this = shift; # å ¨ã¦ã® DB ã³ãã¯ã·ã§ã³ã® InactiveDestroy ãã©ã°ãç«ã¦ã¦ãã忥ç¶ããã foreach my $dbh (values %{$this->{dbname}}) { $dbh->getDbh->{InactiveDestroy} = 1; $dbh->connect($this->{type}); } $this->{tx_state} = _TX_STATE_NONE; $this; } # ã¨ã©ã¼æã«, DBå¥åºæã¨ã©ã¼æ å ±ããå ±éã¨ã©ã¼æ å ±ã«å¤æ. # å ¨ã¦ã®DB種å¥ã§å®è£ ããã¦ããããã§ã¯ãªã. # å®è£ ããã¦ããªããã°å¸¸ã« COMMON_ERROR æ±ã. sub _errinfo { my $pkg = shift; my $dbh = shift; my $type = shift || (ref($pkg)&&$pkg->getType()); $dbh or die __PACKAGE__."#_errinfo, no dbh. (dbhãããã¾ãã)"; $type or die __PACKAGE__."#_errinfo, no type. (typeãããã¾ãã)"; our $_ERRMAP ||= {}; $_ERRMAP->{$type} ||= do{ # $pkg->_load_errmap_mysql(); my $subname = "_load_errmap_$type"; my $sub = $pkg->can($subname); $sub ? $pkg->$sub() : {}; }; my $errno; # DBåºæã®ã¨ã©ã¼ã³ã¼ã. my $errstr; # DBåºæã®ã¨ã©ã¼ã¡ãã»ã¼ã¸. my $errkey; # å ±éã®ã¨ã©ã¼èå¥ãã¼. if( $type eq 'mysql' ) { $errno = $dbh->{mysql_errno}; $errstr = $dbh->{mysql_error}; $errkey = $_ERRMAP->{$type}{$errno} || 'COMMON_ERROR'; }elsif( $type eq 'sqlite' ) { $errno = $dbh->err; $errstr = $dbh->errstr; $errkey = $_ERRMAP->{$type}{$errno} || 'COMMON_ERROR'; if( $errno==1 ) { # ã¡ããã¨ã¨ã©ã¼çªå·ãè¿ã£ã¦ããªã? if( $errstr =~ /^no such table:/ ) { $errkey = 'NO_SUCH_OBJECT'; }elsif( $errstr =~ /^unable to open database file/ ) { $errkey = $!{EACCES} ? 'CONNECT_DENIED' : 'CONNECT_NO_SERVER'; }elsif( $errstr =~ /^attempt to write a readonly database/ ) { $errkey = 'ACCESS_DENIED'; } } }else { $errno = $dbh->err; $errstr = $dbh->errstr; $errkey = 'COMMON_ERROR'; #die __PACKAGE__."#_errinfo: DB type [$type] is not supported. (DB type [$type] ã¯ãµãã¼ãããã¦ãã¾ãã)\n"; } $pkg->_errinfo2($type, $errkey, $errno, $errstr); } sub _errinfo2 { my $pkg = shift; my $type = shift; my $errkey = shift || 'COMMON_ERROR'; my $errno = shift; my $errstr = shift; our $_ERRMSG ||= $pkg->_load_errmsg(); my $errmsg = $_ERRMSG->{$errkey}; my ($errmsg_en, $errmsg_ja) = $errmsg =~ /^(.*) \((.*)\)\z/ or die "invalid message format: $errmsg"; my $info = { $errkey => $errmsg, _key => $errkey, _msg => $errmsg, _msg_en => $errmsg_en, _msg_ja => $errmsg_ja, _dbtype => $type, _dberrno => $errno, _dberrstr => $errstr, }; } # å ±éã¨ã©ã¼ã³ã¼ãã®å¯èªã¡ãã»ã¼ã¸. sub _load_errmsg { +{ ACCESS_DENIED => 'Access denied (ã¢ã¯ã»ã¹æ¨©éãããã¾ãã)', ALREADY_EXISTS => 'Already exists (å¦çå¯¾è±¡ãæ¢ã«åå¨ãã¾ã)', COMMON_ERROR => 'Error (ä½ããã®ã¨ã©ã¼)', CONNECT_DENIED => 'Connection denied (æ¥ç¶ããæ¨©éãããã¾ãã)', CONNECT_NO_SERVER => 'No server to connect (æ¥ç¶å ãµã¼ããåå¨ãã¾ãã)', CONNECT_PROTOCOL_MISMATCH => 'Connection protocol mismatch (æ¥ç¶ãããã³ã«ãä¸è´ãã¾ãã)', NO_ERROR => 'Success (å¦çæå)', NO_SUCH_OBJECT => 'No such object (å¦ç対象ãåå¨ãã¾ãã)', SYNTAX_ERROR => 'Syntax error (æ§æã¨ã©ã¼)', }; } # ã¨ã©ã¼æ å ±ã®DBå¥åºæã¨ã©ã¼ã³ã¼ãããå ±éã³ã¼ãã¸ã®ãããã³ã°(mysql). sub _load_errmap_mysql { # http://dev.mysql.com/doc/refman/5.0/en/error-messages-server.html # http://dev.mysql.com/doc/refman/5.0/en/error-messages-client.html # +{ 0 => 'NO_ERROR', 1044 => 'ACCESS_DENIED', 1045 => 'CONNECT_DENIED', 1050 => 'ALREADY_EXISTS', 1064 => 'SYNTAX_ERROR', 1146 => 'NO_SUCH_OBJECT', 1149 => 'SYNTAX_ERROR', 1251 => 'CONNECT_PROTOCOL_MISMATCH', 2002 => 'CONNECT_NO_SERVER', 2003 => 'CONNECT_NO_SERVER', 2005 => 'CONNECT_NO_SERVER', }; } sub _load_errmap_pgsql { # http://www.postgresql.jp/document/pg836doc/html/errcodes-appendix.html +{ 00000 => 'NO_ERROR', }; } # ã¨ã©ã¼æ å ±ã®DBå¥åºæã¨ã©ã¼ã³ã¼ãããå ±éã³ã¼ãã¸ã®ãããã³ã°(sqlite). sub _load_errmap_sqlite { # http://www.sqlite.org/c3ref/c_abort.html +{ 0 => 'NO_ERROR', }; } sub connect { my $this = shift; # å ¨ã¦ã®DBã³ãã¯ã·ã§ã³ã®æ¥ç¶ã確ç«ããï¼ foreach my $dbh (values %{$this->{dbname}}) { if(!$dbh->ping) { $dbh->connect($this->{type}); } } $this->{tx_state} = _TX_STATE_NONE; $this; } sub disconnect { my $this = shift; foreach my $dbh (values %{$this->{dbname}}) { $dbh->disconnect; } $this->{tx_state} = _TX_STATE_NONE; $this; } sub tx { my $this = shift; my $setname = !ref($_[0]) && shift; my $sub = shift; my @ret; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken(); my $succ = 0; my $anchor = Tripletail::DB::_scope->new(sub{ $succ and return; # on exception. if( $this->{tx_state}==_TX_STATE_ACTIVE ) { $this->rollback(); } $this->{tx_state} = _TX_STATE_NONE; }); $this->begin($setname); local($Tripletail::Error::LAST_DBH) = $this; $this->{tx_state} = _TX_STATE_ACTIVE; if( wantarray ) { @ret = $sub->(); }else { $ret[0] = $sub->(); } if( $this->{trans_dbh} ) { $this->commit(); } $this->{tx_state} = _TX_STATE_NONE; $succ = 1; wantarray ? @ret : $ret[0]; } sub _closewait_broken { my $this = shift; my $where = shift; if( !$where ) { $where = (caller(1))[3]; $where =~ s/.*:://; } die __PACKAGE__."#$where: you can't do anything related to DB after doing rollback or commit in tx(). (txã®ä¸ã§rollback/commitããå¾ã¯SQLãå®è¡ã§ãã¾ãã)\n"; } sub inTx { my $this = shift; my $set = shift; $this->_requireTx($set, 'inTx'); } sub _requireTx { my $this = shift; my $setname = shift; my $where = shift; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken($where); if( my $trans = $this->{trans_dbh} ) { my $set = $this->_getDbSetName($setname); my $trans_set = $trans->getSetName; if($trans_set eq $set) { # same transaction. return 1; } # another transaction running, always die. die __PACKAGE__."#$where: attempted to begin a". " new transaction on DB Set [$set] but". " another DB Set [$trans_set] were already in transaction.". " Commit or rollback it before beginning another one.". " (DB Set [$trans_set] ã§ãã©ã³ã¶ã¯ã·ã§ã³ãå®è¡ä¸ã« DB Set [$set] ã§ãã©ã³ã¶ã¯ã·ã§ã³ãéå§ãããã¨ãã¾ããã". "å¥ã® DB Set ã§ãã©ã³ã¶ã¯ã·ã§ã³ãéå§ããåã«commit/rollbackããå¿ è¦ãããã¾ã)\n"; }else { # no transaction. return 0; } } sub begin { my $this = shift; my $setname = shift; my $set = $this->_getDbSetName($setname); $this->_requireTx($setname, 'begin'); my $begintime = [Time::HiRes::gettimeofday()]; my $dbh = $this->{dbh}{$set}; $dbh->begin; my $elapsed = Time::HiRes::tv_interval($begintime); my $sql = $this->__nameQuery('BEGIN', $dbh); $TL->getDebug->_dbLog(sub{ group => $this->{group}, set => $dbh->getSetName, db => $dbh->getGroup, id => -1, query => $sql, params => [], elapsed => $elapsed, }); $this->{trans_dbh} = $dbh; $this; } sub rollback { my $this = shift; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken(); my $dbh = $this->{trans_dbh}; if(!defined($dbh)) { die __PACKAGE__."#rollback: not in transaction. (ãã©ã³ã¶ã¯ã·ã§ã³ã®å®è¡ä¸ã§ã¯ããã¾ãã)\n"; } my $begintime = [Time::HiRes::gettimeofday()]; $dbh->rollback; if( $this->{tx_state}==_TX_STATE_ACTIVE ) { $this->{tx_state} = _TX_STATE_CLOSEWAIT; } my $elapsed = Time::HiRes::tv_interval($begintime); my $sql = $this->__nameQuery('ROLLBACK', $dbh); $TL->getDebug->_dbLog(sub{ group => $this->{group}, set => $dbh->getSetName, db => $dbh->getGroup, id => -1, query => $sql, params => [], elapsed => $elapsed, }); $this->{trans_dbh} = undef; $this; } sub commit { my $this = shift; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken(); my $dbh = $this->{trans_dbh}; if (!defined($dbh)) { die __PACKAGE__."#commit: not in transaction. (ãã©ã³ã¶ã¯ã·ã§ã³ã®å®è¡ä¸ã§ã¯ããã¾ãã)\n"; } my $begintime = [Time::HiRes::gettimeofday()]; $dbh->commit; if( $this->{tx_state}==_TX_STATE_ACTIVE ) { $this->{tx_state} = _TX_STATE_CLOSEWAIT; } my $elapsed = Time::HiRes::tv_interval($begintime); my $sql = $this->__nameQuery('COMMIT', $dbh); $TL->getDebug->_dbLog(sub{ group => $this->{group}, set => $dbh->getSetName, db => $dbh->getGroup, id => -1, query => $sql, params => [], elapsed => $elapsed, }); $this->{trans_dbh} = undef; $this; } sub setDefaultSet { my $this = shift; my $setname = shift; if(defined($setname)) { $this->{default_set} = $this->_getDbSetName($setname); } else { $this->{default_set} = undef; } $this; } sub execute { my $this = shift; my $dbset = shift; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken(); if(ref($dbset)) { $dbset = $$dbset; } else { unshift(@_, $dbset); $dbset = undef; } my $sql = shift; my $sql_backup = $sql; # ãããã°ç¨ if(!defined($sql)) { die __PACKAGE__."#execute: arg[1] is not defined. (第1弿°ãæå®ããã¦ãã¾ãã)\n"; } elsif(ref($sql)) { die __PACKAGE__."#execute: arg[1] is a reference. (第1弿°ããªãã¡ã¬ã³ã¹ã§ã)\n"; } elsif($sql =~ m/^\s*(LOCK|UNLOCK|BEGIN|ROLLBACK|COMMIT)/i) { # ãããã®SQLæãexecuteããã¨æ´åæ§ã失ãããã die __PACKAGE__."#execute: attempted to execute [$1] statement directly.". " Use special methods not to ruin the consistency of Tripletail::DB.". " ($1ã¯Tripletail::DBã®ç¶æ 管çã«å½±é¿ãä¸ããããexecuteã§å®è¡ã§ãã¾ãããå°ç¨ã®ã¡ã½ãããå©ç¨ãã¦ãã ãã)\n"; } my @params; if($sql =~ m/\?\?/) { # ãã©ã¡ã¼ã¿ã®ä¸ããARRAY Refã®ãã®ãå ¨ã¦æãåºãã ?? ã ?, ?, ... ã«ç½®æ foreach my $param (@_) { if(!ref($param)) { push @params, $param; } elsif(ref($param) eq 'ARRAY') { if(@$param == 0) { # 0è¦ç´ ã®é åããã£ã¦ã¯ãªããªãã die __PACKAGE__."#execute: some arguments are an empty array. (空ã®é åã¸ã®ãªãã¡ã¬ã³ã¹ã渡ããã¾ãã)\n"; } my $n_params = @$param; if(ref($param->[-1]) eq 'SCALAR') { # æå¾ã®è¦ç´ ãSCALAR Refãªããããã¯å ¨ä½ã®åæå®ã my $type = $param->[-1]; $n_params--; for(my $i = 0; $i < @$param - 1; $i++) { if(ref($param->[$i]) eq 'ARRAY') { # ããã¯åå¥ã«åãæå®ããã¦ããã®ã§ãããã©ã«ãã®åãé©ç¨ããªãã push @params, $param->[$i]; } else { push @params, [$param->[$i], $type]; } } } else { push @params, @$param; } unless($sql =~ s{\?\?}{ join(', ', ('?') x $n_params); }e) { die __PACKAGE__."#execute: the number of `??' is fewer than the number of given parameters. (??ã®æ°ãä¸è¶³ãã¦ãã¾ã)\n"; } } else { die __PACKAGE__."#execute: arg[$param] is not a scalar nor ARRAY Ref. (arg[$param]ã¯ã¹ã«ã©ã§ãé åã¸ã®ãªãã¡ã¬ã³ã¹ã§ãããã¾ãã)\n"; } } if($sql =~ m/\?\?/) { die __PACKAGE__."#execute: the number of given parameters is fewer than the number of `??'. (??ã®æ°ã«å¯¾ãã¦å¼æ°ã®æ°ãä¸è¶³ãã¦ãã¾ã)\n"; } } else { @params = @_; # ãã®ä¸ã«ARRAY Refãå ¥ã£ã¦ãã¦ã¯ãªããªãã if(grep {ref eq 'ARRAY'} @params) { die __PACKAGE__."#execute: use `??' instead of `?' if you want to use ARRAY Ref as a bind parameter.". " (é åã¸ã®ãªãã¡ã¬ã³ã¹ã¯ ?? ã«å¯¾ãã¦ã®ã¿ä½¿ç¨ã§ãã¾ã)\n"; } } # executeãè¡ãDBã»ãããæ¢ã my $dbh = undef; if(defined($dbset)) { #DBã»ãããæç¤ºçã«æå®ããã $dbh = $this->{dbh}{$dbset}; if(!$dbh) { die __PACKAGE__."#execute: DB set [$dbset] is unavailable. (DB Set [$dbset] ã®æå®ã䏿£ã§ã)\n"; } } else { $dbh = $this->{trans_dbh}; $dbh = $this->{locked_dbh} if(!$dbh); $dbh = $this->{dbh}{$this->_getDbSetName} if(!$dbh); } if( $dbh->{bindconvert} ) { my $sub = $dbh->{bindconvert}; $dbh->$sub(\$sql, \@params); } my $sth = Tripletail::DB::STH->new( $this, $dbh, $dbh->getDbh->prepare($sql) ); if( $dbh->{fetchconvert} ) { my $sub = $dbh->{fetchconvert}; $dbh->$sub($sth, new => [\$sql, \@params]); } # å ¨ã¦ã®ãã©ã¡ã¼ã¿ãbind_paramããã for(my $i = 0; $i < @params; $i++) { my $p = $params[$i]; my $argno = $i + 2; if(!ref($p)) { $sth->{sth}->bind_param($i + 1, $p); } elsif(ref($p) eq 'ARRAY') { if(@$p != 2 || ref($p->[1]) ne 'SCALAR') { die __PACKAGE__."#execute: arg[$argno]: attempted to bind an invalid array: [".join(', ', @$p)."]". " (第${argno}弿°ã«ä¸æ£ãªå½¢å¼ã®é åãæ¸¡ããã¾ãã)\n"; } my $type = ${$p->[1]}; my $typeconst = $this->{types_symtable}{$type}; if(!$typeconst) { die __PACKAGE__."#execute: arg[$argno] is an invalid sql type: [$type] (第${argno}弿°ã®SQLåæå®ã䏿£ã§ã)\n"; } $p->[1] = *{$typeconst}{CODE}->(); $sth->{sth}->bind_param($i + 1, @$p); } else { die __PACKAGE__."#execute: arg[$argno] is an unacceptable reference. [$p] (第${argno}弿°ã«ä¸æ£ãªãªãã¡ã¬ã³ã¹ã渡ããã¾ãã)\n"; } } $sql = $this->__nameQuery($sql, $dbh); $sql_backup = $this->__nameQuery($sql_backup, $dbh); my $begintime = [Time::HiRes::gettimeofday()]; my $log_params = \@_; while(1) { eval { local $SIG{__DIE__} = 'DEFAULT'; $sth->{ret} = $sth->{sth}->execute; }; if( my $err = $@ ) { my $elapsed = Time::HiRes::tv_interval($begintime); $TL->getDebug->_dbLog(sub{ group => $this->{group}, set => $dbh->getSetName, db => $dbh->getGroup, id => $sth->{id}, query => $sql_backup . " /* ERROR: $err */", params => $log_params, elapsed => $elapsed, names => $TL->eval(sub{ $sth->nameArray }) || undef, error => 1, }); die $err; } else { # dieããªãã£ããªãã«ã¼ãçµäº last; } } my $elapsed = Time::HiRes::tv_interval($begintime); $TL->getDebug->_dbLog(sub{ group => $this->{group}, set => $dbh->getSetName, db => $dbh->getGroup, id => $sth->{id}, query => $sql_backup, params => $log_params, elapsed => $elapsed, names => $TL->eval(sub{ $sth->nameArray }) || undef }); $sth; } sub selectAllHash { my $this = shift; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken(); my $sth = $this->execute(@_); my $result = []; while(my $data = $sth->fetchHash) { push @$result, { %$data }; } $result; } sub selectAllArray { my $this = shift; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken(); my $sth = $this->execute(@_); my $result = []; while (my $data = $sth->fetchArray) { push @$result, [ @$data ]; } $result; } sub selectRowHash { my $this = shift; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken(); my $sth = $this->execute(@_); my $data = $sth->fetchHash(); $data = $data ? {%$data} : undef; $sth->finish(); $data; } sub selectRowArray { my $this = shift; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken(); my $sth = $this->execute(@_); my $data = $sth->fetchArray(); $data = $data ? [@$data] : undef; $sth->finish(); $data; } sub lock { my $this = shift; my $opts = { @_ }; my @tables; # [name, alias, 'WRITE' or 'READ'] foreach my $type (qw(read write)) { if(defined(my $table = $opts->{$type})) { if(!ref($table)) { push @tables, [$table, undef, uc $type]; } elsif (ref($table) eq 'ARRAY') { push @tables, map { if(!defined) { die __PACKAGE__."#lock: $type => [...] contains an undef. (${type}ã«undefãå«ã¾ãã¦ãã¾ã)\n"; } elsif('HASH' eq ref) { [(keys %$_)[0], (values %$_)[0], uc $type]; } elsif(ref) { die __PACKAGE__."#lock: $type => [...] contains a reference. [$_] (${type}ã«ãªãã¡ã¬ã³ã¹ãå«ã¾ãã¦ãã¾ã)\n"; } else { [$_, undef, uc $type]; } } @$table; } else { die __PACKAGE__."#lock: arg[$type] is an unacceptable reference. [$table] (arg[$type]ã¯ä¸æ£ãªãªãã¡ã¬ã³ã¹ã§ã)\n"; } } }; if(!@tables) { die __PACKAGE__."#lock: no tables are being locked. Specify at least one table. (ãã¼ãã«ã1ã¤ãæå®ããã¦ãã¾ãã)\n"; } my $set = $this->_getDbSetName($opts->{set}); if(my $locked = $this->{locked_dbh}) { my $locked_set = $locked->getSetName; if($locked_set ne $set) { die __PACKAGE__."#lock: attempted to lock the DB Set [$set] but ". "another DB Set [$locked_set] were locked. Unlock old one before locking new one.". " (ä»ã® DB Set [$locked_set] ã§ããã¯ä¸ãªã®ã§ DB Set [$set] ã§ããã¯ããããã¨ãã§ãã¾ãã)\n"; } else { die __PACKAGE__."#lock: you are already locking some tables.". " (æ¢ã«ä»ã®ãã¼ãã«ãããã¯ä¸ã§ã)\n"; } } my $dbh = $this->{dbh}{$set}; my $type = $dbh->{type} or die __PACKAGE__."#lock: \$dbh->{type} is undef (æ¥ç¶ããã¦ãã¾ãã)"; my $sql; if( $type eq 'mysql' ) { $sql = 'LOCK TABLES '.join( ', ', map { my $table = $_->[0]; my $alias = $_->[1]; my $type = $_->[2]; defined $alias ? sprintf '%s AS %s %s', $this->symquote($table, $opts->{set}), $this->symquote($alias, $opts->{set}), $type : sprintf '%s %s', $this->symquote($table, $opts->{set}), $type; } @tables); }else { # on pgsql, LOCK [ TABLE ] name [, ...] [ IN lockmode MODE ] [ NOWAIT ] die __PACKAGE__."#lock: DB type [$type] is not supported. (DB type [$type] ã«å¯¾ãã lock ã¯ãµãã¼ãããã¦ãã¾ãã)\n"; } $sql = $this->__nameQuery($sql, $dbh); my $begintime = [Time::HiRes::gettimeofday()]; $dbh->{dbh}->do($sql); $dbh->{locked} = 1; my $elapsed = Time::HiRes::tv_interval($begintime); $TL->getDebug->_dbLog(sub{ group => $this->{group}, set => $dbh->getSetName, db => $dbh->getGroup, id => -1, query => $sql, params => [], elapsed => $elapsed, }); $this->{locked_dbh} = $dbh; $this; } sub unlock { my $this = shift; $this->{tx_state}==_TX_STATE_CLOSEWAIT and $this->_closewait_broken(); my $dbh = $this->{locked_dbh}; if(!defined($dbh)) { die __PACKAGE__."#unlock: no tables are locked. (ããã¯ããã¦ãããã¼ãã«ã¯ããã¾ãã)\n"; } my $type = $dbh->{type} or die __PACKAGE__."#lock: \$dbh->{type} is undef (æ¥ç¶ããã¦ãã¾ãã)"; my $sql; if( $type eq 'mysql' ) { $sql = 'UNLOCK TABLES'; }else { die __PACKAGE__."#unlock: DB type [$type] is not supported. (DB type [$type] ã«å¯¾ãã unlock ã¯ãµãã¼ãããã¦ãã¾ãã)\n"; } $sql &&= $this->__nameQuery($sql, $dbh); my $begintime = [Time::HiRes::gettimeofday()]; if( $sql ) { $dbh->{dbh}->do($sql); } $dbh->{locked} = undef; my $elapsed = Time::HiRes::tv_interval($begintime); $TL->getDebug->_dbLog(sub{ group => $this->{group}, set => $dbh->getSetName, db => $dbh->getGroup, id => -1, query => $sql, params => [], elapsed => $elapsed, }); $this->{locked_dbh} = undef; $this; } sub setBufferSize { my $this = shift; my $kib = shift; if(ref($kib)) { die __PACKAGE__."#setBufferSize: arg[1] is a reference. (第1弿°ããªãã¡ã¬ã³ã¹ã§ã)\n"; } $this->{bufsize} = defined $kib ? $kib * 1024 : undef; $this; } sub getLastInsertId { my $this = shift; my $dbh; if( @_ && ref($_[0]) ) { my $dbset_ref = shift; $dbh ||= $this->{dbh}{$$dbset_ref}; $dbh or warn Dumper([keys %{$this->{dbh}}]); use Data::Dumper; $dbh or die "no such dbset: $$dbset_ref"; }else { $dbh ||= $this->{trans_dbh}; $dbh ||= $this->{locked_dbh}; $dbh ||= $this->{dbh}{$this->_getDbSetName}; } $dbh->getLastInsertId(@_); } sub symquote { my $this = shift; my $str = shift; if(!defined($str)) { die __PACKAGE__."#symquote: arg[1] is not defined. (第1弿°ãæå®ããã¦ãã¾ãã)\n"; } elsif(ref($str)) { die __PACKAGE__."#symquote: arg[1] is a reference. [$str] (第1弿°ããªãã¡ã¬ã³ã¹ã§ã)\n"; } elsif($str =~ m/[\'\"\`]/) { die __PACKAGE__."#symquote: arg[1] contains a quote character. [$str] (第1弿°ãquoteè¨å·\'\"\`ãå«ãã§ãã¾ã)\n"; } if($this->getType eq 'mysql') { qq[`$str`]; } else { qq["$str"]; } } sub getType { my $this = shift; $this->{type}; } sub getDbh { my $this = shift; my $setname = shift; my $set = $this->_getDbSetName($setname); $this->{dbh}{$set}->getDbh; } sub _getDbSetName { my $this = shift; my $setname = shift; if(ref($setname)) { die __PACKAGE__."#_getDbSetName: arg[1] is a reference. [$setname] (第1弿°ããªãã¡ã¬ã³ã¹ã§ã)\n"; } my $set; if(!defined($setname) || !length($setname)) { if($this->{default_set}) { $set = $this->{default_set}; } else { die __PACKAGE__."#_getDbSetName: do not omit the DB Set because no default DB Set has been specified." . " (ããã©ã«ãã® DB Set ãæå®ããã¦ããªãå ´åã¯ãDB Set ã®æå®ãçç¥ã§ãã¾ãã)\n"; } } else { if($this->{dbh}{$setname}) { $set = $setname; } else { die __PACKAGE__."#_getDbSetName: DB set [$setname] was not defined. Please check the INI file.". " (DB Set [$setname] ãåå¨ãã¾ãã)\n"; } } $set; } sub _connect { # ã¯ã©ã¹ã¡ã½ãããTL#startCgiï¼TL#trapErrorã®ã¿ãå¼ã¶ã my $class = shift; my $groups = shift; foreach my $group (@$groups) { if (!defined($group)) { die "TL#startCgi: -DB has an undefined value. (DBæå®ã«undefãå«ã¾ãã¾ã)\n"; } elsif(ref($group)) { die "TL#startCgi: -DB has a reference. (DBæå®ã«ãªãã¡ã¬ã³ã¹ãå«ã¾ãã¾ã)\n"; } $INSTANCES->{$group} = $class->_new($group)->connect; } # initRequest, postRequest, term ãããã¯ãã $TL->setHook( 'initRequest', _INIT_REQUEST_HOOK_PRIORITY, \&__initRequest, ); $TL->setHook( 'postRequest', _POST_REQUEST_HOOK_PRIORITY, \&__postRequest, ); $TL->setHook( 'term', _TERM_HOOK_PRIORITY, \&__term, ); undef; } sub _new { my $class = shift; my $group = shift; my $this = bless {} => $class; $this->{group} = $group; $this->{namequery} = $TL->INI->get($group => 'namequery'); $this->{type} = $TL->INI->get($group => 'type'); $this->{bufsize} = undef; # æ£ã®å¤ã§ãªããã°ç¡å¶é $this->{types_symtable} = \%Tripletail::DB::SQL_TYPES::; $this->{dbh} = {}; # {DBã»ããå => Tripletail::DB::Dbh} $this->{dbname} = {}; # {DBã³ãã¯ã·ã§ã³å => Tripletail::DB::Dbh} $this->{default_set} = $TL->INI->get($group => 'defaultset', undef); # ããã©ã«ãã®ã»ããå $this->{locked_dbh} = undef; # Tripletail::DB::Dbh $this->{trans_dbh} = undef; # Tripletail::DB::Dbh $this->{tx_state} = _TX_STATE_NONE; do { local $SIG{__DIE__} = 'DEFAULT'; eval q{ package Tripletail::DB::SQL_TYPES; use DBI qw(:sql_types); }; }; if($@) { die $@; } # ããã§ã»ããå®ç¾©ãèªã foreach my $setname ($TL->INI->getKeys($group)) { $setname =~ m/^[a-z]+$/ and next; # äºç´æ¸ my @db = split /\s*,\s*/, $TL->INI->get($group => $setname); if (!scalar(@db)) { # ã¼ãåã®DBããæ§æãããDBã»ãããä½ã£ã¦ã¯ãªããªãã die __PACKAGE__."#new: DB Set [$setname] has no databases. (DB Set [$setname] ã«DBã1ã¤ãããã¾ãã)\n"; } my $dbname = $db[$$ % scalar(@db)]; if(!$this->{dbname}{$dbname}) { $this->{dbname}{$dbname} = Tripletail::DB::Dbh->new($setname, $dbname); } $this->{dbh}{$setname} = $this->{dbname}{$dbname}; } $this; } sub __nameQuery { my $this = shift; my $query = shift; my $dbh = shift; if(!$this->{namequery}) { return $query; } # ã¹ã¿ãã¯ã辿ããæåã«ç¾ããTripletail::DB以å¤ã®ããã±ã¼ã¸ãä½ã£ããã¬ã¼ã ãè¦ã¦ã # ãã¡ã¤ã«åã¨è¡çªå·ãå¾ãã for(my $i = 0;; $i++) { my ($pkg, $fname, $lineno) = caller $i; if($pkg !~ m/^Tripletail::DB/) { $fname =~ m!([^/]+)$!; $fname = $1; my $comment = sprintf '/* %s:%d [%s.%s.%s] */', $fname, $lineno, $this->{group}, $dbh->getSetName, $dbh->getGroup; $query =~ s/^(\s*\w+)/$1 $comment/; return $query; } } $query; } sub __initRequest { # $INSTANCESã®ä¸ãããæ¥ç¶ã確ç«ãã¦ããªããã®ãæ¥ç¶ããã foreach my $db (values %$INSTANCES) { $db->connect; } } sub __term { # $INSTANCESã®æ¥ç¶ãåæããã foreach my $db (values %$INSTANCES) { $db->disconnect; } undef $INSTANCES; } sub __postRequest { # $INSTANCESã®ä¸ãããlockedã®ã¾ã¾ã«ãªã£ã¦ãããã®ã«å¯¾ã㦠# UNLOCK TABLESãå®è¡ããã # ã¾ãããã©ã³ã¶ã¯ã·ã§ã³ãæ¸ãã§ããªããã®ã«ã¤ãã¦ã¯rollbackããã # æ´ã«DBã»ããã®ããã©ã«ãå¤ã Ini ã®ç©ã«ãã foreach my $db (values %$INSTANCES) { if(my $dbh = $db->{locked_dbh}) { $db->unlock; my $setname = $dbh->getSetName; $TL->log( __PACKAGE__, "DB [$db->{group}] (DB Set [$setname]) has been left locked after the last request.". " Tripletail::DB automatically unlocked it for safety.". " (DB [$db->{group}] (DB Set [$setname]) ã¯ããã¯ããã¾ã¾ãªã¯ã¨ã¹ãå¦çãçµãã¾ãããå®å ¨ã®ããèªåçã«unlockãã¾ãã)" ); } if(my $dbh = $db->{trans_dbh}) { $db->rollback; my $setname = $dbh->getSetName; $TL->log( __PACKAGE__, "DB [$db->{group}] (DB Set [$setname]) has been left in transaction after the last request.". " Tripletail::DB automatically rollbacked it for safety.". " (DB [$db->{group}] (DB Set [$setname]) ã¯ãã©ã³ã¶ã¯ã·ã§ã³ä¸ã®ã¾ã¾ãªã¯ã¨ã¹ãå¦çãçµãã¾ãããå®å ¨ã®ããèªåçã«rollbackãã¾ãã)" ); } $db->setDefaultSet($TL->INI->get($db->{group} => 'defaultset', undef)); } } package Tripletail::DB::Dbh; use Tripletail; sub new { my $class = shift; my $setname = shift; my $dbname = shift; my $this = bless {} => $class; $this->{setname} = $setname; $this->{inigroup} = $dbname; $this->{dbh} = undef; # DBI-dbh $this->{type} = undef; # set on connect(). $this; } sub getSetName { my $this = shift; $this->{setname}; } sub getGroup { my $this = shift; $this->{inigroup}; } sub getDbh { my $this = shift; $this->{dbh}; } sub ping { my $this = shift; $this->{dbh} and $this->{dbh}->ping; } sub connect { my $this = shift; my $type = shift; $type or die __PACKAGE__."#connect: type is not specified. (typeãæå®ããã¦ãã¾ãã)\n"; $this->{type} = $type; if($type eq 'mysql') { my $opts = { dbname => $TL->INI->get($this->{inigroup} => 'dbname'), }; if( !$opts->{dbname} ) { if( $TL->INI->existsGroup($this->{inigroup}) ) { die __PACKAGE__."#connect: dbname is not set. (dbnameãæå®ããã¦ãã¾ãã)\n"; }else { die __PACKAGE__."#connect: inigroup '$this->{inigroup}' does not exist. (inigroup '$this->{inigroup}' ãåå¨ãã¾ãã)\n"; } } my $host = $TL->INI->get($this->{inigroup} => 'host'); if(defined($host)) { $opts->{host} = $host; } my $port = $TL->INI->get($this->{inigroup} => 'port'); if(defined($port)) { $opts->{port} = $port; } # mysql_read_default_file, mysql_read_default_group ãªãã·ã§ã³ã渡ã my $default_file = $TL->INI->get_reloc($this->{inigroup} => 'mysql_read_default_file'); if(defined($default_file)) { if ( ! -e $default_file ) { die __PACKAGE__."#connect: file $default_file does not exist. ($default_file ãåå¨ãã¾ãã) ('mysql_read_default_file' in [$this->{inigroup}])\n"; } $opts->{mysql_read_default_file} = $default_file; my $default_group = $TL->INI->get($this->{inigroup} => 'mysql_read_default_group'); if(defined($default_group)) { $opts->{mysql_read_default_group} = $default_group; } } no warnings 'redefine'; $DBI::installed_drh{mysql} or DBI->install_driver('mysql'); my $orig = \&DBD::mysql::db::_login; local($Tripletail::Error::LAST_DBH); local(*DBD::mysql::db::_login) = sub{ my @ret; @ret = wantarray ? &$orig : scalar(&$orig); if( !$ret[0] ) { # $_[0]ãdbh. # ä¿æãã¦ãã¾ãã¨ãã®å¾ã®ã¨ã©ã¼ã¡ãã»ã¼ã¸ãã§ãªããªã, # ãªãã¡ã¬ã³ã¹ã§ã¯undefã«æ¶ããã¦ãã¾ãã®ã§ # ããã§ã¨ã©ã¼æ å ±ã使. my $err = Tripletail::DB->_errinfo($_[0], $type); $Tripletail::Error::LAST_DBH = ['error' => $err]; } wantarray ? @ret : $ret[0]; }; $this->{dbh} = DBI->connect( 'dbi:mysql:' . join(';', map { "$_=$opts->{$_}" } keys %$opts), $TL->INI->get($this->{inigroup} => 'user'), $TL->INI->get($this->{inigroup} => 'password'), { AutoCommit => 1, PrintError => 0, RaiseError => 1, }); } elsif($type eq 'pgsql') { my $opts = { dbname => $TL->INI->get($this->{inigroup} => 'dbname'), }; $opts->{dbname} or die __PACKAGE__."#connect: dbname is not set. (dbnameãæå®ããã¦ãã¾ãã)\n"; my $host = $TL->INI->get($this->{inigroup} => 'host'); if(defined($host) && $host ne '') { $opts->{host} = $host; } my $port = $TL->INI->get($this->{inigroup} => 'port'); if(defined($port) && $host ne '') { $opts->{port} = $port; } $this->{dbh} = DBI->connect( 'dbi:Pg:' . join(';', map { "$_=$opts->{$_}" } keys %$opts), $TL->INI->get($this->{inigroup} => 'user'), $TL->INI->get($this->{inigroup} => 'password'), { AutoCommit => 1, PrintError => 0, RaiseError => 1, }); } elsif($type eq 'oracle') { $ENV{ORACLE_SID} = $TL->INI->get($this->{inigroup} => 'sid'); $ENV{ORACLE_SID} or die __PACKAGE__."#connect: sid is not set. (sidãæå®ããã¦ãã¾ãã)\n"; $ENV{ORACLE_HOME} = $TL->INI->get($this->{inigroup} => 'home'); $ENV{ORACLE_HOME} or die __PACKAGE__."#connect: home is not set. (homeãæå®ããã¦ãã¾ãã)\n"; $ENV{ORACLE_TERM} = 'vt100'; $ENV{PATH} = $ENV{PATH} . ':' . $ENV{ORACLE_HOME} . '/bin'; $ENV{LD_LIBRARY_PATH} = $ENV{LD_LIBRARY_PATH} . ':' . $ENV{ORACLE_HOME} . '/lib'; $ENV{ORA_NLS33} = $ENV{ORACLE_HOME} . '/ocommon/nls/admin/data'; $ENV{NLS_LANG} = 'JAPANESE_JAPAN.UTF8'; $TL->INI->get($this->{inigroup} => 'user') or die __PACKAGE__."#connect: user is not set. (userãæå®ããã¦ãã¾ãã)\n"; $TL->INI->get($this->{inigroup} => 'password') or die __PACKAGE__."#connect: password is not set. (passwordãæå®ããã¦ãã¾ãã)\n"; my $option = $TL->INI->get($this->{inigroup} => 'user') . '/' . $TL->INI->get($this->{inigroup} => 'password'); my $host = $TL->INI->get($this->{inigroup} => 'host'); if(defined($host)) { $option .= '@' . $host; } $this->{dbh} = DBI->connect( 'dbi:Oracle:', $option, '', { AutoCommit => 1, PrintError => 0, RaiseError => 1, }); } elsif($type eq 'interbase') { my $opts = { dbname => $TL->INI->get($this->{inigroup} => 'dbname'), ib_charset => 'UNICODE_FSS', }; $opts->{dbname} or die __PACKAGE__."#connect: dbname is not set. (dbnameãæå®ããã¦ãã¾ãã)\n"; my $host = $TL->INI->get($this->{inigroup} => 'host'); if(defined($host)) { $opts->{host} = $host; } my $port = $TL->INI->get($this->{inigroup} => 'port'); if(defined($port)) { $opts->{port} = $port; } $this->{dbh} = DBI->connect( 'dbi:InterBase:' . join(';', map { "$_=$opts->{$_}" } keys %$opts), $TL->INI->get($this->{inigroup} => 'user'), $TL->INI->get($this->{inigroup} => 'password'), { AutoCommit => 1, PrintError => 0, RaiseError => 1, }); } elsif($type eq 'sqlite') { my $opts = { dbname => $TL->INI->get_reloc($this->{inigroup} => 'dbname'), }; $opts->{dbname} or die __PACKAGE__."#connect: dbname is not set. (dbnameãæå®ããã¦ãã¾ãã)\n"; no warnings 'redefine'; $DBI::installed_drh{SQLite} or DBI->install_driver('SQLite'); my $orig = \&DBD::SQLite::db::_login; local($Tripletail::Error::LAST_DBH); local(*DBD::SQLite::db::_login) = sub{ my @ret; @ret = wantarray ? &$orig : scalar(&$orig); if( !$ret[0] ) { # $_[0]ãdbh. # ä¿æãã¦ãã¾ãã¨ãã®å¾ã®ã¨ã©ã¼ã¡ãã»ã¼ã¸ãã§ãªããªã, # ãªãã¡ã¬ã³ã¹ã§ã¯undefã«æ¶ããã¦ãã¾ãã®ã§ # ããã§ã¨ã©ã¼æ å ±ã使. my $err = Tripletail::DB->_errinfo($_[0], $type); $Tripletail::Error::LAST_DBH = ['error' => $err]; } wantarray ? @ret : $ret[0]; }; $this->{dbh} = DBI->connect( 'dbi:SQLite:' . join(';', map { "$_=$opts->{$_}" } keys %$opts), $TL->INI->get($this->{inigroup} => 'user'), $TL->INI->get($this->{inigroup} => 'password'), { AutoCommit => 1, PrintError => 0, RaiseError => 1, }); } elsif($type eq 'mssql' || $type eq 'odbc' ) { my $nl = $Tripletail::_CHKNONLAZY || 0; my $dl = $Tripletail::_CHKDYNALDR || 0; my $opts = { map{ $_ => $TL->INI->get($this->{inigroup} => $_) } qw(dbname host port tdsname odbcdsn odbcdriver), qw(bindconvert fetchconvert) }; $opts->{dbname} or die __PACKAGE__."#connect: dbname is not set. (dbnameãæå®ããã¦ãã¾ãã)\n"; # build data source string. my $dsn; if( $opts->{odbcdsn} ) { my $odbcdsn = $opts->{odbcdsn} || ''; if( $odbcdsn =~ m/^(\w+)$/ ) { $dsn = "dbi:ODBC:DSN=$odbcdsn"; }elsif( $odbcdsn =~ /^Driver=|^DSN=/i ) { $dsn = "dbi:ODBC:$odbcdsn"; }elsif( $odbcdsn =~ /^dbi:/ ) { $dsn = $odbcdsn; }else { die __PACKAGE__."#connect: unknown odbcdsn. (対å¿ãã¦ããªãodbcdsnãæå®ããã¾ãã)\n"; } } # odbc driver. if( !$dsn || $dsn!~/[:;]Driver=/i || $opts->{odbcdriver} ) { my $driver = $opts->{odbcdriver}; $driver ||= $^O eq 'MSWin32' ? 'SQL Server' : 'freetdsdriver'; if( !$dsn ) { $dsn = "dbi:ODBC:DRIVER={$driver}"; }else { $dsn .= ";DRIVER={$driver}"; } } $opts->{tdsname} and $dsn .= ";Servername=$opts->{tdsname}"; $opts->{host} and $dsn .= ";Server=$opts->{host}"; $opts->{port} and $dsn .= ";Port=$opts->{port}"; $opts->{dbname} and $dsn .= ";Database=$opts->{dbname}"; # bindconvert/fetchconvert from driver. my $odbc_driver = $dsn =~ /[:;]DRIVER=\{(.*?)\}/i ? lc($1) : ''; if( $odbc_driver eq 'freetdsdriver' ) { $opts->{bindconvert} ||= 'freetds'; }elsif( $odbc_driver eq 'sql server' ) { local($SIG{__DIE__},$@) = 'DEFAULT'; my $codepage = eval{ require Win32::API; my $get_acp = Win32::API->new("kernel32", "GetACP", "", "N"); $get_acp && $get_acp->Call(); } || 0; if( !$codepage || $codepage==932 ) { $opts->{bindconvert} ||= 'mssql_cp932'; $opts->{fetchconvert} ||= 'mssql_cp932'; #$dsn .= ';AutoTranslate=No'; } } foreach my $key (qw(bindconvert fetchconvert)) { $opts->{$key} or next; $opts->{$key} eq 'no' and next; my $sub = $this->can("_${key}_$opts->{$key}"); $sub or die __PACKAGE__."#connect: no such $key: $opts->{$key} (${key}ãæå®ããã¦ãã¾ãã)"; $this->{$key} = $sub; } #print "dsn = [$dsn]\n"; my $conn = sub{ $this->{dbh} = DBI->connect( $dsn, $TL->INI->get($this->{inigroup} => 'user'), $TL->INI->get($this->{inigroup} => 'password'), { AutoCommit => 1, PrintError => 0, RaiseError => 1, } ); }; if( (!$dl && $nl) || $^O eq 'MSWin32' ) { $conn->(); }else { eval{ $conn->(); }; if( $@ ) { my $err = $@; chomp $err; $err .= " (perhaps you forgot to set env PERL_DL_NONLAZY=1?)"; $err .= " ...propagated"; die $err; } } if( $this->{fetchconvert} ) { my $sub = $this->{fetchconvert}; $this->$sub(undef, connect => undef); } } else { die __PACKAGE__."#connect: DB type [$type] is not supported. (DB type [$type] ã¯ãµãã¼ãããã¦ãã¾ãã)\n"; } if(!$this->{dbh}) { die __PACKAGE__."#connect: DBI->connect failed. (DBI->connectã«å¤±æãã¾ãã)\n"; } $this; } sub getLastInsertId { my $this = shift; # $this->{type}ã¯connectæã«ã»ããããã my $type = $this->{type} or die __PACKAGE__."#getLastInsertId: \$this->{type} is undef (æ¥ç¶ããã¦ãã¾ãã)"; my $obj = shift; # for sequence on pgsql and oracle? if( $type eq 'mysql' ) { return $this->{dbh}{mysql_insertid}; }elsif($type eq 'pgsql') { defined($obj) or die __PACKAGE__."#getLastInsertId: $type requires secuence name"; my ($curval) = $this->{dbh}->selectrow_array(q{ SELECT currval(?) }, undef, $obj); return $curval; }elsif($type eq 'oracle') { $obj =~ /^((?:\w+\.)\w+)$/ or die __PACKAGE__."#getLastInsertId: internal error: it is not possible to quote symbols of Oracle.". " (å é¨ã¨ã©ã¼:Oracleç¨ã®quoteå¦çã¯æªå®è£ ã§ã)\n"; my $obj_sym = $1; my ($curval) = $this->{dbh}->selectrow_array(q{ SELECT $obj_sym.curval FROM dual }); return $curval; }elsif( $type eq 'sqlite' ) { return $this->{dbh}->func('last_insert_rowid'); }elsif( $type eq 'mssql' ) { my ($curval) = $this->{dbh}->selectrow_array(q{ SELECT @@IDENTITY }); return $curval; }else { die __PACKAGE__."#getLastInsertId: $type is not supported. (${type}ã¯ãµãã¼ãããã¦ãã¾ãã)"; } } sub _bindconvert_freetds { my $this = shift; my $ref_sql = shift; my $params = shift; my $i = -1; foreach my $elm (@$params) { ++$i; ref($elm) or next; if( ${$elm->[1]} eq 'SQL_WVARCHAR' ) { my $u = $TL->charconv($elm->[0], 'utf8', 'ucs2'); $elm->[0] = pack("v*",unpack("n*",$u)); $elm->[1] = \'SQL_BINARY'; my $l = length($u)/2; my $j = 0; my $repl = "CAST(? AS NVARCHAR($l))"; $$ref_sql =~ s{\?}{$j++==$i?$repl:'?'}ge; } } } sub _bindconvert_mssql_cp932 { my $this = shift; my $ref_sql = shift; my $params = shift; $$ref_sql = $TL->charconv($$ref_sql, 'utf8' => 'sjis'); my $i = -1; foreach my $elm (@$params) { ++$i; if( !ref($elm) ) { $elm = $TL->charconv($elm, 'utf8', 'sjis'); }elsif( ${$elm->[1]} =~ /^SQL_W(?:(?:LONG)?VAR)?CHAR$/ ) { my $u = $TL->charconv($elm->[0], 'utf8', 'ucs2'); $elm->[0] = pack("v*",unpack("n*",$u)); $elm->[1] = \'SQL_BINARY'; my $l = length($u)/2; my $j = 0; my $repl = "CAST(? AS NVARCHAR($l))"; $$ref_sql =~ s{\?}{$j++==$i?$repl:'?'}ge; }elsif( ${$elm->[1]} =~ /^SQL_(?:(?:LONG)?VAR)?CHAR$/ ) { $elm = $TL->charconv($elm, 'utf8', 'sjis'); } } } sub _fetchconvert { my $this = shift; if( $this->{fetchconvert} ) { my $sub = $this->{fetchconvert}; $this->$sub(@_); } } sub _fetchconvert_mssql_cp932 { my $this = shift; my $sth = shift; my $mode = shift; my $obj = shift; if( $mode eq 'new' ) { # obj is [\$sql, \@params]; # ãªãã ãå ã«ä¸åãã£ã¨ããªãã¨ãããããªã? $this->{dbh}->type_info(1); my $types = $sth->{sth}{TYPE}; my $dbh = $sth->{dbh}{dbh}; my @types = map{ $dbh->type_info($_)->{TYPE_NAME} } @$types; $sth->{_types} = \@types; $sth->{_name_hash} = {%{$sth->{sth}{NAME_hash}||{}}}; # raw encoded. my @names; while(my($k,$v)=each%{$sth->{_name_hash}}) { $names[$v] = $TL->charconv($k, "sjis" => "utf8"); } $sth->{_name_arraymap} = \@names; $sth->{_decode_cols} = []; }elsif( $mode eq 'nameArray' ) { # obj is arrayref.; @$obj = @{$sth->{sth}{NAME}||[]}; foreach my $elm (@$obj) { $elm = lc $TL->charconv($elm, 'cp932' => 'utf8'); } }elsif( $mode eq 'nameHash' ) { # obj is hashref. foreach my $key (keys %$obj) { my $ukey = lc $TL->charconv($key, 'cp932' => 'utf8'); $obj->{$ukey} = delete $obj->{$key}; } }elsif( $mode eq 'fetchArray' ) { # obj is arrayref. my $i = -1; foreach my $val (@$obj) { ++$i; defined($val) or next; my $type = (defined($i) && $sth->{_types}[$i]) || ''; if( $type =~ /^n?((long)?var)?char$/ ) { if( defined($val) && $val =~ /[^\0-\x7f]/ ) { $val = $TL->charconv($val, 'cp932' => 'utf8'); } } my $ukey = $sth->{_name_arraymap}[$i] || "\0"; if( grep{$_ eq $i || $_ eq $ukey} @{$sth->{_decode_cols}} ) { my $bin = pack("v*",unpack("n*",$val)); $val = $TL->charconv($bin,"ucs2","utf8"); } } }elsif( $mode eq 'fetchHash' ) { # obj is hashref. foreach my $key (keys %$obj) { my $ukey = $TL->charconv($key, 'cp932' => 'utf8'); my $i = $sth->{_name_hash}{$key}; # raw encoded. my $type = (defined($i) && $sth->{_types}[$i]) || '*'; my $val = delete $obj->{$key}; if( $type =~ /^n?((long)?var)?char$/ ) { if( defined($val) && $val =~ /[^\0-\x7f]/ ) { $val = $TL->charconv($val, 'cp932' => 'utf8'); } } if( grep{$_ eq $i || $_ eq $ukey} @{$sth->{_decode_cols}} ) { my $bin = pack("v*",unpack("n*",$val)); $val = $TL->charconv($bin,"ucs2","utf8"); } $obj->{$ukey} = $val; } } } sub disconnect { my $this = shift; $this->{dbh} and $this->{dbh}->disconnect; $this->{dbh} = undef; # DBI-dbh $this->{type} = undef; # set on connect(). $this; } sub begin { my $this = shift; $this->{dbh}->begin_work; } sub rollback { my $this = shift; $this->{dbh}->rollback; } sub commit { my $this = shift; $this->{dbh}->commit; } package Tripletail::DB::STH; use Tripletail; our $STH_ID = 0; 1; sub new { my $class = shift; my $db = shift; my $dbh = shift; my $sth = shift; my $this = bless {} => $class; $this->{db_center} = $db; # Tripletail::DB $this->{dbh} = $dbh; # Tripletail::DB::DBH $this->{sth} = $sth; # native sth $this->{ret} = undef; # last return value $this->{id} = $STH_ID++; $this; } sub fetchHash { my $this = shift; my $hash = $this->{sth}->fetchrow_hashref; if($hash) { $TL->getDebug->_dbLogData(sub{ group => $this->{group}, set => $this->{set}{name}, db => $this->{dbh}{inigroup}, id => $this->{id}, data => $hash, }); } if( $this->{dbh}{fetchconvert} ) { my $sub = $this->{dbh}{fetchconvert}; $this->{dbh}->$sub($this, fetchHash => $hash); } if(my $lim = $this->{db_center}{bufsize}) { my $size = 0; foreach(values %$hash) { $size += length; } if($size > $lim) { die __PACKAGE__."#fetchHash: buffer size exceeded: size [$size] / limit [$lim] (ãããã¡ãµã¤ãºãè¶ éãã¾ãããsize [$size] / limit [$lim])\n"; } } $hash; } sub fetchArray { my $this = shift; my $array = $this->{sth}->fetchrow_arrayref; if( $this->{dbh}{fetchconvert} ) { my $sub = $this->{dbh}{fetchconvert}; $this->{dbh}->$sub($this, fetchArray => $array); } if($array) { $TL->getDebug->_dbLogData(sub{ group => $this->{group}, set => $this->{set}{name}, db => $this->{dbh}{inigroup}, id => $this->{id}, data => $array, }); } if(my $lim = $this->{db_center}{bufsize}) { my $size = 0; foreach(@$array) { $size += length; } if($size > $lim) { die __PACKAGE__."#fetchArray: buffer size exceeded: size [$size] / limit [$lim] (ãããã¡ãµã¤ãºãè¶ éãã¾ãããsize [$size] / limit [$lim])\n"; } } $array; } sub ret { my $this = shift; $this->{ret}; } sub rows { my $this = shift; $this->{sth}->rows; } sub finish { my $this = shift; $this->{sth}->finish; } sub nameArray { my $this = shift; my $name_lc = $this->{sth}{NAME_lc}; if( $name_lc && $this->{dbh}{fetchconvert} ) { $name_lc = [@{$this->{sth}{NAME}}]; # start from mixed case. my $sub = $this->{dbh}{fetchconvert}; $this->{dbh}->$sub($this, nameArray => $name_lc); } $name_lc; } sub nameHash { my $this = shift; my $name_lc_hash = $this->{sth}{NAME_lc_hash}; if( $name_lc_hash && $this->{dbh}{fetchconvert} ) { $name_lc_hash = {%{$this->{sth}{NAME_hash}}}; # start from mixed case. my $sub = $this->{dbh}{fetchconvert}; $this->{dbh}->$sub($this, nameHash => $name_lc_hash); } $name_lc_hash; } sub _fetchconvert { my $this = shift; if( $this->{dbh}{fetchconvert} ) { my $sub = $this->{dbh}{fetchconvert}; $this->{dbh}->$sub($this, @_); } } package Tripletail::DB::_scope; # ----------------------------------------------------------------------------- # Tripletail::DB::_scope->new(sub{ ... }); # Tripletail::DB::_scope->new(sub{ ... }, { args=>[...] }); # create colosing object. it is similar to destructor or finally clause. # sub new { my $pkg = shift; my $code = shift; my $opts = shift; my $this = bless {}, $pkg; $this->{code} = $code; $this->{args} = $opts->{args} || undef; $this->{disabled} = $opts->{disabled}; $this; } # ----------------------------------------------------------------------------- # $obj->raise(); # $obj->raise({ args => [...] }); # invoke scope_finalizer code before it run automatically. # sub raise { my $this = shift; my $opts = shift || {}; if( !$this->{disabled} ) { my $args = $opts->{args} || $this->{args} || []; $this->{code}->(@$args); $this->{disabled} = 1; }else { return; } } # ----------------------------------------------------------------------------- # $obj->disable(); # disable auto raise. # sub disable { my $this = shift; $this->{disabled} = @_ ? shift : 1; $this; } # ----------------------------------------------------------------------------- # DESTRUCTOR. # invoke scope_finalizer code. # sub DESTROY { my $this = shift; $this->raise(); } # ----------------------------------------------------------------------------- # End of Module. # ----------------------------------------------------------------------------- __END__