読者です 読者をやめる 読者になる 読者になる

北海道苫小牧市出身の初老PGが書くブログ

永遠のプログラマを夢見る、苫小牧市出身のおじさんのちらしの裏

Tengのソースを読む(2)

perl+web

Teng-0.11*1の続き。今日はSchemaクラスとRowクラス周り。

概要

Teng::Schemaと Teng::Schema::Table はスキーマ情報の保持、Teng::Schema::Declare とTeng::Schema::Loader, Teng::Schema::Dumper はスキーマ情報の作成、Teng::Rowと Teng::Iterator はDB内の行データの実態として振る舞う。

Teng::Schemaと Teng::Schema::Table

Teng::Schema はcamelize をユーティリティ関数として持つ以外は、ただスキーマデータを保管するだけのクラスである。サブクラス化して使われる(Loader使うと直接インスタンス化されるけど)。

Teng::Schema::Table はサブクラス化せずに使われるものだが、その配下にTeng::Row のサブクラスとなるRow クラスを持つ。コンストラクタではRow クラスの初期化も行っており、ユーザがカスタムクラスを作っていなければrow_class フィールドで指定した名前のクラスを勝手に作る。デフォルトでは各カラムのインフレーターを遅延で呼び出すようなクラスが作られる。

後、Teng::Schema::Table はデフレータとインフレータを呼び出すcall_deflate と call_inflate も提供する。デフレータとインフレータは直接カラム名を指定するのではなく正規表現を指定するので、xxxx_date というカラム名に対してまとめて1つのデフレータを指定したりもできる。(当たり前だが)1つのカラムへ同時に作用するデフレータは1個だけであるので、正規表現は被らないように指定する必要がある。

Teng::Schema を直接使ってスキーマも作れるが、DeclareやLoader, Dumper を使うともっと簡単である。

Teng::Schema::Declare

_current_schema ではcaller を遡って呼び元を探し、そいつをTeng::Schema のサブクラスにしてnew を呼んでインスタンスを生成する。インスタンスはシングルトンとして保存され、YourSchema->instance のようにアクセスできるように準備される。

ただし、schema 経由で _current_schema を呼ぶ場合はスタックは遡らずに 渡されたクラス名をTeng::Schema のサブクラスとする。これにより、package を作らなくてもスキーマクラスを作ることができる。

table 関数がDSLの入り口になっていて、name, pk, columns, row_class, inflate_rule はこの中でlocal 宣言される。これらの呼び出しは Teng::Schema::Table へ渡される。name に渡した値から自動的にRowクラスの名前が作られ、これは::Schema ではなく ::Row の名前空間にテーブル名をcamelize した名前が使われる。また、columns にはsql_types も同時に指定が可能で、{name => ..., type => ...} の形式で指定するとうまく動くっぽい*2。このtypeの 指定には DBI qw/:sql_types/ を使うとよいようだ。

DSLによって作られるTeng::Schema::Table は、全て_current_schema にaddされる。

Teng::Schema::Loader, Teng::Schema::Dumper

DBIx::Inspector の結果を、Loader は直接Schema オブジェクトにし、 Dumper は相当するPerlソースコードを生成する。どちらもinflators とdeflators は生成されない*3

Teng::Row

_lazy_get_data は各カラムのインフレータを遅延呼び出しする仕組みを提供する。_get_column_cached に結果を保存している。また、rowオブジェクトを使ってupdate をするときにSQL指定(スカラリファレンス)をするとオブジェクト側では正確な値が不明となるため、これを _untrusted_row_data で追跡していてこの値を取り出そうとすると警告が出る。

set_columnによるカラムの変更は _dirty_columns で追跡しており、update 時に変更されたカラムを登録する。update, delete, refetch はTengインスタンス側の物を使うので、違いと言えばupdate メソッドでカラムの変更を追跡するくらいのもの。

_where_cond はdelete や update 用のwhere句を作るメソッドで、ガッチガチに実装されている(そりゃ影響大きいのでそうか)。スキーマに主キーの情報がきちんと定義されており、データが主キーを全て含む場合のみwhere句を発行する。

AUTOLOADにて未知のメソッドはアクセサとして扱えるようにメソッドを生やすようになっている。また、AUTOLOAD除けのために空のDESTROYを定義している。

Teng::Iterator

select系のメソッドで使われる。渡された $sth に対して fetchrow_hashref しながらイテレーションして::Row を作っていくだけ。all は next 呼び続けるだけの素直な実装。

*1:「> IT'S IN ALPHA QUALITY. IT MAY CHANGE THE API WITHOUT NOTICE.」

*2:ドキュメントはまだないがMock::BasicBindColumn::Schema でテストされている

*3:Dumper はTeng::Schema をダンプするのかと思ったのだけど、inflatorsとdeflatorsを考えたらそうする意味はあまりないってことかも?