プログラミングガイド

Copyright (C) 2004-2007 Mikio Hirabayashi
Last Update: Tue, 06 Mar 2007 12:05:18 +0900

目次

  1. はじめに
  2. アーキテクチャ
  3. ビルド
  4. 文書を扱うAPI
  5. 検索条件を扱うAPI
  6. データベースを扱うAPI
  7. ギャザラのサンプル
  8. サーチャのサンプル
  9. 並列性
  10. 助言

はじめに

このガイドでは、Hyper EstraierのAPIの詳細な使い方を説明します。ユーザガイドをまだお読みでない場合は先にそちらに目を通しておいてください。

全文検索システムの機能要件は様々ですが、代表的なものを挙げてみましょう。

リポジトリへの要求
ファイルシステム、メールボックス、Web、Wiki、RDBMS、各社の文書管理システムなどへの対応。
文書形式への要求
プレーンテキスト、HTML、MIME、PDF、RTF、MS-Word、MP3、各種の画像(OCR)などへの対応。
言語や語彙への要求
日本語、英語、中国語などの各種言語対応。類義語の正規化。表記揺れの正規化。
ユーザインターフェイスへの要求
検索条件の指定方法。結果の表示方法。クライアントの種類。応答時間。同時接続数。
管理機能への要求
対象文書の規模。文書の差分登録。無停止運用。バックアップ。移植性。

考え得る全ての要求に応えることはできませんが、estcmdestseek.cgiを使うだけでは不可能だった多くのことが、ライブラリを組み込んだアプリケーションを作ることで可能になります。estcmdではファイルとして存在している文書しか扱えませんでしたが、ライブラリを使えば、リレーショナルデータベースに格納されたレコードを文書として扱うアプリケーションを作ることもできます。estseek.cgiはWebブラウザで使うものでしたが、ライブラリを使えば、ネイティブOSのGUIを備えた検索アプリケーションを作ることもできます。

Hyper Estraierが提供する「コアAPI」は、転置インデックスというデータ構造を実現する機能を提供するだけです。つまり、文書を取得したり解釈したりする処理はアプリケーションに任されます。検索結果を表示するための処理もアプリケーションに任されます。繰り返しになりますが、Hyper Estraierはリポジトリ(対象文書の格納場所)の種類には依存しませんし、文書形式にも依存しませんし、ユーザインターフェイスにも依存しません。それらはアプリケーション作者が好きなように設計・実装することができます。

Hyper EstraierはUnicode(UCS-2)の文字セットを扱い、UTF-8で表現します。したがって、現在世界で日常的に使われているほとんどの言語の文字が利用できます。また、テキストから検索キーを切り出すにあたってN-gram法を用いるので、言語の語彙に依存しません。

スケーラビリティ(大規模な文書群を扱う能力)が高いことは、Hyper Estraierの特徴の一つです。スケーラビリティを向上するための工夫はコアAPIの内部でやってくれるので、アプリケーション作者は文書規模のことをそれほど気にしないで済みます。

このガイドではC言語版のコアAPIについて説明しますが、コアAPIをJavaRubyPerlから呼び出すためのバインディングもあります。また、Hyper EstraierはP2Pアーキテクチャに基づく「ノードAPI」も提供しています。ノードAPIについてはP2Pガイドをご覧ください。


アーキテクチャ

ここでは、Hyper EstraierのコアAPIのアーキテクチャについて説明します。

ギャザラとフィルタ

検索対象の文書のデータをインデックスに登録する機能を「ギャザラ(gatherer)」と呼ぶことにします。ギャザラはアプリケーションが実装します。例えば、estcmdにはファイルシステムを探索して文書のデータを集めてくる機能がありますが、そこでは以下のような処理をしています。

ファイルなどから文書の属性と本文を取り出す機能を「フィルタ(filter)」と呼びます。フィルタはアプリケーションが実装します。アプリケーションが独自の解析処理を実装してもいいですし、巷に転がっているライブラリを使ってもいいでしょう。あるいは、外部のコマンドを呼び出してその出力を加工してもいいでしょう。

サーチャ

インデックスに検索をかけて該当する文書の情報を取得し、それを表示する機能を「サーチャ(searcher)」と呼びます。サーチャはアプリケーションが実装します。例えば、estseek.cgiはWebサーバ上からCGIで呼び出されて検索結果をHTMLとして生成する機能を持ちますが、それは以下のような処理をしています。

検索結果をわかりやすくするために、各文書のスニペット(紹介文)を表示した方がいいでしょう。本文から検索語の周辺を抜き出したスニペットを作る機能がコアAPIによって提供されますが、アプリケーションが独自に実装してもかまいません。自然言語処理を行って内容の要約をしてもいいでしょう。本文中の頻出語をキーワードとして表示してもいいでしょう。

典型的なシステム構成

システム構成の典型例を以下に図示します。これはあくまで概念的なもので、実際のアプリケーションではサーチャとギャザラを一つのモジュールで実現してもいいですし、リポジトリにギャザラやサーチャを組み込んでもいいでしょう。

[framework]

ビルド

Hyper EstraierのコアAPIはC言語のAPIですので、アプリケーションの実装言語はCまたはC++になります。ただし、他の言語(JavaまたはRuby)のバインディングも提供されています。ここでは、C言語のライブラリを使ったアプリケーションをビルドする方法を説明します。

ヘッダの取り込み

コアAPIを使うアプリケーションのソースコードでは、estraier.hcabin.hstdlib.hをインクルードしてください。estraier.hはHyper Estraierのヘッダファイルで、cabin.hはQDBMのヘッダファイルです。cabin.hが提供する機能についてはQDBMの文書をご覧ください。

#include <estraier.h>
#include <cabin.h>
#include <stdlib.h>

コンパイルとリンク

デフォルトではHyper Estraierのヘッダは「/usr/local/include」の中、ライブラリは「/usr/local/lib」の中にインストールされ、-lestraierの他に前提となるライブラリとして-lqdbm-lresolv-lnsl-lpthread-lz-liconv-lm-lcを利用します。したがって、Hyper Estraierを組み込んだアプリケーションをビルドするには、以下のようなコマンドを実行すればよいことになります。

