DBZ(3)

名前
dbminit, fetch, store, dbmclose - dbm 互換のデータベースルーチン
dbzfresh, dbzagain, dbzfetch, dbzstore - データベースルーチン
dbzsync, dbzsize, dbzincore, dbzcancel, dbzdebug - データベースルーチン

形式
#include <dbz.h>

dbminit(base)
char *base;

datum
fetch(key)
datum key;

store(key, value)
datum key;
datum value;

dbmclose()

dbzfresh(base, size, fieldsep, cmap, tagmask)
char *base;
long size;
int fieldsep;
int cmap;
long tagmask;

dbzagain(base, oldbase)
char *base;
char *oldbase;

datum
dbzfetch(key)
datum key;

dbzstore(key, value)
datum key;
datum value;

dbzsync()

long
dbzsize(nentries)
long nentries;

dbzincore(newvalue)

dbzcancel()

dbzdebug(newvalue)

機能説明
これらの関数により、テキストファイル( base ファイル )への高速なランダムアクセスを行うための検索システムを提供します。 ある制限のもと、これらは dbm(3) の関数と互換のあるようになっていますが、いくつかの拡張がされています。 (これらの関数は dbm でのファイルやそれについての変数では互換が ない ということに留意してください。)

基本的には、 dbz は key と value の対を保持します。 key と value は任意のバイト列で、ヘッダファイル内の typedef で宣言されていて、 そのメンバは dptr (バイト列を示す char * 型の値です) と dsize (バイト列長を示す int 型の値です)の データ のタイプで、関数の引数として指定されます。

実際には、 dbz は dbm より制限されています。 dbz データベースは、ベースとなるファイルへのインデックスでなければいけなくて、 データベースの value は、ベースとなるファイルでの fseek(3) でのオフセット値となります。 このような value それぞれは、 関連した key についてのベースファイル内での場所を``示し''ていなければいけません。 key の長さは、(ヘッダファイルで定義される定数の) DBZMAXKEY バイトより長くてはいけません。 どの key も他の key による結果とすることもできません。 ほとんどのアプリケーションでは key は括弧で括られているか、 なんらかの方法で終了されています(終了文字については以下で説明する dbzfresh の fieldsep パラメータを参照してください。)

dbminit はデータベースとベースファイル base へのインデックスとなる base.dir および base.pag のファイルをオープンします。 このファイルはすでに存在していなければいけません。 (データベースが新しい場合、いずれも 0 バイト長のファイルでなければいけません。) データベースをクローズするための dbmclose が呼ばれるまで以後のアクセスはそのデータベースに対して行われます。 dbminit を呼ぶ時点でベースファイルは存在している必要はありませんが、 アクセスを行おうとするまでには存在していなければいけません。

fetch は指定した key についてのデータベースをサーチし、それが存在すれば関連する value を返します。 store はデータベース中に key と value の対を保持させます。 データベースファイルに出力できなければ、 store はエラーとなります。 大文字小文字のマッピングの指定については後の説明を参照してください。

dbzfresh は より詳細に渡って制御を行う新しいデータベースを生成するための dbminit の変形です。 dbminit とは違い、データベースファイルが存在している必要はありません。 必要であれば生成され、どのような場合にも切り詰められます。

dbzfresh の size パラメータにより、key と value の対でデータベースでの最初のハッシュテーブルの大きさを指定します。 size が初期数で、データベースでの key と value の対の数が size の約 2/3 を越えなければ検索のパフォーマンスはよいでしょう。 (関数 dbzsize では、予想される key と value の対の数を渡すことによりここでのデータベースの大きさを示してくれるでしょう。) fseek の offset の領域が 4 バイトであることを考慮すると、 .pag ファイルは key と value の対の数が size の約 80% を越えるまでは 4*size バイトになるでしょう。 ( .dir ファイルは小さく、常にほぼ同じ大きさです。) (データベースが size の 100% の大きさを越えても問題とはなりませんが、 アクセスは多少遅くなり、 .pag ファイルもいくらか大きくなります。)

dbzfresh での fieldsep パラメータによりデータベースファイルでの区切り文字を指定します。 これが NUL (0) でも無く、引数での key の最後の文字が NUL であれば、その NUL が NUL あるいはデータベースファイルでの fieldsep と比較されます。 これにより、データベースファイルで NUL が使用されることを必要とすることなく、 key の文字列を終了させるために NUL を使用することができます。 dbminit で作成されたデータベースの fieldsep はタブになります。

