QDBMバージョン1仕様書

Copyright (C) 2000-2003 Mikio Hirabayashi
Last Update: Wed, 09 Apr 2003 21:59:41 +0900
[English] [Home]

目次

  1. 概要
  2. 機能詳細
  3. インストール
  4. Depot: 基本API
  5. Depot用コマンド
  6. Curia: 拡張API
  7. Curia用コマンド
  8. Relic: NDBM互換API
  9. Relic用コマンド
  10. Hovel: GDBM互換API
  11. Hovel用コマンド
  12. ファイルフォーマット
  13. バグ
  14. FAQ
  15. ライセンス

概要

QDBMはデータベースを扱うルーチン群のライブラリである。データベースといっても単純なものであり、キーと値のペアからなるレコードを格納したデータファイルである。キーと値は任意の長さを持つ一連のバイトであり、文字列でもバイナリでも扱うことができる。テーブルやデータ型の概念はない。キーはデータベース内で一意であり、キーが重複する複数のレコードを格納することはできない。

このデータベースに対しては、キーと値を指定してレコードを格納したり、キーを指定して対応するレコードを削除したり、キーを指定して対応するレコードを検索することができる。また、データベースに格納してある全てのキーを順不同に1つずつ取り出すこともできる。このような操作は、UNIX標準で定義されているDBMライブラリおよびその互換であるNDBMやGDBMに類するものである。QDBMはDBMのより良い代替として利用することができる。


機能詳細

QDBMはGDBMを参考に次の3点を目標として開発された。処理がより高速であること、データベースファイルがより小さいこと、APIがより単純であること。QDBMの特徴は、まさにそれらを達成していることである。また、伝統的なDBMが抱える3つの制限事項を回避している。すなわち、プロセス内で複数のデータベースを扱うことができ、キーと値のサイズに制限がなく、データベースファイルがスパースでない。

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にはデータベースに接続する方法として、リーダとライタの2種類がある。リーダは読み込み専用であり、ライタは読み書き両用である。データベースにはファイルロックによってプロセス間での排他制御が行われる。ライタが接続している間は、他のプロセスはリーダとしてもライタとしても接続できない。リーダが接続している間は、他のプロセスのリーダは接続できるが、ライタは接続できない。この機構によって、マルチタスク環境での同時接続の整合性が保証される。

伝統的なDBMにはレコードの追加操作に関して挿入モードと置換モードがある。前者では、キーが既存のレコードと重複する際に既存の値を残す。後者では、キーが既存のレコードと重複した際に新しい値に置き換える。QDBMはその2つに加えて連結モードがある。既存の値の末尾に指定された値を連結して格納する操作である。レコードの値を配列として扱う場合、要素を追加するには連結モードが役に立つ。また、DBMではレコードの値を取り出す際にはその全ての領域を処理対象にするしか方法がないが、QDBMでは値の領域の一部のみを選択して取り出すことができる。レコードの値を配列として扱う場合にはこの機能も役に立つ。

データベースにアラインメントを設定すると、各レコードは適当なパディングを空けてファイル中に配置される。既存のキーと重複するレコードに既存の値のサイズよりも大きいサイズの値を書き込もうとした際には、そのレコードの領域をファイル中の別の位置に移動させる必要がある。ただし、増加分のサイズがパディングのサイズに収まれば、領域を移動させる必要はない。レコードの領域の移動させる処理の時間計算量はレコードのサイズに依存する。しかし、パディングのサイズがレコードサイズに応じて増えることによってレコードが移動する確率は低くなるので、データベースの更新処理の平均性能はレコードのサイズの影響を受けにくい。一般的にいって、データベースの更新処理を続けるとファイル内の利用可能領域の断片化が起き、ファイルのサイズが肥大化してしまう。QDBMは隣接する不要領域を連結して再利用することによってこの問題に対処する。

QDBMのAPIには基本APIと拡張APIとNDBM互換APIとGDBM互換APIの4種類がある。基本APIではファイルを単位としてデータベースを扱う。拡張APIではディレクトリを単位としてデータベースを扱う。データベースの全てのデータをひとつのファイルに格納しようとすると、ファイルサイズがファイルシステムの制限を越える場合がある。拡張APIではそれに対処すべく、ディレクトリの中の複数のファイルに分割してデータを格納する。基本APIと拡張APIは互いに酷似した書式を備えるので、一方のAPIを使って書かれたアプリケーションを他方のAPIを用いて書き直すことはたやすい。NDBM互換APIやGDBM互換APIは、互換対象の既存のアプリケーションをQDBMに移植する際に便利である。さらに、4種類のAPIに対応したコマンドラインインタフェースがある。それらはユニットテストやデバッグなどで活躍する。それらを駆使すると、データベースアプリケーションのプロトタイプをシェル等のスクリプト言語で作ることも容易である。