gcc -I/usr/local/include -o foobar foobar.c \
  -L/usr/local/lib -lestraier -lresolv -lnsl -lpthread -lqdbm -lz -liconv -lm -lc

しかし、上記はHyper Estraierのインストール先を変えた場合にはうまく動きません。インテグレーションを自動化する場合には、estconfigコマンドの出力を埋め込むようにして、保守性を向上させましょう。以下のようにします。

gcc `estconfig --cflags` -o foobar foobar.c `estconfig --ldflags` `estconfig --libs`

インテグレーション用コマンド

estconfigは、Hyper Estraierを使ったシステムやアプリケーションのインテグレーションを行う際に役立つユーティリティコマンドです。Hyper Estraierのビルド時の設定を後で参照するために使います。

estconfig --version
バージョン情報を出力します。
estconfig --prefix
全体のインストール先の接頭辞を出力します。
estconfig --execprefix
プラットフォーム依存ファイルのインストール先の接頭辞を出力します。
estconfig --headdir
ヘッダファイルのインストール先の接頭辞を出力します。
estconfig --libdir
ライブラリのインストール先の接頭辞を出力します。
estconfig --bindir
コマンドのインストール先の接頭辞を出力します。
estconfig --libexecdir
CGIスクリプトのインストール先の接頭辞を出力します。
estconfig --datadir
各種設定ファイルのインストール先の接頭辞を出力します。
estconfig --cflags
アプリケーションのビルド時に指定すべきコンパイラオプションを出力します。
estconfig --ldflags
アプリケーションのビルド時に指定すべきリンカオプションを出力します。
estconfig --libs
アプリケーションのビルド時に指定すべきライブラリを出力します。

estconfigは常に0を終了ステータスにします。


文書を扱うAPI

ここでは、検索対象の文書を扱うためのAPIについて説明します。

機能

構造体型 `ESTDOC' は、文書を抽象化したものです。一つの文書は、複数の属性と複数の本文の集合です。`ESTDOC' の実体が直接参照されることはなく、必ずポインタを介して間接参照されます。このポインタおよびその参照先を総じて文書オブジェクトと呼びます。文書オブジェクトは関数 `est_doc_new' によって生成され、`est_doc_delete' によって破棄されます。生成された文書オブジェクトは必ず破棄してください。

検索対象となる文書は、文書オブジェクトとして表現した上で、予めデータベースに登録しておきます。登録済みの文書オブジェクトにはIDが割り当てられます。検索時にはそのIDを元にデータベースに問い合わせて、登録してある文書オブジェクトを取り出すことになります。なお、文書オブジェクトに付加する属性や本文の文字コードはUTF-8にしてください。

文書オブジェクトの典型的なライフサイクルを以下に示します。

ESTDOC *doc;

/* 生成する */
doc = est_doc_new();

/* URIとタイトルを属性として付加する */
est_doc_add_attr(doc, "@uri", "http://foo.bar/baz.txt");
est_doc_add_attr(doc, "@title", "Now Scream");

/* 本文を付加する */
est_doc_add_text(doc, "Give it up, Yo!  Give it up, Yo!");
est_doc_add_text(doc, "Check it out, come on!");

  /* ここでデータベースに登録したり、画面に表示したりする */

/* 破棄する */
est_doc_delete(doc);

API

文書オブジェクトを生成するには、関数 `est_doc_new' を用います。

ESTDOC *est_doc_new(void);
戻り値は文書オブジェクトです。

文書ドラフトのデータから文書オブジェクトを生成するには、関数 `est_doc_new_from_draft' を用います。

ESTDOC *est_doc_new_from_draft(const char *draft);
`draft' は文書ドラフトの文字列を指定します。戻り値は文書オブジェクトです。

文書オブジェクトを破棄するには、関数 `est_doc_delete' を用います。

void est_doc_delete(ESTDOC *doc);
`doc' は文書オブジェクトを指定します。

文書オブジェクトに属性を追加するには、関数 `est_doc_add_attr' を用います。

void est_doc_add_attr(ESTDOC *doc, const char *name, const char *value);
`doc' は文書オブジェクトを指定します。`name' は属性名を指定します。`value' は属性値を指定しますが、`NULL' の場合は属性を削除します。

文書オブジェクトに本文の一文を追加するには、関数 `est_doc_add_text' を用います。

void est_doc_add_text(ESTDOC *doc, const char *text);
`doc' は文書オブジェクトを指定します。`text' は本文の一文を指定します。

文書オブジェクトに隠しテキストの一文を追加するには、関数 `est_doc_add_hidden_text' を用います。

void est_doc_add_hidden_text(ESTDOC *doc, const char *text);
`doc' は文書オブジェクトを指定します。`text' は隠しテキストの一文を指定します。

文書オブジェクトにキーワードを添付するには、関数 `est_doc_set_keywords' を用います。

void est_doc_set_keywords(ESTDOC *doc, CBMAP *kwords);
`doc' は文書オブジェクトを指定します。`kwords' はキーワードのマップオブジェクトを指定します。マップのキーはキーワードの文字列で、値はそのスコアの10進数表現です。マップオブジェクトは内部的にコピーされます。

文書オブジェクトに代替スコアを設定するには、関数 `est_doc_set_score' を用います。

void est_doc_set_score(ESTDOC *doc, int score);
`doc' は文書オブジェクトを指定します。`score' は代替スコアを指定します。負数の場合は代替スコアの既存の設定が無効化されます。

文書オブジェクトのID番号を取得するには、関数 `est_doc_id' を用います。

int est_doc_id(ESTDOC *doc);
`doc' は文書オブジェクトを指定します。戻り値は文書オブジェクトのID番号です。もしそのオブジェクトがまだ登録されていない場合、-1が返されます。

