Take me over?
NAME
(名前)
KiokuDB::Tutorial - KiokuDBを始めよう
Install
(インストール)
KiokuDBとバックエンドを一緒にインストールするには、Task::KiokuDBをインストールするのが一番簡単です。
KiokuDBはMooseと、いくつかのすぐに使えるモジュールに依存していますが、 特定のストレージモジュールには依存していません。
KiokuDBは複数のバックエンドのフロントエンドです。 DBIが実際のデータベースへの接続にDBDを使っているのに似ています。
開発用やテストとして、メモリに保存するKiokuDB::Backend::Hashバックエンドを使うことができます。 プロダクションには、KiokuDB::Backend::DBDかKiokuDB::Backend::DBIをバックエンドとして推奨します。
KiokuDB::Backend::DBDをインストールして、以下のインストラクションを見てください。
CREATING A DIRECTORY
(ディレクトリの作成)
KiokuDBディレクトリはオブジェクトで、バックエンド以外のすべての共通の機能を含みます。
すぐに使えるもっとも単純なディレクトリは次のように作れます:
my $dir = KiokuDB->new(
backend => KiokuDB::Backend::Hash->new
);
このドキュメントの最後に、他のもっと面白いバックエンドの設定を紹介しますが、 とりあえず、やってみます。
いろいろなバックエンドに接続するためのDSN文字列を使うこともできます。
KiokuDB->connect("hash");
KiokuDB->connect("dbi:SQLite:dbname=foo", create => 1);
KiokuDB->connect("bdb:dir=foo", create => 1);
または、設定ファイルでも
KiokuDB->connect("/path/to/my_db.yml");
KiokuDB->connect("/path/to/dir");
設定ファイルは次のようになります:
backend:
class: KiokuDB::Backend::DBI
dsn: dbi:SQLite:dbname=/tmp/test.db
create: 1
USING THE DBI BACKEND
(DBIバックエンドを使う)
2つの理由で、このチュートリアルではDBIバックエンドを使います。 1つ目の理由は、DBIがどこにでもあるからです - ほとんどすべての人がインストール方法も 使い方も知っています。2つ目の理由は、簡単に裏舞台を見ることが出来るからです。 KiokuDBが何をしているかをよりわかりやすくデモンストレーションできるからです。
この例ですべてのバックエンドがまったく同じように動きます。
以下で使う$dir
変数は下記のように作られます:
my $dir = KiokuDB->connect(
"dbi:SQLite:dbname=kiokudb_tutorial.db",
create => 1,
);
ユーザー名とパスワードで接続する場合、名前付きの引数を指定しないといけません:
my $dir = KiokuDB->connect(
$dsn,
user => $user,
password => $password,
);
INSERTING OBJECTS
(オブジェクトのインサート)
Mooseを使った簡単なクラスを定義してみましょう:
package Person;
use Moose;
has name => (
isa => "Str",
is => "rw",
);
それをインスタント化します:
my $obj = Person->new( name => "Homer Simpson" );
下記のようにオブジェクトをデータベースに入れます:
my $scope = $dir->new_scope;
my $homer_id = $dir->store($obj);
これは、KiokuDBのとても普通の使い方です。ですが、いくつか重要なことを示しています。
1番目に、スキーマは必要ありません。KiokuDBはテーブルのような何かを事前に定義する必要はありません。 オブジェクトの情報を取り出すために、Mooseを使うことができます。
2番目に、データベースに入っているすべてのオブジェクトにはIDがあります。 オブジェクトにIDを選ばなけれあば、KiokuDBが代わりにUUIDを割り当てます。 IDはリレーショナルデータベースのプライマリーキーのようなものです。 自分でオブジェクトにIDを振りたければ、次のようにすることができます:
$dir->store( homer => $obj );
これで、$obj
のIDはhomer
になります。IDを与えなければ、UUIDが自動的にふられます。
3番目に、すべてのKiokuDB操作はscope内で行う必要があります。 スコープは上のような簡単な例には適しませんが、weakリファレンスが使われるようになると、 必要になります。後でより詳細に見ていきます。
LOADING OBJECTS
(オブジェクトの読み出し)
さて、データベースにHomerが入りました。store
から得たIDで取り出せます。
my $homer = $dir->lookup($homer_id);
$scope
と$obj
は、スコープ内にあるとします。$homer
と$obj
は実際に、同じリファレンスになります。
refaddr($homer) == refaddr($obj)
生存しているオブジェクトセット (KiokuDB::LiveObjects)内のオブジェクトが "生存"しているかをKiokuDBが追跡しているからです。
$obj
と$scope
が、もうスコープにいなければ、新しいスコープを作らなければいけません。 それから、再びデータベースからオブジェクトを取り出します:
my $scope = $dir->new_scope;
my $homer = $dir->lookup($homer_id);
このケースではHomerのオリジナルのインスタンスはもはや生きておらず、 Perlによりガベージコレクトされています。 KiokuDBはインスタンスをバックエンドから取得します。
WHAT WAS STORED
(何が保存されたか)
すぐにデータベースを覗いてみましょう。SQLコマンドラインツールを起動しましょう:
% sqlite3 kiokudb_tutorial.db
SQLite version 3.4.0
Enter ".help" for instructions
sqlite>
データベースのスキーマには2つのテーブルがあります。entries
とgin_index
です:
sqlite> .tables
entries gin_index
gin_index
はより複雑なクエリに使われます。チュートリアルの最後に扱います。
entries
をよく見ましょう:
sqlite> .schema entries
CREATE TABLE entries (
id varchar NOT NULL,
data blob NOT NULL,
class varchar,
root boolean NOT NULL,
tied char(1),
PRIMARY KEY (id)
);
メインのカラムはid
とdata
です。KiokuDBにある、すべてのオブジェクトにはIDがあり、 プライマリキーとBLOBデータが関連付けられています。
DBIバックエンドのデフォルトのシリアライザーはKiokuDB::Serializer::JSONですので、 データを覗き見ることができます。
最初に、sqlite
の出力モードをline
にセットしてください。大きいカラムでも見やすくなります:
sqlite> .mode line
テーブルからデータを取得します:
sqlite> select id, data from entries;
id = 201C5B55-E759-492F-8F20-A529C7C02C8B
data = {"__CLASS__":"Person","data":{"name":"Homer Simpson"},"id":"201C5B55-E759-492F-8F20-A529C7C02C8B","root":true}
上記のように、name
属性はblob内のdata
キーにオブジェクトのクラスとして保存されています。
data
カラムはオブジェクトを再作成するのに必要なすべてのデータを含んでいます。
他のすべてのカラムは単に検索のために使われます。後で、どのように検索用のカラムを作るのかを見せます。
KiokuDB::Backend::DBDを使った場合は、ディスク上のフォーマットは、実際には、id
からdata
のハッシュになります。
OBJECT RELATIONSHIPS
(オブジェクトのリレーションシップ)
Person
クラスにname
よりも、もっと面白いデータを追加してみましょう:
package Person;
has spouse => (
isa => "Person",
is => "rw",
weak_ref => 1,
);
spouse
属性は他のPersonオブジェクトのリファレンスを持ちます。
まずは、他のオブジェクトを作りましょう:
my $marge_id = $dir->store(
Person->new( name => "Marge Simpson" ),
);
データベースに両方のオブジェクトを持たせます。2つを一緒にリンクしましょう:
{
my $scope = $dir->new_scope;
my ( $marge, $homer ) = $dir->lookup( $marge_id, $homer_id );
$marge->spouse($homer);
$homer->spouse($marge);
$dir->store( $marge, $homer );
}
今、永続的なオブジェクトグラフを作りました。これは、複数のオブジェクトが お互いに参照しています。
spouse
にはweak_ref
オプションがありましたので、この循環構造はリークしません。
データベースでオブジェクトが更新されたら、LinkDBはspouse
属性を含むリファレンスを見て、 この関係はストレージ内でユニークなIDを使ってエンコードされます。
このグラフをロードするために、次のようにできます:
{
my $scope = $dir->new_scope;
my $homer = $dir->lookup($homer_id);
print $homer->spouse->name; # Marge Simpson
}
{
my $scope = $dir->new_scope;
my $marge = $dir->lookup($marge_id);
print $marge->spouse->name; # Homer Simpson
refaddr($marge) == refaddr($marge->spouse->spouse); # true
}
KiokuDBが最初のオブジェクトをロードしたら、そのオブジェクトが依存している すべてのオブジェクトがロードされます。spouse
属性は他のオブジェクトを(IDで) 持っているので、インフレーション時にそのリンクを解決します。
The purpose of new_scope
(new_scope
の目的)
new_scope
が重要になるところです。オブジェクトはデータベースからインフレートされ、 リファレンスカウントを増やすために、生存しているオブジェクトスコープに追加されます。
これがされていなければ、lookup
から$homer
が戻ってくる時までに、 spouse
属性がクリアされます。マージする他のリファレンスがないからです。
もし、一方で、循環構造がweakでなければ、手で壊さなければいけません。 これは、とてもエラーになりやすいです。
次のイディオムを使って:
{
my $scope = $dir->new_scope;
# do all KiokuDB work in here
}
少なくとも必要である時間はオブジェクトが生きていることを確保できます。
Webアプリケーションのコンテキストでは、普通リクエストごとに新しいスコープを作ります。
スコープがネストできるなら、必須ではありません。
少なくとも一つのスコープがあれば、好きなだけ多くの、または、少ないスコープを作ることができます。 その時に作られたスコープにすでにあるすべてのオブジェクトを確実にする親を参照している子供のスコープもまだ生きています。
REFERENCES IN THE DATABASE
(データベース内のリファレンス)
さて、データベースにオブジェクトグラフがあります。内部がどうなっているか見てみましょう。
sqlite> select id, data from entries;
id = 201C5B55-E759-492F-8F20-A529C7C02C8B
data = {"__CLASS__":"Person","data":{"name":"Homer Simpson","spouse":{"$ref":"05A8D61C-6139-4F51-A748-101010CC8B02.data"}},"id":"201C5B55-E759-492F-8F20-A529C7C02C8B","root":true}
id = 05A8D61C-6139-4F51-A748-101010CC8B02
data = {"__CLASS__":"Person","data":{"name":"Marge Simpson","spouse":{"$ref":"201C5B55-E759-492F-8F20-A529C7C02C8B.data"}},"id":"05A8D61C-6139-4F51-A748-101010CC8B02","root":true}
spouse
フィールドがJSONオブジェクトということに気づくでしょう。 そして、その内部の$ref
フィールドには、対象のオブジェクトのUUIDがあります。
データがロードされると、KiokuDBはロードさえていないオブジェクトへのリファレンスを キューに入れて、オブジェクトグラフをメモリに常駐させるために、それらをロードします。
データがこのような方法で表現されている理由について知りたければ、 このフォーマットは、JPSON
か JavaScript Persistent Object notation(http://www.jpson.org)と呼ばれています。 KiokuDB::Backend::Storableを使うと、KiokuDB::EntryとKiokuDB::Referenceオブジェクトは、 代わりに、storableフックでシリアライズされます。
OBJECT SETS
(オブジェクトセット)
より複雑なリレーションシップ(1対1に限らない)は、Set::Objectでかなり簡単にモデル化できます。
Person
クラスを拡張してそのようなリレーションシップを足してみましょう:
package Person;
has children => (
does => "KiokuDB::Set",
is => "rw",
);
KiokuDB::Setオブジェクトは、Set::ObjectのKiokuDB用のラッパーです。
my @kids = map { Person->new( name => $_ ) } qw(maggie lisa bart);
use KiokuDB::Util qw(set);
my $set = set(@kids);
$homer->children($set);
$dir->store($homer);
set
という便利な関数は新しいKiokuDB::Set::Transientオブジェクトを作ります。 一時的なセットはメモリスペースに存在するものです。
weak_set
という便利な関数もあります。 循環構造(例えば、今の例にparent
属性を追加する)を避けるために内部で使われている、 Set::Object::Weakで一時的なセットを作ります。
このオブジェクトは普通のSet::Objectとほとんど同じように振る舞います。
my @kids = $dir->lookup($homer_id)->children->members;
主な違いは、セットがデータベースから来るのがデフォルトで遅延されていることです。 @kids
にあるオブジェクトは、実際に必要になるときまでロードされません。
このことにより、ユーザーのオブジェクトのカプセル化を壊すこと無しに、 部分的にロードされるので、データベースに巨大なオブジェクトグラフがあっても問題になりません。 この振る舞いはKiokuDB::Set::DefferedとKiokuDB::Set::Loadedで実装されています。
このセットオブジェクトは、遅延ロードの操作に最適化されています。 例えば、2つの遅延セットを横断するなら、横断するセットのみがロードされる必要があります。
THE TYPEMAP
KiokuDBにオブジェクトが保存される際に、KiokuDB::Collapserを通過します。 エントリーがバックエンドにインサートされる前に、KiokuDB::Entryに、 "平たく"されたオブジェクトを入れます。
collapserには、KiokuDB::TypeMapオブジェクトを使います。このオブジェクトは、 それぞれのタイプのオブジェクトがどのように破壊するかを教えます。
オブジェクトを取ってくる間、オブジェクトを再インフレートして、 ワーキングオブジェクトにするのに、同じtypemapが使われます。
typemapにないオブジェクトを保存しようとするとエラーになります。 ランタイムの状態に依存する多くのオブジェクトがあるためです(例えば、DBI
はソケット、オブジェクト。 XSベースのモジュールは数値のような内部的なポインタを持ちます)。 大半のオブジェクトは安全にシリアライズできるにもかかわらず、 わずかな報告されないもろさが、大きなデバッグの難しい問題を作るのはありがちなことです。
このルールの例外は、Mooseベースのオブジェクトです。Mooseの強大な リフレクションサポートを通して、十分なメタ情報が利用できるので、 安全にシリアライズ出来ます。
加えて、標準のバックエンドは共通のオブジェクト(DateTime, Path::Classなど>)用に デフォルトのtypemapを提供しています。KiokuDBにどんなカスタムのtypemapが渡されても、 デフォルトとマージされます。
それで、実際にKiokuDBにClass::Accessorベースのオブジェクトのようなものを保存させるには、 次のようにします:
my $dir = KiokuDB->new(
backend => $backend,
typemap => KiokuDB::TypeMap->new(
entries => {
"My::Object" => KiokuDB::TypeMap::Entry::Naive->new,
},
),
);
KiokuDB::TypeMap::Entry::Naiveは単純に再帰的にたどることで、 オブジェクトのナイーブな破壊を行います。
collapser は、オブジェクトを見つけると、KiokuDB::TypeMap::Resolverに、 オブジェクトのクラスに応じた、破壊ルーチンを尋ねます。
この検索は、典型的には、ref $object
で行われ、継承を使いません。 スーパークラスで安全に使われているtypemapエントリーは、 必ずしもサブクラスで安全に使えるとは限らないからです。 継承されたエントリーにしたいなら、isa_entries
を指定してください。
KiokuDB::TypeMap->new(
isa_entries => {
"My::Object" => KiokuDB::TypeMap::Entry::Naive->new,
},
);
オブジェクトに通常の(ref
keyed)エントリーが見つからなければ、 isaエントリーがオブジェクトスーパークラスのために探されます。 サブクラスエントリーはスーパークラスエントリーより前に試されます。 この検索の結果はキャッシュされるので、クラスごとに一回しか起こりません。
Typemap Entries
カスタムのシリアライズのフックが欲しければ、自分のオブジェクトを破壊するための フックを指定できます。
KiokuDB::TypeMap::Entry::Callback->new(
collapse => sub {
my $object = shift;
...
return @some_args;
},
expand => sub {
my ( $class, @some_args ) = @_;
...
return $object;
},
);
これらのフックはオブジェクトを破壊するときに、メソッドとして呼ばれます。
例えば、typemapのISAに関連するPath::Classは:
'Path::Class::Entity' => KiokuDB::TypeMap::Entry::Callback->new(
intrinsic => 1,
collapse => "stringify",
expand => "new",
);
intrinsic
フラグは次のセクションで述べます。
typemapエントリのもう一つの選択はKiokuDB::Typemap::Entry::Passthroughです。 バックエンドのシリアライズがネイティブにデータタイプを扱うことができると分かっていれば、 これは適切です。
例えば、オブジェクトに適切なStorableフックがあり(破壊する必要のあるサブオブジェクトを含まない)、 バックエンドには、KiokuDB::Backend::Serialize::Storableを使う場合です。 DateTimeはそのようにstorableが望むクラスの例です:
'DateTime' => KiokuDB::Backend::Entry::Passthrough->new( intrinsic => 1 )
Intrinsic vs. First Class
KiokuDBでは、すべてのオブジェクトに、通常、IDが割り当てられます。 オブジェクトが複数のオブジェクトに共有されている場合、このリレーションは維持されます。
しかし、いくつかのオブジェクトは望ましい振る舞いをしません。 それらは、DateTimeや、Path::Classエントリ、URIオブジェクトのようなもので、 値を表現します。
KiokuDBはintrinsiclyに、そのようなオブジェクトを、 そのオブジェクトにそれ自身のIDと新しいKiokuDB::Entryを作る代わりに、 破壊するよう要求できます。オブジェクトが直接破壊できれば、親の構造の中に入ります。
破壊され、共有されたリファレンスは、もともと2つの区別されたコピーとして データーベースからロードされます。ですので、一つをアップデートしても、 もう一方には影響がありません。
例えば、下記のようなコードを動かしたとして:
use Path::Class;
my $path = file(qw(path to foo));
$obj_1->file($path);
$obj_2->file($path);
$dir->store( $obj_1, $obj_2 );
データがインサートされるときには、下記は真ですが、 $obj_1
と$obj_2
がデーターベースからロードされると、もはや真ではありません:
refaddr($obj_1->file) == refaddr($obj_2->file)
$obj_1
と$obj_2
の両方が$path
のコピーだからです。
この現象は、通常、変異されず、複製されたり置き換えられたりするオブジェクトに適しています。 そのようなオブジェクトのためには、最初のクラスエントリが独自のIDでバックエンドに作られるのは、 望まれていないからです。
The Default Typemap
それぞれのバックエンドには、デフォルトのtypemapがついています。 それには、共通のCPANモジュールオブジェクトのために、いくつか共通のビルトインのエントリもあります。 KiokuDB::TypeMap::Defaultにより詳細があります。
SIMPLE SEARCHES
(単純な検索)
ほとんどのバックエンドが効率的ではないものの、便利な単純な検索があります。 ろえは、エントリをスキャンして、フィールドにマッチさせます。
このAPIを使いたいなら、KiokuDB::Backend::DBIを使うことをおすすめします。 単純亜検索はSQLのwhere節を使って実装でき、より効率的だからです。 (ただし、手でカラムをセットアップしないといけませんが)
search
メソッドに引数としてハッシュリファレンスのみを渡して呼びます。 単純な検索機能が呼び出され、Data::Stream::Bulkが結果と一緒に戻ってきます:
my $stream = $dir->search({ name => "Homer Simpson" });
while ( my $block = $stream->next ) {
foreach my $object ( @$block ) {
# $object->name eq "Homer Simpson"
}
}
正確なAPIはまだ決められていません。将来的に、DBIx::Class 0.09のシンタックスと 互換にするつもりです。
DBI SEARCH COLUMNS
この簡単な検索APIを使うには、DBIバックエンドにカラムを設定しなければいけません。
検索するために、'name'カラムを作りましょう:
my $dir = KiokuDB->connect(
"dbi:SQLite:dbname=foo",
columns => [
# specify extra columns for the 'entries' table
# in the same format you pass to DBIC's add_columns
name => {
data_type => "varchar",
is_nullable => 1, # probably important
},
],
);
スキーマを手で変更することもできますし、また、データをバックアップするのに、kioku dump
を使い、 データベースを削除し、create => 1
で接続し、kioku load
を使うことも出来ます。
このカラムを埋め込むために、Homerをロードして、更新する必要があります:
{
my $s = $dir->new_scope;
$dir->update( $dir->lookup( $homer_id ) );
}
データベースでは次のようになります:
id = 201C5B55-E759-492F-8F20-A529C7C02C8B
name = Homer Simpson
GETTING STARTED WITH BDB
(BDBを始めよう)
KiokuDBでもっとも成熟したバックエンドは、KiokuDB::Backend::DBDです(訳注:DBIのほうが安定しているとYAPC::Asia 2009で聞きました)。 十分に動きますし、多くの機能をサポートします。 オブジェクトのインデックスのカスタマイズやトランザクションを提供する Search::GINのようなインテグレーションもあります。
KiokuDB::Backend::DBIはより新しいですが、そこまでテストされていません。 ですが、トランザクションもサポートしますし、クエリベースのSearch::GINもあります。 これも、なかなかよく動きます。ですが、KiokuDB::Backend::BDBと同じくらい速くはありません (訳注:YAPC::Asia 2009では、ほぼ変わらないと聞きました)
Installing KiokuDB::Backend::BDB
KiokuDB::Backend::BDBは、BerkeleyDBモジュールが必要です。 また、最近のバージョンのBerkeley DB自身も必要です。Berkeley DBは、以下のURLにあります。 http://www.oracle.com/technology/software/products/berkeley-db/db/index.html.
BerkeleyDB(ライブラリ)は通常、/usr/local/BerkeleyDB.4.7
にインストールされます。 ですが、BerkeleyDB(モジュール)は、/usr/local/BerkeleyDB
を見ようとします。 ですので、シンボリックリンクを作っておけば、インストールが簡単になります。
BerkeleyDBがインストールできれば、KiokuDB::Backend::BDBは問題なくインストールできるはずです。 KiokuDBと一緒に使うことができます。
Using KiokuDB::Backend::BDB
BDBバックエンドを使うために、ストレージを作らなければいけません。 このために、create
フラグを渡さなければいけません。
my $backend = KiokuDB::Backend::BDB->new(
manager => {
home => Path::Class::Dir->new(qw(path to storage)),
create => 1,
},
);
BDBバックエンドは、BerkeleyDB::Managerを使って、たくさんのBerkeleyDBの下働きを行います。 BerkeleyDB::Managerオブジェクトはmanager
属性で提供される引数を使って、インスタンス化されます。
これで、ストレージがつくられました。このバックエンドを、以前と同様に使います。
my $dir = KiokuDB->new( backend => $backend );
その後のオープンには、create
属性が真である必要はありませんが、真であっても特に害はありません。
このconnect
は上記のものと同じです:
my $dir = KiokuDB->connect( "bdb:dir=path/to/storage", create => 1 );
TRANSACTIONS
(トランザクション)
いくつかのバックエンド(KiokuDB::Backend::Role::TXNロールをするもの)は、トランザクションが使えるものがあります。
DBIx::Classに慣れているなら、すぐわかるでしょう:
$dir->txn_do(sub {
$dir->store($obj);
});
BerkeleyDBレベルのトランザクションを作ります。データベースへのすべての変更は ブロックが綺麗に実行されたら、コミットされます。
何らかのエラーが起きれば、トランザクションはロールバックされます。 変更は次の読み込みでは、見えません。
KiokuDB生きているインスタンスには触れません。ですので、次のようにすると
$dir->txn_do(sub {
my $scope = $dir->new_scope;
$obj->name("Dancing Hippy");
$dir->store($obj);
die "an error";
});
name
属性はロールバックされません。store
オペレーションだけが、元に戻ります。
トランザクションは適切にネストできます。また、ほとんどのバックエンドで、一般的に 書き込みのパフォーマンスが良くなります。
QUERIES
(クエリ)
KiokuDB::Backend::BDB::GINはKiokuDB::Backend::BDBのサブクラスで、 Serach::GINインテグレーションを提供しています。
Search::GINはインデックスとクエリーオブジェクトのフレームワークです。 Postgresの内部GIN apiにインスパイアされました。 GINは、Generalized Inverted Indexes(訳注:汎用転置索引)の略です。
Search::GINを使うと、任意の検索キーをオブジェクトにタイしてインデックスできます。 そして、それらのオブジェクトをクエリで検索できます。
例えば、Search::GINがサポートする、すぐに使える、予めある検索の一つに、クラスインデックスがあります。 Search::GIN::Extract::Callback を使って、オブジェクトにカスタムのインデックスを作りましょう:
my $dir = KiokuDB->new(
backend => KiokuDB::Backend::BDB::GIN->new(
extract => Search::GIN::Extract::Callback->new(
extract => sub {
my ( $obj, $extractor, @args ) = @_;
if ( $obj->isa("Person") ) {
return {
type => "user",
name => $obj->name,
};
}
return;
},
),
),
);
$dir->store( @random_objects );
オブジェクトを検索するために、マニュアルキー検索クエリを使います:
my $query = Search::GIN::Query::Manual->new(
values => {
type => "person",
},
);
my $stream = $dir->search($query);
結果として、検索結果を表すData::Stream::Bulkオブジェクトが返ります。 次のようにイテレートできます。
while ( my $block = $stream->next ) {
foreach my $person ( @$block ) {
print "found a person: ", $person->name;
}
}
また、より単純に、メモリに全結果をロードしてもかまわないなら:
my @people = $stream->all;
Search::GINはまだ未成熟です。ドキュメントも書いているところです。 ですが、このような単純な検索は動きますし、Search::GIN::Extract::Classのような 予めある解決を含んでいます。
つまり、現在は動きますが、新しく開発をするときには、これに注意してください。
翻訳について
翻訳者:加藤敦 (ktat@cpan.org)
Perlドキュメント日本語訳 Project にて、 Perlモジュール、ドキュメントの翻訳を行っております。
http://perldocjp.sourceforge.jp/
http://sourceforge.jp/projects/perldocjp/
http://www.freeml.com/ctrl/html/MLInfoForm/perldocjp@freeml.com
http://www.perldoc.jp/