QDBMをC++およびJavaから利用するためのAPIもある。C++用APIは基本APIと拡張APIのデータベース操作関数群をC++のクラス機構でカプセル化したものである。Java用APIはJava Native Interfaceを用いてQDBMの基本APIと拡張APIを呼び出すものである。両方のAPIはマルチスレッドセーフである。


インストール

ソースパッケージを用いてQDBMをインストールするには、GCCとGNU makeとGNU Binutilsが必要である。すなわち、`gcc'、`make'、`cpp'、`cc1'、`as'、`ld' 等のコマンドが存在し、それらがGNUでないものよりもコマンドサーチパス上で先に現れるようにしておくことが必要である。

QDBMの配布用アーカイブファイルを展開したら、生成されたディレクトリに入ってインストール作業を行う。

ビルド環境を設定する。

./configure

プログラムをビルドする。

make

プログラムの自己診断テストを行う。

make check

プログラムをインストールする。作業は `root' ユーザで行う。

make install

一連の作業が終ると、ヘッダファイル `depot.h' と `curia.h' と `relic.h' と `hovel.h' が `/usr/local/include' に、ライブラリ `libqdbm.a' と `libqdbm.so.0.0' 等が `/usr/local/lib' に、コマンド `dpmgr' と `dptest' と `dptsv' と `crmgr' と `crtest' と `rlmgr' と `rltest' と `hvmgr' と `hvtest' が `/usr/local/bin' にインストールされる。

QDBMをアンインストールするには、`./configure' をした後の状態で以下のコマンドを実行する。作業は `root' ユーザで行う。

make uninstall

QDBMの古いバージョンがインストールされている場合、それをアンインストールしてからインストール作業を行うべきである。

C++用APIとJava用APIはデフォルトではインストールされない。C++用APIのインストール方法については、サブディレクトリ `plus' にある `xspex.html' を参照すること。JAVA用APIのインストール方法については、サブディレクトリ `java' にある `jspex.html' を参照すること。

RPM等のバイナリパッケージを用いてインストールを行う場合は、それぞれのパッケージマネージャのマニュアルを参照すること。例えば、RPMを用いる場合、以下のようなコマンドを `root' ユーザで実行する。

rpm -ivh qdbm-1.x.x-x.i386.rpm

Windows(Cygwin)にインストールする場合、以下の手順に従う。

ビルド環境を設定する。

./configure

プログラムをビルドする。

make win

プログラムの自己診断テストを行う。

make check

プログラムをインストールする。なお、アンインストールする場合は `make uninstall-win' とする。

make install-win

Windowsでは、静的ライブラリ `libqdbm.a' の代わりにインポートライブラリ `libqdbm.dll.a' が生成され、動的ライブラリ `libqdbm.so' 等の代わりにダイナミックリンクライブラリ `qdbm.dll' が生成される。`qdbm.dll' は `C:\WINNT\SYSTEM32' のようなシステムディレクトリにインストールされる。


Depot: 基本API

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' で解放するべきである。この関数を繰り返して呼ぶことによって全てのレコードを1回ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。

データベースのアラインメントを設定するには、関数 `dpsetalign' を用いる。

int dpsetalign(DEPOT *depot, int align);
`depot' はライタで接続したデータベースハンドルを指定する。`align' はアラインメントの基本サイズを指定する。戻り値は正常なら真であり、エラーなら偽である。アラインメントを設定しておくと、レコードの上書きを頻繁にする場合の処理効率が良くなる。アラインメントの基本サイズは、一連の更新操作をした後の状態での標準的な値のサイズを指定するのがよい。レコードの値を格納する領域のサイズは、アラインメントの倍数で確保される。アラインメントは値のサイズが適当な大きさになると基本サイズの倍数に増やされる。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。

