QDBMバージョン1仕様書
Copyright (C) 2000-2003 Mikio Hirabayashi
Last Update: Thu, 20 Nov 2003 22:44:08 +0900
目次
- 概要
- 特徴
- インストール
- Depot: 基本API
- Depot用コマンド
- Curia: 拡張API
- Curia用コマンド
- Relic: NDBM互換API
- Relic用コマンド
- Hovel: GDBM互換API
- Hovel用コマンド
- Cabin: ユーティリティAPI
- Cabin用コマンド
- Villa: 上級API
- Villa用コマンド
- Odeum: 転置API
- Odeum用コマンド
- ファイルフォーマット
- 移植方法
- バグ
- よく聞かれる質問
- ライセンス
QDBMはデータベースを扱うルーチン群のライブラリである。データベースといっても単純なものであり、キーと値のペアからなるレコード群を格納したデータファイルである。キーと値は任意の長さを持つ一連のバイト列であり、文字列でもバイナリでも扱うことができる。テーブルやデータ型の概念はない。レコードはハッシュ表またはB+木で編成される。
ハッシュ表のデータベースでは、キーはデータベース内で一意であり、キーが重複する複数のレコードを格納することはできない。このデータベースに対しては、キーと値を指定してレコードを格納したり、キーを指定して対応するレコードを削除したり、キーを指定して対応するレコードを検索することができる。また、データベースに格納してある全てのキーを順不同に一つずつ取り出すこともできる。このような操作は、UNIX標準で定義されているDBMライブラリおよびその追従であるNDBMやGDBMに類するものである。QDBMはDBMのより良い代替として利用することができる。
B+木のデータベースでは、キーが重複する複数のレコードを格納することができる。このデータベースに対しては、ハッシュ表のデータベースと同様に、キーを指定してレコードを格納したり取り出したり削除することができる。レコードはユーザが指示した比較関数に基づいて整列されて格納される。カーソルを用いて各レコードを昇順または降順で参照することができる。この機構によって、文字列の前方一致検索や数値の範囲検索が可能になる。また、B+木のデータベースではトランザクションが利用できる。
QDBMはCで記述され、C、C++、Java、PerlおよびRubyのAPIとして提供される。QDBMはPOSIX準拠のAPIを備えるプラットフォームで利用できる。QDBMはGNU Lesser General Public Licenseに基づくフリーソフトウェアである。
効率的なハッシュデータベースの実装
QDBMはGDBMを参考に次の三点を目標として開発された。処理がより高速であること、データベースファイルがより小さいこと、APIがより単純であること。これらの目標は達成されている。また、伝統的なDBMが抱える三つの制限事項を回避している。すなわち、プロセス内で複数のデータベースを扱うことができ、キーと値のサイズに制限がなく、データベースファイルがスパースでない。
QDBMはレコードの探索にハッシュアルゴリズムを用いる。バケット配列に十分な要素数があれば、レコードの探索にかかる時間計算量は O(1) である。すなわち、レコードの探索に必要な時間はデータベースの規模に関わらず一定である。追加や削除に関しても同様である。ハッシュ値の衝突はセパレートチェーン法で管理する。チェーンのデータ構造は二分探索木である。バケット配列の要素数が著しく少ない場合でも、探索等の時間計算量は O(log n) に抑えられる。
QDBMはバケット配列を全てRAM上に保持することによって、処理の高速化を図る。バケット配列がRAM上にあれば、ほぼ1パスのファイル操作でレコードに該当するファイル上の領域を参照することができる。ファイルに記録されたバケット配列は `read' コールでRAM上に読み込むのではなく、`mmap' コールでRAMに直接マッピングされる。したがって、データベースに接続する際の準備時間が極めて短く、また、複数のプロセスでメモリマップを共有することができる。
バケット配列の要素数が格納するレコード数の半分ほどであれば、データの性質によって多少前後するが、ハッシュ値の衝突率は56.7%ほどである(等倍だと36.8%、2倍だと21.3%、4倍だと11.5%、8倍だと6.0%ほど)。そのような場合、平均2パス以下のファイル操作でレコードを探索することができる。これを性能指標とするならば、例えば100万個のレコードを格納するためには50万要素のバケット配列が求められる。バケット配列の各要素は4バイトである。すなわち、2MバイトのRAMが利用できれば100万レコードのデータベースが構築できる。
QDBMにはデータベースに接続するモードとして、「リーダ」と「ライタ」の二種類がある。リーダは読み込み専用であり、ライタは読み書き両用である。データベースにはファイルロックによってプロセス間での排他制御が行われる。ライタが接続している間は、他のプロセスはリーダとしてもライタとしても接続できない。リーダが接続している間は、他のプロセスのリーダは接続できるが、ライタは接続できない。この機構によって、マルチタスク環境での同時接続に伴うデータの整合性が保証される。
伝統的なDBMにはレコードの追加操作に関して「挿入」モードと「置換」モードがある。前者では、キーが既存のレコードと重複する際に既存の値を残す。後者では、キーが既存のレコードと重複した際に新しい値に置き換える。QDBMはその2つに加えて「連結」モードがある。既存の値の末尾に指定された値を連結して格納する操作である。レコードの値を配列として扱う場合、要素を追加するには連結モードが役に立つ。また、DBMではレコードの値を取り出す際にはその全ての領域を処理対象にするしか方法がないが、QDBMでは値の領域の一部のみを選択して取り出すことができる。レコードの値を配列として扱う場合にはこの機能も役に立つ。
一般的に、データベースの更新処理を続けるとファイル内の利用可能領域の断片化が起き、ファイルのサイズが肥大化してしまう。QDBMは隣接する不要領域を連結して再利用し、またデータベースの最適化機能を備えることによってこの問題に対処する。既存のレコードの値をより大きなサイズの値に上書きする場合、そのレコードの領域をファイル中の別の位置に移動させる必要がある。この処理の時間計算量はレコードのサイズに依存するので、値を拡張していく場合には効率が悪い。しかし、QDBMはアラインメントによってこの問題に対処する。増分がパディングに収まれば領域を移動させる必要はない。
多くのファイルシステムでは、2GBを越えるサイズのファイルを扱うことができない。この問題に対処するために、QDBMは複数のデータベースファイルを含むディレクトリからなるデータベースを扱う機能を提供する。レコードをどのファイルに格納するかはキーに別のハッシュ関数を適用することによって決められる。この機能によって、理論的にはレコードの合計サイズが1TBまでのデータベースを構築することができる。また、データベースファイルを複数のディスクに振り分けることができるため、RAID-0(ストライピング)に見られるような更新操作の高速化が期待できる。NFS等を利用すれば複数のファイルサーバにデータベースを分散させることもできる。
便利なB+木データベースの実装
B+木データベースはハッシュデータベースより遅いが、ユーザが定義した順序に基づいて各レコードを参照できることが特長である。B+木は複数のレコードを整列させた状態で論理的なページにまとめて管理する。各ページに対してはB木すなわち多進平衡木によって階層化された疎インデックスが維持される。したがって、各レコードの探索等にかかる時間計算量は O(log n) である。各レコードを順番に参照するためにカーソルが提供される。カーソルの場所はキーを指定して飛ばすことができ、また現在の場所から次のレコードに進めたり前のレコードに戻すことができる。各ページは双方向リンクリストで編成されるので、カーソルを前後に移動させる時間計算量は O(1) である。
B+木データベースは上述のハッシュデータベースを基盤として実装される。B+木の各ページはハッシュデータベースのレコードとして記録されるので、ハッシュデータベースの記憶管理の効率性を継承している。B+木では各レコードのヘッダが小さく、各ページのアラインメントは統計的に算出されるので、ほとんどの場合、ハッシュデータベースに較べてデータベースファイルのサイズが半減する。B+木を更新する際には多くのページを操作する必要があるが、QDBMはページをキャッシュすることによってファイル操作を減らして処理を効率化する。ほとんどの場合、疎インデックス全体がメモリ上にキャッシュされるので、各レコードを参照するのに必要なファイル操作は平均1パス以下である。
B+木データベースはトランザクション機構を提供する。トランザクションを開始してから終了するまでの一連の操作を一括してデータベースにコミットしたり、一連の更新操作を破棄してデータベースの状態をトランザクションの開始前の状態にロールバックすることができる。トランザクションの間にアプリケーションのプロセスがクラッシュしてもデータベースファイルは破壊されない。
可逆データ圧縮ライブラリであるZLIBを有効化してQDBMをビルドすると、B+木の各ページの内容は圧縮されてファイルに書き込まれる。同一ページ内の各レコードは似たようなパターンを持つため、LZ77アルゴリズムを適用すると高い圧縮効率が期待できる。テキストデータを扱う場合、データベースのサイズが元の25%程度になる。データベースの規模が大きくディスクI/Oがボトルネックとなる場合は、圧縮機能を有効化すると処理速度が大幅に改善される。
単純だが多様なインタフェース群
QDBMのAPIは非常に単純である。ANSI Cで定義された `FILE' ポインタを用いた通常のファイル入出力と同じようにデータベースファイルに対する入出力を行うことができる。QDBMの基本APIでは、データベースの実体は単一のファイルに記録される。拡張APIでは、データベースの実体は単一のディレクトリに含まれる複数のファイルに記録される。二つのAPIは互いに酷似しているので、アプリケーションを一方から他方に移植することはたやすい。
NDBMおよびGDBMに互換するAPIも提供される。NDBMやGDBMのアプリケーションは市場に数多く存在するが、それらをQDBMに移植するのはたやすい。ほとんどの場合、ヘッダファイルの取り込み(#include)を書き換えてコンパイルしなおせばよい。ただし、オリジナルのNDBMやGDBMで作成したデータベースファイルをQDBMで扱うことはできない。
メモリ上でレコードを簡単に扱うために、ユーティリティAPIが提供される。メモリ確保関数とソート関数と拡張可能なデータと配列リストとハッシュマップ等の実装である。それらを用いると、C言語でもPerlやRuby等のスクリプト言語のような手軽さでレコードを扱うことができる。
B+木データベースは上級APIを介して利用する。上級APIは基本APIとユーティリティAPIを利用して実装される。上級APIも基本APIや拡張APIに類似した書式を持つので、使い方を覚えるのは容易である。
全文検索システムで利用される転置インデックスを扱うために、転置APIが提供される。文書群の転置インデックスを容易に扱うことができれば、アプリケーションはテキスト処理や自然言語処理に注力できる。このAPIは文字コードや言語に依存しないので、ユーザの多様な要求に応える全文検索システムを実装することが可能となる。
QDBMはC言語の他にも、C++、Java、PerlおよびRubyのAPIを提供する。C言語のAPIには、基本API、拡張API、NDBM互換API、GDBM互換API、ユーティリティAPI、上級APIおよび転置APIの七種類がある。各APIに対応したコマンドラインインタフェースも用意されている。それらはプロトタイピングやテストやデバッグなどで活躍する。C++用APIは基本APIと拡張APIと上級APIのデータベース操作関数群をC++のクラス機構でカプセル化したものである。Java用APIはJava Native Interfaceを用いて基本APIと拡張APIと上級APIを呼び出すものである。Perl用APIはXS言語を用いて基本APIと拡張APIと上級APIを呼び出すものである。Ruby用APIはRubyのモジュールとして基本APIと拡張APIと上級APIを呼び出すものである。C++用APIとJava用APIとRuby用APIはマルチスレッドセーフである。データベースの管理と全文検索のためのCGIスクリプトも提供される。
幅広い移植性
QDBMはANSI C(C89)の記法に従い、ANSI CまたはPOSIXで定義されたAPIのみを用いて実装される。したがって、ほとんどのUNIXおよびその互換をうたうOSで動作させることができる。C言語のAPIに関しては、少なくともLinux 2.2、Linux 2.4、FreeBSD 4.8、FreeBSD 5.0、SunOS 5.7、SunOS 5.8、SunOS 5.9、HP-UX 11.00、Windows 2000(CygwinとMinGWとVisual C++)、Mac OS X 10.2およびRISC OS 5.03において動作確認されている。QDBMが作成したデータベースファイルは処理系のバイトオーダに依存するが、その対策として、ファイルのバイトオーダを変換する機能が提供される。
準備
ソースパッケージを用いてQDBMをインストールするには、GCCのバージョン2.8以降と `make' が必要である。
QDBMの配布用アーカイブファイルを展開したら、生成されたディレクトリに入ってインストール作業を行う。
普通の手順
LinuxとBSDとSunOSでは以下の手順に従う。
ビルド環境を設定する。
./configure
プログラムをビルドする。
make
プログラムの自己診断テストを行う。
make check
プログラムをインストールする。作業は `root' ユーザで行う。
make install
GNU Libtoolを使う場合
上記の方法でうまくいかない場合、以下の手順に従う。この手順には、GNU Libtoolのバージョン1.5以降が必要である。
ビルド環境を設定する。
./configure
プログラムをビルドする。
make -f LTmakefile
プログラムの自己診断テストを行う。
make -f LTmakefile check
プログラムをインストールする。作業は `root' ユーザで行う。
make -f LTmakefile install
結果
一連の作業が終ると、以下のファイルがインストールされる。その他にも、マニュアルが `/usr/local/man/man3' に、それ以外の文書が `/usr/local/share/qdbm' にインストールされる。
/usr/local/include/depot.h
/usr/local/include/curia.h
/usr/local/include/relic.h
/usr/local/include/hovel.h
/usr/local/include/cabin.h
/usr/local/include/villa.h
/usr/local/include/vista.h
/usr/local/include/odeum.h
/usr/local/lib/libqdbm.a
/usr/local/lib/libqdbm.so.1.0.0
/usr/local/lib/libqdbm.so.1
/usr/local/lib/libqdbm.so
/usr/local/bin/dpmgr
/usr/local/bin/dptest
/usr/local/bin/dptsv
/usr/local/bin/crmgr
/usr/local/bin/crtest
/usr/local/bin/crtsv
/usr/local/bin/rlmgr
/usr/local/bin/rltest
/usr/local/bin/hvmgr
/usr/local/bin/hvtest
/usr/local/bin/cbtest
/usr/local/bin/cbcodec
/usr/local/bin/vlmgr
/usr/local/bin/vltest
/usr/local/bin/vltsv
/usr/local/bin/odmgr
/usr/local/bin/odtest
/usr/local/bin/odidx
`libqdbm.so' と動的にリンクしたプログラムを実行する際には、ライブラリの検索パスに `/usr/local/lib' を含めるべきである。環境変数 `LD_LIBRARY_PATH' でライブラリの検索パスを設定することができる。
QDBMをアンインストールするには、`./configure' をした後の状態で以下のコマンドを実行する。作業は `root' ユーザで行う。
make uninstall
QDBMの古いバージョンがインストールされている場合、それをアンインストールしてからインストール作業を行うべきである。
C言語以外のAPIとCGIスクリプトはデフォルトではインストールされない。C++用APIのインストール方法については、サブディレクトリ `plus' にある `xspex-ja.html' を参照すること。JAVA用APIのインストール方法については、サブディレクトリ `java' にある `jspex-ja.html' を参照すること。Perl用APIのインストール方法については、サブディレクトリ `perl' にある `plspex-ja.html' を参照すること。Ruby用APIのインストール方法については、サブディレクトリ `ruby' にある `rbspex-ja.html' を参照すること。CGIスクリプトのインストール方法については、サブディレクトリ `cgi' にある `cgispex.html' を参照すること。
RPM等のバイナリパッケージを用いてインストールを行う場合は、それぞれのパッケージマネージャのマニュアルを参照すること。例えば、RPMを用いる場合、以下のようなコマンドを `root' ユーザで実行する。
rpm -ivh qdbm-1.x.x-x.i386.rpm
Windowsの場合
Windows(Cygwin)にインストールする場合、以下の手順に従う。
ビルド環境を設定する。
./configure
プログラムをビルドする。
make win
プログラムの自己診断テストを行う。
make check-win
プログラムをインストールする。なお、アンインストールする場合は `make uninstall-win' とする。
make install-win
Windowsでは、静的ライブラリ `libqdbm.a' の代わりにインポートライブラリ `libqdbm.dll.a' が生成され、動的ライブラリ `libqdbm.so' 等の代わりにダイナミックリンクライブラリ `qdbm.dll' が生成される。`qdbm.dll' は `C:\WINNT\SYSTEM32' のようなシステムディレクトリにインストールされる。
Cygwin環境でMinGWを用いてビルドするには、`make win' の代わりに `make mingw' を用いる。CygwinのUNIXエミュレーション層を用いる場合、生成されるプログラムは `cygwin1.dll' に依存したものになる。MinGWによってWin32のネイティブDLLとリンクさせればこの問題を回避できる。
Visual C++を用いてビルドするには、`VCmakefile' を編集してヘッダとライブラリの検索パスを設定した上で、`nmake -f VCMakefile' とすればよい。ただし、インストール方法は未定義であり、またDLLは作られない。
Mac OS Xの場合
Mac OS X(Darwin)にインストールする場合、以下の手順に従う。
ビルド環境を設定する。
./configure
プログラムをビルドする。
make mac
プログラムの自己診断テストを行う。
make check-mac
プログラムをインストールする。なお、アンインストールする場合は `make uninstall-mac' とする。
make install-mac
Mac OS Xでは、`libqdbm.so' 等の代わりに `libqdbm.dylib' 等が生成される。ライブラリの検索パスの指定は環境変数 `DYLD_LIBRARY_PATH' で行うことができる。
HP-UXの場合
HP-UXにインストールする場合、以下の手順に従う。
ビルド環境を設定する。
./configure
プログラムをビルドする。
make hpux
プログラムの自己診断テストを行う。
make check-hpux
プログラムをインストールする。なお、アンインストールする場合は `make uninstall-hpux' とする。
make install-hpux
HP-UXでは、`libqdbm.so' 等の代わりに `libqdbm.sl' が生成される。ライブラリの検索パスの指定は環境変数 `SHLIB_PATH' で行うことができる。
RISC OSの場合
RISC OSにインストールする場合、以下の手順に従う。
プログラムをビルドする。デフォルトではコンパイラに `cc' を用いるようになっているが、`gcc' を用いたければ `CC=gcc' という引数を加えればよい。
make -f RISCmakefile
一連の作業が終ると、`libqdbm' というライブラリファイルと `dpmgr' 等のコマンドが生成される。それらのインストール方法は定義されていないので、手動で任意の場所にコピーしてインストールすること。`depot.h' 等のヘッダファイルも同様に手動でインストールすること。
詳細設定
`./configure' を実行する際に以下のオプション引数を指定することで、ビルド方法の詳細な設定を行うことができる。
- --enable-debug : デバッグ用にビルドする。デバッグシンボルを有効化し、静的にリンクする。
- --enable-devel : 開発用にビルドする。デバッグシンボルを有効化し、動的にリンクする。
- --enable-nolock : ファイルロッキングが実装されていない環境用にビルドする。
- --enable-nommap : メモリマッピングが実装されていない環境用にビルドする。
- --enable-zlib : B+木と転置インデックスのレコードをZLIBを使って圧縮する。
通常、QDBMとそのアプリケーションは `libqdbm.*' 以外の非標準のライブラリには依存しないでビルドすることができる。ただし、ZLIBの圧縮機能を有効化した場合は `libz.*' に依存するようになる。
概要
DepotはQDBMの基本APIである。QDBMが提供するデータベース管理機能のほぼ全てがDepotによって実装される。その他のAPIはDepotのラッパーにすぎない。したがって、QDBMのAPIの中でDepotが最も高速に動作する。
Depotを使うためには、`depot.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。
- #include <depot.h>
- #include <stdlib.h>
Depotでデータベースを扱う際には、`DEPOT' 型へのポインタをハンドルとして用いる。これは、`stdio.h' の各種ルーチンがファイル入出力に `FILE' 型へのポインタを用いるのに似ている。ハンドルは、関数 `dpopen' で開き、関数 `dpclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `dpclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。
API
外部変数 `dpversion' はバージョン情報の文字列である。
- extern const char *dpversion;
- この変数の指す領域は書き込み禁止である。
外部変数 `dpecode' には直前のエラーコードが記録される。エラーコードの詳細については `depot.h' を参照すること。
- extern int dpecode;
- この変数の初期値は `DP_ENOERR' である。その他の値として、`DP_EFATAL'、`DP_EMODE'、`DP_EBROKEN'、`DP_EKEEP'、`DP_ENOITEM'、`DP_EALLOC'、`DP_EMAP'、`DP_EOPEN'、`DP_ECLOSE'、`DP_ETRUNC'、`DP_ESYNC'、`DP_ESTAT'、`DP_ESEEK'、`DP_EREAD'、`DP_EWRITE'、`DP_ELOCK'、`DP_EUNLINK'、`DP_EMKDIR'、`DP_ERMDIR' および `DP_EMISC' がある。
エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。
- const char *dperrmsg(int ecode);
- `ecode' はエラーコードを指定する。戻り値はエラーメッセージの文字列であり、その領域は書き込み禁止である。
データベースのハンドルを作成するには、関数 `dpopen' を用いる。
- DEPOT *dpopen(const char *name, int omode, int bnum);
- `name' はデータベースファイルの名前を指定する。`omode' は接続モードを指定し、`DP_OREADER' ならリーダ、`DP_OWRITER' ならライタとなる。`DP_OWRITER' の場合、`DP_OCREAT' または `DP_OTRUNC' とのビット論理和にすることができる。`DP_OCREAT' はファイルが無い場合に新規作成することを指示し、`DP_OTRUNC' はファイルが存在しても作り直すことを指示する。`DP_OREADER' と `DP_OWRITER' の両方で `DP_ONOLCK' とのビット論理和にすることができるが、それはファイルロックをかけずにデータベースを開くことを指示する。`bnum' はバケット配列の要素数の目安を指定するが、0 以下ならデフォルト値が使われる。バケット配列の要素数はデータベースを作成する時に決められ、最適化以外の手段で変更することはできない。バケット配列の要素数は、格納するレコード数の半分から4倍程度にするのがよい。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。`DP_ONOLCK' を使う場合、アプリケーションが排他制御の責任を負う。
データベースとの接続を閉じてハンドルを破棄するには、関数 `dpclose' を用いる。
- int dpclose(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
レコードを追加するには、関数 `dpput' を用いる。
- int dpput(DEPOT *depot, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
- `depot' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' は `DP_DOVER' か `DP_DKEEP' か `DP_DCAT' で、キーが既存レコードと重複した際の制御を指定する。`DP_DOVER' は既存のレコードの値を上書きし、`DP_DKEEP' は既存のレコードを残してエラーを返し、`DP_DCAT' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。
レコードを削除するには、関数 `dpout' を用いる。
- int dpout(DEPOT *depot, const char *kbuf, int ksiz);
- `depot' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。
レコードを取得するには、関数 `dpget' を用いる。
- char *dpget(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, int *sp);
- `depot' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
レコードの値のサイズを取得するには、関数 `dpvsiz' を用いる。
- int dpvsiz(DEPOT *depot, const char *kbuf, int ksiz);
- `depot' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであり、該当がなかったり、エラーの場合は -1 である。この関数はレコードの有無を調べるのにも便利である。`dpget' と違って実データを読み込まないので効率がよい。
データベースのイテレータを初期化するには、関数 `dpiterinit' を用いる。
- int dpiterinit(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。イテレータは、データベースに格納された全てのレコードを参照するために用いられる。
データベースのイテレータから次のレコードのキーを取り出すには、関数 `dpiternext' を用いる。
- char *dpiternext(DEPOT *depot, int *sp);
- `depot' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常ならキーを格納した領域へのポインタであり、エラーなら `NULL' である。イテレータが最後まできて該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数を繰り返して呼ぶことによって全てのレコードを一度ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。
データベースのアラインメントを設定するには、関数 `dpsetalign' を用いる。
- int dpsetalign(DEPOT *depot, int align);
- `depot' はライタで接続したデータベースハンドルを指定する。`align' はアラインメントのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。アラインメントを設定しておくと、レコードの上書きを頻繁にする場合の処理効率が良くなる。アラインメントには、一連の更新操作をした後の状態での標準的な値のサイズを指定するのがよい。アラインメントが正数の場合、レコードの領域のサイズがアラインメントの倍数になるようにパディングがとられる。アラインメントが負数の場合、`vsiz' を値のサイズとして、パディングのサイズは `(vsiz / pow(2, abs(align) - 1))' として算出される。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。
データベースを更新した内容をファイルとデバイスに同期させるには、関数 `dpsync' を用いる。
- int dpsync(DEPOT *depot);
- `depot' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースファイルを利用させる場合に役立つ。
データベースを最適化するには、関数 `dpoptimize' を用いる。
- int dpoptimize(DEPOT *depot, int bnum);
- `depot' はライタで接続したデータベースハンドルを指定する。`bnum' は新たなバケット配列の要素数を指定するが、0 以下なら現在のレコード数に最適な値が指定される。戻り値は正常なら真であり、エラーなら偽である。レコードを削除したり、置換モードや連結モードで書き込みを繰り返す場合は、データベース内に不要な領域が蓄積するが、この関数はそれを解消するのに役立つ。
データベースの名前を得るには、関数 `dpname' を用いる。
- char *dpname(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
データベースファイルのサイズを得るには、関数 `dpfsiz' を用いる。
- int dpfsiz(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズであり、エラーなら -1 である。
データベースのバケット配列の要素数を得るには、関数 `dpbnum' を用いる。
- int dpbnum(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値は正常ならデータベースのバケット配列の要素数であり、エラーなら -1 である。
データベースのバケット配列の利用要素数を得るには、関数 `dpbusenum' を用いる。
- int dpbusenum(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の利用要素数であり、エラーなら -1 である。この関数はバケット配列の全ての要素を参照するので、効率が悪い。
データベースのレコード数を得るには、関数 `dprnum' を用いる。
- int dprnum(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数であり、エラーなら -1 である。
データベースハンドルがライタかどうかを調べるには、関数 `dpwritable' を用いる。
- int dpwritable(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。
データベースに致命的エラーが起きたかどうかを調べるには、関数 `dpfatalerror' を用いる。
- int dpfatalerror(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
データベースファイルのinode番号を得るには、関数 `dpinode' を用いる。
- int dpinode(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値はデータベースファイルのinode番号である。
データベースファイルのファイルディスクリプタを得るには、関数 `dpfdesc' を用いる。
- int dpfdesc(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値はデータベースファイルのファイルディスクリプタである。データベースのファイルディスクリプタを直接操ることは推奨されない。
データベースファイルを削除するには、関数 `dpremove' を用いる。
- int dpremove(const char *name);
- `name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
異なるバイトオーダのプラットフォーム用にデータベースファイルを変換するには、関数 `dpeconv' を用いる。
- int dpeconv(const char *name, int big);
- `name' はデータベースファイルの名前を指定する。`big' は変換結果がビッグエンディアン用にか否かを指定する。戻り値は正常なら真であり、エラーなら偽である。レコードの内容は変換されず、その責任はアプリケーションが負う。
データベースの内部で用いるハッシュ関数として、関数 `dpinnerhash' がある。
- int dpinnerhash(const char *kbuf, int ksiz);
- `kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値はキーから31ビット長のハッシュ値を算出した値である。この関数はアプリケーションがバケット配列の状態を予測する際に役立つ。
データベースの内部で用いるハッシュ関数と独立したハッシュ関数として、関数 `dpouterhash' がある。
- int dpouterhash(const char *kbuf, int ksiz);
- `kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値はキーから31ビット長のハッシュ値を算出した値である。この関数はアプリケーションがデータベースの更に上でハッシュアルゴリズムを利用する際に役立つ。
ある数以上の自然数の素数を得るには、関数 `dpprimenum' を用いる。
- int dpprimenum(int num);
- `num' は適当な自然数を指定する。戻り値は、指定した数と同じかより大きくかつなるべく小さい自然数の素数である。この関数はアプリケーションが利用するバケット配列のサイズを決める場合に役立つ。
サンプルコード
名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。
#include <depot.h>
#include <stdlib.h>
#include <stdio.h>
#define NAME "mikio"
#define NUMBER "000-1234-5678"
#define DBNAME "book"
int main(int argc, char **argv){
DEPOT *depot;
char *val;
/* データベースを開く */
if(!(depot = dpopen(DBNAME, DP_OWRITER | DP_OCREAT, -1))){
fprintf(stderr, "dpopen: %s\n", dperrmsg(dpecode));
return 1;
}
/* レコードを格納する */
if(!dpput(depot, NAME, -1, NUMBER, -1, DP_DOVER)){
fprintf(stderr, "dpput: %s\n", dperrmsg(dpecode));
}
/* レコードを取得する */
if(!(val = dpget(depot, NAME, -1, 0, -1, NULL))){
fprintf(stderr, "dpget: %s\n", dperrmsg(dpecode));
} else {
printf("Name: %s\n", NAME);
printf("Number: %s\n", val);
free(val);
}
/* データベースを閉じる */
if(!dpclose(depot)){
fprintf(stderr, "dpclose: %s\n", dperrmsg(dpecode));
return 1;
}
return 0;
}
データベースの全てのレコードを表示するアプリケーションのサンプルコードを以下に示す。
#include <depot.h>
#include <stdlib.h>
#include <stdio.h>
#define DBNAME "book"
int main(int argc, char **argv){
DEPOT *depot;
char *key, *val;
/* データベースを開く */
if(!(depot = dpopen(DBNAME, DP_OREADER, -1))){
fprintf(stderr, "dpopen: %s\n", dperrmsg(dpecode));
return 1;
}
/* イテレータを初期化する */
if(!dpiterinit(depot)){
fprintf(stderr, "dpiterinit: %s\n", dperrmsg(dpecode));
}
/* イテレータを走査する */
while((key = dpiternext(depot, NULL)) != NULL){
if(!(val = dpget(depot, key, -1, 0, -1, NULL))){
fprintf(stderr, "dpget: %s\n", dperrmsg(dpecode));
free(key);
break;
}
printf("%s: %s\n", key, val);
free(val);
free(key);
}
/* データベースを閉じる */
if(!dpclose(depot)){
fprintf(stderr, "dpclose: %s\n", dperrmsg(dpecode));
return 1;
}
return 0;
}
注記
Depotを利用したプログラムをビルドするには、ライブラリ `libqdbm.a' または `libqdbm.so' をリンク対象に加える必要がある。例えば、`sample.c' から `sample' を作るには、以下のようにビルドを行う。
gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
Depotの各関数はリエントラントではないが、関数内で静的な参照を保持するものではない。したがって、全ての呼び出しと外部変数 `dpecode' の参照を排他制御することでスレッドセーフな関数として扱うことができる。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることがその前提となる。
Depotに対応するコマンドラインインタフェースは以下のものである。
コマンド `dpmgr' はDepotやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。
- dpmgr create [-bnum num] name
- データベースファイルを作成する。
- dpmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-na] name key val
- キーと値に対応するレコードを追加する。
- dpmgr out [-kx|-ki] name key
- キーに対応するレコードを削除する。
- dpmgr get [-kx|-ki] [-start num] [-max num] [-ox] [-n] name key
- キーに対応するレコードの値を取得して標準出力する。
- dpmgr list [-ox] name
- データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
- dpmgr optimize [-bnum num] [-na] name
- データベースを最適化する。
- dpmgr inform name
- データベースの雑多な情報を出力する。
- dpmgr remove name
- データベースファイルを削除する。
- dpmgr econv [-be|-le] name
- データベースファイルのバイトオーダをローカルシステム用に変換する。
- dpmgr version
- QDBMのバージョン情報を標準出力する。
各オプションは以下の機能を持つ。
- -bnum num : バケット配列の要素数を `num' に指定する。
- -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
- -ki : 10進数による数値表現として `key' を扱う。
- -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
- -vi : 10進数による数値表現として `val' を扱う。
- -vf : 名前が `val' のファイルのデータを値として読み込む。
- -keep : 既存のレコードとキーが重複時に上書きせずにエラーにする。
- -cat : 既存のレコードとキーが重複時に値を末尾に追加する。
- -na : アラインメントを設定しない。
- -start : 値から取り出すデータの開始オフセットを指定する。
- -max : 値から取り出すデータの最大の長さを指定する。
- -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
- -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
- -be : データベースファイルをビッグエンディアン用に変換する。
- -le : データベースファイルをリトルエンディアン用に変換する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `dptest' はDepotの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `dpmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`bnum' はバケット配列の要素数、`pnum' はキーのパターン数、`align' はアラインメントの基本サイズを指定する。
- dptest write name rnum bnum
- `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
- dptest read name
- 上記で生成したデータベースの全レコードを検索する。
- dptest rcat name rnum bnum pnum align
- キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。
- dptest combo name
- 各種操作の組み合わせテストを行う。
- dptest wicked name rnum
- 各種更新操作を無作為に選択して実行する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `dptsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとDepotのデータベースを相互変換する。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。キーが重複するレコードは後者を優先する。`-bnum' オプションの引数 `num' はバケット配列の要素数を指定する。`import' サブコマンドではTSVのデータが標準出力に書き出される。
- dptsv import [-bnum num] name
- TSVファイルを読み込んでデータベースを作成する。
- dptsv export name
- データベースの全てのレコードをTSVファイルとして出力する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
Depotのコマンド群を駆使すると、簡単なデータベースシステムが構築できる。例えば `/etc/password' をユーザ名で検索するためのデータベースを作成するには、以下のようにする。
cat /etc/passwd | tr ':' '\t' | dptsv import casket
そして、`mikio' というユーザの情報を取り出すには、以下のようにする。
dpmgr get casket mikio
これらのコマンドと同等の機能をDepotのAPIを用いて実装することも容易である。
概要
CuriaはQDBMの拡張APIであり、複数のデータベースファイルをディレクトリで一括して扱う機能を提供する。データベースを複数のファイルに分割することで、ファイルシステムによるファイルサイズの制限を回避することができる。複数のデバイスにファイルを分散させれば、スケーラビリティを向上させることができる。
Depotではファイル名を指定してデータベースを構築するが、Curiaではディレクトリ名を指定してデータベースを構築する。指定したディレクトリの直下には、`depot' という名前のデータベースファイルが生成される。これはディレクトリの属性を保持するものであり、レコードの実データは格納されない。それとは別に、データベースを分割した個数だけ、4桁の10進数値の名前を持つサブディレクトリが生成され、各々のサブディレクトリの中には `depot' という名前でデータベースファイルが生成される。レコードの実データはそれらに格納される。例えば、`casket' という名前のデータベースを作成し、分割数を3にする場合、`casket/depot'、`casket/0001/depot'、`casket/0002/depot'、`casket/0003/depot' が生成される。データベースを作成する際にすでにディレクトリが存在していてもエラーとはならない。したがって、予めサブディレクトリを生成しておいて、各々に異なるデバイスのファイルシステムをマウントしておけば、データベースファイルを複数のデバイスに分散させることができる。
Curiaにはラージオブジェクトを扱う機能がある。通常のレコードのデータはデータベースファイルに格納されるが、ラージオブジェクトのレコードのデータは個別のファイルに格納される。ラージオブジェクトのファイルはハッシュ値を元にディレクトリに分けて格納されるので、通常のレコードには劣るが、それなりの速度で参照できる。サイズが大きく参照頻度が低いデータは、ラージオブジェクトとしてデータベースファイルから分離すべきである。そうすれば、通常のレコードに対する処理速度が向上する。ラージオブジェクトのディレクトリ階層はデータベースファイルが格納されるサブディレクトリの中の `lob' という名前のディレクトリの中に作られる。通常のデータベースとラージオブジェクトのデータベースはキー空間が異なり、互いに干渉することはない。
Curiaを使うためには、`depot.h' と `curia.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。
- #include <depot.h>
- #include <curia.h>
- #include <stdlib.h>
Curiaでデータベースを扱う際には、`CURIA' 型へのポインタをハンドルとして用いる。これは、`stdio.h' の各種ルーチンがファイル入出力に `FILE' 型へのポインタを用いるのに似ている。ハンドルは、関数 `cropen' で開き、関数 `crclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `crclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースディレクトリを同時に利用することは可能であるが、同じデータベースディレクトリの複数のハンドルを利用してはならない。
CuriaでもDepotと同じく外部変数 `dpecode' に直前のエラーコードが記録される。エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。
API
データベースのハンドルを作成するには、関数 `cropen' を用いる。
- CURIA *cropen(const char *name, int omode, int bnum, int dnum);
- `name' はデータベースディレクトリの名前を指定する。`omode' は接続モードを指定し、`CR_OREADER' ならリーダ、`CR_OWRITER' ならライタとなる。`CR_OWRITER' の場合、`CR_OCREAT' または `CR_OTRUNC' とのビット論理和にすることができる。`CR_OCREAT' はファイルが無い場合に新規作成することを指示し、`CR_OTRUNC' はファイルが存在しても作り直すことを指示する。`CR_OREADER' と `CR_OWRITER' の両方で `CR_ONOLCK' とのビット論理和にすることができるが、それはファイルロックをかけずにデータベースを開くことを指示する。`bnum' はバケット配列の要素数の目安を指定するが、0 以下ならデフォルト値が使われる。バケット配列の要素数はデータベースを作成する時に決められ、最適化以外の手段で変更することはできない。バケット配列の要素数は、格納するレコード数の半分から4倍程度にするのがよい。`dnum' は要素データベースの数を指定するが、0 以下ならデフォルト値が使われる。データベースファイルの分割数はデータベースを作成する時に指定したものから変更することはできない。データベースファイルの分割数の最大値は 512 個である。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。`CR_ONOLCK' を使う場合、アプリケーションが排他制御の責任を負う。
データベースとの接続を閉じてハンドルを破棄するには、関数 `crclose' を用いる。
- int crclose(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
レコードを追加するには、関数 `crput' を用いる。
- int crput(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
- `curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' は `CR_DOVER' か `CR_DKEEP' か `CR_DCAT' で、キーが既存レコードと重複した際の制御を指定する。`CR_DOVER' は既存のレコードの値を上書きし、`CR_DKEEP' は既存のレコードを残してエラーを返し、`DP_DCAT' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。
レコードを削除するには、関数 `crout' を用いる。
- int crout(CURIA *curia, const char *kbuf, int ksiz);
- `curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。
レコードを取得するには、関数 `crget' を用いる。
- char *crget(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp);
- `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
レコードの値のサイズを取得するには、関数 `crvsiz' を用いる。
- int crvsiz(CURIA *curia, const char *kbuf, int ksiz);
- `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであり、該当がなかったり、エラーの場合は -1 である。この関数はレコードの有無を調べるのにも便利である。`crget' と違って実データを読み込まないので効率がよい。
データベースのイテレータを初期化するには、関数 `criterinit' を用いる。
- int criterinit(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。イテレータは、データベースに格納された全てのレコードを参照するために用いられる。
データベースのイテレータから次のレコードのキーを取り出すには、関数 `criternext' を用いる。
- char *criternext(CURIA *curia, int *sp);
- `curia' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常ならキーを格納した領域へのポインタであり、エラーなら `NULL' である。イテレータが最後まできて該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数を繰り返して呼ぶことによって全てのレコードを一度ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。
データベースのアラインメントを設定するには、関数 `crsetalign' を用いる。
- int crsetalign(CURIA *curia, int align);
- `curia' はライタで接続したデータベースハンドルを指定する。`align' はアラインメントのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。アラインメントを設定しておくと、レコードの上書きを頻繁にする場合の処理効率が良くなる。アラインメントには、一連の更新操作をした後の状態での標準的な値のサイズを指定するのがよい。アラインメントが正数の場合、レコードの領域のサイズがアラインメントの倍数になるようにパディングがとられる。アラインメントが負数の場合、`vsiz' を値のサイズとして、パディングのサイズは `(vsiz / pow(2, abs(align) - 1))' として算出される。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。
データベースを更新した内容をファイルとデバイスに同期させるには、関数 `crsync' を用いる。
- int crsync(CURIA *curia);
- `curia' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースファイルを利用させる場合に役立つ。
データベースを最適化するには、関数 `croptimize' を用いる。
- int croptimize(CURIA *curia, int bnum);
- `curia' はライタで接続したデータベースハンドルを指定する。`bnum' は新たなバケット配列の要素数を指定するが、0 以下なら現在のレコード数に最適な値が指定される。戻り値は正常なら真であり、エラーなら偽である。レコードを削除したり、置換モードや連結モードで書き込みを繰り返す場合は、データベース内に不要な領域が蓄積するが、この関数はそれを解消するのに役立つ。
データベースの名前を得るには、関数 `crname' を用いる。
- char *crname(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
データベースファイルのサイズの合計を得るには、関数 `crfsiz' を用いる。
- int crfsiz(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズの合計であり、エラーなら -1 である。
データベースのバケット配列の要素数の合計を得るには、関数 `crbnum' を用いる。
- int crbnum(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースのバケット配列の要素数の合計であり、エラーなら -1 である。
データベースのバケット配列の利用要素数の合計を得るには、関数 `crbusenum' を用いる。
- int crbusenum(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の利用要素数の合計であり、エラーなら -1 である。この関数はバケット配列の全ての要素を参照するので、効率が悪い。
データベースのレコード数を得るには、関数 `crrnum' を用いる。
- int crrnum(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数であり、エラーなら -1 である。
データベースハンドルがライタかどうかを調べるには、関数 `crwritable' を用いる。
- int crwritable(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。
データベースに致命的エラーが起きたかどうかを調べるには、関数 `crfatalerror' を用いる。
- int crfatalerror(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
データベースディレクトリのinode番号を得るには、関数 `crinode' を用いる。
- int crinode(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値はデータベースディレクトリのinode番号である。
データベースディレクトリを削除するには、関数 `crremove' を用いる。
- int crremove(const char *name);
- `name' はデータベースディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
異なるバイトオーダのプラットフォーム用にデータベースディレクトリを変換するには、関数 `creconv' を用いる。
- int creconv(const char *name, int big);
- `name' はデータベースディレクトリの名前を指定する。`big' は変換結果がビッグエンディアン用にか否かを指定する。戻り値は正常なら真であり、エラーなら偽である。レコードの内容は変換されず、その責任はアプリケーションが負う。
ラージオブジェクト用データベースにレコードを追加するには、関数 `crputlob' を用いる。
- int crputlob(CURIA *curia, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
- `curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' は `CR_DOVER' か `CR_DKEEP' か `CR_DCAT' で、キーが既存レコードと重複した際の制御を指定する。`CR_DOVER' は既存のレコードの値を上書きし、`CR_DKEEP' は既存のレコードを残してエラーを返し、`DP_DCAT' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。
ラージオブジェクト用データベースからレコードを削除するには、関数 `croutlob' を用いる。
- int croutlob(CURIA *curia, const char *kbuf, int ksiz);
- `curia' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。
ラージオブジェクト用データベースからレコードの値を取得するには、関数 `crgetlob' を用いる。
- char *crgetlob(CURIA *curia, const char *kbuf, int ksiz, int start, int max, int *sp);
- `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
ラージオブジェクト用データベースにあるレコードの値のサイズを取得するには、関数 `crvsizlob' を用いる。
- int crvsizlob(CURIA *curia, const char *kbuf, int ksiz);
- `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値のサイズであり、該当がなかったり、エラーの場合は -1 である。この関数はレコードの有無を調べるのにも便利である。`crgetlob' と違って実データを読み込まないので効率がよい。
ラージオブジェクト用データベースのレコード数の合計を得るには、関数 `crrnumlob' を用いる。
- int crrnumlob(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数の合計であり、エラーなら -1 である。
サンプルコード
名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。
#include <depot.h>
#include <curia.h>
#include <stdlib.h>
#include <stdio.h>
#define NAME "mikio"
#define NUMBER "000-1234-5678"
#define DBNAME "book"
int main(int argc, char **argv){
CURIA *curia;
char *val;
/* データベースを開く */
if(!(curia = cropen(DBNAME, CR_OWRITER | CR_OCREAT, -1, -1))){
fprintf(stderr, "cropen: %s\n", dperrmsg(dpecode));
return 1;
}
/* レコードを格納する */
if(!crput(curia, NAME, -1, NUMBER, -1, CR_DOVER)){
fprintf(stderr, "crput: %s\n", dperrmsg(dpecode));
}
/* レコードを取得する */
if(!(val = crget(curia, NAME, -1, 0, -1, NULL))){
fprintf(stderr, "crget: %s\n", dperrmsg(dpecode));
} else {
printf("Name: %s\n", NAME);
printf("Number: %s\n", val);
free(val);
}
/* データベースを閉じる */
if(!crclose(curia)){
fprintf(stderr, "crclose: %s\n", dperrmsg(dpecode));
return 1;
}
return 0;
}
データベースの全てのレコードを表示するアプリケーションのサンプルコードを以下に示す。
#include <depot.h>
#include <curia.h>
#include <stdlib.h>
#include <stdio.h>
#define DBNAME "book"
int main(int argc, char **argv){
CURIA *curia;
char *key, *val;
/* データベースを開く */
if(!(curia = cropen(DBNAME, CR_OREADER, -1, -1))){
fprintf(stderr, "cropen: %s\n", dperrmsg(dpecode));
return 1;
}
/* イテレータを初期化する */
if(!criterinit(curia)){
fprintf(stderr, "criterinit: %s\n", dperrmsg(dpecode));
}
/* イテレータを走査する */
while((key = criternext(curia, NULL)) != NULL){
if(!(val = crget(curia, key, -1, 0, -1, NULL))){
fprintf(stderr, "crget: %s\n", dperrmsg(dpecode));
free(key);
break;
}
printf("%s: %s\n", key, val);
free(val);
free(key);
}
/* データベースを閉じる */
if(!crclose(curia)){
fprintf(stderr, "crclose: %s\n", dperrmsg(dpecode));
return 1;
}
return 0;
}
注記
Curiaを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。
gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
Curiaの各関数はリエントラントではないが、関数内で静的な参照を保持するものではない。したがって、全ての呼び出しと外部変数 `dpecode' の参照を排他制御することでスレッドセーフな関数として扱うことができる。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることがその前提となる。
Curiaに対応するコマンドラインインタフェースは以下のものである。
コマンド `crmgr' はCuriaやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。
- crmgr create [-bnum num] [-dnum num] name
- データベースディレクトリを作成する。
- crmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-lob] [-na] name key val
- キーと値に対応するレコードを追加する。
- crmgr out [-kx|-ki] [-lob] name key
- キーに対応するレコードを削除する。
- crmgr get [-kx|-ki] [-start num] [-max num] [-ox] [-lob] [-n] name key
- キーに対応するレコードの値を取得して標準出力する。
- crmgr list [-ox] name
- データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
- crmgr optimize [-bnum num] [-na] name
- データベースを最適化する。
- crmgr inform name
- データベースの雑多な情報を出力する。
- crmgr remove name
- データベースディレクトリを削除する。
- crmgr econv [-be|-le] name
- データベースディレクトリのバイトオーダをローカルシステム用に変換する。
- crmgr version
- QDBMのバージョン情報を標準出力する。
各オプションは以下の機能を持つ。
- -bnum num : バケット配列の要素数を `num' に指定する。
- -dnum num : データベースファイルの分割数を `num' に指定する。
- -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
- -ki : 10進数による数値表現として `key' を扱う。
- -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
- -vi : 10進数による数値表現として `val' を扱う。
- -vf : 名前が `val' のファイルのデータを値として読み込む。
- -keep : 既存のレコードとキーが重複時に上書きせずにエラーにする。
- -cat : 既存のレコードとキーが重複時に値を末尾に追加する。
- -na : アラインメントを設定しない。
- -start : 値から取り出すデータの開始オフセットを指定する。
- -max : 値から取り出すデータの最大の長さを指定する。
- -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
- -lob : ラージオブジェクトを扱う。
- -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
- -be : データベースディレクトリをビッグエンディアン用に変換する。
- -le : データベースディレクトリをリトルエンディアン用に変換する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `crtest' はCuriaの機能テストや性能テストに用いるツールである。`crtest' によって生成されたデータベースディレクトリを `crmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`bnum' はバケット配列の要素数、`dnum' はデータベースファイルの分割数、`pnum' はキーのパターン数、`align' はアラインメントの基本サイズを指定する。
- crtest write [-lob] name rnum bnum dnum
- `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
- crtest read [-lob] name
- 上記で生成したデータベースの全レコードを検索する。
- crtest rcat name rnum bnum dnum pnum align
- キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。
- crtest combo name
- 各種操作の組み合わせテストを行う。
- crtest wicked name rnum
- 各種更新操作を無作為に選択して実行する。
各オプションは以下の機能を持つ。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `crtsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとCuriaのデータベースを相互変換する。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。キーが重複するレコードは後者を優先する。`-bnum' オプションの引数 `num' はバケット配列の要素数を指定する。`-dnum' オプションの引数 `num' は要素データベースの数を指定する。`import' サブコマンドではTSVのデータが標準出力に書き出される。
- crtsv import [-bnum num] [-dnum num] name
- TSVファイルを読み込んでデータベースを作成する。
- crtsv export name
- データベースの全てのレコードをTSVファイルとして出力する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
Curiaのコマンド群を駆使すると、簡単なデータベースシステムが構築できる。例えば `/etc/password' をユーザ名で検索するためのデータベースを作成するには、以下のようにする。
cat /etc/passwd | tr ':' '\t' | crtsv import casket
そして、`mikio' というユーザの情報を取り出すには、以下のようにする。
crmgr get casket mikio
これらのコマンドと同等の機能をCuriaのAPIを用いて実装することも容易である。
概要
Relicは、NDBMと互換するAPIである。すなわち、Depotの関数群をNDBMのAPIで包んだものである。Relicを使ってNDBMのアプリケーションをQDBMに移植するのはたやすい。ほとんどの場合、インクルードするヘッダファイルを `ndbm.h' から `relic.h' に換え、ビルドの際のリンカオプションを `-lndbm' から `-lqdbm' に換えるだけでよい。
オリジナルのNDBMでは、データベースは二つのファイルの対からなる。ひとつは接尾辞に `.dir' がつく名前で、キーのビットマップを格納する「ディレクトリファイル」である。もうひとつは接尾辞に `.pag' がつく名前で、データの実体を格納する「データファイル」である。Relicではディレクトリファイルは単なるダミーとして作成し、データファイルをデータベースとする。RelicではオリジナルのNDBMと違い、格納するデータのサイズに制限はない。なお、オリジナルのNDBMで生成したデータベースファイルをRelicで扱うことはできない。
Relicを使うためには、`relic.h' と `stdlib.h' と `sys/types.h' と `sys/stat.h' と `fcntl.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。
- #include <relic.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
Relicでデータベースを扱う際には、`DBM' 型へのポインタをハンドルとして用いる。ハンドルは、関数 `dbm_open' で開き、関数 `dbm_close' で閉じる。ハンドルのメンバを直接参照することは推奨されない。
API
データの格納、削除、検索に用いる関数とのデータの授受には、キーと値を表現するのに `datum' 型の構造体を用いる。
- typedef struct { void *dptr; size_t dsize; } datum;
- `dptr' はデータ領域へのポインタである。`dsize' はデータ領域のサイズである。
データベースのハンドルを作成するには、関数 `dbm_open' を用いる。
- DBM *dbm_open(char *name, int flags, int mode);
- `name' はデータベースの名前を指定するが、ファイル名はそれに接尾辞をつけたものになる。`flags' は `open' コールに渡すものと同じだが、`O_WRONLY' は `O_RDWR' と同じになり、追加フラグでは `O_CREAT' と `O_TRUNC' のみが有効である。`mode' は `open' コールに渡すものと同じでファイルのモードを指定する。戻り値は正常ならデータベースハンドルであり、エラーなら `NULL' である。
データベースとの接続を閉じてハンドルを破棄するには、関数 `dbm_close' を用いる。
- void dbm_close(DBM *db);
- `db' はデータベースハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。
レコードを追加するには、関数 `dbm_store' を用いる。
- int dbm_store(DBM *db, datum key, datum content, int flags);
- `db' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。`content' は値の構造体を指定する。`frags' が `DBM_INSERT' ならキーの重複時に書き込みを断念し、`DBM_REPLACE' なら上書きを行う。戻り値は正常なら 0 であり、重複での断念なら 1 であり、その他のエラーなら -1 である。
レコードを削除するには、関数 `dbm_delete' を用いる。
- int dbm_delete(DBM *db, datum key);
- `db' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は正常なら 0 であり、エラーなら -1 である。
レコードを取得するには、関数 `dbm_fetch' を用いる。
- datum dbm_fetch(DBM *db, datum key);
- `db' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は値の構造体である。該当があればメンバ `dptr' がその領域を指し、メンバ `dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。`dptr' の指す領域はハンドルに関連づけられて確保され、同じハンドルに対して次にこの関数を呼び出すか、ハンドルを閉じるまで、有効なデータを保持する。
最初のレコードのキーを得るには、関数 `dbm_firstkey' を用いる。
- datum dbm_firstkey(DBM *db);
- `db' はデータベースハンドルを指定する。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。`dptr' の指す領域はハンドルに関連づけられて確保され、同じハンドルに対して次にこの関数もしくは関数 `dbm_nextkey' を呼び出すか、ハンドルを閉じるまで、有効なデータを保持する。
次レコードのキーを得るには、関数 `dbm_nextkey' を用いる。
- datum dbm_nextkey(DBM *db);
- `db' はデータベースハンドルを指定する。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。`dptr' の指す領域はハンドルに関連づけられて確保され、同じハンドルに対して次にこの関数もしくは関数 `dbm_firstkey' を呼び出すか、ハンドルを閉じるまで、有効なデータを保持する。
データベースに致命的エラーが起きたかどうかを調べるには、関数 `dbm_error' を用いる。
- int dbm_error(DBM *db);
- `db' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
関数 `dbm_clearerr' は何もしない。
- int dbm_clearerr(DBM *db);
- `db' はデータベースハンドルを指定する。戻り値は 0 である。この関数は互換性のためにのみ存在する。
データベースが読み込み専用かどうかを調べるには、関数 `dbm_rdonly' を用いる。
- int dbm_rdonly(DBM *db);
- `db' はデータベースハンドルを指定する。戻り値は読み込み専用なら真であり、そうでなければ偽である。
ディレクトリファイルのファイルディスクリプタを得るには、関数 `dbm_dirfno' を用いる。
- int dbm_dirfno(DBM *db);
- `db' はデータベースハンドルを指定する。戻り値はディレクトリファイルのファイルディスクリプタである。
データファイルのファイルディスクリプタを得るには、関数 `dbm_pagfno' を用いる。
- int dbm_pagfno(DBM *db);
- `db' はデータベースハンドルを指定する。戻り値はデータファイルのファイルディスクリプタである。
サンプルコード
名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。
#include <relic.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#define NAME "mikio"
#define NUMBER "000-1234-5678"
#define DBNAME "book"
int main(int argc, char **argv){
DBM *db;
datum key, val;
int i;
/* データベースを開く */
if(!(db = dbm_open(DBNAME, O_RDWR | O_CREAT, 00644))){
perror("dbm_open");
return 1;
}
/* レコードを準備する */
key.dptr = NAME;
key.dsize = strlen(NAME);
val.dptr = NUMBER;
val.dsize = strlen(NUMBER);
/* レコードを格納する */
if(dbm_store(db, key, val, DBM_REPLACE) != 0){
perror("dbm_store");
}
/* レコードを検索する */
val = dbm_fetch(db, key);
if(val.dptr){
printf("Name: %s\n", NAME);
printf("Number: ");
for(i = 0; i < val.dsize; i++){
putchar(((char *)val.dptr)[i]);
}
putchar('\n');
} else {
perror("dbm_fetch");
}
/* データベースを閉じる */
dbm_close(db);
return 0;
}
注記
Relicを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。リンカに渡すオプションは `-lndbm' ではなく `-lqdbm' である。
gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
Relicの各関数はリエントラントではなく、スレッドセーフではない。
Relicに対応するコマンドラインインタフェースは以下のものである。
コマンド `rlmgr' はRelicやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。
- rlmgr create name
- データベースファイルを作成する。
- rlmgr store [-kx] [-vx|-vf] [-insert] name key val
- キーと値に対応するレコードを追加する。
- rlmgr delete [-kx] name key
- キーに対応するレコードを削除する。
- rlmgr fetch [-kx] [-ox] [-n] name key
- キーに対応するレコードの値を取得して標準出力する。
- rlmgr list [-ox] name
- データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
各オプションは以下の機能を持つ。
- -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
- -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
- -vf : 名前が `val' のファイルのデータを値として読み込む。
- -insert : 既存のレコードとキーが重複時に上書きせずにエラーにする。
- -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
- -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `rltest' はRelicの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `rlmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数を指定する。
- rltest write name rnum
- `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
- rltest read name rnum
- 上記で生成したデータベースを検索する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
概要
Hovelは、GDBMと互換するAPIである。すなわち、DepotおよびCuriaの関数群をGDBMのAPIで包んだものである。Hovelを使ってGDBMのアプリケーションをQDBMに移植するのはたやすい。ほとんどの場合、インクルードするヘッダファイルを `gdbm.h' から `hovel.h' に換え、ビルドの際のリンカオプションを `-lgdbm' から `-lqdbm' に換えるだけでよい。なお、オリジナルのGDBMで生成したデータベースファイルをHovelで扱うことはできない。
Hovelを使うためには、`hovel.h' と `stdlib.h' と `sys/types.h' と `sys/stat.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。
- #include <hovel.h>
- #include <stdlib.h>
- #include <sys/types.h>
- #include <sys/stat.h>
Hovelでデータベースを扱う際には、`GDBM_FILE' 型のオブジェクト(それ自体がポインタ型)をハンドルとして用いる。ハンドルは、関数 `gdbm_open' で開き、関数 `gdbm_close' で閉じる。ハンドルのメンバを直接参照することは推奨されない。Hovelは通常はDepotのラッパーとして動作してデータベースファイルを扱うが、ハンドルを開く際に関数 `gdbm_open2' を用いることによってCuriaのラッパーとしてデータベースディレクトリを扱うようにすることができる。
API
データの格納、削除、検索に用いる関数とのデータの授受には、キーと値を表現するのに `datum' 型の構造体を用いる。
- typedef struct { char *dptr; size_t dsize; } datum;
- `dptr' はデータ領域へのポインタである。`dsize' はデータ領域のサイズである。
外部変数 `gdbm_version' はバージョン情報の文字列である。
- extern char *gdbm_version;
- この変数の指す領域は書き込み禁止である。
外部変数 `gdbm_errno' には直前のエラーコードが記録される。エラーコードの詳細については `hovel.h' を参照すること。
- extern gdbm_error gdbm_errno;
- この変数の初期値は `GDBM_NO_ERROR' である。その他の値として、`GDBM_MALLOC_ERROR'、`GDBM_BLOCK_SIZE_ERROR'、`GDBM_FILE_OPEN_ERROR'、`GDBM_FILE_WRITE_ERROR'、`GDBM_FILE_SEEK_ERROR'、`GDBM_FILE_READ_ERROR'、`GDBM_BAD_MAGIC_NUMBER'、`GDBM_EMPTY_DATABASE'、`GDBM_CANT_BE_READER'、`GDBM_CANT_BE_WRITER'、`GDBM_READER_CANT_DELETE'、`GDBM_READER_CANT_STORE'、`GDBM_READER_CANT_REORGANIZE'、`GDBM_UNKNOWN_UPDATE'、`GDBM_ITEM_NOT_FOUND'、`GDBM_REORGANIZE_FAILED'、`GDBM_CANNOT_REPLACE'、`GDBM_ILLEGAL_DATA'、`GDBM_OPT_ALREADY_SET' および `GDBM_OPT_ILLEGAL' がある。
エラーコードに対応するメッセージ文字列を得るには、関数 `gdbm_strerror' を用いる。
- char *gdbm_strerror(gdbm_error gdbmerrno);
- `gdbmerrno' はエラーコードを指定する。戻り値はエラーメッセージの文字列であり、その領域は書き込み禁止領域である。
GDBM流にデータベースのハンドルを作成するには、関数 `gdbm_open' を用いる。
- GDBM_FILE gdbm_open(char *name, int block_size, int read_write, int mode, void (*fatal_func)(void));
- `name' はデータベースの名前を指定する。`block_size' は無視される。`read_write' は接続モードを指定し、`GDBM_READER' ならリーダ、`GDBM_WRITER' と `GDBM_WRCREAT' と `GDBM_NEWDB' ならライタとなる。`GDBM_WRCREAT' の場合はデータベースが存在しなければ作成し、`GDBM_NEWDB' の場合は既に存在していても新しいデータベースを作成する。ライタに対しては、`GDBM_SYNC' か `GDBM_NOLOCK' か `GDBM_FAST' とのビット論理和にすることができる。`GDBM_SYNC' は全てのデータベース操作をディスクと同期させ、`GDBM_NOLOCK' はファイルロックを伴わずにデータベースを開き、`GDBM_FAST' は無視される。`mode' は `open' コールに渡すものと同じでファイルのモードを指定する。`fatal_func' は無視される。戻り値は正常ならデータベースハンドルであり、エラーなら `NULL' である。
QDBM流にデータベースのハンドルを作成するには、関数 `gdbm_open2' を用いる。
- GDBM_FILE gdbm_open2(char *name, int read_write, int mode, int bnum, int dnum, int align);
- `name' はデータベースの名前を指定する。`read_write' は接続モードを指定し、`GDBM_READER' ならリーダ、`GDBM_WRITER' と `GDBM_WRCREAT' と `GDBM_NEWDB' ならライタとなる。`GDBM_WRCREAT' の場合はデータベースが存在しなければ作成し、`GDBM_NEWDB' の場合は既に存在していても新しいデータベースを作成する。ライタに対しては、`GDBM_SYNC' か `GDBM_NOLOCK' か `GDBM_FAST' とのビット論理和にすることができる。`GDBM_SYNC' は全てのデータベース操作をディスクと同期させ、`GDBM_NOLOCK' はファイルロックを伴わずにデータベースを開き、`GDBM_FAST' は無視される。`mode' は `open' コールもしくは `mkdir' コールに渡すものと同じでファイルやディレクトリのモードを指定する。`bnum' はバケット配列の要素数の目安を指定するが、0 以下ならデフォルト値が使われる。`dnum' は要素データベースの数を指定するが、0 以下なら返されるハンドルはDepotのラッパーとして生成され、そうでなければCuriaのラッパーになる。`align' はアラインメントの基本サイズを指定する。戻り値は正常ならデータベースハンドルであり、エラーなら `NULL' である。既にデータベースが存在する場合、それがDepotのものかCuriaのものかが自動的に判断される。
データベースとの接続を閉じてハンドルを破棄するには、関数 `gdbm_close' を用いる。
- void gdbm_close(GDBM_FILE dbf);
- `dbf' はデータベースハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。
レコードを追加するには、関数 `gdbm_store' を用いる。
- int gdbm_store(GDBM_FILE dbf, datum key, datum content, int flag);
- `dbf' はライタで接続したデータベースハンドルを指定する。`key' はキーの構造体を指定する。`content' は値の構造体を指定する。`frags' が `GDBM_INSERT' ならキーの重複時に書き込みを断念し、`GDBM_REPLACE' なら上書きを行う。戻り値は正常なら 0 、重複での断念なら 1 、その他のエラーなら -1 である。
レコードを削除するには、関数 `gdbm_delete' を用いる。
- int gdbm_delete(GDBM_FILE dbf, datum key);
- `dbf' はライタで接続したデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は正常なら 0 、エラーなら -1 である。
レコードを取得するには、関数 `gdbm_fetch' を用いる。
- datum gdbm_fetch(GDBM_FILE dbf, datum key);
- `dbf' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は値の構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。戻り値のメンバ `dptr' の指す領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
レコードが存在するか調べるには、関数 `gdbm_exists' を用いる。
- int gdbm_exists(GDBM_FILE dbf, datum key);
- `dbf' はデータベースハンドルを指定する。`key' はキーの構造体を指定する。戻り値は該当があれば真であり、該当がなかったり、エラーの場合は偽である。
最初のレコードのキーを得るには、関数 `gdbm_firstkey' を用いる。
- datum gdbm_firstkey(GDBM_FILE dbf);
- `dbf' はデータベースハンドルを指定する。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。戻り値のメンバ `dptr' の指す領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
次のレコードのキーを得るには、関数 gdbm_nextkey を用いる。
- datum gdbm_nextkey(GDBM_FILE dbf, datum key);
- `dbf' はデータベースハンドルを指定する。`key' は無視される。戻り値はキーの構造体である。該当があればメンバ `dptr' がその領域を指し、`dsize' がそのサイズを示す。該当がなければ `dptr' の値は `NULL' となる。戻り値のメンバ `dptr' の指す領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
データベースを更新した内容をファイルとデバイスに同期させるには、関数 `gdbm_sync' を用いる。
- void gdbm_sync(GDBM_FILE dbf);
- `dbf' はライタで接続したデータベースハンドルを指定する。
データベースを最適化するには、関数 `gdbm_reorganize' を用いる。
- int gdbm_reorganize(GDBM_FILE dbf);
- `dbf' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら 0 であり、エラーなら -1 である。
データベースファイルのファイルディスクリプタを得るには、関数 `gdbm_fdesc' を用いる。
- int gdbm_fdesc(GDBM_FILE dbf);
- `dbf' はデータベースハンドルを指定する。戻り値はデータベースファイルのファイルディスクリプタである。データベースがディレクトリなら、戻り値は -1 である。
関数 `gdbm_setopt' は何もしない。
- int gdbm_setopt(GDBM_FILE dbf, int option, int *value, int size);
- `dbf' はデータベースハンドルを指定する。`option' は無視される。`value' は無視される。`size' は無視される。戻り値は 0 である。この関数は互換性のためにのみ存在する。
サンプルコード
名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。
#include <hovel.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#define NAME "mikio"
#define NUMBER "000-1234-5678"
#define DBNAME "book"
int main(int argc, char **argv){
GDBM_FILE dbf;
datum key, val;
int i;
/* データベースを開く */
if(!(dbf = gdbm_open(DBNAME, 0, GDBM_WRCREAT, 00644, NULL))){
fprintf(stderr, "gdbm_open: %s\n", gdbm_strerror(gdbm_errno));
return 1;
}
/* レコードを準備する */
key.dptr = NAME;
key.dsize = strlen(NAME);
val.dptr = NUMBER;
val.dsize = strlen(NUMBER);
/* レコードを格納する */
if(gdbm_store(dbf, key, val, GDBM_REPLACE) != 0){
fprintf(stderr, "gdbm_store: %s\n", gdbm_strerror(gdbm_errno));
}
/* レコードを検索する */
val = gdbm_fetch(dbf, key);
if(val.dptr){
printf("Name: %s\n", NAME);
printf("Number: ");
for(i = 0; i < val.dsize; i++){
putchar(val.dptr[i]);
}
putchar('\n');
free(val.dptr);
} else {
fprintf(stderr, "gdbm_fetch: %s\n", gdbm_strerror(gdbm_errno));
}
/* データベースを閉じる */
gdbm_close(dbf);
return 0;
}
注記
Hovelを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。リンカに渡すオプションは `-lgdbm' ではなく `-lqdbm' である。
gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
Hovelの各関数はリエントラントではなく、スレッドセーフではない。
Hovelに対応するコマンドラインインタフェースは以下のものである。
コマンド `hvmgr' はHovelやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。
- hvmgr [-qdbm bnum dnum] create name
- データベースファイルを作成する。
- hvmgr store [-qdbm] [-kx] [-vx|-vf] [-insert] name key val
- キーと値に対応するレコードを追加する。
- hvmgr delete [-qdbm] [-kx] name key
- キーに対応するレコードを削除する。
- hvmgr fetch [-qdbm] [-kx] [-ox] [-n] name key
- キーに対応するレコードの値を取得して標準出力する。
- hvmgr list [-qdbm] [-ox] name
- データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
- hvmgr optimize [-qdbm] name
- データベースを最適化する。
各オプションは以下の機能を持つ。
- -qdbm [bnum dnum] : `gdbm_open2' でデータベースを開く。`bnum' と `dnum' はバケット配列の要素数とデータベースの分割数を指定する。
- -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
- -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
- -vf : 名前が `val' のファイルのデータを値として読み込む。
- -insert : 既存のレコードとキーが重複時に上書きせずにエラーにする。
- -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
- -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `hvtest' はHovelの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `hvmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数を指定する。
- hvtest write [-qdbm] name rnum
- `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
- hvtest read [-qdbm] name rnum
- 上記で生成したデータベースを検索する。
各オプションは以下の機能を持つ。
- -qdbm : `gdbm_open2' を用いてCuriaのハンドルを開く。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
概要
Cabinはメモリ上で簡単にレコードを扱うためのメモリ確保関数やソート関数や拡張可能なデータや配列リストやハッシュマップなど提供するユーティリティのAPIである。
Cabinを使うためには、`cabin.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。
- #include <cabin.h>
- #include <stdlib.h>
拡張可能なデータを扱う際には、`CBDATUM' 型へのポインタをハンドルとして用いる。データハンドルは、関数 `cbdatumopen' で開き、関数 `cbdatumclose' で閉じる。リストを扱う際には、`CBLIST' 型へのポインタをハンドルとして用いる。リストハンドルは、関数 `cblistopen' で開き、関数 `cblistclose' で閉じる。マップを扱う際には、`CBMAP' 型へのポインタをハンドルとして用いる。マップハンドルは、関数 `cbmapopen' で開き、関数 `cbmapclose' で閉じる。各ハンドルのメンバを直接参照することは推奨されない。
API
外部変数 `cbfatalfunc' は致命的エラーをハンドリングするコールバック関数である。
- extern void (*cbfatalfunc)(const char *message);
- 引数はエラーメッセージを指定する。この変数の初期値は `NULL' であり、`NULL' ならば致命的エラーの発生時にはデフォルトの関数が呼ばれる。致命的エラーはメモリの割り当てに失敗した際に起こる。
メモリ上に領域を確保するには、関数 `cbmalloc' を用いる。
- void *cbmalloc(size_t size);
- `size' は領域のサイズを指定する。戻り値は確保した領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
メモリ上の領域を再確保するには、関数 `cbrealloc' を用いる。
- void *cbrealloc(void *ptr, size_t size);
- `ptr' は領域へのポインタを指定する。`size' は領域のサイズを指定する。戻り値は再確保した領域へのポインタである。戻り値の領域は `remalloc' で確保されるので、不要になったら `free' で解放するべきである。
メモリ上の領域を複製するには、関数 `memdup' を用いる。
- char *cbmemdup(const char *ptr, int size);
- `ptr' は領域へのポインタを指定する。`size' は領域のサイズを指定する。戻り値は再確保した領域へのポインタである。戻り値は複製の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
配列の各要素を挿入ソートで整列させるには、関数 `cbisort' を用いる。
- void cbisort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
- `base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。挿入ソートは、ほとんどの要素が既に整列済みの場合にのみ有用である。
配列の各要素をシェルソートで整列させるには、関数 `cbssort' を用いる。
- void cbssort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
- `base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。ほとんどの要素が整列済みの場合、シェルソートの方がヒープソートやクイックソートより速いかもしれない。
配列の各要素をヒープソートで整列させるには、関数 `cbhsort' を用いる。
- void cbhsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
- `base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。ヒープソートは入力の偏りに対して頑丈であるが、ほとんどの場合でクイックソートの方が速い。
配列の各要素をクイックソートで整列させるには、関数 `cbqsort' を用いる。
- void cbqsort(void *base, int nmemb, int size, int(*compar)(const void *, const void *));
- `base' は配列のポインタを指定する。`nmemb' は配列の要素数を指定する。`size' は各要素のサイズを指定する。`compar' は比較関数を指定する。二つの引数は要素へのポインタである。比較関数は前者が大きければ正数を、後者が大きければ負数を、両者が等しければ 0 を返すべきである。入力の偏りに敏感ではあるが、クイックソートは最速のソートアルゴリズムである。
データハンドルを作成するには、関数 `cbdatumopen' を用いる。
- CBDATUM *cbdatumopen(const char *ptr, int size);
- `ptr' は初期内容の領域へのポインタを指定するか、`NULL' なら空のデータを作成する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値はデータハンドルである。
データを複製するには、関数 `cbdatumdup' を用いる。
- CBDATUM *cbdatumdup(const CBDATUM *datum);
- `datum' はデータハンドルを指定する。戻り値は新しいデータハンドルである。
データハンドルを破棄するには、関数 `cbdatumclose' を用いる。
- void cbdatumclose(CBDATUM *datum);
- `datum' はデータハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。
データに別の領域を連結するには、関数 `cbdatumcat' を用いる。
- void cbdatumcat(CBDATUM *datum, const char *ptr, int size);
- `datum' はデータハンドルを指定する。`ptr' は連結する領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。
データの領域へのポインタを得るには、関数 `cbdatumptr' を用いる。
- const char *cbdatumptr(const CBDATUM *datum);
- `datum' はデータハンドルを指定する。戻り値はデータの領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。
データの領域のサイズを得るには、関数 `cbdatumsize' を用いる。
- int cbdatumsize(const CBDATUM *datum);
- `datum' はデータハンドルを指定する。戻り値はデータの領域のサイズである。
データの領域のサイズを変更するには、関数 `cbdatumsetsize' を用いる。
- void cbdatumsetsize(CBDATUM *datum, int size);
- `datum' はデータハンドルを指定する。`size' は領域の新しいサイズを指定する。新しいサイズが既存のサイズより大きい場合、余った領域は終端文字で埋められる。
リストハンドルを作成するには、関数 `cblistopen' を用いる。
- CBLIST *cblistopen(void);
- 戻り値はリストハンドルである。
リストを複製するには、関数 `cblistdup' を用いる。
- CBLIST *cblistdup(const CBLIST *list);
- `list' はリストハンドルを指定する。戻り値は新しいリストハンドルである。
リストハンドルを破棄するには、関数 `cblistclose' を用いる。
- void cblistclose(CBLIST *list);
- `list' はリストハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用することができなくなる。
リストに格納された要素数を得るには、関数 `cblistnum' を用いる。
- int cblistnum(const CBLIST *list);
- `list' はリストハンドルを指定する。戻り値はリストに格納された要素数である。
ある要素の領域へのポインタを得るには、関数 `cblistval' を用いる。
- const char *cblistval(const CBLIST *list, int index, int *sp);
- `list' はリストハンドルを指定する。`index' は取り出す要素のインデックスを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。`index' が要素数以上ならば、戻り値は `NULL' である。
要素をリストの末尾に加えるには、関数 `cblistpush' を用いる。
- void cblistpush(CBLIST *list, const char *ptr, int size);
- `list' はリストハンドルを指定する。`ptr' は追加する要素の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。
リストの末尾の要素を削除するには、関数 `cblistpop' を用いる。
- char *cblistpop(CBLIST *list, int *sp);
- `list' はリストハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。リストが空ならば、戻り値は `NULL' である。
要素をリストの先頭に加えるには、関数 `cblistunshift' を用いる。
- void cblistunshift(CBLIST *list, const char *ptr, int size);
- `list' はリストハンドルを指定する。`ptr' は追加する要素の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。
リストの先頭の要素を削除するには、関数 `cblistshift' を用いる。
- char *cblistshift(CBLIST *list, int *sp);
- `list' はリストハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。リストが空ならば、戻り値は `NULL' である。
リスト内の指定した位置に要素を加えるには、関数 `cblistinsert' を用いる。
- void cblistinsert(CBLIST *list, int index, const char *ptr, int size);
- `list' はリストハンドルを指定する。`index' は追加する要素のインデックスを指定する。`ptr' は追加する要素の領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。
リスト内の指定した位置の要素を削除するには、関数 `cblistremove' を用いる。
- char *cblistremove(CBLIST *list, int index, int *sp);
- `list' はリストハンドルを指定する。`index' は削除する要素のインデックスを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は該当要素の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。`index' が要素数以上ならば、要素は削除されず、戻り値は `NULL' である。
リストの要素を辞書順で整列させるには、関数 `cblistsort' を用いる。
- void cblistsort(CBLIST *list);
- `list' はリストハンドルを指定する。整列にはクイックソートが用いられる。
リストの要素を線形探索を使って検索するには、関数 `cblistlsearch' を用いる。
- int cblistlsearch(const CBLIST *list, const char *ptr, int size);
- `list' はリストハンドルを指定する。`ptr' は検索キーの領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。戻り値は該当の要素のインデックスであるが、該当がなければ -1 である。複数の要素が該当した場合、前者が返される。
リストの要素を二分探索を使って検索するには、関数 `cblistbsearch' を用いる。
- int cblistbsearch(const CBLIST *list, const char *ptr, int size);
- `list' はリストハンドルを指定する。リストは辞書順にソートされている必要がある。`ptr' は検索キーの領域へのポインタを指定する。`size' はその領域のサイズを指定するが、負数なら `strlen(ptr)' の値となる。戻り値は該当の要素のインデックスであるが、該当がなければ -1 である。複数の要素が該当した場合にどちらが返るかは未定義である。
リストを直列化してバイト配列にするには、関数 `cblistdump' を用いる。
- char *cblistdump(const CBLIST *list, int *sp);
- `list' はリストハンドルを指定する。`sp' は抽出した領域のサイズを格納する領域へのポインタを指定する。戻り値は直列化された領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
直列化されたリストを復元するには、関数 `cblistload' を用いる。
- CBLIST *cblistload(const char *ptr, int size);
- `ptr' はバイト配列へのポインタを指定する。`size' はその領域のサイズを指定する。戻り値は新しいリストハンドルである。
マップハンドルを作成するには、関数 `cbmapopen' を用いる。
- CBMAP *cbmapopen(void);
- 戻り値はマップハンドルである。
マップを複製するには、関数 `cbmapdup' を用いる。
- CBMAP *cbmapdup(CBMAP *map);
- `map' はマップハンドルを指定する。戻り値は新しいマップハンドルである。コピー元のマップのイテレータは初期化される。
マップハンドルを破棄するには、関数 `cbmapclose' を用いる。
- void cbmapclose(CBMAP *map);
- `map' はマップハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
レコードを追加するには、関数 `cbmapput' を用いる。
- int cbmapput(CBMAP *map, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int over);
- `map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`over' は重複したレコードを上書きするか否かを指定する。`over' が偽でキーが重複した場合は戻り値は偽であるが、そうでない場合は真である。
レコードを削除するには、関数 `cbmapout' を用いる。
- int cbmapout(CBMAP *map, const char *kbuf, int ksiz);
- `map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、該当のレコードがない場合は偽である。
レコードを取得するには、関数 `cbmapget' を用いる。
- const char *cbmapget(const CBMAP *map, const char *kbuf, int ksiz, int *sp);
- `map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、該当のレコードがない場合は `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。
レコードを端に移動させるには、関数 `cbmapmove' を用いる。
- int cbmapmove(CBMAP *map, const char *kbuf, int ksiz, int head);
- `map' はマップハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`head' は移動先を指定し、真なら先頭、偽なら末尾となる。戻り値は正常なら真であり、該当のレコードがない場合は偽である。
マップのイテレータを初期化するには、関数 `cbmapiterinit' を用いる。
- void cbmapiterinit(CBMAP *map);
- `map' はマップハンドルを指定する。イテレータは、マップに格納された全てのレコードを参照するために用いられる。
マップのイテレータから次のレコードのキーを取り出すには、関数 `cbmapiternext' を用いる。
- const char *cbmapiternext(CBMAP *map, int *sp);
- `map' はマップハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常ならキーを格納した領域へのポインタであり、エラーなら `NULL' である。イテレータが最後まできて該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。取り出す順番は格納した際の順番に一致することが保証されている。
マップのレコード数を得るには、関数 `cbmaprnum' を用いる。
- int cbmaprnum(const CBMAP *map);
- `map' はマップハンドルを指定する。戻り値はデータベースのレコード数である。
マップを直列化してバイト配列にするには、関数 `cbmapdump' を用いる。
- char *cbmapdump(const CBMAP *map, int *sp);
- `map' はマップハンドルを指定する。`sp' は抽出した領域のサイズを格納する領域へのポインタを指定する。戻り値は直列化された領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
直列化されたマップを復元するには、関数 `cbmapload' を用いる。
- CBMAP *cbmapload(const char *ptr, int size);
- `ptr' はバイト配列へのポインタを指定する。`size' はその領域のサイズを指定する。戻り値は新しいマップハンドルである。
書式に基づいた文字列をメモリ上で確保するには、関数 `cbsprintf' を用いる。
- char *cbsprintf(const char *format, ...);
- `format' はprintf風の書式文字列を指定する。変換文字 `%' をフラグ文字 `d'、`o'、`u'、`x'、`X'、`e'、`E'、`f'、`g'、`G'、`c'、`s' および `%' を伴わせて使用することができる。フィールドの幅と精度の指示子を変換文字とフラグ文字の間に置くことができる。その指示子は10進数字、`.'、`+'、`-' およびスペース文字からなる。その他の引数は書式文字列によって利用される。戻り値は結果の文字列の領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
文字列中のパターンを置換するには、関数 `cbreplace' を用いる。
- char *cbreplace(const char *str, CBMAP *pairs);
- `str' は置換前の文字列を指定する。`pairs' は置換のペアからなるマップのハンドルを指定する。各ペアのキーは置換前のパターンを指定し、値は置換後のパターンを指定する。戻り値は結果の文字列の領域へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
一連のデータを分割してリストを作成するには、関数 `cbsplit' を用いる。
- CBLIST *cbsplit(const char *ptr, int size, const char *delim);
- `ptr' は内容の領域へのポインタを指定するか、`NULL' なら空のリストを作成する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`delim' は区切り文字を含む文字列を指定するか、`NULL' なら終端文字を区切り文字とする。戻り値はリストハンドルである。戻り値のハンドルは、関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
ファイルの全データを読み込むには、関数 `cbreadfile' を用いる。
- char *cbreadfile(const char *name, int *sp);
- `name' はファイルの名前を指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら読み込んだデータを格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
ファイルの各行を読み込むには、関数 `cbreadlines' を用いる。
- CBLIST *cbreadlines(const char *name);
- `name' はファイルの名前を指定する。成功すれば戻り値は各行のデータを保持するリストのハンドルであり、失敗なら `NULL' である。改行文字は削除される。
ディレクトリに含まれるファイルの名前のリストを得るには、関数 `cbdirlist' を用いる。
- CBLIST *cbdirlist(const char *name);
- `name' はディレクトリの名前を指定する。成功すれば戻り値は各ファイルの名前を保持するリストのハンドルであり、失敗なら `NULL' である。
ファイルやディレクトリの状態を得るには、関数 `cbfilestat' を用いる。
- int cbfilestat(const char *name, int *isdirp, int *sizep, int *mtimep);
- `name' はファイルやディレクトリの名前を指定する。`dirp' が `NULL' でなければ、その参照先にファイルがディレクトリか否かを格納する。`sizep' が `NULL' でなければ、その参照先にファイルのサイズを格納する。`mtimep' が `NULL' でなければ、その参照先にファイルの最終更新時刻を格納する。戻り値は正常なら真であり、エラーなら偽である。ファイルが存在しなかったりパーミッションがない場合も偽を返す。
一連のオブジェクトをURLエンコーディングで符号化するには、関数 `cburlencode' を用いる。
- char *cburlencode(const char *ptr, int size);
- `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
URLエンコーディングで符号化された文字列を復元するには、関数 `cburldecode' を用いる。
- char *cburldecode(const char *str, int *sp);
- `str' は符号化された文字列へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は結果の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
一連のオブジェクトをBase64エンコーディングで符号化するには、関数 `cbbaseencode' を用いる。
- char *cbbaseencode(const char *ptr, int size);
- `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
Base64エンコーディングで符号化された文字列を復元するには、関数 `cbbasedecode' を用いる。
- char *cbbasedecode(const char *str, int *sp);
- `str' は符号化された文字列へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は結果の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
一連のオブジェクトをquoted-printableエンコーディングで符号化するには、関数 `cbquoteencode' を用いる。
- char *cbquoteencode(const char *ptr, int size);
- `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。戻り値は結果の文字列へのポインタである。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
quoted-printableエンコーディングで符号化された文字列を復元するには、関数 `cbquotedecode' を用いる。
- char *cbquotedecode(const char *str, int *sp);
- `str' は符号化された文字列へのポインタを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は結果の領域へのポインタである。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
ZLIBを用いて一連のオブジェクトを圧縮するには、関数 `cbdeflate' を用いる。
- char *cbdeflate(const char *ptr, int size, int *sp);
- `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`sp' の参照先には抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。
ZLIBを用いて圧縮されたオブジェクトを伸長するには、関数 `cbinflate' を用いる。
- char *cbinflate(const char *ptr, int size, int *sp);
- `ptr' は領域へのポインタを指定する。`size' はその領域のサイズを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら結果の領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はQDBMがZLIBを有効にしてビルドされた場合のみ利用できる。
サンプルコード
以下のサンプルコードは典型的な利用例である。
#include <cabin.h>
#include <stdlib.h>
#include <stdio.h>
int main(int argc, char **argv){
CBDATUM *datum;
CBLIST *list;
CBMAP *map;
int i;
/* データハンドルを開く */
datum = cbdatumopen("123", -1);
/* データを連結する */
cbdatumcat(datum, "abc", -1);
/* データを表示する */
printf("%s\n", cbdatumptr(datum));
/* データハンドルを閉じる */
cbdatumclose(datum);
/* リストハンドルを開く */
list = cblistopen();
/* リストに要素を追加する */
cblistpush(list, "apple", -1);
cblistpush(list, "orange", -1);
/* 全ての要素を表示する */
for(i = 0; i < cblistnum(list); i++){
printf("%s\n", cblistval(list, i, NULL));
}
/* リストハンドルを閉じる */
cblistclose(list);
/* マップハンドルを開く */
map = cbmapopen();
/* マップにレコードを追加する */
cbmapput(map, "dog", -1, "bowwow", -1, 1);
cbmapput(map, "cat", -1, "meow", -1, 1);
/* 値を取得して表示する */
printf("%s\n", cbmapget(map, "dog", -1, NULL));
printf("%s\n", cbmapget(map, "cat", -1, NULL));
/* マップハンドルを閉じる */
cbmapclose(map);
return 0;
}
注記
Cabinを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。
gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
Cabinの各関数はリエントラントではないが、関数内で静的な参照を保持するものではない。したがって、全ての呼び出しを排他制御することでスレッドセーフな関数として扱うことができる。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることがその前提となる。
Cabinに対応するコマンドラインインタフェースは以下のものである。
コマンド `cbtest' はCabinの機能テストや性能テストに用いるツールである。`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`rnum' はレコード数を指定する。
- cbtest sort [-d] rnum
- ソートアルゴリズムのテストを行う。
- cbtest list [-d] rnum
- リストの書き込みテストを行う。
- cbtest map [-d] rnum
- マップの書き込みテストを行う。
- cbtest wicked rnum
- リストとマップの各種更新操作を無作為に選択して実行する。
- cbtest misc
- 雑多なルーチンのテストを実行する。
各オプションは以下の機能を持つ。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `cbcodec' はCabinが提供するエンコードおよびデコードの機能を利用するツールである。以下の書式で用いる。`file' は入力ファイルを指定するが、省略されれば標準入力を読み込む。
- cbcodec url [-d] [-l] [-e expr] [file]
- URLエンコードとそのデコードを行う。
- cbcodec base [-d] [-l] [-c num] [-e expr] [file]
- Base64エンコードとそのデコードを行う。
- cbcodec quote [-d] [-l] [-c num] [-e expr] [file]
- quoted-printableエンコードとそのデコードを行う。
- cbcodec zlib [-d] [file]
- ZLIBの圧縮とその伸長を行う。
各オプションは以下の機能を持つ。
- -d : エンコードではなく、デコードを行う。
- -l : 出力の末尾に改行文字を加える。
- -e expr : 入力データを直接指定する。
- -c num : エンコードの際の桁数制限を指定する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
概要
VillaはQDBMの上級APIであり、B+木のデータベースを管理するルーチンを提供する。各レコードはユーザが指定した順序で整列されて格納される。ハッシュデータベースではレコードの検索はキーの完全一致によるしかなかった。しかし、Villaを用いると範囲を指定してレコードを検索することができる。各レコードを順番に参照するにはカーソルを用いる。データベースにはキーが重複する複数のレコードを格納することができる。また、トランザクション機構によってデータベースの操作を一括して反映させたり破棄することができる。
VillaはDepotおよびCabinを基盤として実装される。VillaのデータベースファイルはDepotのデータベースファイルそのものである。レコードの検索や格納の処理速度はDepotより遅いが、データベースファイルのサイズはより小さい。
Villaを使うためには、`depot.h' と `cabin.h' と `villa.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。
- #include <depot.h>
- #include <cabin.h>
- #include <villa.h>
- #include <stdlib.h>
Villaでデータベースを扱う際には、`VILLA' 型へのポインタをハンドルとして用いる。これは、`stdio.h' の各種ルーチンがファイル入出力に `FILE' 型へのポインタを用いるのに似ている。ハンドルは、関数 `vlopen' で開き、関数 `vlclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `vlclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。カーソルを使う前には `vlcurfirst' か `vlcurlast' か `vlcurjump' のどれかで初期化する必要がある。レコードの更新や削除の後にもカーソルを初期化する必要がある。
VillaでもDepotと同じく外部変数 `dpecode' に直前のエラーコードが記録される。エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。
API
レコードの順番を指定するためには、比較関数を定義する。比較関数には以下の型を用いる。
- typedef int(*VLCFUNC)(const char *aptr, int asiz, const char *bptr, int bsiz);
- `aptr' は一方のキーのデータ領域へのポインタを指定する。`asiz' はそのキーのデータ領域のサイズを指定する。`bptr' は他方のキーのデータ領域へのポインタを指定する。`bsiz' はそのキーのデータ領域のサイズを指定する。戻り値は前者が大きければ正、後者が大きければ負、両者が等価なら 0 である。
データベースのハンドルを作成するには、関数 `vlopen' を用いる。
- VILLA *vlopen(const char *name, int omode, VLCFUNC cmp);
- `name' はデータベースファイルの名前を指定する。`omode' は接続モードを指定し、`VL_OREADER' ならリーダ、`VL_OWRITER' ならライタとなる。`VL_OWRITER' の場合、`VL_OCREAT' または `VL_OTRUNC' とのビット論理和にすることができる。`VL_OCREAT' はファイルが無い場合に新規作成することを指示し、`VL_OTRUNC' はファイルが存在しても作り直すことを指示する。`VL_OREADER' と `VL_OWRITER' の両方で `VL_ONOLCK' とのビット論理和にすることができるが、それはファイルロックをかけずにデータベースを開くことを指示する。`cmp' は比較関数を指定する。`VL_CMPLEX' はキーを辞書順で比較する。`VL_CMPINT' はキーを `int' 型のオブジェクトとみなして比較する。`VL_CMPNUM' はキーをビッグエンディアンの数値とみなして比較する。`VL_CMPDEC' はキーを10進数の数値を表す文字列とみなして比較する。`VLCFUNC' 型の宣言に基づく関数であれば比較関数として用いることができる。同じデータベースには常に同じ比較関数を用いる必要がある。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。`VL_ONOLCK' を使う場合、アプリケーションが排他制御の責任を負う。
データベースとの接続を閉じてハンドルを破棄するには、関数 `vlclose' を用いる。
- int vlclose(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。トランザクションが有効でコミットされていない場合、それは破棄される。
レコードを追加するには、関数 `vlput' を用いる。
- int vlput(VILLA *villa, const char *kbuf, int ksiz, const char *vbuf, int vsiz, int dmode);
- `villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vbuf' は値のデータ領域へのポインタを指定する。`vsiz' は値のデータ領域のサイズを指定するか、負数なら `strlen(vbuf)' の値となる。`dmode' は `VL_DOVER' か `VL_DKEEP' か `VL_DDUP' で、キーが既存レコードと重複した際の制御を指定する。`VL_DOVER' は既存のレコードの値を上書きし、`VL_DKEEP' は既存のレコードを残してエラーを返し、`VL_DDUP' はキーの重複を許す。戻り値は正常なら真であり、エラーなら偽である。重複したレコードは同じキーのレコード群の末尾に格納される。データベースの更新によってカーソルは使用不能になる。
レコードを削除するには、関数 `vlout' を用いる。
- int vlout(VILLA *villa, const char *kbuf, int ksiz);
- `villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。重複するレコード群のキーが指定された場合、その最初のものが削除される。データベースの更新によってカーソルは使用不能になる。
レコードを取得するには、関数 `vlget' を用いる。
- char *vlget(VILLA *villa, const char *kbuf, int ksiz, int *sp);
- `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。重複するレコード群のキーが指定された場合、その最初のものが選択される。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
キーに一致するレコードの数を取得するには、関数 `vlvnum' を用いる。
- int vlvnum(VILLA *villa, const char *kbuf, int ksiz);
- `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は該当レコードの値の数であり、該当がなかったり、エラーの場合は 0 である。この関数はレコードの有無を調べるのにも便利である。
キーに一致する複数のレコードを追加するには、関数 `vlputlist' を用いる。
- int vlputlist(VILLA *villa, const char *kbuf, int ksiz, CBLIST *vals);
- `villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`vals' は値のリストのハンドルを指定する。リストは空であってはならない。戻り値は正常なら真であり、エラーなら偽である。データベースの更新によってカーソルは使用不能になる。
キーに一致する全てのレコードを削除するには、関数 `vloutlist' を用いる。
- int vloutlist(VILLA *villa, const char *kbuf, int ksiz);
- `villa' はライタで接続したデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら真であり、エラーなら偽である。該当のレコードがない場合も偽を返す。データベースの更新によってカーソルは使用不能になる。
キーに一致する全てのレコードを取得するには、関数 `vlgetlist' を用いる。
- CBLIST *vlgetlist(VILLA *villa, const char *kbuf, int ksiz);
- `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は一致するレコードの値のリストのハンドルであるか、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。
カーソルを最初のレコードに移動させるには、関数 `vlcurfirst' を用いる。
- int vlcurfirst(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースが空の場合も偽を返す。
カーソルを最後のレコードに移動させるには、関数 `vlcurlast' を用いる。
- int vlcurlast(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースが空の場合も偽を返す。
カーソルを前のレコードに移動させるには、関数 `vlcurprev' を用いる。
- int vlcurprev(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。前のレコードがない場合も偽を返す。
カーソルを次のレコードに移動させるには、関数 `vlcurnext' を用いる。
- int vlcurnext(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。次のレコードがない場合も偽を返す。
カーソルを特定のレコードの前後に移動させるには、関数 `vlcurjump' を用いる。
- int vlcurjump(VILLA *villa, const char *kbuf, int ksiz, int jmode);
- `villa' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`jmode' は詳細な調整を指定する。`VL_JFORWARD' はキーが同じレコード群の最初のレコードにカーソルが設定され、また完全に一致するレコードがない場合は次の候補にカーソルが設定されることを意味する。`VL_JBACKWORD' はキーが同じレコード群の最後のレコードにカーソルが設定され、また完全に一致するレコードがない場合は前の候補にカーソルが設定されることを意味する。戻り値は正常なら真であり、エラーなら偽である。条件に一致するレコードがない場合も偽を返す。
カーソルのあるレコードのキーを取得するには、関数 `vlcurkey' を用いる。
- char *vlcurkey(VILLA *villa, int *sp);
- `villa' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
カーソルのあるレコードの値を取得するには、関数 `vlcurkey' を用いる。
- char *vlcurval(VILLA *villa, int *sp);
- `villa' はデータベースハンドルを指定する。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
性能を調整するパラメータを指定するには、関数 `vlsettuning' を用いる。
- void vlsettuning(VILLA *villa, int lrecmax, int nidxmax, int lcnum, int ncnum);
- `villa' はデータベースハンドルを指定する。`lrecmax' はB+木のひとつのリーフに入れるレコードの最大数を指定するが、0 以下ならデフォルト値が使われる。`nidxmax' はB+木の非リーフノードに入れるインデックスの最大数を指定するが、0 以下ならデフォルト値が使われる。`lcnum' はキャッシュに入れるリーフの最大数を指定するが、0 以下ならデフォルト値が使われる。`ncnum' はキャッシュに入れる非リーフノードの最大数を指定するが、0 以下ならデフォルト値が使われる。デフォルトの設定は `vlsettuning(49, 192, 1024, 512)' に相当する。性能調整のパラメータはデータベースに保存されないので、データベースを開く度に指定する必要がある。
データベースを更新した内容をファイルとデバイスに同期させるには、関数 `vlsync' を用いる。
- int vlsync(VILLA *villa);
- `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースファイルを利用させる場合に役立つ。トランザクションが有効な間はこの関数を使用すべきではない。
データベースを最適化するには、関数 `vloptimize' を用いる。
- int vloptimize(VILLA *villa);
- `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。レコードを削除したり、置換モードや連結モードで書き込みを繰り返す場合は、データベース内に不要な領域が蓄積するが、この関数はそれを解消するのに役立つ。トランザクションが有効な間はこの関数を使用すべきではない。
データベースの名前を得るには、関数 `vlname' を用いる。
- char *vlname(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
データベースファイルのサイズを得るには、関数 `vlfsiz' を用いる。
- int vlfsiz(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズであり、エラーなら -1 である。I/Oバッファにより、戻り値は実際のサイズより小さくなる場合がある。
B+木のリーフノードの数を得るには、関数 `vllnum' を用いる。
- int vllnum(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常ならリーフノードの数であり、エラーなら -1 である。
B+木の非リーフノードの数を得るには、関数 `vlnnum' を用いる。
- int vlnnum(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常なら非リーフノードの数であり、エラーなら -1 である。
データベースのレコード数を得るには、関数 `vlrnum' を用いる。
- int vlrnum(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は正常ならデータベースのレコード数であり、エラーなら -1 である。
データベースハンドルがライタかどうかを調べるには、関数 `vlwritable' を用いる。
- int vlwritable(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。
データベースに致命的エラーが起きたかどうかを調べるには、関数 `vlfatalerror' を用いる。
- int vlfatalerror(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
データベースファイルのinode番号を得るには、関数 `vlinode' を用いる。
- int vlinode(VILLA *villa);
- `villa' はデータベースハンドルを指定する。戻り値はデータベースファイルのinode番号である。
トランザクションを開始するには、関数 `vltranbegin' を用いる。
- int vltranbegin(VILLA *villa);
- `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はマルチスレッドでの相互排他制御を行わないので、アプリケーションがその責任を負う。一つのデータベースハンドルで同時に有効にできるトランザクションは一つだけである。
トランザクションをコミットするには、関数 `vltrancommit' を用いる。
- int vltrancommit(VILLA *villa);
- `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。トランザクションの中でのデータベースの更新はコミットが成功した時に確定する。
トランザクションを破棄するには、関数 `vltranabort' を用いる。
- int vltranabort(VILLA *villa);
- `villa' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。トランザクションの中でのデータベースの更新はアボートした時には破棄される。データベースの状態はトランザクションの前の状態にロールバックされる。
データベースファイルを削除するには、関数 `vlremove' を用いる。
- int vlremove(const char *name);
- `name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
異なるバイトオーダのプラットフォーム用にデータベースファイルを変換するには、関数 `vleconv' を用いる。
- int vleconv(const char *name, int big);
- `name' はデータベースファイルの名前を指定する。`big' は変換結果がビッグエンディアン用にか否かを指定する。戻り値は正常なら真であり、エラーなら偽である。レコードの内容は変換されず、その責任はアプリケーションが負う。
サンプルコード
名前と対応させて電話番号を格納し、それを検索するアプリケーションのサンプルコードを以下に示す。
#include <depot.h>
#include <cabin.h>
#include <villa.h>
#include <stdlib.h>
#include <stdio.h>
#define NAME "mikio"
#define NUMBER "000-1234-5678"
#define DBNAME "book"
int main(int argc, char **argv){
VILLA *villa;
char *val;
/* データベースを開く */
if(!(villa = vlopen(DBNAME, VL_OWRITER | VL_OCREAT, VL_CMPLEX))){
fprintf(stderr, "vlopen: %s\n", dperrmsg(dpecode));
return 1;
}
/* レコードを格納する */
if(!vlput(villa, NAME, -1, NUMBER, -1, VL_DOVER)){
fprintf(stderr, "vlput: %s\n", dperrmsg(dpecode));
}
/* レコードを取得する */
if(!(val = vlget(villa, NAME, -1, NULL))){
fprintf(stderr, "vlget: %s\n", dperrmsg(dpecode));
} else {
printf("Name: %s\n", NAME);
printf("Number: %s\n", val);
free(val);
}
/* データベースを閉じる */
if(!vlclose(villa)){
fprintf(stderr, "vlclose: %s\n", dperrmsg(dpecode));
return 1;
}
return 0;
}
文字列の前方一致検索を行うアプリケーションのサンプルコードを以下に示す。
#include <depot.h>
#include <cabin.h>
#include <villa.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define DBNAME "words"
#define PREFIX "apple"
int main(int argc, char **argv){
VILLA *villa;
char *key, *val;
/* データベースを開く */
if(!(villa = vlopen(DBNAME, VL_OWRITER | VL_OCREAT, VL_CMPLEX))){
fprintf(stderr, "vlopen: %s\n", dperrmsg(dpecode));
return 1;
}
/* レコードを格納する */
if(!vlput(villa, "applet", -1, "little application", -1, VL_DDUP) ||
!vlput(villa, "aurora", -1, "polar wonderwork", -1, VL_DDUP) ||
!vlput(villa, "apple", -1, "delicious fruit", -1, VL_DDUP) ||
!vlput(villa, "amigo", -1, "good friend", -1, VL_DDUP) ||
!vlput(villa, "apple", -1, "big city", -1, VL_DDUP)){
fprintf(stderr, "vlput: %s\n", dperrmsg(dpecode));
}
/* カーソルを候補の先頭に置く */
vlcurjump(villa, PREFIX, -1, VL_JFORWARD);
/* カーソルを走査する */
while((key = vlcurkey(villa, NULL)) != NULL){
if(strstr(key, PREFIX) != key){
free(key);
break;
}
if(!(val = vlcurval(villa, NULL))){
fprintf(stderr, "vlcurval: %s\n", dperrmsg(dpecode));
free(key);
break;
}
printf("%s: %s\n", key, val);
free(val);
free(key);
vlcurnext(villa);
}
/* データベースを閉じる */
if(!vlclose(villa)){
fprintf(stderr, "vlclose: %s\n", dperrmsg(dpecode));
return 1;
}
return 0;
}
注記
Villaを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。
gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
Villaの各関数はリエントラントではないが、関数内で静的な参照を保持するものではない。したがって、全ての呼び出しと外部変数 `dpecode' の参照を排他制御することでスレッドセーフな関数として扱うことができる。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることがその前提となる。
Vista: 拡張上級API
VistaはVillaを拡張したAPIである。Villaが2GB以上のファイルを扱うことができないという欠点を補うために、VistaではDepotではなくCuriaを用いて内部データベースを管理する。VistaはVillaと同じくB+木のデータ構造とその操作を提供するが、そのデータベースはディレクトリで実現される。
Vistaを使うには、`villa.h' の代わりに `vista.h' をインクルードすればよい。VistaはVillaのシンボルをマクロでオーバーライドして実装されているため、Villaと全く同様のAPIで利用することができる。すなわち、双方のシグネチャは全く同じである。ただし、その副作用として、Vistaを使うモジュール(コンパイルユニット)はDepotとVillaを利用することができない。
Villaに対応するコマンドラインインタフェースは以下のものである。
コマンド `vlmgr' はVillaやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。
- vlmgr create name
- データベースファイルを作成する。
- vlmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-dup] name key val
- キーと値に対応するレコードを追加する。
- vlmgr out [-l] [-kx|-ki] name key
- キーに対応するレコードを削除する。
- vlmgr get [-l] [-kx|-ki] [-ox] [-n] name key
- キーに対応するレコードの値を取得して標準出力する。
- vlmgr list [-kx|-ki] [-ox] [-top key] [-bot key] [-gt] [-lt] [-max num] [-desc] name
- データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
- vlmgr optimize name
- データベースを最適化する。
- vlmgr inform name
- データベースの雑多な情報を出力する。
- vlmgr remove name
- データベースファイルを削除する。
- vlmgr econv [-be|-le] name
- データベースファイルのバイトオーダをローカルシステム用に変換する。
- vlmgr version
- QDBMのバージョン情報を標準出力する。
各オプションは以下の機能を持つ。
- -l : キーに一致する全てのレコードを処理対象とする。
- -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
- -ki : 10進数による数値表現として `key' を扱う。
- -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
- -vi : 10進数による数値表現として `val' を扱う。
- -vf : 名前が `val' のファイルのデータを値として読み込む。
- -keep : 既存のレコードとキーが重複時に上書きせずにエラーにする。
- -dup : レコードのキーが重複するのを許す。
- -top key : リストの最小のキーを指定する。
- -bot key : リストの最大のキーを指定する。
- -gt : 最小のキーをリストに含めない。
- -lt : 最大のキーをリストに含めない。
- -max num : リストする最大数を指定する。
- -desc : リストを降順で行う。
- -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
- -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
- -be : データベースファイルをビッグエンディアン用に変換する。
- -le : データベースファイルをリトルエンディアン用に変換する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `vltest' はVillaの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `vlmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`pnum' はキーのパターン数を指定する。
- vltest write [-tune lrecmax nidmax lcnum ncnum] name rnum
- `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
- vltest read name
- 上記で生成したデータベースの全レコードを検索する。
- vltest rdup [-tune lrecmax nidmax lcnum ncnum] name rnum pnum
- キーがある程度重複するようにレコードの追加を行い、重複モードで処理する。
- vltest combo name
- 各種操作の組み合わせテストを行う。
- vltest wicked name rnum
- 各種更新操作を無作為に選択して実行する。
各オプションは以下の機能を持つ。
- -int : `int' 型のオブジェクトをキーと値に用い、比較関数もそれに合わせる。
- -tune lrecmax nidmax lcnum ncnum : 性能パラメータを指定する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `vltsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとVillaのデータベースを相互変換する。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。`import' サブコマンドではTSVのデータが標準出力に書き出される。
- vltsv import name
- TSVファイルを読み込んでデータベースを作成する。
- vltsv export name
- データベースの全てのレコードをTSVファイルとして出力する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
Villaのコマンド群を駆使すると、簡単なデータベースシステムが構築できる。例えば `/etc/password' をユーザ名で検索するためのデータベースを作成するには、以下のようにする。
cat /etc/passwd | tr ':' '\t' | vltsv import casket
そして、`mikio' というユーザの情報を取り出すには、以下のようにする。
vlmgr get casket mikio
これらのコマンドと同等の機能をVillaのAPIを用いて実装することも容易である。
概要
Odeumは転置インデックスを扱うAPIである。転置インデックスとは、母集団の文書群に含まれる語を抽出して、各語をキーとし、その語を含む文書のリストを検索するためのデータ構造である。転置インデックスを用いると、全文検索システムを容易に実現することができる。Odeumは文書を語や属性の集合として扱うための抽象データ型を提供する。それは、各アプリケーションがOdeumのデータベースに文書を格納する際や、データベースから文書を検索する際に用いられる。
Odeumは元来の文書データからテキストを抽出する方法は提供しない。それはアプリケーションが実装する必要がある。Odeumはテキストを分解して語群を抽出するユーティリティを提供するが、それは空白で語が分割される英語などの言語を指向している。形態素解析やN-gram解析が必要な日本語などの言語を扱う際や、ステミングなどのより高度な自然言語処理を行う際には、アプリケーションは独自の解析方法を適用することができる。検索結果は文書のID番号とそのスコアをメンバに持つ構造体を要素とする配列として得られる。複数の語を用いて検索を行うために、Odeumは結果の配列の集合演算を行うユーティリティを提供する。
OdeumはCuriaとCabinとVillaを基盤として実装される。Odeumではディレクトリ名を指定してデータベースを構築する。特定のディレクトリの直下にCuriaやVillaのデータベースを構築する。例えば、`casket' という名前のデータベースを作成する場合、`casket/docs'、`casket/index' および `casket/rdocs' が生成される。`docs' はCuriaのデータベースディレクトリである。キーは文書のID番号であり、値はURI等の文書属性である。`index' はCuriaのデータベースディレクトリである。キーは語の正規形であり、値はその語を含む文書のID番号とそのスコアを要素とする配列である。`rdocs' はVillaのデータベースファイルである。キーは文書のURIであり、値は文書のID番号である。
Odeumを使うためには、`depot.h' と `cabin.h' と `odeum.h' と `stdlib.h' をインクルードすべきである。通常、ソースファイルの冒頭付近で以下の記述を行う。
- #include <depot.h>
- #include <cabin.h>
- #include <odeum.h>
- #include <stdlib.h>
Odeumでデータベースを扱う際には、`ODEUM' 型へのポインタをハンドルとして用いる。ハンドルは、関数 `odopen' で開き、関数 `odclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。データベースに致命的なエラーが起きた場合は、以後そのハンドルに対する `odclose' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。
各文書を扱う際には、`ODDOC' 型へのポインタをハンドルとして用いる。ハンドルは、関数 `oddocopen' で開き、関数 `oddocclose' で閉じる。ハンドルのメンバを直接参照することは推奨されない。文書は属性と語の集合からなる。語は正規形と出現形のペアとして表現される。
OdeumでもDepotと同じく外部変数 `dpecode' に直前のエラーコードが記録される。エラーコードに対応するメッセージ文字列を得るには、関数 `dperrmsg' を用いる。
API
検索結果を扱うためには、`ODPAIR' 型の構造体を用いる。
- typedef struct { int id; int score; } ODPAIR;
- `id' は文書のID番号である。`score' は文書に含まれる検索語の数を元に算出されるスコアである。
データベースのハンドルを作成するには、関数 `odopen' を用いる。
- ODEUM *odopen(const char *name, int omode);
- `name' はデータベースディレクトリの名前を指定する。`omode' は接続モードを指定し、`OD_OREADER' ならリーダ、`OD_OWRITER' ならライタとなる。`OD_OWRITER' の場合、`OD_OCREAT' または `OD_OTRUNC' とのビット論理和にすることができる。`OD_OCREAT' はファイルが無い場合に新規作成することを指示し、`OD_OTRUNC' はファイルが存在しても作り直すことを指示する。`OD_OREADER' と `OD_OWRITER' の両方で `OD_ONOLCK' とのビット論理和にすることができるが、それはファイルロックをかけずにデータベースを開くことを指示する。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。`OD_ONOLCK' を使う場合、アプリケーションが排他制御の責任を負う。
データベースとの接続を閉じてハンドルを破棄するには、関数 `odclose' を用いる。
- int odclose(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。データベースの更新内容は、接続を閉じた時点で初めてファイルと同期される。ライタでデータベースを開いた場合、適切に接続を閉じないとデータベースが破壊される。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
文書を追加するには、関数 `odput' を用いる。
- int odput(ODEUM *odeum, const ODDOC *doc, int wmax, int over);
- `odeum' はライタで接続したデータベースハンドルを指定する。`doc' は文書ハンドルを指定する。`wmax' は文書データベースに格納する語の最大数を指定するが、負数なら無制限となる。`over' は重複した文書の上書きを行うか否かを指定する。それが偽で文書のURIが重複した場合はエラーとなる。戻り値は正常なら真であり、エラーなら偽である。
URIで指定した文書を削除するには、関数 `odout' を用いる。
- int odout(ODEUM *odeum, const char *uri);
- `odeum' はライタで接続したデータベースハンドルを指定する。`uri' は文書のURIの文字列を指定する。戻り値は正常なら真であり、エラーなら偽である。該当する文書がない場合も偽を返す。
ID番号で指定した文書を削除するには、関数 `odoutbyid' を用いる。
- int odoutbyid(ODEUM *odeum, int id);
- `odeum' はライタで接続したデータベースハンドルを指定する。`id' は文書のID番号を指定する。戻り値は正常なら真であり、エラーなら偽である。該当する文書がない場合も偽を返す。
URIで指定した文書を取得するには、関数 `odget' を用いる。
- ODDOC *odget(ODEUM *odeum, const char *uri);
- `odeum' はデータベースハンドルを指定する。`uri' は文書のURIの文字列を指定する。戻り値は正常なら該当の文書のハンドルであり、エラーなら `NULL' である。該当の文書がない場合も `NULL' を返す。戻り値のハンドルは、関数 `oddocopen' で開かれるので、不要になったら `oddocclose' で閉じるべきである。
ID番号で指定した文書を取得するには、関数 `odget' を用いる。
- ODDOC *odgetbyid(ODEUM *odeum, int id);
- `odeum' はデータベースハンドルを指定する。`id' は文書のID番号を指定する。戻り値は正常なら該当の文書のハンドルであり、エラーなら `NULL' である。該当の文書がない場合も `NULL' を返す。戻り値のハンドルは、関数 `oddocopen' で開かれるので、不要になったら `oddocclose' で閉じるべきである。
転置インデックスを検索して特定の語を含む文書群を知るには、関数 `odsearch' を用いる。
- ODPAIR *odsearch(ODEUM *odeum, const char *word, int max, int *np);
- `odeum' はデータベースハンドルを指定する。`word' は検索語を指定する。`max' は取り出す文書の最大数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は正常なら配列へのポインタであり、エラーなら `NULL' である。その配列の各要素は文書のID番号とスコアのペアであり、スコアの降順で並べられる。検索語に該当する文書が一つもなかったとしてもエラーにはならずに、空の配列を返す。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。配列の各要素には既に削除された文書のデータも含まれることに注意すべきである。
特定の語を含む文書の数を知るには、関数 `odsearchdnum' を用いる。
- int odsearchdnum(ODEUM *odeum, const char *word);
- `odeum' はデータベースハンドルを指定する。`word' は検索語を指定する。戻り値は正常なら検索語を含む文書の数であり、該当がなかったり、エラーの場合は -1 である。この関数は転置インデックスの実データを読み込まないので効率がよい。
データベースのイテレータを初期化するには、関数 `oditerinit' を用いる。
- int oditerinit(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。イテレータは、データベースに格納された全ての文書を参照するために用いられる。
データベースのイテレータから次の文書を取り出すには、関数 `oditernext' を用いる。
- ODDOC *oditernext(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値は正常なら文書ハンドルであり、エラーなら `NULL' である。イテレータが最後まできて該当の文書がない場合も `NULL' を返す。この関数を繰り返して呼ぶことによって全ての文書を一度ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。戻り値のハンドルは、関数 `oddocopen' で開かれるので、不要になったら `oddocclose' で閉じるべきである。
データベースを更新した内容をファイルとデバイスに同期させるには、関数 `odsync' を用いる。
- int odsync(ODEUM *odeum);
- `odeum' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。この関数はデータベースを閉じないうちに別プロセスにデータベースディレクトリを利用させる場合に役立つ。
データベースを最適化するには、関数 `odoptimize' を用いる。
- int odoptimize(ODEUM *odeum);
- `odeum' はライタで接続したデータベースハンドルを指定する。戻り値は正常なら真であり、エラーなら偽である。転置インデックスにおける削除された文書の要素は削除される。
データベースの名前を得るには、関数 `odname' を用いる。
- char *odname(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値は正常なら名前を格納した領域へのポインタであり、エラーなら `NULL' である。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
データベースファイルのサイズの合計を得るには、関数 `odfsiz' を用いる。
- int odfsiz(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値は正常ならデータベースファイルのサイズの合計であり、エラーなら -1 である。
転置インデックスで使われるバケット配列の要素数の合計を得るには、関数 `odbnum' を用いる。
- int odbnum(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値は正常ならバケット配列の要素数の合計であり、エラーなら -1 である。
データベースに格納された文書数を得るには、関数 `oddnum' を用いる。
- int oddnum(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値は正常ならデータベースに格納された文書の数であり、エラーなら -1 である。
データベースに格納された単語数を得るには、関数 `odwnum' を用いる。
- int odwnum(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値は正常ならデータベースに格納された語の数であり、エラーなら -1 である。
データベースハンドルがライタかどうかを調べるには、関数 `odwritable' を用いる。
- int odwritable(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値はライタなら真であり、そうでなければ偽である。
データベースに致命的エラーが起きたかどうかを調べるには、関数 `odfatalerror' を用いる。
- int odfatalerror(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値は致命的エラーがあれば真であり、そうでなければ偽である。
データベースディレクトリのinode番号を得るには、関数 `odinode' を用いる。
- int odinode(ODEUM *odeum);
- `odeum' はデータベースハンドルを指定する。戻り値はデータベースディレクトリのinode番号である。
データベースディレクトリを削除するには、関数 `odremove' を用いる。
- int odremove(const char *name);
- `name' はデータベースディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
文書ハンドルを作成するには、関数 `oddocopen' を用いる。
- ODDOC *oddocopen(const char *uri);
- `uri' は文書のURIを指定する。戻り値は文書ハンドルである。新しい文書のID番号は定義されない。それはデータベースに格納した際に定義される。
文書ハンドルを破棄するには、関数 `oddocclose' を用いる。
- void oddocclose(ODDOC *doc);
- `doc' は文書ハンドルを指定する。閉じたハンドルの領域は解放されるので、以後は利用できなくなる。
文書に属性を追加するには、関数 `oddocaddattr' を用いる。
- void oddocaddattr(ODDOC *doc, const char *name, const char *value);
- `doc' は文書ハンドルを指定する。`name' は属性名の文字列を指定する。`value' は属性値の文字列を指定する。
文書に語を追加するには、関数 `oddocaddword' を用いる。
- void oddocaddword(ODDOC *doc, const char *normal, const char *asis);
- `doc' は文書ハンドルを指定する。`normal' は語の正規形の文字列を指定する。正規形は転置インデックスのキーとして扱われる。正規形が空文字列の場合、その語は転置インデックスに反映されない。`asis' は語の出現形の文字列を指定する。出現形はアプリケーションが文書を取得した際に利用される。
文書のIDを得るには、関数 `oddocid' を用いる。
- int oddocid(const ODDOC *doc);
- `doc' は文書ハンドルを指定する。戻り値は文書のID番号である。
文書のURIを得るには、関数 `oddocuri' を用いる。
- const char *oddocuri(const ODDOC *doc);
- `doc' は文書ハンドルを指定する。戻り値は文書のURIの文字列である。
文書の属性値を得るには、関数 `oddocgetattr' を用いる。
- const char *oddocgetattr(const ODDOC *doc, const char *name);
- `doc' は文書ハンドルを指定する。`name' は属性名の文字列を指定する。戻り値は属性値の文字列であるか、該当がなければ `NULL' である。
文書内の語群の正規形のリストを得るには、関数 `oddocnwords' を用いる。
- const CBLIST *oddocnwords(const ODDOC *doc);
- `doc' は文書ハンドルを指定する。戻り値は正規形の語群を格納したリストハンドルである。
文書内の語群の出現形のリストを得るには、関数 `oddocawords' を用いる。
- const CBLIST *oddocawords(const ODDOC *doc);
- `doc' は文書ハンドルを指定する。戻り値は出現形の語群を格納したリストハンドルである。
文書のキーワードを出現形で得るには、関数 `oddockwords' を用いる。
- CBLIST *oddockwords(const ODDOC *doc, int max, ODEUM *odeum);
- `doc' は文書ハンドルを指定する。`max' は取得するキーワードの最大数を指定する。`odeum' が `NULL' でなければ、それを用いて重みづけのためのIDFが算出される。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
テキストを分解して語群の出現形のリストを得るには、関数 `odbreaktext' を用いる。
- CBLIST *odbreaktext(const char *text);
- `text' はテキストの文字列を指定する。戻り値は語群の出現形のリストハンドルである。語群は空白文字とピリオドやコンマ等の区切り文字で分割される。戻り値のハンドルは関数 `cblistopen' で開かれるので、不要になったら `cblistclose' で閉じるべきである。
語の正規形を取得するには、関数 `odnormalizeword' を用いる。
- char *odnormalizeword(const char *asis);
- `asis' は語の出現形の文字列を指定する。戻り値は語の正規形の文字列である。ASCIIコードのアルファベットは小文字に統一される。区切り文字のみからなる文字は空文字列として扱われる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
二つの文書集合からその共通集合を得るには、関数 `odpairsand' を用いる。
- ODPAIR *odpairsand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
- `apairs' は前者の文書集合の配列を指定する。`anum' は前者の配列の要素数を指定する。`apairs' は後者の文書集合の配列を指定する。`anum' は後者の配列の要素数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は新しい文書集合の配列へのポインタであり、各要素は二つの集合に共通して属するものである。各要素はスコアの降順で並べられる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
二つの文書集合からその和集合を得るには、関数 `odpairsor' を用いる。
- ODPAIR *odpairsor(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
- `apairs' は前者の文書集合の配列を指定する。`anum' は前者の配列の要素数を指定する。`apairs' は後者の文書集合の配列を指定する。`anum' は後者の配列の要素数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は新しい文書集合の配列へのポインタであり、各要素は二つの集合の両方あるいはどちらか一方に属するものである。各要素はスコアの降順で並べられる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
二つの文書集合からその差集合を得るには、関数 `odpairsnotand' を用いる。
- ODPAIR *odpairsnotand(ODPAIR *apairs, int anum, ODPAIR *bpairs, int bnum, int *np);
- `apairs' は前者の文書集合の配列を指定する。`anum' は前者の配列の要素数を指定する。`apairs' は後者の文書集合の配列を指定する。`anum' は後者の配列の要素数を指定する。`np' の参照先には、戻り値の配列の要素数が格納される。戻り値は新しい文書集合の配列へのポインタであり、各要素は前者の集合には属するが後者の集合には属さないものである。各要素はスコアの降順で並べられる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。
ある数の自然対数を得るには、関数 `odlogarithm' を用いる。
- double odlogarithm(double x);
- `x' はある数を指定する。戻り値はその数の自然対数である。もしその数が 1.0 以下であれば、戻り値は 0.0 となる。この関数はアプリケーションが検索結果のIDFを算出する際に便利である。
サンプルコード
文書をデータベースに格納するサンプルコードを以下に示す。
#include <depot.h>
#include <cabin.h>
#include <odeum.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define DBNAME "index"
int main(int argc, char **argv){
ODEUM *odeum;
ODDOC *doc;
CBLIST *awords;
const char *asis;
char *normal;
int i;
/* データベースを開く */
if(!(odeum = odopen(DBNAME, OD_OWRITER | OD_OCREAT))){
fprintf(stderr, "odopen: %s\n", dperrmsg(dpecode));
return 1;
}
/* 文書ハンドルを取得する */
doc = oddocopen("http://www.foo.bar/baz.txt");
/* 文書の属性を設定する */
oddocaddattr(doc, "title", "Balcony Scene");
oddocaddattr(doc, "author", "Shakespeare");
/* テキストを分解して語のリストを得る */
awords = odbreaktext("Parting is such sweet sorrow.");
/* 各語を文書ハンドルに設定する */
for(i = 0; i < cblistnum(awords); i++){
/* 語のリストから一語を取り出す */
asis = cblistval(awords, i, NULL);
/* 出現形から正規形を生成する */
normal = odnormalizeword(asis);
/* 語を文書ハンドルに設定する */
oddocaddword(doc, normal, asis);
/* 正規形の領域を解放する */
free(normal);
}
/* 文書をデータベースに登録する */
if(!odput(odeum, doc, -1, 1)){
fprintf(stderr, "odput: %s\n", dperrmsg(dpecode));
}
/* 語のリストを解放する */
cblistclose(awords);
/* 文書ハンドルを解放する */
oddocclose(doc);
/* データベースを閉じる */
if(!odclose(odeum)){
fprintf(stderr, "odclose: %s\n", dperrmsg(dpecode));
return 1;
}
return 0;
}
データベース内の文書を検索するサンプルコードを以下に示す。
#include <depot.h>
#include <cabin.h>
#include <odeum.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define DBNAME "index"
int main(int argc, char **argv){
ODEUM *odeum;
ODPAIR *pairs;
ODDOC *doc;
const CBLIST *words;
const char *title, *author, *asis;
int i, j, pnum;
/* データベースを取得する */
if(!(odeum = odopen(DBNAME, OD_OREADER))){
fprintf(stderr, "odopen: %s\n", dperrmsg(dpecode));
return 1;
}
/* 文書の検索を行う */
if((pairs = odsearch(odeum, "sorrow", -1, &pnum)) != NULL){
/* 文書の配列を走査する */
for(i = 0; i < pnum; i++){
/* 文書ハンドルを取得する */
if(!(doc = odgetbyid(odeum, pairs[i].id))) continue;
/* 文書の属性を表示する */
printf("URI: %s\n", oddocuri(doc));
title = oddocgetattr(doc, "title");
if(title) printf("TITLE: %s\n", title);
author = oddocgetattr(doc, "author");
if(author) printf("AUTHOR: %s\n", author);
/* 文書内の語を出現形で表示する */
printf("WORDS:");
words = oddocawords(doc);
for(j = 0; j < cblistnum(words); j++){
asis = cblistval(words, j, NULL);
printf(" %s", asis);
}
putchar('\n');
/* 文書ハンドルを解放する */
oddocclose(doc);
}
/* 文書の配列を解放する */
free(pairs);
} else {
fprintf(stderr, "odsearch: %s\n", dperrmsg(dpecode));
}
/* データベースを閉じる */
if(!odclose(odeum)){
fprintf(stderr, "odclose: %s\n", dperrmsg(dpecode));
return 1;
}
return 0;
}
注記
Odeumを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。
gcc -I/usr/local/include -o sample sample.c -L/usr/local/lib -lqdbm
Odeumの各関数はリエントラントではないが、関数内で静的な参照を保持するものではない。したがって、全ての呼び出しと外部変数 `dpecode' の参照を排他制御することでスレッドセーフな関数として扱うことができる。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることがその前提となる。
Odeumに対応するコマンドラインインタフェースは以下のものである。
コマンド `odmgr' はOdeumやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトで全文検索システムを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`file' はファイル名、`expr' は文書のURIかID番号、`words' は検索語を指定する。
- odmgr create name
- データベースファイルを作成する。
- odmgr put [-uri str] [-title str] [-author str] [-date str] [-wmax num] [-keep] name [file]
- ファイルを読み込んで文書を追加する。`file' を省略すると標準入力を読み込むが、その場合はURIの指定が必須となる。
- odmgr out [-id] name expr
- URIに対応する文書を削除する。
- odmgr get [-id] [-t|-h] name expr
- URIに対応する文書を表示する。出力は文書のID番号とURIとスコアをタブで区切ったものである。
- odmgr search [-max num] [-or] [-idf] [-t|-h|-n] name words...
- 指定した語を含む文書を検索する。出力の第1行は、検索語全体の該当数と各検索語およびその該当数をタブで区切ったものである。第2行以降は、該当の各文書のID番号とURIとスコアをタブで区切ったものである。
- odmgr list [-t|-h] name
- データベース内の全ての文書を表示する。出力の各行は文書のID番号とURIとスコアをタブで区切ったものである。
- odmgr optimize name
- データベースを最適化する。
- odmgr inform name
- データベースの雑多な情報を出力する。
- odmgr remove name
- データベースディレクトリを削除する。
- odmgr wbrk [-h|-k|-s] [file]
- ファイルを読み込んで、テキストを語に分解して出力する。出力の各行は各語の正規形と出現形をタブで区切ったものである。
- odmgr version
- QDBMのバージョン情報を出力する。
各オプションは以下の機能を持つ。
- -uri str : 文書のURIを明示的に指定する。
- -title str : 文書のタイトルを指定する。
- -author str : 文書の著者名を指定する。
- -date str : 文書の更新日時を指定する。
- -wmax num : 格納する語の最大数を指定する。
- -keep : 同じURIの文書が既存であれば上書きを行わない。
- -id : URIでなくID番号で文書を指定する。
- -t : 文書の詳細情報をタブ区切りで出力する。
- -h : 文書の詳細情報を人間が読みやすい形式で出力する。
- -k : 文書のキーワードのみを出力する。
- -s : 文書の要約のみを出力する。
- -max num : 出力する文書の最大数を指定する。
- -or : AND検索でなくOR検索を行う。
- -idf : IDFでスコアを重みづけする。
- -n : 文書のIDとスコアのみを表示する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `odtest' はOdeumの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースディレクトリを `odmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`name' はデータベース名、`dnum' は文書数、`wnum' は文書毎の語数、`pnum' は語のパターン数を指定する。
- odtest write name dnum wnum pnum
- 無作為な属性と語を持つ文書を連続してデータベースに追加する。
- odtest read name
- 上記で生成したデータベースの全文書を検索する。
- odtest combo name
- 各種操作の組み合わせテストを行う。
- odtest wicked name dnum
- 各種更新操作を無作為に選択して実行する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
コマンド `odidx' はローカルファイルシステム上のファイルを読み込んでOdeumのデータベースに登録するユーティリティである。このコマンドはWebサイトの全文検索システムを構築する際に役立つ。サポートされるファイルフォーマットはプレーンテキストとHTMLである。サポートされる文字コードはUS-ASCIIとISO-8859-1である。各文書のURIにはファイルのパスが指定される。各文書には、`title' と `date' という属性が付与される。既にデータベース登録してあるファイルを登録しようとした場合、更新時刻が新しければ登録され、そうでなければ無視される。以下の書式で用いる。`name' はデータベース名、`dir' はディレクトリ名を指定する。
- odidx register [-l file] [-wmax num] [-tsuf sufs] [-hsuf sufs] [-ft] name [dir]
- 特定のディレクトリ以下のファイル群をデータベース登録する。`dir' が省略された場合、カレントディレクトリが指定される。
- odidx purge name
- ファイルシステムに存在しない文書をデータベースから削除する。
各オプションは以下の機能を持つ。
- -l file : 登録すべきファイルのパスのリストをファイルから読み込む。`-' を指定した場合、標準入力が読み込まれる。
- -wmax num : データベースに格納する語の最大数を指定する。
- -tsuf sufs : プレーンテキストファイルの拡張子をカンマ区切りで指定する。デフォルトは `-tsuf .txt,.text' と同意である。
- -hsuf sufs : HTMLファイルの拡張子をカンマ区切りで指定する。デフォルトは `-hsuf .html,.htm' と同意である。
- -ft : 文書内でタイトルが未定義の場合、ファイル名をタイトルにする。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
Odeumのコマンド群を駆使すると、全文検索システムを簡単に実現することができる。例えば `/home/mikio' 以下にあり、かつ `.txt' か `.c' か `.h' という接尾辞を持つファイル群を `casket' という名前のインデックスに登録するなら、以下のようにする。
odidx register -tsuf ".txt,.c,.h" -hsuf "" casket /home/mikio
そして、`unix' および `posix' という語を含む文書を検索し、上位8件を表示するには、以下のようにする。
odmgr search -max 8 -h casket "unix posix"
`odmgr' と同じような検索機能をCGIスクリプトとして実装すれば、Webサイトを対象とした全文検索システムを構築することも容易である。
Depotのファイルフォーマット
Depotが管理するデータベースファイルの内容は、ヘッダ部、バケット部、レコード部の三つに大別される。
ヘッダ部はファイルの先頭から 48 バイトの固定長でとられ、以下の情報が記録される。
- 識別のためのマジックナンバ : オフセット 0 から始まる。ビッグエンディアン用なら文字列 "[DEPOT]\n\f" を内容とし、リトルエンディアン用なら文字列 "[depot]\n\f" を内容とする。
- 検証のためのファイルサイズ : オフセット 16 から始まる。`int' 型の整数である。
- バケット配列の要素数 : オフセット 24 から始まる。`int' 型の整数である。
- 格納しているレコードの数 : オフセット 32 から始まる。`int' 型の整数である。
バケット部はヘッダ部の直後にバケット配列の要素数に応じた大きさでとられ、チェーンの先頭要素のオフセットが各要素に記録される。
レコード部はバケット部の直後からファイルの末尾までを占め、各レコードの以下の情報を持つ要素が記録される。
- フラグ : `int' 型の整数である。
- キーの第二ハッシュ値 : `int' 型の整数である。
- キーのサイズ : `int' 型の整数である。
- 値のサイズ : `int' 型の整数である。
- パディングのサイズ : `int' 型の整数である。
- 左の子の位置 : `int' 型の整数である。
- 右の子の位置 : `int' 型の整数である。
- キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。
- 値の実データ : 値のサイズで定義される長さを持つ一連のバイトである。
- パディング : 値のサイズとアラインメントにより算出される長さを持つ一連のバイトである。
Villaのファイルフォーマット
Villaの扱う全てのデータはDepotのデータベースに記録される。記録されるデータは、メタデータと論理ページに分類される。論理ページはリーフノードと非リーフノードに分類される。メタデータはレコード数等の管理情報を記録するもので、キーと値ともに `int' 型である。リーフノードはレコードを保持する。非リーフノードはページを参照する疎インデックスを保持する。
Villaは、小さい自然数を直列化して扱う際に記憶領域を節約するために、可変長整数フォーマットを用いる。可変長整数のオブジェクトは、領域の先頭から解析し、値が正のバイトを読んだらそこで終端とする。各バイトは絶対値で評価され、リトルエンディアンの128進数として算出される。
レコードはユーザデータの論理的な単位である。キーが重複する論理レコードは物理的には単一のレコードにまとめられる。物理レコードは以下の形式で直列化される。
- キーのサイズ : 可変長整数型である。
- キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。
- 値の数 : 可変長整数型である。
- 値のリスト : 以下の表現を値の数だけ繰り返した一連のバイトである。
- サイズ : 可変長整数型である。
- 実データ : サイズで定義される長さを持つ一連のバイトである。
リーフノードはレコードの集合を格納するための物理的な単位である。リーフノードは `int' 型のIDをキーとし、以下の値を持つレコードとしてDepotのデータベースに格納される。レコードは常にキーの昇順に整列した状態で保持される。
- 前のリーフのID : 可変長整数型である。
- 次のリーフのID : 可変長整数型である。
- レコードのリスト : 直列化したレコードを連結したもの。
インデックスはページを探索するためのポインタの論理的な単位である。インデックスは以下の形式で直列化される。
- 参照先のページのID : 可変長整数型である。
- キーのサイズ : 可変長整数型である。
- キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。
非リーフノードはインデックスの集合を格納するための物理的な単位である。非リーフノードは `int' 型のIDをキーとし、以下の値を持つレコードとしてDepotのデータベースに格納される。インデックスは常にキーの昇順に整列した状態で保持される。
- 最初の子ノードのID : 可変長整数型である。
- インデックスのリスト : 直列化したインデックスを連結したもの。
注記
データベースファイルはスパースではないので、通常のファイルと同様に複製等の操作を行うことができる。Depotはバイトオーダの調整をしないでファイルの読み書きを行っているので、バイトオーダの異なる環境にデータベースファイルを移設してもそのままでは利用できない。
DepotやVillaのデータベースファイルをネットワークで配布する際には、MIMEタイプを `application/x-qdbm' にしてほしい。ファイル名の接尾辞は `.qdb' にしてほしい。Curiaのデータベースディレクトリをネットワークで配布する際には、TAR形式等を用いたアーカイブに変換して行うことができる。
データベースファイルのマジックナンバを `file' コマンドに識別させたい場合は、`magic' ファイルに以下の行を追記するとよい。
0 string [DEPOT]\n\f database file of QDBM, big endian
>16 belong x \b, filesize: %d
>24 belong x \b, buckets: %d
>32 belong x \b, records: %d
0 string [depot]\n\f database file of QDBM, little endian
>16 lelong x \b, filesize: %d
>24 lelong x \b, buckets: %d
>32 lelong x \b, records: %d
QDBMはPOSIX互換の全てのプラットフォームで動作することを目標としている。ただし、いくつかのAPIが実装されていないプラットフォームでも動作することが望ましい。また、GCC以外のコンパイラを利用してもビルドができることが望ましい。様々なプラットフォームへの移植作業は、新しい `Makefile' を追加したりソースファイルの一部を修正することによってなされる。C言語のAPIであれば、おそらく以下のファイルのいくつかを修正することになる。もしくはそれらを基に新しいファイルを作ってもよい。
- Makefile.in : `./configure' に利用され、`Makefile' のベースとなる。
- myconf.h : システム依存の設定ファイル。
- depot.h : 基本APIのヘッダ。
- curia.h : 拡張APIのヘッダ。
- relic.h : NDBM互換APIのヘッダ。
- hovel.h : GDBM互換APIのヘッダ。
- cabin.h : ユーティリティAPIのヘッダ。
- villa.h : 上級APIのヘッダ。
- vista.h : 拡張上級APIのヘッダ。
- odeum.h : 転置APIのヘッダ。
- myconf.c : システム依存の実装。
- depot.c : 基本APIの実装。
- curia.c : 拡張APIの実装。
- relic.c : NDBM互換APIの実装。
- hovel.c : GDBM互換APIの実装。
- cabin.c : ユーティリティAPIの実装。
- villa.c : 上級APIの実装。
- odeum.c : 転置APIの実装。
`fcntl' コールによるファイルロックがサポートされていないプラットフォームでは、`Makefile' で定義される `CFLAGS' マクロに `-DMYNOLOCK' を追加するとよい。その際にはプロセス間の排他制御を行う別の方法を考える必要がある。同様に、`mmap' コールがないプラットフォームでは、`CFLAGS' に `-DMYNOMMAP' を追加するとよい。`mmap' に関しては `malloc' 等を用いたエミュレーションが用意されている。その他のシステムコールが実装されていない場合は、`myconf.h' と `myconf.c' を修正して該当のシステムコールのエミュレーションを行えばよい。
C++用のAPIではPOSIXスレッドを使っているので、そのパッケージが実装されていない環境にはC++用APIは移植できない。Java用のAPIではJNIを使っているので、そのヘッダやライブラリの場所に注意すべきである。また、`long long' や `int64' といった型定義にも注意すべきである。PerlやRuby用のAPIでは各々の言語処理系で用意されたビルドコマンドを用いているので、その仕様に精通すべきである。
QDBMの各文書は英語を母国語とする人達によって校正されるべきである。
segmentation faultによるクラッシュ、予期せぬデータの消失等の不整合、メモリリーク、その他諸々のバグに関して、既知のもので未修正のものはない。
バグを発見したら、是非とも作者にフィードバックしてほしい。その際、QDBMのバージョンと、利用環境のOSとコンパイラのバージョンも教えてほしい。
- Q. : QDBMはSQLをサポートするか。
- A. : QDBMはSQLをサポートしない。QDBMはRDBMS(関係データベース管理システム)ではない。組み込みのRDBMSを求めるなら、SQLiteなどを利用するとよい。
- Q. : 結局のところ、GDBM(NDBM、SDBM、Berkeley DB)とどう違うのか。
- A. : 処理が速い。データベースファイルが小さい。APIが簡潔である。特筆すべきは、レコードの上書きを繰り返す場合の時間的および空間的効率がとてもよく、実用上のスケーラビリティが高いことである。また、レコード数が100万を越えるような大規模なデータベースを構築する際にも、処理が極端に遅くなったり、ファイルのサイズが極端に大きくなったりしない。とはいえ、用途によっては他のDBMやDBMSを使う方が適切かもしれないので、各自で性能や機能の比較をしてみてほしい。
- Q. : 参考文献は何か。
- A. : QDBMの各種アルゴリズムは、主にAho他の `Data Structures and Algorithms'(邦訳は「データ構造とアルゴリズム」)およびSedgewickの `Algorithms in C'(邦訳は「アルゴリズムC」)の記述に基礎を置いている。
- Q. : どのAPIを使えばよいのか。
- A. : レコードの検索が完全一致だけで済むのなら、Depotを試すとよい。その規模が大きいなら、Curiaを試すとよい。レコードを順序に基づいて参照したいなら、Villaを試すとよい。その規模が大きいなら、Vistaを試すとよい。最大のロバストネスを求めるなら、ZLIBを有効にしてQDBMをビルドし、その上でVistaを用いるのがよい。
- Q. : アプリケーションの良いサンプルコードはあるか。
- A. : 各APIのコマンドのソースコードを参考にしてほしい。`dptsv.c' と `crtsv.c' と `vltsv.c' が最も簡潔である。
- Q. : DepotとCuriaのアラインメントの使い方がよくわからないが。
- A. : 上書きモードや連結モードでの書き込みを繰り返す場合に、アラインメントはデータベースファイルのサイズが急激に大きくなるのを防ぐ。アラインメントの適切なサイズはアプリケーションによって異なるので、各自で実験してみてほしい。さしあたりは32くらいにしておくとよい。
- Q. : Villaの性能パラメータの調整がよくわからないが。
- A. : レコードを順番に参照することが多いならば、`lrecmax' と `nidxmax' をより大きくした方がよい。レコードを無作為に参照することが多いならば、それらは小さくした方がよい。RAMに余裕があるならば、`lcnum' と `ncnum' を増やすと性能がかなり向上する。ZLIBを有効化した場合、`lrecmax' を大きくした方が圧縮効率がよくなる。
- Q. : 性能を引き出すシステムの設定はどうであるか。
- A. : データベースのサイズと同等以上のRAMをマシンに搭載することが望ましい。そして、I/Oバッファのサイズを大きくし、ダーティバッファをフラッシュする頻度が少なくするように設定するとよい。ファイルシステムの選択も重要である。Linux上では、通常はEXT2が最高速であるが、EXT3の `writeback' モードの方が速いこともある。ReiserFSはそれなりである。EXT3のその他のモードはかなり遅い。他のファイルシステムに関しては各自で実験してみてほしい。
- Q. : `gcc' の代わりに `cc' を使ってビルドできるか。
- A. : 実はできる。`make unix' でビルドして、`make install-unix' でインストールするとよい。
- Q. : 「QDBM」とはどういう意味なのか。
- A. : 「QDBM」は「Quick Database Manager」の略である。高速に動作するという意味と、アプリケーションの開発が迅速にできるという意味が込められている。
- Q. : 各APIの名前はどういう意味なのか。どう発音するのか。
- A. : 5文字の英単語から適当に選択しただけで、深い意味はない。なお、「depot」は、空港、倉庫、補給所など、物質が集まる場所を意味するらしい。発音を片仮名で表現するなら「ディーポゥ」が妥当だろう。「curia」は、宮廷、法廷など、権威が集まる場所を意味するらしい。発音を片仮名で表現するなら「キュリア」が妥当だろう。「relic」は、遺物、遺跡など、過去の残骸を意味するらしい。発音を片仮名で表現するなら「レリック」が妥当だろう。「hovel」は、小屋、物置、離れ家など、粗末な建物を意味するらしい。発音を片仮名で表現するなら「ハヴル」が妥当だろう。「cabin」は、機室、客室、小屋など、簡易的な居住空間を意味するらしい。発音を片仮名で表現するなら「キャビン」が妥当だろう。「villa」は、別荘、郊外住宅など、普段目にしない住居を意味するらしい。発音を片仮名で表現するなら「ヴィラ」が妥当だろう。「odeum」は、音楽堂、劇場など、音楽や詩吟を行う建物を意味するらしい。発音を片仮名で表現するなら「オディアム」が妥当だろう。
QDBMはフリーソフトウェアである。あなたは、Free Software Foundationが公表したGNU Lesser General Public Licenseのバージョン2.1あるいはそれ以降の各バージョンの中からいずれかを選択し、そのバージョンが定める条項に従ってQDBMを再頒布または変更することができる。
QDBMは有用であると思われるが、頒布にあたっては、市場性及び特定目的適合性についての暗黙の保証を含めて、いかなる保証も行なわない。詳細についてはGNU Lesser General Public Licenseを読んでほしい。
あなたは、QDBMと一緒にGNU Lesser General Public Licenseの写しを受け取っているはずである(`COPYING' ファイルを参照)。そうでない場合は、Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA へ連絡してほしい。
QDBMは平林幹雄が作成した。作者と連絡をとるには、`mikio@users.sourceforge.net' 宛に電子メールを送ってほしい。感想や改善案やバグレポートなどを寄せると作者は喜ぶ。