文書オブジェクトの属性名のリストを取得するには、関数 `est_doc_attr_names' を用います。

CBLIST *est_doc_attr_names(ESTDOC *doc);
`doc' は文書オブジェクトを指定します。戻り値は文書オブジェクトの属性名のリストです。戻り値のオブジェクトは `cblistopen' で生成されているので、不要になったら `cblistclose' で破棄してください。

文書オブジェクトの属性の値を取得するには、関数 `est_doc_attr' を用います。

const char *est_doc_attr(ESTDOC *doc, const char *name);
`doc' は文書オブジェクトを指定します。`name' は属性名を指定します。戻り値は属性値ですが、該当する属性がない場合は `NULL' が返されます。戻り値の文字列の寿命は文書オブジェクトのそれと同期します。

文書オブジェクトの本文のリストを取得するには、関数 `est_doc_texts' を用います。

const CBLIST *est_doc_texts(ESTDOC *doc);
`doc' は文書オブジェクトを指定します。戻り値は本文のリストオブジェクトです。戻り値のオブジェクトの寿命は文書オブジェクトのそれと同期します。

文書オブジェクトの本文を連結した文字列を取得するには、関数 `est_doc_cat_texts' を用います。

char *est_doc_cat_texts(ESTDOC *doc);
`doc' は文書オブジェクトを指定します。戻り値は本文を連結した文字列のデータです。戻り値の領域は `malloc' で生成されているので、不要になったら `free' で破棄してください。

文書オブジェクトに添付されたキーワードを取得するには、関数 `est_doc_keywords' を用います。

CBMAP *est_doc_keywords(ESTDOC *doc);
`doc' は文書オブジェクトを指定します。戻り値はキーワードのマップオブジェクトです。マップのキーはキーワードの文字列で、値はそのスコアの10進数表現です。戻り値のオブジェクトの寿命は文書オブジェクトのそれと同期します。

文書オブジェクトから文書ドラフトを生成するには、関数 `est_doc_dump_draft' を用います。

char *est_doc_dump_draft(ESTDOC *doc);
`doc' は文書オブジェクトを指定します。戻り値は文書ドラフトのデータです。戻り値の領域は `malloc' で生成されているので、不要になったら `free' で破棄してください。

文書オブジェクトの本文のスニペットを生成するには、関数 `est_doc_make_snippet' を用います。

char *est_doc_make_snippet(ESTDOC *doc, const CBLIST *words, int wwidth, int hwidth, int awidth);
`doc' は文書オブジェクトを指定します。`words' はハイライトすべき語句のリストオブジェクトを指定します。`wwidth' は結果全体の幅(≒文字数)を指定します。`hwidth' は本文の冒頭から抽出する幅を指定します。`awidth' はハイライトされる語の周辺から抽出する幅を指定します。戻り値は文書オブジェクトのスニペットの文字列です。その形式はタブ区切り文字列(TSV)です。その各行は表示すべき文字列です。ほとんどの行は単一のフィールドしか持ちませんが、いくつかは二つのフィールドを持ちます。もし第2フィールドが存在したならば、第1フィールドはハイライトして表示すべき文字列で、第2フィールドはその正規化された文字列です。戻り値の領域は `malloc' で生成されているので、不要になったら `free' で破棄してください。

検索条件を扱うAPI

ここでは、検索条件を扱うためのAPIについて説明します。

機能

構造体型 `ESTCOND' は、検索条件を抽象化したものです。一つの検索条件は、一つの検索フレーズと複数の属性検索条件と一つの順序指定の集合です。`ESTCOND' の実体が直接参照されることはなく、必ずポインタを介して間接参照されます。このポインタおよびその参照先を総じて検索条件オブジェクトと呼びます。検索条件オブジェクトは関数 `est_cond_new' によって生成され、`est_cond_delete' によって破棄されます。生成された検索条件オブジェクトは必ず破棄してください。

検索条件オブジェクトをデータベースに渡すことにより、その条件に該当する文書IDのリストを取得することができます。各種の条件式の書式についてはユーザガイドの検索条件式の項目を参照してください。なお、各種の条件式の文字コードはUTF-8にしてください。

検索条件オブジェクトの典型的なライフサイクルを以下に示します。

ESTCOND *cond;

/* 生成する */
cond = est_cond_new();

/* 本文に「check」と「out」を含むと指定する */
est_cond_set_phrase(cond, "check AND out");

/* URIが「.txt」で終わると指定する */
est_cond_add_attr(cond, "@uri ISTREW .txt");

  /* ここでデータベースに問い合わせを行う */

/* 破棄する */
est_cond_delete(cond);

API

検索条件オブジェクトを生成するには、関数 `est_cond_new' を用います。

ESTCOND *est_cond_new(void);
戻り値は検索条件オブジェクトです。

検索条件オブジェクトを破棄するには、関数 `est_cond_delete' を用います。

void est_cond_delete(ESTCOND *cond);
`cond' は検索条件オブジェクトを指定します。

検索条件オブジェクトに検索フレーズを設定するには、関数 `est_cond_set_phrase' を用います。

void est_cond_set_phrase(ESTCOND *cond, const char *phrase);
`cond' は検索条件オブジェクトを指定します。`phrase' は検索フレーズを指定します。

検索条件オブジェクトに属性検索条件を追加するには、関数 `est_cond_add_attr' を用います。

void est_cond_add_attr(ESTCOND *cond, const char *expr);
`cond' は検索条件オブジェクトを指定します。`expr' は属性検索条件の式を指定します。

検索条件オブジェクトに順序指定を設定するには、関数 `est_cond_set_order' を用います。

void est_cond_set_order(ESTCOND *cond, const char *expr);
`cond' は検索条件オブジェクトを指定します。`expr' 順序指定の式を指定します。デフォルトでは、順序はスコアの降順です。

検索条件オブジェクトに取得文書数の最大数を設定するには、関数 `est_cond_set_max' を用います。

void est_cond_set_max(ESTCOND *cond, int max);
`cond' は検索条件オブジェクトを指定します。`max' は取得文書数の最大数を指定します。デフォルトでは、取得文書数は無制限です。