データベースを更新した内容をファイルとデバイスに同期させるには、関数 `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' はデータベースファイルの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。

データベースの内部で用いるハッシュ関数として、関数 `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, "%s\n", dperrmsg(dpecode));
    return 1;
  }

  /* レコードを格納する */
  if(!dpput(depot, NAME, -1, NUMBER, -1, DP_DOVER)){
    fprintf(stderr, "%s\n", dperrmsg(dpecode));
  }

  /* レコードを検索する */
  if(!(val = dpget(depot, NAME, -1, 0, -1, NULL))){
    fprintf(stderr, "%s\n", dperrmsg(dpecode));
  } else {
    printf("Name: %s\n", NAME);
    printf("Number: %s\n", val);
    free(val);
  }

  /* データベースを閉じる */
  if(!dpclose(depot)){
    fprintf(stderr, "%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用コマンド

Depotに対応するコマンドラインインタフェースは以下のものである。

コマンド `dpmgr' はDepotやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はファイル名、`key' はレコードのキー、`val' はレコードの値を指定する。

データベースファイルを作成する。
dpmgr create [-v] [-bnum num] name
キーと値に対応するレコードを追加する。
dpmgr put [-v] [-kx] [-vx] [-vf] [-keep] [-cat] [-na] name key val
キーに対応するレコードを削除する。
dpmgr out [-v] [-kx] name key
キーに対応するレコードの値を取得して標準出力する。
dpmgr get [-v] [-kx] [-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
QDBMのバージョン情報を標準出力する。
dpmgr version
各オプションは以下の機能を持つ。
-v : デバッグ情報を出力する。
-bnum num : バケット配列の要素数を `num' に指定する。
-kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
-vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
-vf : 名前が `val' のファイルのデータを値として読み込む。
-keep : 既存のレコードとキーが重複時に上書きせずにエラーにする。
-cat : 既存のレコードとキーが重複時に値を末尾に追加する。
-na : アラインメントを設定しない。
-start : 値から取り出すデータの開始オフセットを指定する。
-max : 値から取り出すデータの最大の長さを指定する。
-ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
-n : 標準出力の末尾に付加される改行文字の出力を抑制する。

このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

コマンド `dptest' はDepotの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `dpmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`name' はファイル名、`rnum' はレコード数、`bnum' はバケット配列の要素数を指定する。

`00000001' 、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
dptest write [-cat num] [-align num] name rnum bnum
上記で生成したデータベースの全レコードを検索する。
dptest read name
各種操作の組み合わせテストを行う。
dptest combo name
無作為な長さの無作為なデータを連続してデータベースに追加する。
dptest rand [-cat num] [-align num] [-poor] name rnum bnum
各種更新操作を無作為に選択して実行する。
dptest wicked name rnum
各オプションは以下の機能を持つ。
-cat num : `num' の回数だけ処理を繰り返してキーの重複を起こし、連結モードで処理する。
-align num : アラインメントの基本サイズを `num' に指定する。
-poor : 乱数パターンを減らしてキー重複を起こす。

このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

コマンド `dptsv' はタブ区切りでキーと値を表現した行からなるTSVファイルとDepotのデータベースを相互変換する。以下の書式で用いる。`name' はデータベース名を指定する。`export' サブコマンドではTSVのデータは標準入力から読み込む。キーが重複するレコードは後者を優先する。`-bnum' オプションの引数 `num' はバケット配列の要素数を指定する。`import' サブコマンドではTSVのデータが標準出力に書き出される。

TSVファイルを読み込んでデータベースを作成する。
dptsv import [-bnum num] name
データベースの全てのレコードをTSVファイルとして出力する。
dptsv export name

このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。


Curia: 拡張API

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' で解放するべきである。この関数を繰り返して呼ぶことによって全てのレコードを1回ずつ参照することができる。ただし、繰り返しの間にデータベースの更新があった場合はその限りではない。なお、取り出すレコードの順序は制御できず、格納した順番でレコードを取り出せるとは限らない。

データベースのアラインメントを設定するには、関数 `crsetalign' を用いる。

int crsetalign(CURIA *curia, int align);
`curia' はライタで接続したデータベースハンドルを指定する。`align' はアラインメントの基本サイズを指定する。戻り値は正常なら真であり、エラーなら偽である。アラインメントを設定しておくと、レコードの上書きを頻繁にする場合の処理効率が良くなる。アラインメントの基本サイズは、一連の更新操作をした後の状態での標準的な値のサイズを指定するのがよい。レコードの値を格納する領域のサイズは、アラインメントの倍数で確保される。アラインメントは値のサイズが適当な大きさになると基本サイズの倍数に増やされる。アラインメントの設定はデータベースに保存されないので、データベースを開く度に指定する必要がある。

データベースを更新した内容をファイルとデバイスに同期させるには、関数 `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' はデータベースディレクトリの名前を指定する。戻り値は正常なら真であり、エラーなら偽である。

ラージオブジェクト用データベースにレコードを追加するには、関数 `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, "%s\n", dperrmsg(dpecode));
    return 1;
  }

  /* レコードを格納する */
  if(!crput(curia, NAME, -1, NUMBER, -1, CR_DOVER)){
    fprintf(stderr, "%s\n", dperrmsg(dpecode));
  }

  /* レコードを検索する */
  if(!(val = crget(curia, NAME, -1, 0, -1, NULL))){
    fprintf(stderr, "%s\n", dperrmsg(dpecode));
  } else {
    printf("Name: %s\n", NAME);
    printf("Number: %s\n", val);
    free(val);
  }

  /* データベースを閉じる */
  if(!crclose(curia)){
    fprintf(stderr, "%s\n", dperrmsg(dpecode));
    return 1;
  }

  return 0;
}

