QDBMバージョン1仕様書
Copyright (C) 2000-2003 Mikio Hirabayashi
Last Update: Mon, 23 Jun 2003 08:13:31 +0900
目次
- 概要
- 機能詳細
- インストール
- Depot: 基本API
- Depot用コマンド
- Curia: 拡張API
- Curia用コマンド
- Relic: NDBM互換API
- Relic用コマンド
- Hovel: GDBM互換API
- Hovel用コマンド
- Cabin: ユーティリティAPI
- Cabin用コマンド
- ファイルフォーマット
- バグ
- FAQ
- ライセンス
QDBMはデータベースを扱うルーチン群のライブラリである。データベースといっても単純なものであり、キーと値のペアからなるレコードを格納したデータファイルである。キーと値は任意の長さを持つ一連のバイトであり、文字列でもバイナリでも扱うことができる。テーブルやデータ型の概念はない。キーはデータベース内で一意であり、キーが重複する複数のレコードを格納することはできない。
このデータベースに対しては、キーと値を指定してレコードを格納したり、キーを指定して対応するレコードを削除したり、キーを指定して対応するレコードを検索することができる。また、データベースに格納してある全てのキーを順不同に一つずつ取り出すこともできる。このような操作は、UNIX標準で定義されているDBMライブラリおよびその互換であるNDBMやGDBMに類するものである。QDBMはDBMのより良い代替として利用することができる。
効率的なハッシュデータベースの実装
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はアラインメントによってこの問題に対処する。増分がパディングに収まれば領域を移動させる必要はない。
単純だが多様なAPI
QDBMのAPIは非常に単純である。ANSI Cで定義された `FILE' ポインタを用いた通常のファイル入出力と同じようにデータベースファイルに対する入出力を行うことができる。QDBMの基本APIでは、データベースの実体は単一のファイルに記録される。拡張APIでは、データベースの実体は単一のディレクトリに含まれる複数のファイルに記録される。後者を用いると、32ビットの処理系でも2GBを越えるサイズのデータベースを構築することができる。二つのAPIは互いに酷似しているので、アプリケーションを一方から他方に移植することはたやすい。
NDBMおよびGDBMに互換するAPIも提供される。NDBMやGDBMのアプリケーションは市場に数多く存在するが、それらをQDBMに移植するのはたやすい。ほとんどの場合、ヘッダファイルの取り込み(#include)を書き換えてコンパイルしなおせばよい。ただし、オリジナルのNDBMやGDBMで作成したデータベースファイルをQDBMで扱うことはできない。
メモリ上でレコードを簡単に扱うために、ユーティリティAPIが提供される。メモリ確保関数とソート関数と拡張可能なデータと配列リストとハッシュマップの実装である。それらを用いると、C言語でもPerlやRuby等のスクリプト言語のような手軽さでレコードを扱うことができる。
QDBMはC言語の他にも、C++、Java、PerlおよびRubyのAPIを提供する。C言語のAPIには、基本API、拡張API、NDBM互換API、GDBM互換APIおよびユーティリティAPIの五種類がある。各APIに対応したコマンドラインインタフェースも用意されている。それらはプロトタイピングやテストやデバッグなどで活躍する。C++用APIは基本APIと拡張APIのデータベース操作関数群をC++のクラス機構でカプセル化したものである。Java用APIはJava Native Interfaceを用いてQDBMの基本APIと拡張APIを呼び出すものである。Perl用APIはXS言語を用いてQDBMの基本APIと拡張APIを呼び出すものである。Ruby用APIはQDBMの基本APIと拡張APIをRubyのモジュールとして呼び出すものである。C++用APIとJava用APIとRuby用APIはマルチスレッドセーフである。
幅広い移植性
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、Cygwin 1.3.10およびMacOS X 10.2において動作確認されている。QDBMが作成したデータベースファイルは処理系のバイトオーダに依存するが、その対策として、ファイルのバイトオーダを変換する機能が提供される。
準備
ソースパッケージを用いてQDBMをインストールするには、GCCのバージョン2.8以降と `make' が必要である。
QDBMの配布用アーカイブファイルを展開したら、生成されたディレクトリに入ってインストール作業を行う。
普通の手順
ビルド環境を設定する。
./configure
プログラムをビルドする。
make
プログラムの自己診断テストを行う。
make check
プログラムをインストールする。作業は `root' ユーザで行う。
make install
Libtoolを使う場合
上記の方法でうまくいかない場合、以下の手順に従う。この手順には、GNU libtoolのバージョン1.5以降が必要である。
ビルド環境を設定する。
./configure
プログラムをビルドする。
make -f LTmakefile
プログラムの自己診断テストを行う。
make -f LTmakefile check
プログラムをインストールする。作業は `root' ユーザで行う。
make -f LTmakefile install
結果
一連の作業が終ると、ヘッダファイル `depot.h' と `curia.h' と `relic.h' と `hovel.h' が `/usr/local/include' に、ライブラリ `libqdbm.a' と `libqdbm.so' 等が `/usr/local/lib' に、コマンド `dpmgr' と `dptest' と `dptsv' と `crmgr' と `crtest' と `rlmgr' と `rltest' と `hvmgr' と `hvtest' と `cbtest' が `/usr/local/bin' にインストールされる。
`libqdbm.so' と動的にリンクしたプログラムを実行する際には、ライブラリの検索パスに `/usr/local/lib' を含めるべきである。環境変数 `LD_LIBRARY_PATH' でライブラリの検索パスを設定することができる。
QDBMをアンインストールするには、`./configure' をした後の状態で以下のコマンドを実行する。作業は `root' ユーザで行う。
make uninstall
QDBMの古いバージョンがインストールされている場合、それをアンインストールしてからインストール作業を行うべきである。
C言語以外のAPIはデフォルトではインストールされない。C++用APIのインストール方法については、サブディレクトリ `plus' にある `xspex-ja.html' を参照すること。JAVA用APIのインストール方法については、サブディレクトリ `java' にある `jspex-ja.html' を参照すること。Perl用APIのインストール方法については、サブディレクトリ `perl' にある `pspex-ja.html' を参照すること。Ruby用APIのインストール方法については、サブディレクトリ `ruby' にある `rspex-ja.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' のようなシステムディレクトリにインストールされる。
MacOS Xの場合
MacOS X(Darwin)にインストールする場合、以下の手順に従う。
ビルド環境を設定する。
./configure
プログラムをビルドする。
make mac
プログラムの自己診断テストを行う。
make check-mac
プログラムをインストールする。なお、アンインストールする場合は `make uninstall-mac' とする。
make install-mac
MacOS Xでは、`libqdbm.so' 等の代わりに `libqdbm.dylib' 等が生成される。ライブラリの検索パスの指定は環境変数 `DYLD_LIBRARY_PATH' で行うことができる。
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' を除く全ての操作は何もせずにエラーを返す。ひとつのプロセスで複数のデータベースファイルを同時に利用することは可能であるが、同じデータベースファイルの複数のハンドルを利用してはならない。
外部変数 `dpversion' はバージョン情報の文字列である。
- extern const char *dpversion;
- この変数の指す領域は書き込み禁止である。
外部変数 `dpdbgfd' にはデバッグ情報を出力するファイルディスクリプタを指定する。
- extern int dpdbgfd;
- この変数の初期値は -1 であり、値が負数ならば出力を行わない。
外部変数 `dpecode' には直前のエラーコードが記録される。エラーコードの詳細については `depot.h' を参照すること。
- extern int dpecode;
- この変数の初期値は `DP_ENOERR' である。
エラーコードに対応するメッセージ文字列を得るには、関数 `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' はファイルが存在しても作り直すことを指示する。`bnum' はバケット配列の要素数の目安を指定するが、0 以下ならデフォルト値が使われる。バケット配列の要素数はデータベースを作成する時に決められ、最適化以外の手段で変更することはできない。バケット配列の要素数は、格納するレコード数の半分から4倍程度にするのがよい。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。
データベースとの接続を閉じてハンドルを破棄するには、関数 `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 NAME "mikio"
#define NUMBER "000-1234-5678"
#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の各関数はリエントラントではないが、関数内で静的な参照を保持するものではない。したがって、全ての呼び出しと外部変数 `depcode' の参照を排他制御することでスレッドセーフな関数として扱うことができる。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることがその前提となる。
Depotに対応するコマンドラインインタフェースは以下のものである。
コマンド `dpmgr' はDepotやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はファイル名、`key' はレコードのキー、`val' はレコードの値を指定する。
- dpmgr create [-v] [-bnum num] name
- データベースファイルを作成する。
- dpmgr put [-v] [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-na] name key val
- キーと値に対応するレコードを追加する。
- dpmgr out [-v] [-kx|-ki] name key
- キーに対応するレコードを削除する。
- dpmgr get [-v] [-kx|-ki] [-start num] [-max num] [-ox] [-n] name key
- キーに対応するレコードの値を取得して標準出力する。
- dpmgr list [-v] [-ox] name
- データベース内の全てのレコードのキーを改行で区切って標準出力する。
- dpmgr optimize [-v] [-bnum num] [-na] name
- データベースを最適化する。
- dpmgr inform [-v] name
- データベースの雑多な情報を出力する。
- dpmgr remove [-v] name
- データベースファイルを削除する。
- dpmgr econv [-v] [-be|-le] name
- データベースファイルのバイトオーダをローカルシステム用に変換する。
- dpmgr version
- QDBMのバージョン情報を標準出力する。
各オプションは以下の機能を持つ。
- -v : デバッグ情報を出力する。
- -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 を返し、エラーがあればそれ以外の値を返して終了する。
CuriaはQDBMの拡張APIであり、複数のデータベースファイルをディレクトリで一括して扱う機能を提供する。データベースを複数のファイルに分割することで、ファイルシステムによるファイルサイズの制限を回避することができる。複数のデバイスにファイルを分散させれば、スケーラビリティを向上させることができる。
Depotではファイル名を指定してデータベースを構築するが、Curiaではディレクトリ名を指定してデータベースを構築する。指定したディレクトリの直下には、`depot' という名前のデータベースファイルが生成される。これはディレクトリの属性を保持するものであり、レコードの実データは格納されない。それとは別に、データベースを分割した個数だけ、4桁の十進数値の名前を持つサブディレクトリが生成され、各々のサブディレクトリの中には `depot' という名前でデータベースファイルが生成される。レコードの実データはそれらに格納される。例えば、`casket' という名前のデータベースを作成し、分割数を3にする場合、`casket/depot' 、`casket/0001/depot' 、`casket/0002/depot' 、`casket/0003/depot' が生成される。データベースを作成する際にすでにディレクトリが存在していてもエラーとはならない。したがって、予めサブディレクトリを生成しておいて、各々に異なるデバイスのファイルシステムをマウントしておけば、データベースファイルを複数のデバイスに分散させることができる。NFS等を利用すれば複数のファイルサーバにデータベースを分散させることもできる。
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' を用いる。
データベースのハンドルを作成するには、関数 `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' はファイルが存在しても作り直すことを指示する。`bnum' はバケット配列の要素数の目安を指定するが、0 以下ならデフォルト値が使われる。バケット配列の要素数はデータベースを作成する時に決められ、最適化以外の手段で変更することはできない。バケット配列の要素数は、格納するレコード数の半分から4倍程度にするのがよい。`dnum' は要素データベースの数を指定するが、0 以下ならデフォルト値が使われる。データベースファイルの分割数はデータベースを作成する時に指定したものから変更することはできない。データベースファイルの分割数の最大値は 256 個である。戻り値はデータベースハンドルであるか、エラーなら `NULL' である。ライタ(読み書き両用モード)でデータベースファイルを開く際にはそのファイルに対して排他ロックがかけられ、リーダ(読み込み専用モード)で開く際には共有ロックがかけられる。その際には該当のロックがかけられるまで制御がブロックする。
データベースとの接続を閉じてハンドルを破棄するには、関数 `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 NAME "mikio"
#define NUMBER "000-1234-5678"
#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の場合と全く同じである。
Curiaの各関数はリエントラントではないが、関数内で静的な参照を保持するものではない。したがって、全ての呼び出しと外部変数 `depcode' の参照を排他制御することでスレッドセーフな関数として扱うことができる。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることがその前提となる。
Curiaに対応するコマンドラインインタフェースは以下のものである。
コマンド `crmgr' はCuriaやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はディレクトリ名、`key' はレコードのキー、`val' はレコードの値を指定する。
- crmgr create [-v] [-bnum num] [-dnum num] name
- データベースディレクトリを作成する。
- crmgr put [-v] [-kx|-ki] [-vx|-vi|-vf] [-keep|-cat] [-lob] [-na] name key val
- キーと値に対応するレコードを追加する。
- crmgr out [-v] [-kx|-ki] [-lob] name key
- キーに対応するレコードを削除する。
- crmgr get [-v] [-kx|-ki] [-start num] [-max num] [-ox] [-lob] [-n] name key
- キーに対応するレコードの値を取得して標準出力する。
- crmgr list [-v] [-ox] name
- データベース内の全てのレコードのキーを改行で区切って標準出力する。
- crmgr optimize [-v] [-bnum num] [-na] name
- データベースを最適化する。
- crmgr inform [-v] name
- データベースの雑多な情報を出力する。
- crmgr remove [-v] name
- データベースディレクトリを削除する。
- crmgr econv [-v] [-be|-le] name
- データベースディレクトリのバイトオーダをローカルシステム用に変換する。
- crmgr version
- QDBMのバージョン情報を標準出力する。
各オプションは以下の機能を持つ。
- -v : デバッグ情報を出力する。
- -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' はバケット配列の要素数を指定する。
- crtest write [-lob] name rnum bnum
- `00000001' 、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
- crtest read [-lob] name
- 上記で生成したデータベースの全レコードを検索する。
- crtest combo name
- 各種操作の組み合わせテストを行う。
各オプションは以下の機能を持つ。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
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' で閉じる。ハンドルのメンバを直接参照することは推奨されない。
データの格納、削除、検索に用いる関数とのデータの授受には、キーと値を表現するのに `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' である。
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のラッパとしてデータベースディレクトリを扱うようにすることができる。
データの格納、削除、検索に用いる関数とのデータの授受には、キーと値を表現するのに `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_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' は全てのデータベース操作をディスクと同期させ、残り2つは無視される。`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' は全てのデータベース操作をディスクと同期させ、残り2つは無視される。`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, "%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, "%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, "%s\n", gdbm_strerror(gdbm_errno));
}
/* データベースを閉じる */
gdbm_close(dbf);
return 0;
}
Hovelを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。リンカに渡すオプションは `-lgdbm' ではなく `-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' で閉じる。各ハンドルのメンバを直接参照することは推奨されない。
外部変数 `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' はリストハンドルを指定する。戻り値は新しいリストハンドルである。
一連のデータを分割してリストを作成するには、関数 `cbsplit' を用いる。
- CBLIST *cbsplit(const char *ptr, int size, const char *delim);
- `ptr' は内容の領域へのポインタを指定するか、`NULL' なら空のリストを作成する。`size' はその領域のサイズを指定するか、負数なら `strlen(ptr)' の値となる。`delim' は区切り文字を含む文字列を指定するか、`NULL' なら終端文字を区切り文字とする。戻り値はリストハンドルである。
リストハンドルを破棄するには、関数 `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 である。複数の要素が該当した場合にどちらが返るかは未定義である。
マップハンドルを作成するには、関数 `cbmapopen' を用いる。
- CBMAP *cbmapopen(int bnum);
- `bnum' はバケット配列の要素数を指定するが、0 以下ならデフォルト値が使われる。戻り値はマップハンドルである。
マップを複製するには、関数 `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バイト多く確保して終端文字が置かれるので、文字列として利用できる。
マップのイテレータを初期化するには、関数 `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' はマップハンドルを指定する。戻り値はデータベースのレコード数である。
Cabinを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。
Cabinの各関数はリエントラントではないが、関数内で静的な参照を保持するものではない。したがって、全ての呼び出しを排他制御することでスレッドセーフな関数として扱うことができる。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることがその前提となる。
Cabinに対応するコマンドラインインタフェースは以下のものである。
コマンド `cbtest' はCabinの機能テストや性能テストに用いるツールである。`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`rnum' はレコード数を指定する。
- cbtest sort [-d] rnum
- ソートアルゴリズムのテストを行う。
- cbtest list [-d] rnum
- リストの書き込みテストを行う。
- cbtest map [-d] rnum
- マップの書き込みテストを行う。
- cbtest wicked rnum
- リストとマップの各種更新操作を無作為に選択して実行する。
各オプションは以下の機能を持つ。
このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。
Depotが管理するデータベースファイルの内容は、ヘッダ部、バケット部、レコード部の三つに大別される。
ヘッダ部はファイルの先頭から 48 バイトの固定長でとられ、以下の情報が記録される。
- 識別のためのマジックナンバ : オフセット 0 から始まる。ビッグエンディアン用なら文字列 "[DEPOT]\n\f" を内容とし、リトルエンディアン用なら文字列 "[depot]\n\f" を内容とする。
- 検証のためのファイルサイズ : オフセット 16 から始まる。`int' 型の整数である。
- バケット配列の要素数 : オフセット 24 から始まる。`int' 型の整数である。
- 格納しているレコードの数 : オフセット 32 から始まる。`int' 型の整数である。
バケット部はヘッダ部の直後にバケット配列の要素数に応じた大きさでとられ、チェーンの先頭要素のオフセットが各要素に記録される。
レコード部はバケット部の直後からファイルの末尾までを占め、各レコードの以下の情報を持つ要素が記録される。
- フラグ(削除用) : `int' 型の整数である。
- キーの第二ハッシュ値 : `int' 型の整数である。
- キーのサイズ : `int' 型の整数である。
- 値のサイズ : `int' 型の整数である。
- パディングのサイズ : `int' 型の整数である。
- 左の子の位置 : `int' 型の整数である。
- 右の子の位置 : `int' 型の整数である。
- キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。
- 値の実データ : 値のサイズで定義される長さを持つ一連のバイトである。
- パディング : 値のサイズとアラインメントにより算出される長さを持つ一連のバイトである。
データベースファイルはスパースではないので、通常のファイルと同様に複製等の操作を行うことができる。Depotはバイトオーダの調整をしないでファイルの読み書きを行っているので、バイトオーダの異なる環境にデータベースファイルを移設してもそのままでは利用できない。
Depotのデータベースファイルをネットワークで配布する際には、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の各ドキュメントは英語を母国語とする人達によって校正されるべきである。
segmentation faultによるクラッシュ、予期せぬデータの消失等の不整合、メモリリーク、その他諸々のバグに関して、既知のもので未修正のものはない。
QDBMのデータベースは書き込みエラーに対して脆弱である。ロールバックやバックアップの機能はない。外的エラー要因への対処はアプリケーションに任される。例えば、ディスクが一杯になったり、シグナル等を受け取って処理を中断せざるを得ない時の対処がそれにあたる。
ファイルロックをサポートしていないファイルシステム上にデータベースファイルを置くことはできない。NFSの実装の一部でその問題がある。
バグを発見したら、是非とも作者にフィードバックしてほしい。その際、QDBMのバージョンと、利用環境のOSとコンパイラのバージョンも教えてほしい。
- Q. : 結局のところ、GDBM(NDBM、SDBM、Berkeley DB)とどう違うのか。
- A. : 処理が速い。データベースファイルが小さい。APIが簡潔である。特筆すべきは、レコードの上書きを繰り返す場合の時間的および空間的効率がとてもよく、実用上のスケーラビリティが高いことである。また、レコード数が100万を越えるような大規模なデータベースを構築する際にも、処理が極端に遅くなったり、ファイルのサイズが極端に大きくなったりしない。
- Q. : アプリケーションの良いサンプルコードはあるか。
- A. : `dptsv.c' 、`dptest.c' 、`dpmgr.c' の順に見てほしい。それらはQDBMの配布用アーカイブに含まれる。
- Q. 性能の実測値はどのようなものか。
- A. : コマンド `dptest' によって重複しないレコードの追加と検索にかかる時間を計った。バケット配列の要素数はレコード数の2倍とした。2.53GHzのPentium 4プロセッサを1個、333MHzのRAMを1GB搭載したLinux 2.4のマシンでは、100万レコードの追加に5.0秒、その全ての検索に4.5秒、1000万レコードの追加に50.9秒、その全ての検索に44.9秒かかった。500MHzPentium 3プロセッサを1個、133MHzのRAMを192MB搭載したLinux 2.4のマシンでは、100万レコードの追加に12.0秒、その全ての検索に11.1秒、1000万レコードの追加に495.0秒、その全ての検索に414.8秒かかった。
- Q. : アラインメントの使い方がよくわからないが。
- A. : 上書きモードや連結モードでの書き込みを繰り返す場合に、アラインメントはデータベースファイルのサイズが急激に大きくなるのを防ぐ。アラインメントの適切なサイズはアプリケーションによって異なるので、各自で実験してみてほしい。さしあたりは32くらいにしておくとよい。
- 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. : 「Depot」「Curia」「Relic」「Hovel」「Cabin」はどういう意味なのか。どう発音するのか。
- A. : 「depot」は、空港、倉庫、補給所など、物質が集まる場所を意味するらしい。発音を片仮名で表現するなら「ディーポゥ」が妥当だろう。「curia」は、宮廷、法廷など、権威が集まる場所を意味するらしい。発音を片仮名で表現するなら「キュリア」が妥当だろう。「relic」は、遺物、遺跡など、過去の残骸を意味するらしい。発音を片仮名で表現するなら「レリック」が妥当だろう。「hovel」は、小屋、物置、離れ家など、粗末な建物を意味するらしい。発音を片仮名で表現するなら「ハブル」が妥当だろう。「cabin」は、機室、客室、小屋など、簡易的な居住空間を意味するらしい。発音を片仮名で表現するなら「キャビン」が妥当だろう
QDBMはフリーソフトウェアである。あなたは、Free Software Foundationが公表したGNU General Public Licenseのバージョン2あるいはそれ以降の各バージョンの中からいずれかを選択し、そのバージョンが定める条項に従ってQDBMを再頒布または変更することができる。
QDBMは有用である思われるが、頒布にあたっては、市場性及び特定目的適合性についての暗黙の保証を含めて、いかなる保証も行なわない。詳細についてはGNU General Public Licenseを読んでほしい。
あなたは、QDBMと一緒にGNU General Public Licenseの写しを受け取っているはずである(`COPYING' ファイルを参照)。そうでない場合は、Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA へ連絡してほしい。
QDBMは平林幹雄が作成した。作者と連絡をとるには、<mikio@users.sourceforge.net> 宛に電子メールを送ってほしい。感想や改善案やバグレポートなどを寄せると作者は喜ぶ。