検索条件オブジェクトに検索結果からスキップする文書数を設定するには、関数 `est_cond_set_skip' を用います。

void est_cond_set_skip(ESTCOND *cond, int skip);
`cond' は検索条件オブジェクトを指定します。`skip' は検索結果からスキップする文書数を指定します。

検索条件オブジェクトに検索オプションを設定するには、関数 `est_cond_set_options' を用います。

void est_cond_set_options(ESTCOND *cond, int options);
`cond' は検索条件オブジェクトを指定します。`options' はオプションを指定します。`ESTCONDSURE' だと全てのN-gramキーを検索します。`ESTCONDUSUAL' はデフォルトですが、N-gramのキーを1個置きで検査します。`ESTCONDFAST' だとN-gramのキーを2個置き、`ESTCONDAGITO' だと3個置きで検査します。`ESTCONDNOIDF' だとTF-IDF法による重みづけを省略します。`ESTCONDSIMPLE' だと検索フレーズを簡便書式のものとして扱います。`ESTCONDROUGH' だと検索フレーズを粗略書式のものとして扱います。`ESTCONDUNION' だと検索フレーズを論理和書式(OR結合)のものとして扱います。`ESTCONDISECT' だと検索フレーズを論理積書式(AND結合)のものとして扱います。`ESTCONDSCFB' だとスコアをフィードバックします(デバッグ専用)。それぞれのオプションはビット和で同時に指定できます。N-gramのキーの検査が省略されればされるほど、検索速度は向上しますが、検索精度は低下します。

検索条件オブジェクトに補助インデックスの結果を採用する許可を設定するには、関数 `est_cond_set_auxiliary' を用います。

void est_cond_set_auxiliary(ESTCOND *cond, int min);
`cond' は検索条件オブジェクトを指定します。`min' は補助インデックスが返す結果を採用するための下限該当件数を指定します。0を越えない値を指定すると、補助インデックスは使われません。デフォルトは32件です。

検索条件オブジェクトに類似隠蔽における下限類似度を設定するには、関数 `est_cond_set_eclipse' を用います。

void est_cond_set_eclipse(ESTCOND *cond, double limit);
`cond' は検索条件オブジェクトを指定します。`limit' は隠蔽される文書の下限の類似度を0.0から1.0までの値で指定しますが、`ESTECLSIMURL' を加算するとURLを重み付けに使うようになります。`ESTECLSERV' を指定すると類似度を無視して同じサーバの文書を隠蔽します。`ESTECLDIR' を指定すると類似度を無視して同じディレクトリの文書を隠蔽します。`ESTECLSERV' を指定すると類似度を無視して同じファイルの文書を隠蔽します。

検索条件オブジェクトに属性重複除去フィルタを設定するには、関数 `est_cond_set_distinct' を用います。

void est_cond_set_distinct(ESTCOND *cond, const char *name);
`cond' は検索条件オブジェクトを指定します。`name' は値が重複してはならない属性の名前を指定します。このフィルタが設定された場合、検索結果の候補の中で重複する属性値を持つものは除外されます。

検索条件オブジェクトにメタ検索の対象のマスクを設定するには、関数 `est_cond_set_mask' を用います。

void est_cond_set_mask(ESTCOND *cond, int mask);
`cond' は検索条件オブジェクトを指定します。`mask' は検索対象のマスクを指定します。1は1番目の対象、2は2番目の対象、4は3番目の対象といった2の累乗の値の合計で検索を抑止する対象を指定します。

データベースを扱うAPI

ここでは、データベースを扱うためのAPIについて説明します。

機能

構造体型 `ESTDB' は、データベースの参照手段を抽象化したものです。データベースは、転置インデックスと書誌情報とメタデータを保持します。データベースに接続する際には、リーダ(読み込み専用)かライタ(読み書き両用)の2つのモードのどちらかを選択します。`ESTDB' の実体が直接参照されることはなく、必ずポインタを介して間接参照されます。このポインタおよびその参照先を総じてデータベースオブジェクトと呼びます。データベースオブジェクトは関数 `est_db_open' によって生成され、`est_db_close' によって破棄されます。生成されたデータベースオブジェクトは必ず破棄してください。

各種の操作によってデータベースに起きたエラーは、関数 `est_db_error' によって取得することができます。各エラーコードの意味は、エラーコードを関数 `est_err_msg' に渡して知ることもできます。

データベースオブジェクトの典型的なライフサイクルを以下に示します。

ESTDB *db
int ecode;

/* ライタとして開く */
if(!(db = est_db_open("casket", ESTDBWRITER | ESTDBCREAT, &ecode))){
  /* 失敗した場合、エラーメッセージを出して終了する */
  fprintf(stderr, "error: %s\n", est_err_msg(ecode));
  return -1;
}

  /* ここで文書を登録したり、検索を行ったりする */

/* データベースを閉じる */
if(!est_db_close(db, &ecode)){
  /* 失敗した場合、エラーメッセージを出して終了する */
  fprintf(stderr, "error: %s\n", est_err_msg(ecode));
  return -1;
}

API

エラーコードの値として以下の定数が定義されています。

エラーコードに対応した文字列を取得するには、関数 `est_err_msg' を用います。

const char *est_err_msg(int ecode);
`ecode' はエラーコードを指定します。戻り値はエラーコードに対応した文字列です。

