QDBMバージョン1基本仕様書
Copyright (C) 2000-2007 Mikio Hirabayashi
Last Update: Thu, 26 Oct 2006 15:00:20 +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がより単純であること。これらの目標は達成されている。また、GDBMと同様に、伝統的な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かLZOかBZIP2を有効化してQDBMをビルドすると、B+木の各ページの内容は圧縮されてファイルに書き込まれる。同一ページ内の各レコードは似たようなパターンを持つため、Lempel-Zivなどのアルゴリズムを適用すると高い圧縮効率が期待できる。テキストデータを扱う場合、データベースのサイズが元の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を呼び出すものである。データベースの管理とファイルアップロードと全文検索のためのCGIスクリプトも提供される。
幅広い移植性
QDBMはANSI C(C89)の記法に従い、ANSI CまたはPOSIXで定義されたAPIのみを用いて実装される。したがって、ほとんどのUNIXおよびその互換をうたうOSで動作させることができる。C言語のAPIに関しては、少なくとも以下のプラットフォームで動作確認されている。
- Linux (2.2, 2.4, 2.6) (IA32, IA64, AMD64, PA-RISC, Alpha, PowerPC, M68000, ARM)
- FreeBSD (4.9, 5.0, 5.1, 5.2, 5.3) (IA32, IA64, SPARC, Alpha)
- NetBSD (1.6) (IA32)
- OpenBSD (3.4) (IA32)
- SunOS (5.6, 5.7, 5.8, 5.9, 5.10) (IA32, SPARC)
- HP-UX (11.11, 11.23) (IA64, PA-RISC)
- AIX (5.2) (POWER)
- Windows (2000, XP) (IA32, IA64, AMD64) (Cygwin, MinGW, Visual C++)
- Mac OS X (10.2, 10.3, 10.4) (IA32, PowerPC)
- Tru64 (5.1) (Alpha)
- RISC OS (5.03) (ARM)
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/man1' と `/usr/local/man/man3' に、それ以外の文書が `/usr/local/share/qdbm' に、`pkg-config' 用の設定ファイルが `/usr/local/lib/pkgconfig' にインストールされる。
/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.14.13.0
/usr/local/lib/libqdbm.so.14
/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
/usr/local/bin/qmttest
`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' は `/usr/local/bin' にインストールされる。
Cygwin環境でMinGWを用いてビルドするには、`make win' の代わりに `make mingw' を用いる。CygwinのUNIXエミュレーション層を用いる場合、生成されるプログラムは `cygwin1.dll' に依存したものになる(GNU GPLの影響を受ける)。MinGWによってWin32のネイティブDLLとリンクさせればこの問題を回避できる。
Visual C++を用いてビルドするには、`VCmakefile' を編集してヘッダとライブラリの検索パスを設定した上で、`nmake /f VCMakefile' とすればよい。生成された `qdbm.dll' とリンクするアプリケーションは、コンパイラの `/MD' または `/MDd' オプションを用いて `msvcrt.dll' とリンクさせる必要がある。詳細設定に関しては `VCmakefile' を参照のこと。
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-stable : 安定版のリリース用にビルドする。保守的な最適化を行い、動的にリンクする。
- --enable-pthread : POSIXスレッドを用い、グローバル変数をスレッド固有データとして扱う。
- --disable-lock : ファイルロッキングが実装されていない環境用にビルドする。
- --disable-mmap : メモリマッピングが実装されていない環境用にビルドする。
- --enable-zlib : ZLIBによるB+木と転置インデックスのレコード圧縮を機能させる。
- --enable-lzo : LZOによるB+木と転置インデックスのレコード圧縮を機能させる。
- --enable-bzip : BZIP2によるB+木と転置インデックスのレコード圧縮を機能させる。
- --enable-iconv : ICONVによる文字コード変換ユーティリティを機能させる。
通常、QDBMとそのアプリケーションは `libqdbm.*' 以外の非標準のライブラリには依存しないでビルドすることができる。ただし、POSIXスレッドを有効にした場合は `libpthread.*' に依存し、ZLIBを有効にした場合は `libz.*' に依存し、LZOを有効にした場合は `liblzo2.*' に依存し、BZIP2を有効にした場合は `libbz2.*' に依存し、ICONVを有効にした場合は `libiconv.*' に依存するようになる。
LZOのライセンスはGNU GPLなので、`liblzo2.*' とリンクしたアプリケーションはGNU GPLの制約を受けることに注意すること。
概要
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' または `DP_OLCKNB' とのビット論理和にすることができるが、前者はファイルロックをかけずにデータベースを開くことを指示し、後者はブロックせずにロックをかけることを指示する。`DP_OCREAT' は `DP_OSPARSE' とのビット論理和にすることができるが、それは生成されるファイルをスパースにすることを指示する。`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' は指定された値を既存の値の末尾に加える。戻り値は正常なら真であり、エラーなら偽である。
レコードを削除するには、関数 `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' で解放するべきである。
レコードを取得してバッファに書き込むには、関数 `dpgetwb' を用いる。
- int dpgetwb(DEPOT *depot, const char *kbuf, int ksiz, int start, int max, char *vbuf);
- `depot' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定する。それは書き込み用のバッファのサイズ以下である必要がある。`vbuf' は抽出したデータを書き込むバッファへのポインタを指定する。戻り値は正常ならバッファに書き込まれたデータのサイズであり、エラーなら -1 である。該当のレコードがない場合も -1 を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。書き込み用バッファの末尾に終端文字が追加されないことに注意すべきである。
レコードの値のサイズを取得するには、関数 `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))' として算出される。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。
データベースのフリーブロックプールのサイズ設定するには、関数 `dpsetfbpsiz' を用いる。
- int dpsetfbpsiz(DEPOT *depot, int size);
- `depot' はライタで接続したデータベースハンドルを指定する。`size' はフリーブロックプールのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。フリーブロックプールのデフォルトのサイズは16である。サイズをより大きくすると、レコードの上書きを繰り返す際の空間効率は上がるが、時間効率が下がる。
データベースを更新した内容をファイルとデバイスに同期させるには、関数 `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番号である。
データベースの最終更新時刻を得るには、関数 `dpmtime' を用いる。
- time_t dpmtime(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。
データベースファイルのファイルディスクリプタを得るには、関数 `dpfdesc' を用いる。
- int dpfdesc(DEPOT *depot);
- `depot' はデータベースハンドルを指定する。戻り値はデータベースファイルのファイルディスクリプタである。データベースのファイルディスクリプタを直接操ることは推奨されない。
データベースファイルを削除するには、関数 `dpremove' を用いる。
- int dpremove(const char *name);
- `name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
壊れたデータベースファイルを修復するには、関数 `dprepair' を用いる。
- int dprepair(const char *name);
- `name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。修復されたデータベースの全レコードが元来もしくは期待される状態に戻る保証はない。
全てのレコードをエンディアン非依存のデータとしてダンプするには、関数 `dpexportdb' を用いる。
- int dpexportdb(DEPOT *depot, const char *name);
- `depot' はデータベースハンドルを指定する。`name' は出力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
エンディアン非依存データから全てのレコードをロードするには、関数 `dpimportdb' を用いる。
- int dpimportdb(DEPOT *depot, const char *name);
- `depot' はライタで接続したデータベースハンドルを指定する。データベースは空でなければならない。`name' は入力ファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
データベースファイルからレコードを直接取得するには、関数 `dpsnaffle' を用いる。
- char *dpsnaffle(const char *name, const char *kbuf, int ksiz, int *sp);
- `name' はデータベースファイルの名前を指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はデータベースファイルが別のプロセスにロックされていても利用できるが、最新の更新が反映されている保証はない。
データベースの内部で用いるハッシュ関数として、関数 `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
POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Depotの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。
Depotに対応するコマンドラインインタフェースは以下のものである。
コマンド `dpmgr' はDepotやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。
- dpmgr create [-s] [-bnum num] name
- データベースファイルを作成する。
- dpmgr put [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-na] name key val
- キーと値に対応するレコードを追加する。
- dpmgr out [-kx|-ki] name key
- キーに対応するレコードを削除する。
- dpmgr get [-nl] [-kx|-ki] [-start num] [-max num] [-ox] [-n] name key
- キーに対応するレコードの値を取得して標準出力する。
- dpmgr list [-nl] [-k|-v] [-ox] name
- データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
- dpmgr optimize [-bnum num] [-na] name
- データベースを最適化する。
- dpmgr inform [-nl] name
- データベースの雑多な情報を出力する。
- dpmgr remove name
- データベースファイルを削除する。
- dpmgr repair name
- 壊れたデータベースファイルを修復する。
- dpmgr exportdb name file
- 全てのレコードをエンディアン非依存のデータとしてダンプする。
- dpmgr importdb [-bnum num] name file
- エンディアン非依存データから全てのレコードをロードする。
- dpmgr snaffle [-kx|-ki] [-ox] [-n] name key
- ロックされたデータベースからキーに対応するレコードの値を取得して標準出力する。
- dpmgr version
- QDBMのバージョン情報を標準出力する。
各オプションは以下の機能を持つ。
- -s : ファイルをスパースにする。
- -bnum num : バケット配列の要素数を `num' に指定する。
- -kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
- -ki : 10進数による数値表現として `key' を扱う。
- -vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
- -vi : 10進数による数値表現として `val' を扱う。
- -vf : 名前が `val' のファイルのデータを値として読み込む。
- -keep : 既存のレコードとキーが重複時に上書きせずにエラーにする。
- -cat : 既存のレコードとキーが重複時に値を末尾に追加する。
- -na : アラインメントを設定しない。
- -nl : ファイルロックをかけずにデータベースを開く。
- -start : 値から取り出すデータの開始オフセットを指定する。
- -max : 値から取り出すデータの最大の長さを指定する。
- -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
- -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
- -k : キーのみを出力する。
- -v : 値のみを出力する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。
コマンド `dptest' はDepotの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `dpmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`bnum' はバケット配列の要素数、`pnum' はキーのパターン数、`align' はアラインメントの基本サイズ、`fbpsiz' はフリーブロックプールのサイズを指定する。
- dptest write [-s] name rnum bnum
- `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
- dptest read [-wb] name
- 上記で生成したデータベースの全レコードを検索する。
- dptest rcat [-c] name rnum bnum pnum align fbpsiz
- キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。
- dptest combo name
- 各種操作の組み合わせテストを行う。
- dptest wicked [-c] name rnum
- 各種更新操作を無作為に選択して実行する。
各オプションは以下の機能を持つ。
- -s : ファイルをスパースにする。
- -wb : 関数 `dpget' の代わりに関数 `dpgetwb' を用いる。
- -c : Cabinのマップを使って比較テストを行う。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。
コマンド `dptsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとDepotのデータベースを相互変換する。このコマンドは、QDBMの他のバージョンや他のDBMとの間でデータの交換を行う際に役立つ。また、バイトオーダの違うシステムの間でデータを交換する際にも役立つ。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。キーが重複するレコードは後者を優先する。`-bnum' オプションの引数 `num' はバケット配列の要素数を指定する。`import' サブコマンドではTSVのデータが標準出力に書き出される。
- dptsv import [-bnum num] [-bin] name
- TSVファイルを読み込んでデータベースを作成する。
- dptsv export [-bin] name
- データベースの全てのレコードをTSVファイルとして出力する。
各オプションは以下の機能を持つ。
- -bnum num : バケット配列の要素数を `num' に指定する。
- -bin : Base64形式でレコードを扱う。
このコマンドは処理が正常に終了すれば 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' または `CR_OLCKNB' とのビット論理和にすることができるが、前者はファイルロックをかけずにデータベースを開くことを指示し、後者はブロックせずにロックをかけることを指示する。`CR_OCREAT' は `CR_OSPARSE' とのビット論理和にすることができるが、それは生成されるファイルをスパースにすることを指示する。`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' は既存のレコードを残してエラーを返し、`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' で解放するべきである。
レコードを取得してバッファに書き込むには、関数 `crgetwb' を用いる。
- int crgetwb(CURIA *curia, const char *kbuf, int ksiz, int start, int max, char *vbuf);
- `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定する。それは書き込み用のバッファのサイズ以下である必要がある。`vbuf' は抽出したデータを書き込むバッファへのポインタを指定する。戻り値は正常ならバッファに書き込まれたデータのサイズであり、エラーなら -1 である。該当のレコードがない場合も -1 を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。書き込み用バッファの末尾に終端文字が追加されないことに注意すべきである。
レコードの値のサイズを取得するには、関数 `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))' として算出される。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。
データベースのフリーブロックプールのサイズ設定するには、関数 `crsetfbpsiz' を用いる。
- int crsetfbpsiz(CURIA *curia, int size);
- `curia' はライタで接続したデータベースハンドルを指定する。`size' はフリーブロックプールのサイズを指定する。戻り値は正常なら真であり、エラーなら偽である。フリーブロックプールのデフォルトのサイズは16である。サイズをより大きくすると、レコードの上書きを繰り返す際の空間効率は上がるが、時間効率が下がる。
データベースを更新した内容をファイルとデバイスに同期させるには、関数 `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 である。戻り値が2GBを越えた場合は桁溢れが起こる。
データベースファイルのサイズの合計を倍精度浮動小数として得るには、関数 `crfsizd' を用いる。
- double crfsizd(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番号である。
データベースの最終更新時刻を得るには、関数 `crmtime' を用いる。
- time_t crmtime(CURIA *curia);
- `curia' はデータベースハンドルを指定する。戻り値はデータベースの最終更新時刻である。
データベースディレクトリを削除するには、関数 `crremove' を用いる。
- int crremove(const char *name);
- `name' はデータベースディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
壊れたデータベースディレクトリを修復するには、関数 `crrepair' を用いる。
- int crrepair(const char *name);
- `name' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。修復されたデータベースの全レコードが元来もしくは期待される状態に戻る保証はない。
全てのレコードをエンディアン非依存のデータとしてダンプするには、関数 `crexportdb' を用いる。
- int crexportdb(CURIA *curia, const char *name);
- `curia' はデータベースハンドルを指定する。`name' は出力ディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
エンディアン非依存データから全てのレコードをロードするには、関数 `crimportdb' を用いる。
- int crimportdb(CURIA *curia, const char *name);
- `curia' はライタで接続したデータベースハンドルを指定する。データベースは空でなければならない。`name' は入力ディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。
データベースディレクトリからレコードを直接取得するには、関数 `crsnaffle' を用いる。
- char *crsnaffle(const char *name, const char *kbuf, int ksiz, int *sp);
- `name' はデータベースディレクトリの名前を指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。`start' は値の領域から抽出する最初のバイトのオフセットを指定する。`max' は値の領域から抽出するサイズを指定するか、負数なら無制限となる。`sp' が `NULL' でなければ、その参照先に抽出した領域のサイズを格納する。戻り値は正常なら値を格納した領域へのポインタであり、エラーなら `NULL' である。該当のレコードがない場合も `NULL' を返す。取り出そうとした値のサイズが `start' より小さかった場合には該当とみなさない。戻り値の領域は、実際には1バイト多く確保して終端文字が置かれるので、文字列として利用できる。戻り値の領域は `malloc' で確保されるので、不要になったら `free' で解放するべきである。この関数はデータベースディレクトリが別のプロセスにロックされていても利用できるが、最新の更新が反映されている保証はない。
ラージオブジェクト用データベースにレコードを追加するには、関数 `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' は既存のレコードを残してエラーを返し、`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' で解放するべきである。
ラージオブジェクト用データベースにあるレコードのファイルディスクリプタを取得するには、関数 `crgetlobfd' を用いる。
- int crgetlobfd(CURIA *curia, const char *kbuf, int ksiz);
- `curia' はデータベースハンドルを指定する。`kbuf' はキーのデータ領域へのポインタを指定する。`ksiz' はキーのデータ領域のサイズを指定するか、負数なら `strlen(kbuf)' の値となる。戻り値は正常なら該当のファイルディスクリプタであり、エラーなら -1 である。該当のレコードがない場合も -1 を返す。戻り値のファイルディスクリプタは `open' コールで開かれる。データベースがライタで接続された場合はそのディスクリプタは書き込み可能(O_RDWR)であり、そうでなければ書き込み不可能(O_RDONLY)である。ディスクリプタが不要になったら `close' で閉じるべきである。
ラージオブジェクト用データベースにあるレコードの値のサイズを取得するには、関数 `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
POSIXスレッドを有効にしてQDBMをビルドした場合、外部変数 `dpecode' はスレッド固有データへの参照として扱われ、Curiaの各関数はリエントラントになる。その場合、スレッド間で同時に同じハンドルにアクセスしない限りは、各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。
Curiaに対応するコマンドラインインタフェースは以下のものである。
コマンド `crmgr' はCuriaやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べたりする機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。
- crmgr create [-s] [-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 [-nl] [-kx|-ki] [-start num] [-max num] [-ox] [-lob] [-n] name key
- キーに対応するレコードの値を取得して標準出力する。
- crmgr list [-nl] [-k|-v] [-ox] name
- データベース内の全てのレコードのキーと値をタブと改行で区切って標準出力する。
- crmgr optimize [-bnum num] [-na] name
- データベースを最適化する。
- crmgr inform [-nl] name
- データベースの雑多な情報を出力する。
- crmgr remove name
- データベースディレクトリを削除する。
- crmgr repair name
- 壊れたデータベースディレクトリを修復する。
- crmgr exportdb name dir
- 全てのレコードをエンディアン非依存のデータとしてダンプする。
- crmgr importdb [-bnum num] [-dnum num] name dir
- エンディアン非依存データから全てのレコードをロードする。
- crmgr snaffle [-kx|-ki] [-ox] [-n] name key
- ロックされたデータベースからキーに対応するレコードの値を取得して標準出力する。
- crmgr version
- QDBMのバージョン情報を標準出力する。
各オプションは以下の機能を持つ。
- -s : ファイルをスパースにする。
- -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 : アラインメントを設定しない。
- -nl : ファイルロックをかけずにデータベースを開く。
- -start : 値から取り出すデータの開始オフセットを指定する。
- -max : 値から取り出すデータの最大の長さを指定する。
- -ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
- -lob : ラージオブジェクトを扱う。
- -n : 標準出力の末尾に付加される改行文字の出力を抑制する。
- -k : キーのみを出力する。
- -v : 値のみを出力する。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。
コマンド `crtest' はCuriaの機能テストや性能テストに用いるツールである。`crtest' によって生成されたデータベースディレクトリを `crmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計ったりするとよい。以下の書式で用いる。`name' はデータベース名、`rnum' はレコード数、`bnum' はバケット配列の要素数、`dnum' はデータベースファイルの分割数、`pnum' はキーのパターン数、`align' はアラインメントの基本サイズ、`fbpsiz' はフリーブロックプールのサイズを指定する。
- crtest write [-s] [-lob] name rnum bnum dnum
- `00000001'、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
- crtest read [-wb] [-lob] name
- 上記で生成したデータベースの全レコードを検索する。
- crtest rcat [-c] name rnum bnum dnum pnum align fbpsiz
- キーがある程度重複するようにレコードの追加を行い、連結モードで処理する。
- crtest combo name
- 各種操作の組み合わせテストを行う。
- crtest wicked [-c] name rnum
- 各種更新操作を無作為に選択して実行する。
各オプションは以下の機能を持つ。
- -s : ファイルをスパースにする。
- -lob : ラージオブジェクトを扱う。
- -wb : 関数 `crget' の代わりに関数 `crgetwb' を用いる。
- -c : Cabinのマップを使って比較テストを行う。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。環境変数 `QDBMDBGFD' の値として、変数 `dpecode' の変更履歴を出力するファイルディスクリプタを指定ことができる。
コマンド `crtsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとCuriaのデータベースを相互変換する。このコマンドは、QDBMの他のバージョンや他のDBMとの間でデータの交換を行う際に役立つ。また、バイトオーダの違うシステムの間でデータを交換する際にも役立つ。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。キーが重複するレコードは後者を優先する。`-bnum' オプションの引数 `num' はバケット配列の要素数を指定する。`-dnum' オプションの引数 `num' は要素データベースの数を指定する。`import' サブコマンドではTSVのデータが標準出力に書き出される。
- crtsv import [-bnum num] [-dnum num] [-bin] name
- TSVファイルを読み込んでデータベースを作成する。
- crtsv export [-bin] name
- データベースの全てのレコードをTSVファイルとして出力する。
各オプションは以下の機能を持つ。
- -bnum num : バケット配列の要素数を `num' に指定する。
- -dnum num : データベースファイルの分割数を `num' に指定する。
- -bin : Base64形式でレコードを扱う。
このコマンドは処理が正常に終了すれば 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の各関数はスレッドセーフである。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることが前提となる。
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_LOCKNB' か `GDBM_FAST' か `GDBM_SPARSE' とのビット論理和にすることができる。`GDBM_SYNC' は全てのデータベース操作をディスクと同期させ、`GDBM_NOLOCK' はファイルロックを伴わずにデータベースを開き、`GDBM_LOCKNB' はブロックなしのロックを行い、`GDBM_FAST' は無視される。`GDBM_SPARSE' はQDBM独自のものであり、作成するファイルをスパースにする。`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_LOCKNB' か `GDBM_FAST' か `GDBM_SPARSE' とのビット論理和にすることができる。`GDBM_SYNC' は全てのデータベース操作をディスクと同期させ、`GDBM_NOLOCK' はファイルロックを伴わずにデータベースを開き、`GDBM_LOCKNB' はブロックなしのロックを行い、`GDBM_FAST' は無視される。`GDBM_SPARSE' はQDBM独自のものであり、作成するファイルをスパースにする。`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' はデータベー