jammy (3) KiokuDB::Tutorial.3pm.gz
NAME
POD2::JA::KiokuDB::Tutorial - KiokuDBを始めよう
Install
(インストール) KiokuDBとバックエンドと一緒にインストールするには、Task::KiokuDBをインストールするのが一番 簡単です。 KiokuDBはMooseと、いくつかのすぐに使えるモジュールに依存していますが、 特定のストレージモ ジュールには依存していません。 KiokuDBは複数のバックエンドのフロントエンドです。 DBIが実際のデータベースへの接続にDBDを 使っているのに似ています。 開発用やテストとして、メモリに保存するKiokuDB::Backend::Hashバックエンドを使うことができま す。 プロダクションに は、KiokuDB::Backend::DBDかKiokuDB::Backend::DBIかKiokuDB::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つのテーブルがあります。"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"属性がクリアされ ます。マージする他のリファレンスがないからです。 次のコードが理由をデモンストレートします: 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::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ベースのオブジェクトのようなものを保存させるには、 次のようにします: 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オブジェクトのようなもので、 値を表現します。 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/