データベースを開いてデータベースオブジェクトを生成するには、関数 `est_db_open' を用います。

ESTDB *est_db_open(const char *name, int omode, int *ecp);
`name' はデータベースのディレクトリ名を指定します。`omode' はオープンモードを指定します。`ESTDBWRITER' ならライタ(書き込みモード)で、`ESTDBREADER' ならリーダ(読み込みモード)です。ライタの場合、ビット和で次のものを指定できます。`ESTDBCREAT' ならデータベースが存在しない場合に新規に作成することを指示し、`ESTDBTRUNC' ならデータベースが既存だった場合にも新規に作りなおすことを指示します。リーダでもライタでも `ESTDBNOLCK' または `ESTDBLCKNB' をビット和で指定することができますが、前者はファイルロックをかけないでデータベースを開くことを指示し、後者はブロックせずにロックをかけることを指示します。その場合は排他制御の責任はアプリケーションが負います。`ESTDBCREAT' に `ESTDBPERFNG' をビット和で加えてデータベースを作成すると、欧文も完全なN-gram法で処理されるようになります。`ESTDBCREAT' に `ESTDBCHRCAT' をビット和で加えてデータベースを作成すると、N-gram法の代わりに文字分類法が解析に用いられます。`ESTDBCREAT' に `ESTDBSMALL' をビット和で加えてデータベースを作成すると50000件未満の文書を登録することを想定してインデックスがチューニングされ、`ESTDBLARGE' をビット和で加えてデータベースを作成すると300000件以上の文書を登録することを想定してインデックスがチューニングされ、`ESTDBHUGE' をビット和で加えてデータベースを作成すると1000000件以上の文書を登録することを想定してインデックスがチューニングされ、`ESTDBHUGE2' をビット和で加えてデータベースを作成すると5000000件以上の文書を登録することを想定してインデックスがチューニングされ、`ESTDBHUGE3' をビット和で加えてデータベースを作成すると10000000件以上の文書を登録することを想定してインデックスがチューニングされ、`ESTDBSCVOID' をビット和で加えてデータベースを作成するとスコア情報を破棄するようになり、`ESTDBSCINT' をビット和で加えてデータベースを作成するとスコア情報を32ビットの数値として記録するようになり、`ESTDBSCASIS' をビット和で加えてデータベースを作成するとスコア情報をそのまま保存した上で検索時に調整されないようにマークします。`ecp' はエラーコードを格納する変数へのポインタを指定します。戻り値はデータベースオブジェクトか、エラーの場合は `NULL' です。

データベースを閉じてデータベースオブジェクトを破棄するには、関数 `est_db_close' を用います。

int est_db_close(ESTDB *db, int *ecp);
`db' はデータベースオブジェクトを指定します。`ecp' はエラーコードを格納する変数へのポインタを指定します。戻り値は成功なら真、エラーなら偽です。

データベースに直前に起きたエラーコードを取得するには、関数 `est_db_error' を用います。

int est_db_error(ESTDB *db);
`db' はデータベースオブジェクトを指定します。戻り値はデータベースに直前に起きたエラーコードです。

データベースに致命的エラーがあったかどうか検査するには、関数 `est_db_fatal' を用います。

int est_db_fatal(ESTDB *db);
`db' はデータベースオブジェクトを指定します。戻り値はデータベースに致命的エラーがあれば真、そうでなければ偽です。

データベースに属性の絞り込み用またはソート用のインデックスを加えるには、関数 `est_db_add_attr_index' を用いる。

int est_db_add_attr_index(ESTDB *db, const char *name, int type);
`db' はライタとして接続したデータベースオブジェクトを指定します。`name' は対象となる属性の名前を指定します。`type' はインデックスのデータ型を指定します。`ESTIDXATTRSEQ' なら多目的のシーケンシャルアクセス用になり、`ESTIDXATTRSTR' なら文字列の絞り込み用になり、`ESTIDXATTRNUM' なら数値型の絞り込み用になります。戻り値は成功なら真、エラーなら偽です。この関数はどの文書を登録するよりも前に呼ぶ必要があります。

データベースのキャッシュ内の索引語をフラッシュするには、関数 `est_db_flush' を用います。

int est_db_flush(ESTDB *db, int max);
`db' はライタとして接続したデータベースオブジェクトを指定します。`max' はフラッシュする語の最大数を指定しますが、0以下ならば全ての索引語がフラッシュされます。戻り値は成功なら真、エラーなら偽です。

データベースの更新内容を同期させるには、関数 `est_db_sync' を用います。

int est_db_sync(ESTDB *db);
`db' はライタとして接続したデータベースオブジェクトを指定します。戻り値は成功なら真、エラーなら偽です。

データベースを最適化するには、関数 `est_db_optimize' を用います。

int est_db_optimize(ESTDB *db, int options);
`db' はライタとして接続したデータベースオブジェクトを指定します。`options' はオプションを指定します。`ESTOPTNOPURGE' は削除された文書の情報を消去する処理を省略することを指示し、`ESTOPTNODBOPT' はデータベースファイルの最適化を省略することを指示します。オプションはビット和で同時に指定できます。戻り値は成功なら真、エラーなら偽です。

別のデータベースをマージするには、関数 `est_db_merge' を用います。

int est_db_merge(ESTDB *db, const char *name, int options);
`db' はライタとして接続したデータベースオブジェクトを指定します。`name' は別のデータベースのディレクトリ名を指定します。`options' はオプションを指定します。`ESTODCLEAN' は削除された文書の領域を整理することを指示します。戻り値は成功なら真、エラーなら偽です。双方のデータベースを作成した際のオプションは完全に同一である必要があります。取り込んだ文書のURLが既存の文書のURLと同じ場合は、既存の文書は削除されます。

データベースに文書を追加するには、関数 `est_db_put_doc' を用います。

int est_db_put_doc(ESTDB *db, ESTDOC *doc, int options);
`db' はライタとして接続したデータベースオブジェクトを指定します。`doc' は文書オブジェクトを指定します。文書オブジェクトはURI属性を持っていなければなりません。`options' はオプションを指定します。`ESTPDCLEAN' は上書きされた文書の領域を整理することを指示し、`ESTPDWEIGHT' はインデクシングの際に重み付け属性を静的に適用することを指示します。戻り値は成功なら真、エラーなら偽です。指定された文書オブジェクトのURI属性がデータベース内の既存の文書と一致する場合、既存の方は削除されます。

データベースから文書を削除するには、関数 `est_db_out_doc' を用います。

int est_db_out_doc(ESTDB *db, int id, int options);
`db' はライタとして接続したデータベースオブジェクトを指定します。`id' は登録文書のID番号を指定します。`options' はオプションを指定します。`ESTODCLEAN' は削除された文書の領域を整理することを指示します。戻り値は成功なら真、エラーなら偽です。

