SELECTのAPI(3) イテレータによるデータアクセス
これまで、テーブルからのデータの読み出しそのものに関しては触れませんでした。ここでは、どのようにテーブルからデータを読み出すかについて考えます。
テーブルから数百万件というような大量のデータを読み出す場合、すべてのデータをメモリ上に読み込んでからデータを返すとメモリが溢れてしまいます。このため、通常のRDBMSではイテレータを介して1行ずつデータを読み出せるようになっています。PerlShellKoherent::DBでもイテレータによってデータの読み出しを行うようにしたいと思います。
イテレータの利用
membersテーブルから全データを読み出すためのコードは、次のようなものを考えています。
my $iterator = $members->iterator; while(my $row = $iterator->next){ print "Family name: $row->{'family_name'}, First name: $row->{'first_name'}\n"; }
Viewオブジェクトのiteratorメソッドを用いてViewIteratorオブジェクトを得ます。ViewIteratorオブジェクトからはnextメソッドを用いて行を取り出します。最後の行を取り出した後にnextメソッドを呼び出すとundefを返すので、上記のようなwhileループですべての行を取り出すことができます。
iteratorメソッドはViewクラスのインスタンスメソッドであり、whereやorder_by、limit等の各種メソッドの戻り値はViewオブジェクトであるため、次のような使い方もできます。
my $iterator = $members->where(sub{ $_[0]->{'age'} >= 20; })->order_by('family_name')->limit(1)->iterator; while(my $row = $iterator->next){ print "Family name: $row->{'family_name'}, First name: $row->{'first_name'}\n"; }
whereやorder_byなど、複数の条件を連ねた最後にiteratorを呼び出すことで、それらのすべての条件を適用したViewオブジェクトのデータにアクセスすることができます。
条件が評価されるタイミング
where等の条件が評価されるのはiteratorメソッドを呼び出したタイミングであるべきだと考えられます。
例えば、次のようなコードを考えてみます。
# 成人のメンバーだけを保持するView、$adultsを取得 my $adults = $members->where(sub{$_[0]->{'age'} >= 20}); # 成人のメンバーだけを取り出すイテレータを取得 my $iterator_before = $adults->iterator; # membersテーブルにメンバーを1人追加 $members->insert($new_member); # 再度、成人のメンバーだけを取り出すイテレータを取得 my $iterator_after = $adults->iterator;
もしも$adultsがwhereメソッドを用いて$adultsを作成した時点のデータを保持するのであれば、その後membersテーブルを更新してもその結果は$adultsには影響を及ぼしません。つまり、$iterator_beforeと$iterator_afterは同じ結果を返すことになります。
しかし、whereメソッドが呼び出された時点では条件は評価せずにiteratorメソッドが呼び出されて初めて条件が評価されるように実装すると、$adultsはまるでRDBMSにおけるビューのような存在になります。この場合、$iterator_afterから得られるデータは、$iterator_beforeから得られるデータに加えて$new_memberも返すようになります。このような実装を行うことで、検索のコードの再利用性を高めることができます。
実装は次のように行います。
- $adultsのViewIteratorオブジェクトは内部で$membersのViewIteratorオブジェクトを保持する。
- そのViewIteratorオブジェクトから得られた行に対してwhereで与えられたメソッドを適用し、その行が条件に適合するかどうかを判別する。
これは、whereメソッドが返すViewオブジェクトは条件判別専用のViewクラス(例えば、Viewクラスを継承した条件判別用のConditionedViewクラス)のインスタンスであるということになります。同様に、order_by用のViewクラス、limit用のViewクラスなどを用意し、それぞれの返すViewIteratorオブジェクトの挙動を変化させることでこれまでに述べたような挙動を実現できるでしょう。