データベースシステム(EDS)
はじめに
多くの人たちにとってEuphoriaプログラムを使用してデータベースにアクセスする方法に関心があります。この人たちはEuphoriaから有名ブランドのデータベース管理システムにアクセスしたいか、データを格納するために単純で、簡単に使えるEuphoria指向のデータベースのどちらかを必要としています。 EDSは後者です。それはEuphoriaで利用可能な単純で非常に柔軟なデータベースシステムを提供します。
EDSデータベースの構造
EDSでは、データベースは拡張子.edbを持つ単一のファイルです。EDSデータベースは0以上のテーブルをもちます。テーブルごとに名前があり、0以上のレコードをもちます。各レコードはキー部分と、データ部分から構成されます。キーは任意のEuphoriaオブジェクトにできます。 - アトム、シーケンス、深い入れ子構造のシーケンス。同様にデータは任意のEuphoriaオブジェクトにできます。キーまたはデータに対する構造または大きさの制限はありません。テーブル内の全てのキーは固有のものが与えられます。すなわち、同一テーブル内にある2つのレコードには同じキー部分を持つことができません。
テーブル内のレコードはキーの値を元にして昇順で格納します。キーからレコードを参照するときは効率的なバイナリサーチが使用されます。テーブル内の現在のレコード番号が判明しているのであれば、検索を行うことなく、直接レコードにアクセスできます。レコード番号は整数であり1からテーブル(現在のレコード総数)の長さまでとなっています。レコード番号を増加させることにより、全てのレコードをキーの順序で効率的に渡り歩くことができます。しかし新しいレコードを挿入するか、既存のレコードを削除したときは常にレコード番号が変更されることに注意してください。
キーとデータ部分は圧縮形式で格納されますが、浮動小数点数まはた他の全てのEuphoriaデータを保存したり復元してもデータの欠落はありません。
database.eはWindows, DOS, LinuxまたFreeBSDのいずれでも動作します。EDSデータベースファイルはコピーすることができ、さらにLinux/FreeBSDおよびDOS/Windows上で動作するプログラムで共有することができます。
なお、"テキスト"または"アスキー"モードでは改行文字が変更されてしまうため、正確にバイトがコピーされるようにするために"バイナリ"モードを使用してコピーしてください。
用例:
database: "mydata.edb"
first table: "passwords"
record #1: key: "jones" data: "euphor123"
record #2: key: "smith" data: "billgates"
second table: "parts"
record #1: key: 134525 data: {"hammer", 15.95, 500}
record #2: key: 134526 data: {"saw", 25.95, 100}
record #3: key: 134530 data: {"screw driver", 5.50, 1500}
どのようにキーとデータの意味を解釈するかは利用者に委ねられています。これによりEuphoriaの精神を保持したまま、完全な柔軟性を持たせることができます。
その他のほとんどのデータベースシステムとは違い、EDSのレコードは固定値のフィールドまたは、事前にフィルードの最大長を定義すら必要としません。
ほとんどの場合でレコードに対して任意のキーの値はありません。この場合はキーとして固有の無意味な整数を作成する必要があります。レコード番号により常にデータにアクセスできることを思い出してください。それは特定のフィールド値をループで通過してレコードを探し出すのが簡単になります。
データへのアクセス方法
渡す必要のある引数の数を減らすために、現在のデータベースと現在のテーブルという概念があります。ほとんどのルーチンは自動的にこれら現在の値を使用します。通常はデータベースファイルを開く(か作成する)ことから開始して、どのテーブルで作業を行うか選択します。
db_find_key()を使用してレコード番号を見つけ出すことができます。これは効率的なバイナリサーチを使用します。ほとんどのかのレコードレベルルーチンは引数としてレコード番号が与えられることを想定して実装されています。番号が与えられるのであれば、任意のレコードへのアクセスは非常に迅速に行うことができます。レコード番号1から開始して、db_table_size()に返された返値により終端になるまでループすることにより、すべてのレコードにアクセスすることができます。
記憶領域の再利用方法は?
例えばレコードのような何かを削除したとき、将来利用するときのために、そのアイテムの領域は開放リストに記録されます。隣接した空き領域は非常に大きな空き領域に統合されます。より多くの空き領域が必要であり、さらに適切な空き領域が開放リストに無いとき、ファイルの大きさは増大します。現在のところ自動的にファイルの大きさを圧縮する方法ありませんが、db_compress()を使用することにより未使用領域を削除して、データベースを完全に整理しなおして書き出します。
セキュリティ / マルチユーザアクセス
このリリースでは他のプロセスからの安全ではないアクセスを防止するために全てのデータベースをロックするだけの単純な方法を提供しています。
拡張性
内部ポインタは4byteです。データベースファイルの大きさは理論上4GBに制限されます。実際の制限はEuphoriaで使用される様々なC言語ファイル関数の制限により2GBまでとなります。たくさんの利用者からの要望があれば将来はEDSデータベースは2GB以上を扱えるように拡張されるでしょう。
現在のアルゴリズムでは、現在のテーブル内のレコード当たり4byteのメモリーを確保します。そのためディスク上の100万件のレコードにつき最低4MBのRAMを必要とします。
キーの検索に使用するバイナリサーチは非常に大きなテーブルでは合理的に動作します。
挿入と削除をするとテーブルの長さは僅かに長くなり、より大きくなります。
低予算規模の設備でも、ディスクスペースのオーバーヘッドの負荷なしで非常に小さいデータベースを作成することは可能です。
免責事項
バックアップせずに重要なデータを格納しないでください。RDSはEDSの使用の結果により発生した各種損害またはデータ破壊・損失の責務を一切負いません。
警告
.edbファイルはバイナリファイルであり、テキストファイルではありません。ftp経由で他のマシンに.edbファイルを転送するときはバイナリモードを使用する必要があります。.edbファイルをエディタに読み込ませて誤って保存してしまうような事態を避けるように気をつける必要もあります。推奨されない行為ですが.edbファイルを直接Euphoiraのopen()関数で開くならばテキストモードではなく、必ずバイナリモードを使用する必要があります。これらの規約に従わなかったことにより最悪の結果となり10(ラインフィード)または13(キャリッジリターン)のバイトは変更されてしまい、潜在的または非潜在的にデータベースの主な構造が破壊されます。
データベースルーチン
詳細は後述にあり、さらにオブジェクトを渡したり返すために、オブジェクトの種類を示す次の接頭辞が使用されます:
db_create
書式: |
include database.e
i1 = db_create(s, i2)
|
解説: |
データベースを新しく作成します。新しいデータベースはパスsにより与えられたファイル名で作成されます。 i2は作成したファイルに適用するロック方法を指定します。i1はエラーコードであり成功または失敗を返します。i2の値にはDB_LOCK_NO (ロック無し)またはDB_LOCK_EXCLUSIVE (排他的ロック)のいずれかを指定できます。i1がDB_OKならば新しいデータベースの作成は成功しています。このデータベースは他のデータベースの操作を適用することが可能な現在のデータベースとなります。
|
注釈: |
パスsの拡張子.edbで終わっていないのであれば、自動的に付加されます。
既にデータベースが存在するときは、上書きされません。db_create()はDB_EXISTS_ALREADYを返します。
データベースファイルにバージョン番号が格納されるためデータベースソフトの将来のバージョンでフォーマットを判別することができ、恐らく読み込んでから取り扱うことができるようになります。
|
用例: |
|
|
if db_create("mydata", DB_LOCK_NO) != DB_OK then
puts(2, "Couldn't create the database!\n")
abort(1)
end if
|
関連事項: |
db_open,
db_close
|
db_open
書式: |
include database.e
i1 = db_open(s, i2)
|
解説: |
既存のEuphoriaデータベースを開きます。sにはデータベースが含まれているファイルを指定します。i1には返り値として成功または失敗が返されます。i2はファイルを開いている間のデータベースのロック方法を指定します。このデータベースは他のデータベースの操作を適用することが可能な現在のデータベースとなります。
返値は次のとおりです:
|
|
global constant DB_OK = 0 -- 成功
DB_OPEN_FAIL = -1 -- ファイルを開けません。
DB_LOCK_FAIL = -3 -- 指定された方法で
-- ファイルをロックできません。
|
注釈: |
利用可能なロックの方式は次のとおりです: DB_LOCK_NO(ロックなし)、DB_LOCK_SHARED(読み取り専用の共有ロック)、DB_LOCK_EXCLUSIVE(読み書きアクセス可能な排他ロック)があります。DB_LOCK_SHAREDはLinux/FreeBSD専用です。これはデータベースを読むことは許されますが、書き込むことは一切できません。WIN32またはDOS32でDB_LOCK_SHAREDを指定したときはDB_LOCK_EXCLUSIVEに読み代えられて扱われます。
ロックが失敗したときは、プログラムは数秒後にロックを再試行します。このとき別のプロセスがデータベースにアクセス中であることが考えられます。
一般的にDOSプログラムがロック中のデータベースプログラムにアクセスしてしまったときは"致命的エラー"メッセージを返します。
|
用例: |
|
|
tries = 0
while 1 do
err = db_open("mydata", DB_LOCK_SHARED)
if err = DB_OK then
exit
elsif err = DB_LOCK_FAIL then
tries += 1
if tries > 10 then
puts(2, "too many tries, giving up\n")
abort(1)
else
sleep(5)
end if
else
puts(2, "Couldn't open the database!\n")
abort(1)
end if
end while
|
関連事項: |
db_create,
db_close
|
db_select
書式: |
include database.e
i = db_select(s)
|
解説: |
新規または既に開いているデータベースを現在のデータベースとして選択します。
以降のデータベース操作は本データベースに適用されます。sはdb_open()またdb_create()に与えられたデータベースファイルのパスです。iは成功(DB_OK)または失敗を返値として返します。
|
注釈: |
あなたがデータベースを作成(db_create)または開いた(db_open)とき、それは自動的に現在のデータベースになります。以前と以後のデータベースを切り替えたいとき、恐らくレコードを他へコピーしたいときにdb_select()を使用します。
新しいデータベースを選択した後に、b_select_table()を使用してデータベース内のテーブルを選択する必要があります。
|
用例: |
|
|
if db_select("employees") != DB_OK then
puts(2, "Couldn't select employees database\n")
end if
|
関連事項: |
db_open
|
db_close
書式: |
include database.e
db_close()
|
解説: |
ロックを解除してから現在のデータベースを閉じます。
|
注釈: |
現在のデータベースでの処理を完了したときに、この手続きを呼び出します。これにより一切のロックが解除され、他プロセスからデータベースファイルにアクセスすることができるようになります。
|
関連事項: |
db_open
|
db_create_table
書式: |
include database.e
i = db_create_table(s)
|
解説: |
現在のデータベース内に新しいテーブルを作成します。テーブルの名前は文字列シーケンスsにより与えられ、現在のデータベースにある既存のテーブルとは全て同一ではありません。
|
注釈: |
作成されたテーブルは最初に0個のレコードを持ちます。これが現在のテーブルとなります。
|
用例: |
|
|
if db_create_table("my_new_table") != DB_OK then
puts(2, "Couldn't create my_new_table!\n")
end if
|
関連事項: |
db_delete_table
|
db_select_table
書式: |
include database.e
i = db_select_table(s)
|
解説: |
sにテーブル名を指定すると現在のテーブルになります。現在のデータベース内にテーブルがあるときはDB_OKを、その他はDB_OPEN_FAILをiに返値として返します。
|
注釈: |
全てのレコードレベルのデータベース処理は自動的に現在のテーブルに適用されます。
|
用例: |
|
|
if db_select_table("salary") != DB_OK then
puts(2, "Couldn't find salary table!\n")
abort(1)
end if
|
関連事項: |
db_create_table,
db_delete_table
|
db_rename_table
書式: |
include database.e
db_rename_table(s1, s2)
|
解説: |
現在のデータベースにあるテーブルの名前を変更します。sには現在のテーブル名を、s2には新しいテーブル名を指定します。
|
注釈: |
テーブル名として現在のテーブル、または現在のデータベースにある他に存在するテーブルの名前を変更できます。s1が現在のデータベースにあるテーブル名ではないとき、またはs2が現在のデータベースにおいて既存のテーブル名と同じときはエラーになります。
|
関連事項: |
db_create_table
db_select_table
db_delete_table
|
db_delete_table
書式: |
include database.e
db_delete_table(s)
|
解説: |
現在のデータベースにあるテーブルを削除します。sにはテーブル名を指定します。
|
注釈: |
全レコードは削除されテーブルで使用されていた全領域は開放されます。もしテーブルが現在のテーブルが指定されたならば、現在のテーブルは未定義になります。
sにテーブル名が指定されていないときは、何も処理を行いません。
|
関連事項: |
db_create_table
db_select_table
|
db_table_list
書式: |
s = db_table_list()
|
解説: |
現在のデータベースにある全テーブル名をシーケンスで返します。sの各要素はテーブル名を含むシーケンス文字列です。
|
用例: |
|
|
sequence names
names = db_table_list()
for i = 1 to length(names) do
puts(1, names[i] & '\n')
end for
|
関連事項: |
db_create_table
|
db_table_size
書式: |
include database.e
i = db_table_size()
|
解説: |
現在のテーブル内にある現在のレコードの総数を返します。
|
用例: |
|
|
-- 現在のテーブル内にある全てのレコードを検索します。
for i = 1 to db_table_size() do
if db_record_key(i) = 0 then
puts(1, "0 key found\n")
exit
end if
end for
|
関連事項: |
db_select_table
|
db_find_key
書式: |
include database.e
i = db_find_key(x)
|
解説: |
現在のテーブル内のレコードからキー値を検索します。
見つかったときは、レコード番号が返されます。見つからない、キーが既に使用されている、または挿入されているならば、レコード番号には負数が返されます。
|
注釈: |
キーを現在のテーブルから検索するために高速なバイナリサーチが使用されます。比較回数はテーブル内のレコード総数の対数に比例します。
最初と最後のキーの値の範囲を検索することによりレコードの範囲を選択することができます。キーの値が存在しないとき、最低でもどこに存在するのかを表す負数が返されます。すなわち、どこレコードにあるキーが"GGG"より大きく"MMM"より少ないかを仮定することができます。そのキーがレコードで番号27として挿入されており"MMM"に対して-27で"MMM"がレコードにあることを意味します。これは全てのレコードある >= 5と < 27が適合するということを迅速に報告します。
|
用例: |
|
|
rec_num = db_find_key("Millennium")
if rec_num > 0 then
? db_record_key(rec_num)
? db_record_data(rec_num)
else
puts(2, "Not found, but if you insert it,\n")
printf(2, "it will be #%d\n", -rec_num)
end if
|
関連事項: |
db_record_key,
db_record_data,
db_insert
|
db_record_key
書式: |
include database.e
x = db_record_key(i)
|
解説: |
現在のテーブル内のレコード番号iのキーの一部を返します。
|
注釈: |
各レコードはキー部分とデータ部分から構成されています。各構成要素は全てのEuphoriaアトムまたはシーケンスを指定できます。
|
用例: |
|
|
puts(1, "The 6th record has key value: ")
? db_record_key(6)
|
関連事項: |
db_record_data
|
db_record_data
書式: |
include database.e
x = db_record_data(i)
|
解説: |
現在のテーブル内のレコード番号iのデータの一部を返します。
|
注釈: |
各レコードはキー部分とデータ部分から構成されています。各構成要素は全てのEuphoriaアトムまたはシーケンスを指定できます。
|
用例: |
|
|
puts(1, "The 6th record has data value: ")
? db_record_data(6)
|
関連事項: |
db_record_key
|
db_insert
書式: |
include database.e
i = db_insert(x1, x2)
|
解説: |
現在のテーブルに新しいレコードを挿入します。レコードのキーはx1でありレコードのデータはx2です。x1およびx2の双方は任意のEuphoriaデータオブジェクト、アトムまたはシーケンスを指定できます。返値iがDB_OKならばレコードは挿入されています。
|
注釈: |
テーブル内の全てのキーは固有のものである必要があります。レコードに同じキーの値が既に存在するときはdb_insert()の処理は失敗してiにDB_EXISTS_ALREADYを返します。
|
用例: |
|
|
if db_insert("Smith", {"Peter", 100, 34.5}) != DB_OK then
puts(2, "insert failed!\n")
end if
|
関連事項: |
db_find_key,
db_record_key,
db_record_data
|
db_delete_record
書式: |
include database.e
db_delete_record(i)
|
解説: |
現在のテーブルからレコード番号iを削除します。
|
注釈: |
レコード番号iは、1から現在のテーブルのレコード番号までの整数値である必要があります。
|
用例: |
|
|
db_delete_record(55)
|
関連事項: |
db_insert,
db_table_size
|
db_replace_data
書式: |
include database.e
db_replace_data(i, x)
|
解説: |
現在のテーブルにあるレコード番号iのデータの一部をxと置き換えます。xにはEuphoriaのアトムまたはシーケンスをを指定できます。
|
注釈: |
レコード番号iは、1から現在のテーブルのレコード番号までの整数値である必要があります。
|
用例: |
|
|
db_replace_data(67, {"Peter", 150, 34.5})
|
関連事項: |
db_delete_record
|
db_compress
書式: |
include database.e
i = db_compress()
|
解説: |
現在のデータベースを圧縮します。現在のデータベースの全未使用領域を除去してから新しいファイルへコピーします。成功したときはiにDB_OKが返され、圧縮された新しいデータベースは以前と同じ名前のままになります。現在のテーブルは未定義です。元の圧縮されていないファイルの名前は拡張子を.t0 (or .t1, .t2 ,..., .t99)に変更してバックアップされます。非常に稀ですが圧縮が失敗した場合はデータベースは変更されず、バックアップも作成されません。
|
注釈: |
データベースからアイテムを削除すると、データベースファイル内に空き領域のブロックが作成されます。システムは、これらのブロックの状態を記録して新しいデータを格納するために、これらの再利用を試みます。db_compress()は現在のデータベースから空き領域を取り除いてからコピーします。したがってデータベースファイルの大きさは激減することがあります。
バックアップのファイル名の拡張子が.t99に到達したときは、いくつかファイルを削除する必要があります。
|
用例: |
|
|
if db_compress() != DB_OK then
puts(2, "compress failed!\n")
end if
|
関連事項: |
db_create
|
db_dump
書式: |
include database.e
db_dump(fn, i)
|
解説: |
既に開いているEuphoriaデータベースの内容を出力します。内容はファイルまたはデバイスfnに出力されます。全テーブルの全レコードが出力されます。iが0以外ならば低レベルのバイトによるバイトダンプも出力されます。低レベルダンプはEuphoriaデータベースの内部構造を熟知している方にだけ意味があります。
|
用例: |
|
|
if db_open("mydata", DB_LOCK_SHARED) != DB_OK then
puts(2, "Couldn't open the database!\n")
abort(1)
end if
fn = open("db.txt", "w")
db_dump(fn, 0)
|
関連事項: |
db_open
|
db_fatal_id
書式: |
include database.e
db_fatal_id = i
|
解説: |
作成したの致命的エラーハンドラーを導入することにより特定の致命的なデータベースエラーを捕まえることができます。これは単にグローバル変数db_fatal_idに手続きのルーチンidを代入するだけです。手続きは引数としてシーケンスを一つ持つ必要があります。特定のエラーが発生すると手続きの引数にエラーメッセージが渡されて呼び出されます。必ず手続きはabort()を呼び出して終了する必要があります。
|
用例: |
|
|
procedure my_fatal(sequence msg)
puts(2, "A fatal error occurred - " & msg & '\n')
abort(1)
end procedure
db_fatal_id = routine_id("my_fatal")
|
関連事項: |
db_close
|
|