データベース内の文書の属性を編集するには、関数 `est_db_edit_doc' を用います。

int est_db_edit_doc(ESTDB *db, ESTDOC *doc);
`db' はライタとして接続したデータベースオブジェクトを指定します。`id' は登録文書のID番号を指定します。`doc' は文書オブジェクトを指定します。戻り値は成功なら真、エラーなら偽です。ID属性を変更することはできません。変更したURI属性が他の登録文書と重なる場合はエラーとなります。

データベースから文書を取得するには、関数 `est_db_get_doc' を用います。

ESTDOC *est_db_get_doc(ESTDB *db, int id, int options);
`db' はデータベースオブジェクトを指定します。`id' は登録文書のID番号を指定します。`options' はオプションを指定します。`ESTGDNOATTR' は属性を取得しないことを指示し、`ESTGDNOTEXT' は本文を取得しないことを指示し、`ESTGDNOKWD' はキーワードを取得しないことを指示します。オプションはビット和で同時に指定できます。戻り値は文書オブジェクトか、エラーなら `NULL' です。戻り値のオブジェクトは `est_doc_new' で生成されているので、不要になったら `est_doc_delete' で破棄してください。

データベースから文書の属性を取得するには、関数 `est_db_get_doc_attr' を用います。

char *est_db_get_doc_attr(ESTDB *db, int id, const char *name);
`db' はデータベースオブジェクトを指定します。`id' は登録文書のID番号を指定します。`name' は属性名を指定します。戻り値は属性値ですが、該当する文書か属性がない場合は `NULL' が返されます。戻り値の領域は `malloc' で生成されているので、不要になったら `free' で破棄してください。

URIに対応する文書のID番号を取得するには、関数 `est_db_uri_to_id' を用います。

int est_db_uri_to_id(ESTDB *db, const char *uri);
`db' はデータベースオブジェクトを指定します。`uri' は登録文書のURIを指定します。戻り値は文書のID番号であるか、エラーなら-1です。

データベースの名前を取得するには、関数 `est_db_name' を用います。

const char *est_db_name(ESTDB *db);
`db' はデータベースオブジェクトを指定します。戻り値はデータベースの名前です。戻り値の文字列の寿命はデータベースオブジェクトのそれと同期します。

データベースに登録された文書の数を取得するには、関数 `est_db_doc_num' を用います。

int est_db_doc_num(ESTDB *db);
`db' はデータベースオブジェクトを指定します。戻り値はデータベースに登録された文書の数です。

データベースに登録された異なり語の数を取得するには、関数 `est_db_word_num' を用います。

int est_db_word_num(ESTDB *db);
`db' はデータベースオブジェクトを指定します。戻り値はデータベースに登録された異なり語の数です。

データベースのサイズを取得するには、関数 `est_db_size' を用います。

double est_db_size(ESTDB *db);
`db' はデータベースオブジェクトを指定します。戻り値はデータベースのサイズです。

検索条件に該当する文書の一覧を取得するには、関数 `est_db_search' を用います。

int *est_db_search(ESTDB *db, ESTCOND *cond, int *nump, CBMAP *hints);
`db' はデータベースオブジェクトを指定します。`cond' は検索条件オブジェクトを指定します。`nump' は戻り値の配列の要素数を格納する変数へのポインタです。`hints' は各検索語に該当する文書数を格納するマップオブジェクトを指定しますが、`NULL' なら無視されます。否定条件の中の語の該当数は負数になります。マップ内の空文字列のキーには全体の該当数が関連づけられます。戻り値は該当した文書のIDを格納した配列です。この関数は決してエラーになりません。もし該当する文書がない場合にも、空の配列が返されます。戻り値の領域は `malloc' で生成されているので、不要になったら `free' で破棄してください。

複数のデータベースを検索して検索条件に該当する文書の一覧を取得するには、関数 `est_db_search_meta' を用います。

int *est_db_search_meta(ESTDB **dbs, int dbnum, ESTCOND *cond, int *nump, CBMAP *hints);
`dbs' はデータベースオブジェクトを要素とした配列を指定します。`dbnum' はその配列の要素数を指定します。`cond' は検索条件オブジェクトを指定します。`nump' は戻り値の配列の要素数を格納する変数へのポインタです。`hints' は各検索語に該当する文書数を格納するマップオブジェクトを指定しますが、`NULL' なら無視されます。否定条件の中の語の該当数は負数になります。マップ内の空文字列のキーには全体の該当数が関連づけられます。戻り値は該当した文書を格納したデータベースのインデックスと文書IDを交互に要素とする配列です。この関数は決してエラーになりません。もし該当する文書がない場合にも、空の配列が返されます。戻り値の領域は `malloc' で生成されているので、不要になったら `free' で破棄してください。

文書が検索条件のフレーズに完全に一致するか調べるには、関数 `est_db_scan_doc' を用います。

int est_db_scan_doc(ESTDB *db, ESTDOC *doc, ESTCOND *cond);
`db' はデータベースオブジェクトを指定します。`doc' は文書オブジェクトを指定します。`cond' は検索条件オブジェクトを指定します。戻り値は、文書が検索条件のフレーズに完全に一致するなら真、そうでなければ偽です。

データベースのキャッシュメモリの最大サイズを指定するには、関数 `est_db_set_cache_size' を用います。

void est_db_set_cache_size(ESTDB *db, size_t size, int anum, int tnum, int rnum);
`db' はデータベースオブジェクトを指定します。`size' はインデックス用のキャッシュメモリの最大サイズを指定します。デフォルトは64MBです。負数を指定すると、現状の設定を変更しません。`anum' は文書の属性用のキャッシュのレコード数を指定します。デフォルトは8192個です。負数を指定すると、現状の設定を変更しません。`tnum' は文書のテキスト用のキャッシュのレコード数を指定します。デフォルトは1024個です。負数を指定すると、現状の設定を変更しません。`rnum' は出現結果用のキャッシュのレコード数を指定します。デフォルトは256個です。負数を指定すると、現状の設定を変更しません。

