NAME

POD2::JA::KiokuDB::Tutorial - KiokuDBを始めよう

Install

(インストール)

KiokuDBとバックエンドと一緒にインストールするには、Task::KiokuDBをインストールするのが一番簡単です。

KiokuDBMooseと、いくつかのすぐに使えるモジュールに依存していますが、 特定のストレージモジュールには依存していません。

KiokuDBは複数のバックエンドのフロントエンドです。 DBIが実際のデータベースへの接続にDBDを使っているのに似ています。

開発用やテストとして、メモリに保存するKiokuDB::Backend::Hashバックエンドを使うことができます。 プロダクションには、KiokuDB::Backend::DBDKiokuDB::Backend::DBIKiokuDB::Backend::BDB をバックエンドとして推奨します。

KiokuDB::Backend::DBDをインストールして、以下のインストラクションを見てください。

CREATING A DIRECTORY HANDLE

(ディレクトリハンドルの作成)

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");

設定YAMLファイルです:

---
# these are basically the arguments for 'new'
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, # this causes the tables to be created
);

ユーザー名とパスワードで接続する場合、名前付きの引数を指定しないといけません:

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 );

3番目に、すべてのKiokuDB操作はscope内で行う必要があります。 スコープは上のような簡単な例では大して重要ではありませんが、 循環参照やweakリファレンスが使われるようになると、必要になります。 後でより詳細に見ていきます。

LOADING OBJECTS

(オブジェクトの読み出し)

さて、データベースにHomerが入りました。storeから得たIDで取り出せます。

my $homer = $dir->lookup($homer_id);

$scope$objは、スコープ内にあるとします。$homer$objは実際に、同じオブジェクトになります。

# this is true:
refaddr($homer) == refaddr($obj)

生存しているオブジェクトセット (KiokuDB::LiveObjects)内のオブジェクトが "生存"しているかをKiokuDBが追跡しているからです。

オブジェクト既にメモリにあるなら、KiokuDBはインスタンスを バックエンドから取得します。

WHAT WAS STORED

(何が保存されたか)

データベースを覗いてみましょう:

% sqlite3 kiokudb_tutorial.db
SQLite version 3.4.0
Enter ".help" for instructions
sqlite>

データベースのスキーマには2つのテーブルがあります。entriesgin_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)
);

メインのカラムはiddataです。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オプションがありましたので、この循環構造はリークしません。

データベースでオブジェクトが更新されたら、LinkDBspouse属性を含むリファレンスを見て、 この関係はストレージ内でユニークな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属性がクリアされます。マージする他のリファレンスがないからです。

次のコードが理由をデモンストレートします:

sub get_homer {
    my $homer = Person->new( name => "Homer Simpson" );
    my $marge = Person->new( name => "Marge Simpson" );

    $homer->spouse($marge);
    $marge->spouse($homer);

    return $homer;

    # at this point $homer and $marge go out of scope
    # $homer has a refcount of 1 because it's the return value
    # $marge has a refcount of 0, and gets destroyed
    # the weak reference in $homer->spouse is cleared
}

my $homer = get_homer();

$homer->spouse; # this returns undef

次のイディオムを使って:

{
    my $scope = $dir->new_scope;

    # do all KiokuDB work in here
}

少なくとも必要である時間はオブジェクトが生きていることを確保できます。

Webアプリケーションのコンテキストでは、普通リクエストごとに新しいスコープを作ります。 実際、Catalyst::Model::KiokuDBは、自動的にそうしています。

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::EntryKiokuDB::Referenceオブジェクトは、 代わりに、storableフックでシリアライズされます。

OBJECT SETS

(オブジェクトセット)

より複雑なリレーションシップ(1対1に限らない)は、Set::Objectでふつう簡単にモデル化できます。

Personクラスを拡張してそのようなリレーションシップを足してみましょう:

package Person;

has children => (
    does => "KiokuDB::Set",
    is   => "rw",
);

KiokuDB::Setオブジェクトは、Set::ObjectKiokuDB用のラッパーです。

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::DefferedKiokuDB::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が渡されても、 デフォルトとマージされます。

それで、実際にKiokuDBClass::Accessorベースのオブジェクトのようなものを保存させるには、 次のようにします:

KiokuDB->new(
    backend => $backend,
    allow_classes => [qw(My::Object)],
);

これは次の省略形です:

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オブジェクトのようなもので、 値を表現します。

KiokuDBintrinsiclyに、そのようなオブジェクトを、 そのオブジェクトにそれ自身の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::GINKiokuDB::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/