Curiaを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。

Curiaの各関数はリエントラントではないが、関数内で静的な参照を保持するものではない。したがって、全ての呼び出しと外部変数 `depcode' の参照を排他制御することでスレッドセーフな関数として扱うことができる。ただし、`errno' や `malloc' 等がスレッドセーフな処理系であることがその前提となる。


Curia用コマンド

Curiaに対応するコマンドラインインタフェースは以下のものである。

コマンド `crmgr' はCuriaやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はディレクトリ名、`key' はレコードのキー、`val' はレコードの値を指定する。

データベースディレクトリを作成する。
crmgr create [-v] [-bnum num] [-dnum num] name
キーと値に対応するレコードを追加する。
crmgr put [-v] [-kx] [-vx] [-vf] [-keep] [-cat] [-lob] [-na] name key val
キーに対応するレコードを削除する。
crmgr out [-v] [-kx] [-lob] name key
キーに対応するレコードの値を取得して標準出力する。
crmgr get [-v] [-kx] [-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
QDBMのバージョン情報を標準出力する。
crmgr version
各オプションは以下の機能を持つ。
-v : デバッグ情報を出力する。
-bnum num : バケット配列の要素数を `num' に指定する。
-dnum num : データベースファイルの分割数を `num' に指定する。
-kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
-vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
-vf : 名前が `val' のファイルのデータを値として読み込む。
-keep : 既存のレコードとキーが重複時に上書きせずにエラーにする。
-cat : 既存のレコードとキーが重複時に値を末尾に追加する。
-na : アラインメントを設定しない。
-start : 値から取り出すデータの開始オフセットを指定する。
-max : 値から取り出すデータの最大の長さを指定する。
-ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
-lob : ラージオブジェクトを扱う。
-n : 標準出力の末尾に付加される改行文字の出力を抑制する。

このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

コマンド `crtest' はCuriaの機能テストや性能テストに用いるツールである。`crtest' によって生成されたデータベースディレクトリを `crmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`name' はディレクトリ名、`rnum' はレコード数、`bnum' はバケット配列の要素数を指定する。

`00000001' 、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
crtest write [-cat num] [-align num] [-lob] name rnum bnum
上記で生成したデータベースの全レコードを検索する。
crtest read [-lob] name
各種操作の組み合わせテストを行う。
crtest combo name
各オプションは以下の機能を持つ。
-cat num : `num' の回数だけ処理を繰り返してキーの重複を起こし、連結モードで処理する。
-align num : アラインメントの基本サイズを `num' に指定する。
-lob : ラージオブジェクトを扱う。

このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。


Relic: NDBM互換API

Relicは、NDBMと互換するAPIである。すなわち、Depotの関数群をNDBMのAPIで包んだものである。Relicを使ってNDBMのアプリケーションをQDBMに移植するのはたやすい。ほとんどの場合、インクルードするヘッダファイルを `ndbm.h' から `relic.h' に換え、ビルドの際のリンカオプションを `-lndbm' から `-lqdbm' に換えるだけでよい。

オリジナルのNDBMでは、データベースは2つのファイルの対からなる。ひとつは接尾辞に `.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' はデータベースハンドルを指定する。戻り値はデータファイルのファイルディスクリプタである。

Relicを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。リンカに渡すオプションは `-lndbm' ではなく `-lqdbm' である。

Relicの各関数はリエントラントではなく、スレッドセーフではない。


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' はレコード数を指定する。

`00000001' 、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
rltest write name rnum
上記で生成したデータベースを検索する。
rltest read name rnum

このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。


Hovel: GDBM互換API

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_setcuria' を呼ぶことによって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_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' である。

データベースとの接続を閉じてハンドルを破棄するには、関数 `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' はデータベースハンドルを指定する。戻り値はデータベースファイルのファイルディスクリプタである。

関数 `gdbm_setopt' は何もしない。

int gdbm_setopt(GDBM_FILE dbf, int option, int *value, int size);
`dbf' はデータベースハンドルを指定する。`option' は無視される。`value' は無視される。`size' は無視される。戻り値は 0 である。この関数は互換性のためにのみ存在する。

Hovelの各関数をCuriaのラッパに切り替えるには、関数 `gdbm_setcuria' を用いる。

void gdbm_setcuria(int bnum, int dnum, int align, int mode);
`bnum' はバケット配列の要素数の目安を指定する。`dnum' は要素データベースの数を指定する。`align' はアラインメントの基本サイズを指定する。`mode' はデータベースディレクトリのモードを指定する。この関数はデータベースハンドルを開く前に呼ぶべきである。この設定は保存されないので、プロセスが起動する度にこの関数を呼ぶべきである。この関数を呼び出した後は、関数 `gdbm_fdesc' は機能しなくなる。

Hovelを利用したプログラムをビルドする方法は、Depotの場合と全く同じである。リンカに渡すオプションは `-lgdbm' ではなく `-lqdbm' である。

Hovelの各関数はリエントラントではなく、スレッドセーフではない。


Hovel用コマンド

Hovelに対応するコマンドラインインタフェースは以下のものである。

コマンド `hvmgr' はHovelやそのアプリケーションのデバッグに役立つツールである。データベースを更新したり、データベースの状態を調べる機能を持つ。シェルスクリプトでデータベースアプリケーションを作るのにも利用できる。以下の書式で用いる。`name' はデータベース名、`key' はレコードのキー、`val' はレコードの値を指定する。

データベースファイルを作成する。
hvmgr create [-dir] name
キーと値に対応するレコードを追加する。
hvmgr store [-dir] [-kx] [-vx] [-vf] [-insert] name key val
キーに対応するレコードを削除する。
hvmgr delete [-dir] [-kx] name key
キーに対応するレコードの値を取得して標準出力する。
hvmgr fetch [-dir] [-kx] [-ox] [-n] name key
データベース内の全てのレコードのキーを改行で区切って標準出力する。
hvmgr list [-dir] [-ox] name
データベースを最適化する。
hvmgr optimize [-dir] name
各オプションは以下の機能を持つ。
-dir : Curiaを用いてデータベースディレクトリを扱う。
-kx : 2桁単位の16進数によるバイナリ表現として `key' を扱う。
-vx : 2桁単位の16進数によるバイナリ表現として `val' を扱う。
-vf : 名前が `val' のファイルのデータを値として読み込む。
-insert : 既存のレコードとキーが重複時に上書きせずにエラーにする。
-ox : 2桁単位の16進数によるバイナリ表現として標準出力を行う。
-n : 標準出力の末尾に付加される改行文字の出力を抑制する。

このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。

コマンド `hvtest' はHovelの機能テストや性能テストに用いるツールである。このコマンドによって生成されたデータベースファイルを `hvmgr' によって解析したり、`time' コマンドによってこのコマンドの実行時間を計るとよい。以下の書式で用いる。`name' はファイル名、`rnum' はレコード数を指定する。

`00000001' 、`00000002' のように変化する8バイトのキーと適当な8バイトの値を連続してデータベースに追加する。
hvtest write name rnum
上記で生成したデータベースを検索する。
hvtest read name rnum

このコマンドは処理が正常に終了すれば 0 を返し、エラーがあればそれ以外の値を返して終了する。


ファイルフォーマット

Depotが管理するデータベースファイルの内容は、ヘッダ部、バケット部、レコード部の3つに大別される。

ヘッダ部はファイルの先頭から 48 バイトの固定長でとられ、以下の情報が記録される。

  1. 識別のためのマジックナンバー : オフセット 0 から始まる。文字列 "[depot]\n\f" を内容とする。
  2. 検証のためのファイルサイズ : オフセット 16 から始まる。`int' 型の整数である。
  3. バケット配列の要素数 : オフセット 24 から始まる。`int' 型の整数である。
  4. 格納しているレコードの数 : オフセット 32 から始まる。`int' 型の整数である。

バケット部はヘッダ部の直後にバケット配列の要素数に応じた大きさでとられ、チェーンの先頭要素のオフセットが各要素に記録される。

レコード部はバケット部の直後からファイルの末尾までを占め、各レコードの以下の情報を持つ要素が記録される。

  1. フラグ(削除用) : `int' 型の整数である。
  2. キーの第2ハッシュ値 : `int' 型の整数である。
  3. キーのサイズ : `int' 型の整数である。
  4. 値のサイズ : `int' 型の整数である。
  5. パディングのサイズ : `int' 型の整数である。
  6. 左の子の位置 : `int' 型の整数である。
  7. 右の子の位置 : `int' 型の整数である。
  8. キーの実データ : キーのサイズで定義される長さを持つ一連のバイトである。
  9. 値の実データ : 値のサイズで定義される長さを持つ一連のバイトである。
  10. パディング : 値のサイズとアラインメントにより算出される長さを持つ一連のバイトである。

データベースファイルはスパースではないので、通常のファイルと同様に複製等の操作を行うことができる。Depotはバイトオーダの調整をしないでファイルの読み書きを行っているので、バイトオーダの異なる環境にデータベースファイルをコピーしてもそのままでは利用できない。

データベースファイルのマジックナンバを `file' コマンドに識別させたい場合は、`magic' ファイルに以下の行を追記するとよい。

0       string          [depot]\n\f     database file of QDBM
>16     long            x               \b, filesize:%d
>24     long            x               \b, buckets:%d
>32     long            x               \b, records:%d
0       string          [depot]\0\v     dummy file of QDBM

バグ

segmentation faultによるクラッシュ、予期せぬデータの消失等の不整合、メモリリーク、その他諸々のバグに関して、既知のもので未修正のものはない。

QDBMのデータベースは書き込みエラーに対して脆弱である。ロールバックやバックアップの機能はない。外的エラー要因への対処はアプリケーションに任される。例えば、ディスクが一杯になったり、シグナル等を受け取って処理を中断せざるを得ない時の対処がそれにあたる。

バグを発見したら、是非とも作者にフィードバックしてほしい。その際、QDBMのバージョンと、利用環境のOSとコンパイラのバージョンも教えてほしい。


FAQ

Q.: どのプラットホームで利用できるか。
A.: POSIX互換の環境であれば利用できるはずである。少なくとも、Linux 2.4とSunOS 5.8では利用できる。WindowのCygwin環境でも利用できる。ただし、MinGWには対応していない。
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.: なぜB木を使わないのか。
A.: QDBMは範囲検索や順序に基づく参照を目的とするデータベースではない。B木はそれらが可能な点でハッシュより優れているが、単純な一致検索と更新の性能はハッシュの方が優れている。ハッシュチェーンに二分探索木を用いるのは、B木よりも各ノードに必要な領域のサイズが小さいため、その分バケット配列の要素数を増やせるからである。
Q.: アラインメントの使い方がよくわからないが。
A.: 上書きモードや連結モードでの書き込みを繰り返す場合に、アラインメントはデータベースファイルのサイズが急激に大きくなるのを防ぐ。アラインメントの適切なサイズはアプリケーションによって異なるので、各自で実験してみてほしい。差しあたりは32くらいにしておくとよい。
Q.: 「QDBM」とはどういう意味なのか。
A.: 「QDBM」は「Quick DataBase Manager」の略である。高速に動作するという意味と、アプリケーションの開発が迅速にできるという意味が込められている。
Q.: 「Depot」「Curia」「Relic」「Hovel」はどういう意味なのか。どう発音するのか。
A.: 「depot」は、空港、倉庫、補給所など、物質が集まる場所を意味する。発音を片仮名で表現するなら「ディーポゥ」となる。「curia」は、宮廷、法廷など、権威が集まる場所を意味する。発音を片仮名で表現するなら「キュリア」となる。「relic」は、遺物、遺跡など、過去の残骸を意味する。発音を片仮名で表現するなら「レリック」となる。「hovel」は、小屋、物置、離れ家など、粗末な建物を意味する。発音を片仮名で表現するなら「ハブル」となる。

ライセンス

QDBMは平林幹雄が作成し、著作権を保持するものである。QDBMはGNU GENERAL PUBLIC LICENSE Version 2に基づくフリーソフトウェアとして無償で配布される。QDBMは誰でも自由に使用し、改編し、頒布することができる。一方で、QDBMは完全に無保証であり、その使用および不使用に伴ういかなる損害に対しても作者は責任を負わない。

作者と連絡をとるには mikio1234@lycos.jp 宛に電子メールを送ればよい。感想や改善案やバグレポートなどを寄せると作者は喜ぶ。