ギャザラのサンプル

最も単純なギャザラの実装を以下に示します。全文検索システムがこんなに短いコードで実現できるなんて、便利な世の中になったものです。

#include <estraier.h>
#include <cabin.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv){
  ESTDB *db;
  ESTDOC *doc;
  int ecode;

  /* データベースを開く */
  if(!(db = est_db_open("casket", ESTDBWRITER | ESTDBCREAT, &ecode))){
    fprintf(stderr, "error: %s\n", est_err_msg(ecode));
    return 1;
  }

  /* 文書オブジェクトを生成する */
  doc = est_doc_new();

  /* 文書オブジェクトに属性を追加する */
  est_doc_add_attr(doc, "@uri", "http://estraier.gov/example.txt");
  est_doc_add_attr(doc, "@title", "Over the Rainbow");

  /* 文書オブジェクトに本文を追加する */
  est_doc_add_text(doc, "Somewhere over the rainbow.  Way up high.");
  est_doc_add_text(doc, "There's a land that I heard of once in a lullaby.");

  /* 文書オブジェクトをデータベースに登録する */
  if(!est_db_put_doc(db, doc, ESTPDCLEAN))
    fprintf(stderr, "error: %s\n", est_err_msg(est_db_error(db)));

  /* 文書オブジェクトを破棄する */
  est_doc_delete(doc);

  /* データベースを閉じる */
  if(!est_db_close(db, &ecode)){
    fprintf(stderr, "error: %s\n", est_err_msg(ecode));
    return 1;
  }

  return 0;
}

コアAPIでは、データベースの概念が重要になります。転置インデックスや登録文書などのデータを参照するための手段を抽象化したものです。データベースを開く際には、読み込みモードか書き込みモードを指定します。いずれにしても、開いたデータベースは必ず閉じてください。特に書き込みモードの場合、適切にデータベースを閉じないとデータベースが壊れてしまいます。

データベースオブジェクトに限らず、Hyper Estraierにおける大抵のオブジェクトは、生成するための関数と破棄するための関数を備えています。生成したオブジェクトは必ず破棄してください。そうしないとメモリリークになります。

est_db_openはデータベースを開く関数です。第1引数はデータベースの名前を指定しています。第2引数はオープンモードです。ESTDBWRITERは書き込みモードであることを意味し、ESTDBCREATはデータベースが存在しない場合に新しく作成することを意味します。第3引数はエラーコードを受け取る変数へのポインタを指定します。戻り値はデータベースオブジェクトですが、エラーがあった場合はNULLです。データベースを閉じるには、est_db_close関数を使います。

この例では、エラーが起きた際にはエラーメッセージを出力しています。データベースを開いたり閉じたりする関数ではエラーコードを受け取る変数へのポインタを引数で指定し、その他の関数でエラーが起きた場合にはデータベースオブジェクトの中にエラーコードがセットされます。その値はest_db_errorという関数を使って取得できます。est_err_msgという関数を使うとエラーコードに対応する文字列を取得できます。

文書オブジェクトは、各種の文書を抽象化したものです。文書オブジェクトには属性と本文を登録できます。この例ではそれらを文字列定数で指定していますが、実際のアプリケーションでは、ファイルを読むなどして取得したデータを指定することでしょう。est_doc_newは文書オブジェクトを作り、est_doc_add_attrは属性を追加し、est_doc_add_textは本文を追加し、est_doc_deleteは文書オブジェクトを破棄する関数です。文書の属性、本文とも文字コードはUTF-8にしてください。

文書オブジェクトを作ったら、それをest_db_put_doc関数に渡して登録します。登録する文書は必ずURI属性("@uri")を備えている必要があります。そうでない場合はエラーになります。また、既に登録してある文書のURIと重複した場合は、上書き(既存の文書が削除)されます。ここでは、削除された領域を整理するために、オプションでESTPDCLEANを指示しています。


サーチャのサンプル

最も単純なサーチャの実装を以下に示します。サーチャはギャザラに比べると少し長いですが、やっていることはそれほど難しくはありません。

#include <estraier.h>
#include <cabin.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char **argv){
  ESTDB *db;
  ESTCOND *cond;
  ESTDOC *doc;
  const CBLIST *texts;
  int ecode, *result, resnum, i, j;
  const char *value;

  /* データベースを開く */
  if(!(db = est_db_open("casket", ESTDBREADER, &ecode))){
    fprintf(stderr, "error: %s\n", est_err_msg(ecode));
    return 1;
  }

  /* 検索条件オブジェクトを生成する */
  cond = est_cond_new();

  /* 検索条件オブジェクトに検索式を設定する */
  est_cond_set_phrase(cond, "rainbow AND lullaby");

  /* データベースから検索結果を得る */
  result = est_db_search(db, cond, &resnum, NULL);

  /* 各該当文書を取得して表示する */
  for(i = 0; i < resnum; i++){

    /* 文書オブジェクトを取得する */
    if(!(doc = est_db_get_doc(db, result[i], 0))) continue;

    /* 属性を表示する */
    if((value = est_doc_attr(doc, "@uri")) != NULL)
      printf("URI: %s\n", value);
    if((value = est_doc_attr(doc, "@title")) != NULL)
      printf("Title: %s\n", value);

    /* 本文を表示する */
    texts = est_doc_texts(doc);
    for(j = 0; j < cblistnum(texts); j++){
      value = cblistval(texts, j, NULL);
      printf("%s\n", value);
    }

    /* 文書オブジェクトを破棄する */
    est_doc_delete(doc);

  }

  /* 検索結果を破棄する */
  free(result);

  /* 検索条件オブジェクトを破棄する */
  est_cond_delete(cond);

  /* データベースを閉じる */
  if(!est_db_close(db, &ecode)){
    fprintf(stderr, "error: %s\n", est_err_msg(ecode));
    return 1;
  }

  return 0;
}

