--- /tmp/DOCMAN2di6MFY 2024-03-23 00:26:34.948187617 +0900
+++ /tmp/DOCMAN2IbSOiT 2024-03-23 00:26:34.948187617 +0900
@@ -4,7 +4,7 @@
<head>
<meta http-equiv="Content-Type" content="text/html; charset=euc-jp" />
<meta name="generator" content="Docutils 0.4: http://docutils.sourceforge.net/" />
-<title>Ludia 0.8.0 ユーザガイド</title>
+<title>Ludia 0.9.0 ユーザガイド</title>
<style type="text/css">
/*
@@ -290,12 +290,45 @@
</style>
</head>
<body>
-<div class="document" id="ludia-0-8-0">
-<h1 class="title">Ludia 0.8.0 ユーザガイド</h1>
+<div class="document" id="ludia-0-9-0">
+<h1 class="title">Ludia 0.9.0 ユーザガイド</h1>
+<div class="contents topic">
+<p class="topic-title first"><a id="id1" name="id1">目次</a></p>
+<ul class="simple">
+<li><a class="reference" href="#ludia" id="id21" name="id21">Ludiaについて</a><ul>
+<li><a class="reference" href="#id2" id="id22" name="id22">概要</a></li>
+<li><a class="reference" href="#id3" id="id23" name="id23">ライセンス</a></li>
+<li><a class="reference" href="#id4" id="id24" name="id24">動作環境</a></li>
+<li><a class="reference" href="#id5" id="id25" name="id25">制限事項</a></li>
+<li><a class="reference" href="#id6" id="id26" name="id26">問い合わせ先</a></li>
+</ul>
+</li>
+<li><a class="reference" href="#id7" id="id27" name="id27">インストール</a></li>
+<li><a class="reference" href="#id8" id="id28" name="id28">使い方</a><ul>
+<li><a class="reference" href="#id9" id="id29" name="id29">インデックスアクセスメソッドの登録</a></li>
+<li><a class="reference" href="#id10" id="id30" name="id30">設定ファイルの編集</a></li>
+<li><a class="reference" href="#id11" id="id31" name="id31">インデックスの作成</a></li>
+<li><a class="reference" href="#id12" id="id32" name="id32">検索の実行</a></li>
+<li><a class="reference" href="#id13" id="id33" name="id33">インデックスの削除</a></li>
+</ul>
+</li>
+<li><a class="reference" href="#id14" id="id34" name="id34">実行時の設定</a><ul>
+<li><a class="reference" href="#id15" id="id35" name="id35">シーケンシャルスキャンの抑制</a></li>
+<li><a class="reference" href="#id16" id="id36" name="id36">検索ヒット数の上限の設定</a></li>
+<li><a class="reference" href="#id17" id="id37" name="id37">Sennaインデックス作成時のオプション</a></li>
+</ul>
+</li>
+<li><a class="reference" href="#id18" id="id38" name="id38">使い方(応用編)</a><ul>
+<li><a class="reference" href="#id19" id="id39" name="id39">ヒット件数を高速に取得する</a></li>
+<li><a class="reference" href="#id20" id="id40" name="id40">テキストフィルタを利用する</a></li>
+</ul>
+</li>
+</ul>
+</div>
<div class="section">
-<h1><a id="ludia" name="ludia">Ludiaについて</a></h1>
+<h1><a class="toc-backref" href="#id21" id="ludia" name="ludia">Ludiaについて</a></h1>
<div class="section">
-<h2><a id="id1" name="id1">概要</a></h2>
+<h2><a class="toc-backref" href="#id22" id="id2" name="id2">概要</a></h2>
<p>LudiaはPostgreSQLに高速な全文検索機能を提供します。
全文検索エンジンSennaを利用し、データベース内のテキスト情報を高速検索します。
Ludiaは以下のような特徴をもっています。</p>
@@ -304,7 +337,7 @@
<dd>PostgreSQLのインデックスアクセスメソッドとして実装されているため、
B-treeインデックスなど他の種類のインデックスと同じように、
あるいは他の種類のインデックスと組み合わせて使うことができます。
-検索は追加定義の「@@」演算子を用いて行います。
+検索は追加定義の「@@」演算子を用いて行います。
また、テーブルにレコードの追加、更新、削除を行った際は、
インデックス側の情報も自動的に更新されます。</dd>
<dt>スコアを利用したクエリ文</dt>
@@ -313,7 +346,7 @@
</dl>
</div>
<div class="section">
-<h2><a id="id2" name="id2">ライセンス</a></h2>
+<h2><a class="toc-backref" href="#id23" id="id3" name="id3">ライセンス</a></h2>
<p>LudiaはOSS(オープンソースソフトウェア)です。
あなたは、Free Software Foundationが公表した
GNU Lesser General Public Licenseのバージョン2.1が定める条項に従って、
@@ -324,20 +357,7 @@
詳細は GNU LESSER GENERAL PUBLIC LICENSE Version 2.1 をお読みください。</p>
</div>
<div class="section">
-<h2><a id="id3" name="id3">制限事項</a></h2>
-<ul class="simple">
-<li>複数列インデックスとしては使用できません。</li>
-<li>一意性インデックスの機能は提供しません。</li>
-<li>VACUUMには完全に対応していません。
-無効なTIDのチェックは行われますが、インデックスのサイズは減少しません。</li>
-<li>REINDEXには対応していません。
-インデックスの再構築は、
-インデックスファイルの削除、インデックスのDROP、インデックスの再作成、
-という手順で行ってください。</li>
-</ul>
-</div>
-<div class="section">
-<h2><a id="id4" name="id4">動作環境</a></h2>
+<h2><a class="toc-backref" href="#id24" id="id4" name="id4">動作環境</a></h2>
<p>以下の環境で動作確認をしています。</p>
<table class="docutils field-list" frame="void" rules="none">
<col class="field-name" />
@@ -345,9 +365,9 @@
<tbody valign="top">
<tr class="field"><th class="field-name">OS:</th><td class="field-body">RedHat Enterprise Linux AS[ES] 4</td>
</tr>
-<tr class="field"><th class="field-name">DBMS:</th><td class="field-body">PostgreSQL 8.1.4</td>
+<tr class="field"><th class="field-name">DBMS:</th><td class="field-body">PostgreSQL 8.1.5</td>
</tr>
-<tr class="field"><th class="field-name">Senna:</th><td class="field-body">0.8.1</td>
+<tr class="field"><th class="field-name">Senna:</th><td class="field-body">0.8.2</td>
</tr>
<tr class="field"><th class="field-name">MeCab:</th><td class="field-body">0.93</td>
</tr>
@@ -355,29 +375,62 @@
</table>
</div>
<div class="section">
-<h2><a id="id5" name="id5">連絡先</a></h2>
-<p>株式会社NTTデータ 基盤システム事業本部
-オープンソース開発センタ 技術開発担当
-E-mail: <a class="reference" href="mailto:osdquery@nttdata.co.jp">osdquery@nttdata.co.jp</a></p>
+<h2><a class="toc-backref" href="#id25" id="id5" name="id5">制限事項</a></h2>
+<ul class="simple">
+<li>複数列インデックスとしては使用できません。</li>
+<li>一意性インデックスとしては使用できません。</li>
+<li>VACUUMには対応していません。
+VACUUM FULL後にテーブルを更新すると、
+インデックスとテーブルの内容の整合性が取れなくなる場合があります。
+VACUUM FULLを行った場合には、インデックスを再構築してください。</li>
+<li>DROP、REINDEXを実行すると、Sennaのインデックスファイルが残ります。
+( <a class="reference" href="#id13">インデックスの削除</a> の節に削除方法があります。)</li>
+<li>LudiaのインデックスによるCLUSTERには対応していません。</li>
+<li>(@@演算子は)シーケンシャルスキャンとして実行された場合に、
+インデックススキャンと同等な結果を返却することができません。</li>
+</ul>
+</div>
+<div class="section">
+<h2><a class="toc-backref" href="#id26" id="id6" name="id6">問い合わせ先</a></h2>
+<p>バグ報告や技術的な質問については、
+<a class="reference" href="http://lists.sourceforge.jp/mailman/listinfo/ludia-users">Ludia-usersメーリングリスト</a> でお問い合わせください。</p>
</div>
</div>
<div class="section">
-<h1><a id="id6" name="id6">インストール</a></h1>
+<h1><a class="toc-backref" href="#id27" id="id7" name="id7">インストール</a></h1>
<p>インストール方法については、
このファイルと同じディレクトリにあるINSTALLを参照してください。</p>
</div>
<div class="section">
-<h1><a id="id7" name="id7">使い方</a></h1>
+<h1><a class="toc-backref" href="#id28" id="id8" name="id8">使い方</a></h1>
<div class="section">
-<h2><a id="id8" name="id8">インデックスアクセスメソッドの登録</a></h2>
+<h2><a class="toc-backref" href="#id29" id="id9" name="id9">インデックスアクセスメソッドの登録</a></h2>
<p>Ludiaを使用するデータベースに対してインデックスアクセスメソッドを登録します。
-ソースアーカイブに含まれている pgsenna2.sql をpsqlから実行してください。:</p>
+ソースアーカイブに含まれている pgsenna2.sql をpsqlから実行してください。
+(pgsenna2.sqlはPostgreSQLのshareディレクトリにインストールされます。):</p>
<pre class="literal-block">
-$ psql -f ./pgsenna2.sql testdb
+$ psql -f /usr/local/pgsql/share/pgsenna2.sql testdb
</pre>
</div>
<div class="section">
-<h2><a id="id9" name="id9">インデックスの作成</a></h2>
+<h2><a class="toc-backref" href="#id30" id="id10" name="id10">設定ファイルの編集</a></h2>
+<p>Ludiaを使用するデータベースクラスタのpostgresql.confファイルに、
+以下の設定内容を追加してください。
+設定を反映するためにはPostgreSQLを再起動する必要があります。
+postgresql.confの設定が反映されていないと、
+実行時にエラーになってしまうので注意してください。
+設定内容についての詳細は、 <a class="reference" href="#id14">実行時の設定</a> の節を参照してください。:</p>
+<pre class="literal-block">
+custom_variable_classes = 'ludia'
+ludia.max_n_sort_result = 10000
+ludia.enable_seqscan = on
+ludia.sen_index_flags = 31
+</pre>
+<p>もしすでにcustom_variable_classesが設定されている場合は、
+そこにludiaというクラス名を追加してください。</p>
+</div>
+<div class="section">
+<h2><a class="toc-backref" href="#id31" id="id11" name="id11">インデックスの作成</a></h2>
<p>ここでは、例として以下のようなテーブルを利用します。:</p>
<pre class="literal-block">
CREATE TABLE table1 (col1 text, col2 varchar(128));
@@ -388,27 +441,26 @@
<pre class="literal-block">
CREATE INDEX index1 ON table1 USING fulltext(col1);
</pre>
-<p>インデックスアクセスメソッド名には</p>
-<ul class="simple">
-<li>fulltext : 形態素解析</li>
-<li>fulltextb : 2-gram</li>
-</ul>
-<p>の2種類があり、どちらを指定するかによって分かち書き方式が変わります。
-fulltext を利用する場合は MeCab がインストールされている必要があります。</p>
-<p>ここで、Ludiaがインデックス対象とできるのはtext型のみです。
-char型などの列に対してインデックスを作成したい場合には、キャストしてください。:</p>
+<p>Ludiaがインデックス対象とできるのはtext型のみなので、
+char型などの列に対してインデックスを作成したい場合はキャストしてください。:</p>
<pre class="literal-block">
CREATE INDEX index2 ON table1 USING fulltextb((col2::text));
</pre>
+<p>インデックスアクセスメソッド名には</p>
+<ul class="simple">
+<li>fulltext : 正規化 + 形態素解析 (SEN_INDEX_NORMALIZE)</li>
+<li>fulltextb : 正規化 + 2-gram (SEN_INDEX_NORMALIZE|SEN_INDEX_NGRAM)</li>
+<li>fulltextu : ユーザ定義</li>
+</ul>
+<p>の3種類があり、どれを指定するかによってSennaインデックスのフラグが変わります。
+ユーザ定義(fulltextu)の詳細は <a class="reference" href="#id17">Sennaインデックス作成時のオプション</a> の節を参照してください。</p>
</div>
<div class="section">
-<h2><a id="id10" name="id10">検索の実行</a></h2>
-<p>Ludiaのインデックスを用いた検索を行う場合には @@ 演算子を使用します。
-@@ 演算子の右辺にはSennaの検索クエリを指定してください。
-Sennaのクエリの書式については、以下のURLを参照してください。
-<a class="reference" href="http://qwik.jp/senna/query.html">http://qwik.jp/senna/query.html</a></p>
+<h2><a class="toc-backref" href="#id32" id="id12" name="id12">検索の実行</a></h2>
+<p>Ludiaのインデックスを用いた検索を行う場合には @@ 演算子を使用します。
+@@ 演算子の右辺には <a class="reference" href="http://qwik.jp/senna/query.html">Sennaの検索クエリ</a> を指定してください。</p>
<pre class="literal-block">
-SELECT * FROM table1 WHERE col1 @@ 'もも';
+SELECT * FROM table1 WHERE col1 @@ 'もも';
col1 | col2
--------------------------+--------------------
すもももももももものうち | あの壺はよいものだ
@@ -421,7 +473,7 @@
1番目の引数には検索対象となった行のTIDを、
2番目の引数にはインデックス名を指定してください。:</p>
<pre class="literal-block">
-SELECT col1, pgs2getscore(table1.ctid, 'index1') FROM table1 WHERE col1 @@ 'もも';
+SELECT col1, pgs2getscore(table1.ctid, 'index1') FROM table1 WHERE col1 @@ 'もも';
col1 | pgs2getscore
--------------------------+--------------
すもももももももものうち | 10
@@ -429,8 +481,8 @@
</pre>
</div>
<div class="section">
-<h2><a id="id11" name="id11">インデックスの削除</a></h2>
-<p>PostgreSQLのインデックスリレーションファイルは、
+<h2><a class="toc-backref" href="#id33" id="id13" name="id13">インデックスの削除</a></h2>
+<p>PostgreSQLのインデックスリレーションファイルと、
Ludiaのインデックスファイルは以下の5つから構成されます。
(テーブル空間を使用している場合は、テーブル空間定義時に指定した場所に置かれます。)</p>
<ol class="arabic simple">
@@ -455,6 +507,284 @@
<pre class="literal-block">
DROP INDEX index1;
</pre>
+<p>あるいは、pgs2destroy関数を利用すると、
+データベース中の不要になったSennaインデックスファイルを一括して削除できます。
+pgs2destroy関数は、2~5が存在するが1のファイルが存在しない、という場合に、
+2~5のファイルを削除します。:</p>
+<pre class="literal-block">
+# DROP TABLE table1;
+DROP TABLE
+
+# SELECT pgs2destroy();
+ pgs2destroy
+-------------
+ 1
+(1 row)
+</pre>
+<p>関数の返り値は、削除したインデックス数です。
+(上記の2~5のファイルで1セットです。)</p>
+</div>
+</div>
+<div class="section">
+<h1><a class="toc-backref" href="#id34" id="id14" name="id14">実行時の設定</a></h1>
+<div class="section">
+<h2><a class="toc-backref" href="#id35" id="id15" name="id15">シーケンシャルスキャンの抑制</a></h2>
+<p>@@演算子を用いた全文検索条件を指定しても、シーケンシャルスキャンが実行された場合には、
+インデックススキャンの場合と同様の検索を行うことができません。
+そのためLudiaでは、
+デフォルトではシーケンシャルスキャンが実行された場合にエラーにする設定になっています。
+(この例ではenable_indexscanをoffにして、シーケンシャルスキャンを実行しています。):</p>
+<pre class="literal-block">
+# SET enable_indexscan TO off;
+SET
+
+# EXPLAIN SELECT col1 FROM table1 WHERE col1 @@ 'もも';
+ QUERY PLAN
+-------------------------------------------------------
+ Seq Scan on table1 (cost=0.00..1.02 rows=1 width=32)
+ Filter: (col1 @@ 'もも'::text)
+(2 rows)
+
+# SELECT col1 FROM table1 WHERE col1 @@ 'もも';
+ERROR: pgsenna2: sequencial scan disabled.
+ERROR: pgsenna2: sequencial scan disabled.
+</pre>
+<p>この設定はpostgresql.confのludia.enable_seqscan変数で指定されますが、
+SETコマンドでも変更することができます。
+(SETコマンドによる変更はそのセッション内でのみ有効です。):</p>
+<pre class="literal-block">
+# SET ludia.enable_seqscan TO on;
+SET
+
+# SELECT col1 FROM table1 WHERE col1 @@ 'もも';
+ col1
+--------------------------
+ すもももももももものうち
+ ももから生まれた桃太郎
+(2 rows)
+</pre>
+<p>しかし、この場合@@演算子は、単純な文字列の比較をしているだけで、
+全文検索を行っている場合と同じ結果が得られるわけではないので注意してください。
+たとえば、Sennaの+演算子を付けて検索しようとした場合、
+単純に'+もも'という文字列が含まれるかが検査されるため、文字列は一致しなくなります。:</p>
+<pre class="literal-block">
+# SELECT col1 FROM table1 WHERE col1 @@ '+もも';
+ col1
+------
+(0 rows)
+</pre>
+</div>
+<div class="section">
+<h2><a class="toc-backref" href="#id36" id="id16" name="id16">検索ヒット数の上限の設定</a></h2>
+<p>Ludiaのデフォルトの設定では、
+検索でヒットした行をスコアが高い順に
+postgresql.confのludia.max_n_sort_resultで設定された行数まで返却します。:</p>
+<pre class="literal-block">
+# SHOW ludia.max_n_sort_result;
+ ludia.max_n_sort_result
+-------------------------
+ 10000
+(1 row)
+
+# SELECT col1, pgs2getscore(ctid, 'index1') FROM table1 WHERE col1 @@ 'もも';
+ col1 | pgs2getscore
+--------------------------+--------------
+ すもももももももものうち | 10
+ ももから生まれた桃太郎 | 5
+(2 rows)
+</pre>
+<p>この上限はSETコマンドでも変更することができます。
+(SETコマンドによる変更はそのセッション内でのみ有効です。):</p>
+<pre class="literal-block">
+# SET ludia.max_n_sort_result TO 1;
+SET
+
+# SELECT col1, pgs2getscore(ctid, 'index1') FROM table1 WHERE col1 @@ 'もも';
+ col1 | pgs2getscore
+--------------------------+--------------
+ すもももももももものうち | 10
+(1 row)
+</pre>
+</div>
+<div class="section">
+<h2><a class="toc-backref" href="#id37" id="id17" name="id17">Sennaインデックス作成時のオプション</a></h2>
+<p>アクセスメソッドとしてfulltextuを選択すると、
+インデックス作成時にSennaインデックスのフラグを指定することができます。
+利用できるフラグは(Senna 0.8.2では)以下のような定義と意味をもっています。
+(詳しくは <a class="reference" href="http://qwik.jp/senna/APIJ.html">SennaのAPIドキュメント</a> を参照してください。)</p>
+<pre class="literal-block">
+#define SEN_INDEX_NORMALIZE 0x0001
+#define SEN_INDEX_SPLIT_ALPHA 0x0002
+#define SEN_INDEX_SPLIT_DIGIT 0x0004
+#define SEN_INDEX_SPLIT_SYMBOL 0x0008
+#define SEN_INDEX_NGRAM 0x0010
+#define SEN_INDEX_DELIMITED 0x0020
+</pre>
+<dl class="docutils">
+<dt>SEN_INDEX_NORMALIZE</dt>
+<dd>英文字の大文字/小文字、全角文字/半角文字を正規化してインデックスに登録する</dd>
+<dt>SEN_INDEX_SPLIT_ALPHA</dt>
+<dd>N-gramインデックスで正規化を指定した際、英文字列もN文字の要素に分割する
+(それ以外の場合は連続した英文字列を1単語とする)</dd>
+<dt>SEN_INDEX_SPLIT_DIGIT</dt>
+<dd>N-gramインデックスで正規化を指定した際、数字文字列もN文字の要素に分割する
+(それ以外の場合は連続した数字文字列を1単語とする)</dd>
+<dt>SEN_INDEX_SPLIT_SYMBOL</dt>
+<dd>N-gramインデックスで正規化を指定した際、記号文字列もN文字の要素に分割する
+(それ以外の場合は、連続した記号文字列を1単語とする)</dd>
+<dt>SEN_INDEX_NGRAM</dt>
+<dd>(形態素解析ではなく)n-gramを用いる</dd>
+<dt>SEN_INDEX_DELIMITED</dt>
+<dd>(形態素解析ではなく)空白区切りで単語を区切る。</dd>
+</dl>
+<p>postgresql.confの設定には、10進数の値を指定してください。
+例えば、
+SEN_INDEX_NGRAM|SEN_INDEX_NORMALIZE|SEN_INDEX_SPLIT_ALPHA
+というフラグを指定する場合には、:</p>
+<pre class="literal-block">
+ludia.sen_index_flags = 19
+</pre>
+<p>となります。</p>
+</div>
+</div>
+<div class="section">
+<h1><a class="toc-backref" href="#id38" id="id18" name="id18">使い方(応用編)</a></h1>
+<div class="section">
+<h2><a class="toc-backref" href="#id39" id="id19" name="id19">ヒット件数を高速に取得する</a></h2>
+<p>pgs2getnhits() 関数を用いると、
+セッション内で最後に行われたSennaの検索ヒット件数を取得することができます。:</p>
+<pre class="literal-block">
+# SELECT * FROM table1 WHERE col1 @@ 'もも';
+ col1 | col2
+--------------------------+--------------------
+ すもももももももものうち | あの壺はよいものだ
+ ももから生まれた桃太郎 | あの壷はよいものだ
+(2 rows)
+
+# SELECT pgs2getnhits();
+ pgs2getnhits
+--------------
+ 2
+(1 row)
+</pre>
+<p>これを利用すると、ヒット件数が非常に多い場合でも、
+LIMIT句と組み合わせて利用することで、高速にヒット件数を取得することができます。:</p>
+<pre class="literal-block">
+# SELECT * FROM table1 WHERE col1 @@ 'もも' LIMIT 0;
+ col1 | col2
+------+------
+(0 rows)
+
+# SELECT pgs2getnhits();
+ pgs2getnhits
+--------------
+ 2
+(1 row)
+</pre>
+<p>ただし、ここで得られるヒット件数はSennaの検索結果についての値であるため、
+以下に挙げるような制限があります。</p>
+<ul class="simple">
+<li>この方法で得られるヒット件数は、セッション内で最後に行われたSennaの検索に関するものです。
+一回の問い合わせ中に複数回、同一インデックスに対する検索が行われるような場合には、
+最後に行われるSennaインデックスのスキャンに関するヒット件数が得られます。</li>
+<li>問い合わせに全文検索条件以外の条件が指定されていても、反映はされません。</li>
+<li>得られるヒット件数にはUPDATEやDELETEで無効になった行も含まれています。
+インデックスの更新が頻繁に行われる場合には、誤差が大きくなります。</li>
+<li>検索ヒット数の上限設定(ludia.max_n_sort_result の値)は、
+ここで得られるヒット件数には反映されません。</li>
+</ul>
+</div>
+<div class="section">
+<h2><a class="toc-backref" href="#id40" id="id20" name="id20">テキストフィルタを利用する</a></h2>
+<p>Ludiaのユーティリティ関数を利用することで、PDFファイルに対してインデックスを作成することができます。
+ここでは <a class="reference" href="http://www.foolabs.com/xpdf/">Xpdf</a> というツールに含まれている、pdftotextというコマンドを利用します。
+まずはXpdfと日本語サポートパッケージをインストールしてください。</p>
+<p>Ludiaではpdftotextを利用するための関数が2種類用意されています、
+pgs2pdftotext1という関数は、PDFファイルのpathを引数としてとり、
+pdftotextを呼び出してPDFファイルからテキストを取り出します。:</p>
+<pre class="literal-block">
+# select pgs2pdftotext1('/tmp/PostgresForest.pdf');
+ pgs2pdftotext1
+-----------------
+
+高性能・高信頼の並列分散データベース環境を低コストで実現 複数ノード上
+でそれぞれ稼動している PostgreSQL をシングルシステムイメー ジとしてユー
+ザに提供 PostgreSQL と互換性があるため、アプリケーション開発時に新たな
+トレーニン グが不要 オープンソースでのシステム構築可能性を向上
+...(省略)
+</pre>
+<p>また、pdf2pdftotext2という関数はPDFファイルそのものをbytea型のデータとして受け取り、
+(それをtmpディレクトリに一時ファイルとして書き出して)
+pdftotextを呼び出し、PDFファイルからテキストを書き出します。:</p>
+<pre class="literal-block">
+# select pgs2pdftotext1('\120\104\106\055\061\056\064\012...(省略)');
+</pre>
+<p>ここでは例として、以下のようなテーブルを使用します。:</p>
+<pre class="literal-block">
+# CREATE TABLE pdffiles (id SERIAL PRIMARY KEY, filepath text, filedata bytea);
+
+# \d pdffiles
+ Table "public.pdffiles"
+ Column | Type | Modifiers
+----------+---------+-------------------------------------------------------
+ id | integer | not null default nextval('pdffiles_id_seq'::regclass)
+ filepath | text |
+ filedata | bytea |
+Indexes:
+ "pdffiles_pkey" PRIMARY KEY, btree (id)
+</pre>
+<p>PDFファイルは、</p>
+<ol class="arabic simple">
+<li>filepath列にはPDFファイルのPATHが格納され、ファイルそのものはファイルシステム上に格納する。</li>
+<li>filedata列にPDFファイルそのものをbatea型で格納する。</li>
+</ol>
+<p>のいずれかの方法で格納されているとします。:</p>
+<pre class="literal-block">
+# SELECT id, filepath, substring(encode(filedata, 'hex') from 1 for 30) FROM pdffiles;
+ id | filepath | substring
+----+-------------------------+--------------------------------
+ 1 | /tmp/PostgresForest.pdf | 255044462d312e340a25c7ec8fa20a
+(1 row)
+</pre>
+<p>1の場合にはpgs2pdftotext1を、2の場合にはpgs2pdftotext2を利用して
+関数インデックスを作成することができます。:</p>
+<pre class="literal-block">
+# CREATE INDEX pidx1 on pdffiles USING fulltextb(pgs2pdftotext1(filepath));
+CREATE INDEX
+
+# CREATE INDEX pidx2 on pdffiles USING fulltextb(pgs2pdftotext2(filedata));
+CREATE INDEX
+
+# \d pdffiles
+ Table "public.pdffiles"
+ Column | Type | Modifiers
+----------+---------+-------------------------------------------------------
+ id | integer | not null default nextval('pdffiles_id_seq'::regclass)
+ filepath | text |
+ filedata | bytea |
+Indexes:
+ "pdffiles_pkey" PRIMARY KEY, btree (id)
+ "pidx1" fulltextb (pgs2pdftotext1(filepath))
+ "pidx2" fulltextb (pgs2pdftotext2(filedata))
+</pre>
+<p>このインデックスを利用することで、PDFファイル中のテキストに対する検索を行うことができます。
+検索を実行する際にも列名に対して関数を適用してください。
+(検索の際には関数は実行されません。):</p>
+<pre class="literal-block">
+# SELECT id FROM pdffiles WHERE pgs2pdftotext1(filepath) @@ '高性能';
+ id
+----
+ 1
+(1 row)
+
+# SELECT id FROM pdffiles WHERE pgs2pdftotext2(filedata) @@ '高信頼';
+ id
+----
+ 1
+(1 row)
+</pre>
+<p>ここで、PDFファイルに複製不可やパスワードの設定が行われていると、
+この関数はエラーを返すことに注意してください。</p>
</div>
</div>
</div>