Pixel Pedals of Tomakomai

北海道苫小牧市出身の初老の日常

Tengのソースを読む(1)

次はORMの最近の事情を知りたいのでTeng-0.11(ただし、「> IT'S IN ALPHA QUALITY. IT MAY CHANGE THE API WITHOUT NOTICE.」)。今日はTengクラス周り。


Tengでは Teng->new で得られるオブジェクトを起点に様々な操作を行う。DBやテーブルやカラムの情報を設定するスキーマクラスがデフォルトで「クラス名::Schema」となるようになってるので、Tengを継承してYourModel.pm を作っておくのがよい(そうすれば、YourModel::Schema にスキーマ情報を書ける)。スキーマは Class::Load::load_class を使ってロードされ、instance でインスタンスが作られ保持される。テーブルの情報はスキーマに書かれるので、DBからのロード時に各レコードに対応するオブジェクトを作るには schema->get_table して $tabel->row_class->new するのが基本。


コンストラクタでは他に、起動時のpidを保存している。これは$dbhがforkを跨いで利用されないように監視するため。また、DBへの接続がまだな場合は接続も行われる。このとき、TransactionManager が古い$dbh を握っているので、こいつも一旦破棄する。ここでon_connect_do フィールドにフックを仕掛けておくと、接続後に割り込めそうだが、ドキュメント化はされてないっぽい? on_connect_do にはSQLも直接仕掛けることができる。接続後に_prepare_from_dbh 内では接続後の初期化として Teng::QueryBuilder をDriverに合わせて作成する。Teng::QueryBuilder はまんまSQL::Maker 。


SQLの実行はほとんどの場合_execute メソッドを経由するが、ここでsql_comment パラメータ、またはTENG_SQL_COMMENT があるとスタックを辿って呼び出し元を特定し、SQLのコメントにその情報を追記する。これ、面白い。


insert は SQLMaker 使ってinsert した後、row_class->new して返す。ただし、主キーが指定されていればsingle でSELECT を発行して読み直す。それが鬱陶しければ fast_insert すれば余計なことは一切やらない。insert 時には_insert 内でschema->get_table の持っている情報を使って 値の deflate などを行う。なお、_last_insert_id では各RDBMSごとの差異を吸収しているが、Oracleだけ実装されてないようなので注意。


Teng クラスの持つupdate() やdelete() はバルクアップデートで、更新した件数を返す。do() はDBI->do のラッパーだが、0.11 の実装では_executeを経由しないのでTENG_SQL_COMMENT の恩恵は受けられない。row_class もこれらのメソッドを持つようだが、それは次回以降。


search(), search_named(), single() はsearch_by_sql() のラッパーという構成。single() はlimit => 1 を指定しているだけで実態はsearch()。search_by_sql() では$table の指定を省略された場合でもrow_class と紐づけを行うため、_guess_table_name でSQLからテーブル名を認識させている。ただ、from を拾うだけなのでサブクエリがあるSQLだとしくじるかも?


トランザクションはほぼDBIx::TransactionManager に丸投げ。connection() 時にトランザクション中だとまずいのでエラーを出すコードがあるのと、txn_scope でcaller を適切に詰め直す処理が追加されているくらい。txn_begin でcaller の詰め直しが要らないのかはちょっと疑問。


最後にpluginを読み込むためのload_plugin。+をつけないとTeng::Plugin:: が名前空間のベースになる。プラグインはExporter.pm は使ってないが、@EXPORT にエクスポートするメソッドを指定しておくとload_plugin メソッドがメソッドを生やしてくれる仕組み。load_pluginを経由せずにプラグインをuse しても意味ないし、プラグイン内で@EXPORT_OK などは使えない。