ヘッダのインクルードやデータベースの開閉についてはギャザラと同じです。ただし、サーチャの場合、データベースは読み込みモードで開きます。書き込みモードでも検索はできるのですが、読み込みモードの場合は、プロセスがクラッシュしてもデータベースが壊れないという利点と、複数のプロセスが同時にデータベースを開けるという利点があります。

検索を行う前に、検索条件を抽象化した検索条件オブジェクトを準備します。est_cond_newで生成して、est_cond_set_phraseで検索式を指定して、使いおわったらest_cond_deleteで破棄します。

検索条件オブジェクトを渡してest_db_search関数を呼ぶことで、条件に該当する文書のIDの配列を得ることができます。配列の要素数は第3引数のポインタが指す変数に格納されます。第4引数は今は気にしないでください。

検索結果が得られたら、あとはそれに含まれる個々の文書を取り出してから表示します。est_db_get_docはIDに対応した文書オブジェクトを取り出す関数です。既に削除した文書がヒットすることもあるので、その場合にはNULLが返されます。NULLの場合は単に無視して次の周回に進みます。est_doc_get_attrは属性を取得する関数です。est_doc_get_textsは本文のリストを取得する関数です。この関数の戻り値は、QDBMのcabin.hが提供するリストオブジェクトです。cblistnum関数で要素の数を得たり、cblistval関数で特定の番号の要素を取り出すことができます。この例では、取り出した属性や本文の値をそのまま画面に出力しています。


並列性

Hyper Estraierのデータベースは、ファイルロックによって保護されます。読み込みモードで接続された場合は共有ロックがかかり、書き込みモードで接続された場合は排他ロックがかかります。したがって、読み込みモード同士であれば複数のプロセスが同じデータベースを同時に開くことができますが、書き込みモードで接続できるのは一つのデータベースに対して一つのプロセスだけです。読み込みモードの場合、複数のプロセスが同一のデータベースを開くだけでなく、読み込みモードでデータベースを開いたプロセスをforkさせることもできます。

書き込みモードであっても読み込みモードであっても、同一プロセス内で同一のデータベースに対する複数のコネクションを生成することはできません。マルチスレッドのプログラムにおいて複数のスレッドでデータベースを処理したい場合は、スレッド間でコネクションを共有するようにしてください。なお、コアAPIの各関数はリエントラントですが、コネクションはマルチスレッドセーフではありませんので、mutexで各コネクションを保護するようにしてください。

コネクションを自分で保護するのが面倒な場合は、Hyper EstraierのスレッドAPIを使うとよいでしょう。コアAPIのデータベース管理機能をマルチスレッドセーフにしたものです。スレッドAPIはコアAPIと全く同じ機能を提供しますが、以下の点が異なります。なお、データベース関連以外の機能(ESTDOCとESTCOND)はコアAPIとスレッドAPIで共通です。

基本的にはスレッドAPIはコネクションに対する排他制御をグローバルに行いますが、QDBMをビルドする際に「--enable-pthread」をつけてスレッド対応にさせていた場合は、コネクション単位で排他制御が行われます。どちらでも動作に支障はありませんが、QDBMがスレッド対応している方が並列性は大幅に向上します。


助言

ここでは、ライブラリとしてのHyper Estraierを活用するためのコツをいくつか紹介します。

CGIスクリプトでの利用

Webインターフェイスの検索アプリケーションをCGIスクリプトとして実装することも多いかと思います。そこで注意してほしいのは、書き込みモードでデータベースを開いてはならないということです。

CGIのプロセスは、呼び出し側のサーバによっていつ殺されるかわかりません。データの転送中にネットワークが切断された場合(ユーザが停止ボタンを押した場合)には、SIGPIPEによって殺される可能性があります。サーバが停止または再起動する場合、SIGTERMによって殺される可能性があります。プロセスサイズの制限や実行時間の制限に触れたことよってSIGKILLで殺される可能性もあります。

そのような不慮の停止の際に書き込みモードでデータベースを開いていた場合、データベースが壊れてしまいます。したがって、CGIスクリプトは更新処理には不向きです。それでも敢えてCGIで更新作業を行うならば、必ずバックアップを取りながら運用するようにしてください。

検索サーバ

コマンドラインインターフェイスでもWebインターフェイスでも、検索要求を処理する度にデータベースを開いたり閉じたりするのは非効率です(estcmdestseek.cgiではそうしていますが)。データベースに接続する際にはそれなりのオーバーヘッドがかかるので、同時接続数が増えると負荷が高まり、ひいては検索速度の低下を招きます。したがって、なるべくなら、常駐型のプロセスを実装して、データベースのコネクションを使いまわすようにしてください。それが面倒ならば、ノードAPIの利用を検討してみるのもよいでしょう。

裏API

実は、このガイドで紹介した以外にも関数がたくさん提供されています。それらはちょっと使い方が難しい、いわばハッカー用のAPIです。興味を持たれた方は、estraier.hを覗いてみてください。

ミニライブラリ

libestraier.aはPOSIXスレッドに依存する構成になっています。一方で、組み込み系ではPOSIXスレッドが実装されていないこともあります。コアAPIはスレッドの機能を一切使っていませんので、そういった環境にも移植することができます。「make corelib」を実行すると、コアAPIのオブジェクトファイル(estraier.oとmyconf.o)のみを含んだライブラリであるlibestcore.aが生成されます。libestcore.alibiconv.alibz.alibm.alibc.aに依存するのみですので、多くの環境で利用することができるでしょう。

言語バインディング

C言語以外のプログラミング言語からHyper Estraierを使うためのバインディングを作るプロジェクトも進行中です。あなたが新たな言語のバインディングを作ってくれるのも歓迎します。その際には、estraier.idlファイルに記述してあるインターフェイスを参考にしてください。各種のバインディングのインターフェイスはなるべく似ている方が覚えやすいですよね。

現状ではHyper EstraierのソースパッケージにはJavaとRubyとPerlの言語バインディングが収録されていますが、それらはC言語のスレッドAPIをベースとして実装されています。したがって、並列性の性質はスレッドAPIと同様になります。