ニュースシステムでの利用のため、key での大文字小文字のマッピング(例えば、 大文字から小文字へ)を使用することができます。 dbzfresh での cmap は単一の文字で、マッピングについてどの方式を使用するかを指定します。 使用可能な方式は次のとおりです。

    0   大文字小文字を区別する: マッピングしない

    B   0 と同じ

    NUL 0 と同じ

    =   大文字小文字を区別しない: 大文字も小文字も同じ

    b   = と同じ

    C   RFC822 での Message-ID の規則で、(例外を除いて)`@' より前は大文字小文字を区別し、 それより後は大文字小文字を区別しない

    ?   ローカルなデフォルトが何であっても通常の C

0 というマッピング方式(マッピングしない)では他の方式よりも速く、 多くのアプリケーションにおいて極めて正しい選択となります。 互換性を保つ必要がないのであれば、各検索毎に dbz でマッピングするよりも、データベースファイルでマッピングした key を保持するようにあらかじめ key をマッピングする方がより有効です。

歴史的な理由により、 fetch と store はその引数の key があらかじめマッピングされていることを前提としていて、 データベースファイルではマッピングされていないことを前提としています。 dbzfetch と dbzstore は内部でのすべてのマッピングを扱う以外は同じ処理をするため、 これを使用する場合にマッピングを考慮する必要はありません。

dbz では key についての確認を行うためにデータベースファイルへの参照に依存していますが、 ファイル中にはデータベースの value のみを保持します。 key についての情報が dbz ファイルに少しでも保持されていれば、検索時間は大幅に短縮され、 データベースファイルへの参照は最低限に抑えることができます。 fseek でのオフセットに未使用のビットがあるかどうかは``自由''であるため、 オフセットは key についての情報によって、 タグ とすることができます。 dbzfresh の引数である tagmask により、未使用のビットの位置を指定することができます。 tagmask は連続した 1 ビット列でなければいけません。 マスクでセットされているビットはオフセットでは未使用となっていなければいけません。 上記マスク( flag ビット)のビットは すべての オフセットで未使用(0)でなければいけません。 (dbz)store は key と value の対を保持しようとしても value にマスクにビットが立っているものは拒否します。 この制限とは別に、タグはユーザには見えません。 特別な場合として、 tagmask が 1 となっていれば、``タグは使用しない''ということになり、 巨大なデータベースファイルを使用したり、 特別なオフセットの表記を使用するシステムで使用します。

dbzfresh に渡す size を 0 とすれば、ローカルなデフォルトと同じになります。 通常、デフォルトでは 9 万から 10 万の key と value の対のテーブルが適当なものとなります。 cmap を 0 (NUL) とすれば、大文字小文字のマッピングをしないということを示す 0 という文字と同じになります。 ( ? という文字はローカルなデフォルトのマッピングを指定することになり、通常は C です。) tagmask を 0 とすれば、ローカルなデフォルトのタグマスクと同じになり、通常は 0x7f000000 です。 (先頭ビットは 32 ビットのオフセット内でフラグビットを指定し、次の 7 ビットはマスクを指定し、24MB までのデータベースファイルに適しています。) dbminit(name) をデータベースファイルを空にして呼ぶことは dbzfresh(name,0,'\t','?',0) を呼ぶことと同じです。

ニュースのようにデータベースが周期的に再生成される場合、 古いデータベースをもとに新しいデータベースのパラメータを決めるのが最も単純です。 これによって古いデータベースのこれまでの大きさを使用することができるようになり、 新しいデータベースの大きさは予想される大きさの差異を考慮したものとすることができます。 dbzagain は古いデータベースから次のデータベースを生成するための dbminit の変形です。 oldbase で指定するデータベースファイルは存在していなければいけません。 dbzagain では dbzfresh を呼ぶ場合に使用した同じフィールド区切り文字、大文字小文字のマッピング、 古いデータベースでのタグマスクと oldbase のデータベースと最後の 10 回の生成での最大のエントリ数に dbzsize を適用した結果に等しい size を用います。

多くのアクセスが同じプログラムからされる場合、 最初のハッシュテーブルがメモリ上にあれば、 dbz は非常に速くなります。 内部フラグが 1 の場合、 データベースがオープンされるとハッシュテーブルの読み込みを行い、 dbmclose により、(正常に読み込み、変更されていれば)ディスクに書き出します。 dbzincore ではフラグを newvalue に設定し(これは 0 あるいは 1 でないといけません)、戻り値は設定前の値となります。 これはすでにオープンされたデータベースの状態には影響しません。 デフォルトでは 0 です。 テーブルの読み込みはメモリ不足により失敗することがあります。 この場合、 dbz はデフォルトの動作を行おうとします。 store により、メモリ上のデータベースは dbmclose あるいは dbzsync を実行するまで(通常は)ファイルに出力されることは無く、 したがってシステムクラッシュや複数アクセスによる安全性は低く、 メモリ上のデータベースの使用は避けるべきです。

dbzsync によりすべてのバッファ等はファイルにフラッシュされます。 これは通常、 dbz を使用するプロセスが長時間起動しているような場合のシステムクラッシュや複数 アクセスに対する予防策として使用されます。 この処理は多少負荷となり、特にメモリ上でのデータベースではそうなります。

dbzcancel は write 待ちのバッファを削除します。 他の write ではすぐに処理されるため、 これは通常 in-core でのデータベースでのみ有用となります。 これの主な目的は fork した子プロセスが親プロセスのデータをディスクに dbmclose で出力しないようにするためです。

dbz がデバッグ付きでコンパイルされていれば(その分大きくなり遅くなりますが)、 dbzdebug により、内部フラグを知らせ(戻り値が元の値となり)ます。 内部フラグ(デフォルトでは 0 ですが、1 の場合)により標準出力にそのままの出力と内部データの出力のいずれかのデバッグ情報を出力します。

複数アクセスによる read はほぼ安全ですが、(内部的な)ロックは無いため、 複数アクセスによる更新は安全とはなりません。

データベースファイルはデータベースを作成したプロセスのバイトオーダでのデータで 構成され、別のバイトオーダのプロセスによるアクセスも可能です。 ただし、これは若干遅くなります。 dbzagain によりバイトオーダはわかります。 しかし、character セットの一致と同様に大きさと fseek の内部構造体を合わせることは必要です。

オープンしたデータベースは三つの stdio と関連するファイルディスクリプタを占有します。 四つ目はメモリ上のデータベースのために必要です。 メモリ上のデータベースを除いてメモリの消費量は無視できます( stdio のバッファを除く)。

関連項目
dbz(1)、dbm(3)

診断
関数の戻り値が int のものは、正常終了すれば 0 を、エラーの場合は -1 となります。 関数の戻り値が datum のものは、正常終了すれば dptr を、エラーの場合は NULL となります。 dbminit は関数の終了時に errno を適切に設定し、それ以外では設定するかどうかは保障されていません。 dbminit での errno が EDOM の場合、データベースが dbz フォーマットとして見なされていないことを意味します。

履歴
オリジナルの dbz は Jon Zeeff (zeeff@b-tech.ann-arbor.mi.us) によって記述されました。 その後、David Butler と Mark Moraes も contribute しました。 このドキュメントを含めた拡張のリワークは Cnews プロジェクトの一部として Henry Spencer (henry@zoo.toronto.edu) によって行われました。 ハッシュの使用は Peter Honeyman によって行われました。 日本語訳は kondou@uxd.fc.nec.co.jp によって行われました。

バグ
戻り値の datum のメンバの dptr はスタティックな領域を指し、その後の関数により上書きされます。

dbm とは異なり、すでに存在する key と value の対が新たな (dbz)store で同じ key で`上書き'されると、 dbz は間違った動作をします。 これを避けるために、 (dbz)fetch を使用してまずは重複が無いようにしなければいけません。 内部の最適化処理がこの検索の結果を記憶しているため、 これによるオーバヘッドは少ないものとなります。

dbminit の後、 chdir(2) を使用するとデータベースファイルを出力するまでの待機がエラーとなります。

RFC822 での大文字小文字のマッピングの実装は恐ろしく複雑な RFC822 のルールでの最初の予想のみとなっています。

dbzsize での最初の計算は部分的に速くはありません。

dbm の関数に delete 、 firstkey および nextkey を実装しなければいけません。

整数のオーバフローを捕捉する C の実装においては dbz は (dbz)store での表記可能な最大正数に等しい fseek のオフセットの保持は、バイアスされた表記が使用される場合にはオーバフローを起こすため、 失敗します。

dbzagain は古いデータベースでのオフセットがタグとしては大きすぎる場合、 それを察知すべきで、一致するようにタグマスクを切り詰めるべきです。

dbzcancel によって発生する問題に対応して dbz のファイルディスクリプタを close-on-exec とすることは良いことですが、それによって移植性を保つのは難しいでしょう。