手のひらサイズの構造化データベース
[ 用語説明
| インストール
| 基本的な使い方
| Mk4py リファレンス ]
ギョーカイ向けの解説 - Metakit
は組込み型データベースで Unix、Windows、Macintosh などのプラットフォーム上で動作します。アプリケーションの開発にあたり効率的なデータ保存をプラットフォームに依存しないかたちで可能にします。しかも面倒なランタイムのイントールは必要ありません。データモデルでいえば Metakit は RDBMS と OODBMS とフラットファイル・データベースの中間に位置しますが、これらの何れともまったく異なるものです。
技術面での特徴 - すべてのデータを可変長で保存しながら、効率的な行アクセスを可能にします。既存のデータベースの構造変更は新たな形式でオープンし直すだけで済みます。すべての変更処理がトランザクションとして扱われ、C++、Python、Tcl それぞれで書いたソフトウェアを融合させたり、共存させることが可能です。これ以上柔軟なデータベースはほかにありません。
Python - Python
の拡張モジュールは "Mk4py" といいます。初期のバージョンに比べ、より低レベルの C++ API が利用可能になっています。また C++ のラッパーには Gordon McMillan の SCXX を使用しています。
Mk4py 2.4.9.2 - は安定版の最新リリースです。ホームページ からは Unix、Windows、Macintosh それぞれのコンパイル済み共有ライブラリがダウンロードできます。Metakit ソースコードにはこのドキュメントのほか、Mk4py の C++ ソースコード、Metakit の memo フィールドを使って効率的かつフェイルセーフな I/O (その結果としての pickling) を実現するクラス "MkMemoIO.py" などが含まれています。
バージョン 2.01 からの変更点 - MK コアが大幅に変更されています。
- commit-aside モードと commit-extend モードが新たに加わりました(storage オブジェクトの項を参照)。
- パフォーマンスが改善されました。その大半はファイルフォーマットのスケーラビリティ改善に因るものです。
- データ型 "M" (memo) は廃止され、代わってより大きなアイテムを扱える "B" が使えるようになりました。
- hash/ordered/blocked ビューを効率改善のための内部的変更をおこないました。
- Mk4py メンバ変数の storage、view、property は小文字を使用するようになりました。
- "2.x.y" のような文字列を持つ属性として Mk4py.version を追加しました。
- "import metakit" のようにして使うメイン・ラッパーとして "metakit.py" を追加しました。
ライセンスとサポート - Metakit バージョン2以降は X/MIT スタイルのオープンソースライセンスで配布されます。また企業向けライセンスと商用サポートも提供しています。詳細はライセンスのページを参照してください。
貢献者 - Gordon McMillan はオリジナルの Mk4py だけでなく、さらにパイソニックなインターフェースを書いてくれました。Christian Tismer は当初の設計を越える改善を提供してくれました。また GvR と Python コミュニティはスクリプティングというものを興味深い水準に押し上げてくれました。
更新情報 - このドキュメントの最新バージョンは
http://www.equi4.com/metakit/python.html にあります。
技術的な背景よって同じものごとが別の言葉で表現される場合があります。たとえば
テーブル、
リスト、
コレクション、
配列、
シーケンス、
ベクター などの言葉はいずれも類似した概念を指しています。混乱を避けるために Metakit ではシンプルな(そして明確であることを意図した)用語を用いています。
Metakit で採用している用語は以下の通りです。
- ビュー (view) とはインデックス付け可能な行 (row) の集合です(レコードの集合としてのテーブル、あるいは要素の集合としての配列と同等の意味です)。
- インデックスとはビューの中で行の位置を特定するための値です(先頭行のインデックスが 0 となります)。
- 各ビューは順序化されたプロパティの集合を持っており、これは各行のデータの値を参照するために用いられます。
- Metakit で特定のデータの値を差し示す場合はこれら(ビュー、インデックス、プロパティ)を組み合わせて示します。
- 上記を別の言葉で表現するとそれぞれ (表、行位置、カラム位置) となります。
- データの型としては文字列(string)、数値(numeric)、型なしデータ(untyped data)、のほかサブビューと呼ばれる入れ子のビューが利用可能です。
Metakit の動作に関する補足説明。
- ビューは均質です。同一ビュー内の各行はいずれも同じ型のデータを保持しています。
- 上記は同一ビュー内のサブビューが常に同じ構造を持つことを意味しています。
- 行にはビューの一部としてファイルに保存されるものと一時的な(参照されなくなると消去されてしまう)もの、この2種類が存在します。
- 最新のバージョンを http://www.equi4.com/pub/download.html からダウンロードします。
- たとえば Unix ならば Unix 用のコンパイル済み拡張ライブラリを "Mk4py.so" にリネームします(Windows や Mac の場合も同様に各プラットフォーム用のファイルを使用します)。
- Mk4py 拡張モジュールのラッパー "metakit.py" を Python モジュール検索パスとして設定されている場所、たとえば site-packages ディレクトリ(あるいは実行時のカレントディレクトリ)に置きます。
- "demo.py" を使って簡単な実行テストをおこなってください。テストにパスすると結果が表示されます。
データベースの作成:
import metakit
db = metakit.storage("datafile.mk",1)
ビュー(Metakit 用語でテーブルの意味)の作成:
vw = db.getas("people[first:S,last:S,shoesize:I]")
2つの行(Metakit 用語でレコードの意味)を追加:
vw.append(first='John',last='Lennon',shoesize=44)
vw.append(first='Flash',last='Gordon',shoesize=42)
ファイルに対する変更内容をコミット:
db.commit()
people の内容を一覧表示:
for r in vw: print r.first, r.last, r.shoesize
people の内容をラストネームでソートして表示:
for r in vw.sort(vw.last): print r.first, r.last, r.shoesize
ファーストネームが 'John' のデータを一覧表示:
for r in vw.select(first='John'): print r.first, r.last, r.shoesize
- モジュール関数
- Storage オブジェクト
- View オブジェクト
- 派生ビュー
- ビューの操作
- ビューのマッピング
- Rowref オブジェクト
- Property オブジェクト
1. モジュール関数
以下はモジュール・レベルの関数です。次の例のようにインポート文の後で使用します。
import metakit
print metakit.version
書式
-
db = metakit.storage()
- インメモリ・データベースを作成します(コミット/ロールバック不可)。
- db = metakit.storage(file)
- ファイル・オブジェクトを指定してストレージを作成します。
- db = metakit.storage(name, mode)
- 既存のファイルをオープンします。読み書きの mode がゼロ以外で、かつファイルが存在しないときは新規に作成します。mode が 0 ならリードオンリー、1 にすると読み書き可能なモード(共有不可)、2 は commit-extend モードでオープンします(mode が 1 か 2 なら、必要に応じてファイルを新規作成します)。
- vw = metakit.view()
- どのストレージにも属しない単独のビューを作成します。
- pr = metakit.property(type, name)
- プロパティ(ビューに関連付けた場合はカラム)を作成します。
- vw = metakit.wrap(sequence, proplist, byPos=0)
- Python のシーケンスをビューとしてラップします。
説明
storage- 引数をひとつだけ指定する場合、ファイル・オブジェクトはファイル I/O を実装したクラスではなく、実際のファイルのものでなくてはなりません。storage オブジェクトが破壊されると(たとえば 'db = None' などとした場合)、関連付けられたデータファイルはクローズされるので、使用中は storage オブジェクトへの参照を維持し続けるように注意してください。
wrap- Python のシーケンスをラップするときに使用します。シーケンスの要素は辞書またはプロパティ名と同名の属性を持つオブジェクトであることが前提です。ただし byPos にゼロ以外の値を設定すると、リストやタプルを要素にすることができます。なおその場合は位置指定によるデータ・アクセスになります。この方法で作成したビューは join などのビュー操作が可能になります。
2. Storage オブジェクト
書式
- vw = storage.getas(description)
- storage オブジェクト内にビューの作成とその定義、あるいは再定義をおこないます。
- vw = storage.view(viewname)
- 既存のビューから内容を取り出す一般的な方法です。
- storage.rollback(full=0)
- データとその構造を最後にコミットしたときの状態に戻します。commit-aside モードの "full" ロールバックはオリジナル・ファイルの状態までし、aside ファイルの内容を破棄します。
ロールバックを実行するとそれまでの View オブジェクトは使用できなくなります(storage オブジェクトに対し、もう一度 view または getas メソッドを使ってビューを取得し直す必要があります)。さらに full ロールバックを実行したときは aside ストレージもメイン・ストレージから切り離されてしまうので、aside メソッドを使ってメイン・ストレージに関連付けし直す必要があります。再関連付けをしなければ、それ以降のコミット処理は直接メイン・ストレージに書き込まれてしまいます。
- storage.commit(full=0)
- ディスクのデータやその構造に対しておこなわれた変更内容を不可逆的に確定(コミット)します。commit-aside モードの "full" コミットは現在の状態をオリジナル・ファイルに書き出し、aside データファイルの内容をクリアします。
- ds = storage.description(viewname='')
- getas に使われた定義文字列を返します。
- vw = storage.contents()
- ストレージのメタデータとして保存されている内容をビューにして返します。
- storage.autocommit()
- storage オブジェクトを消去するとき、それまでに実行された変更内容を自動的にコミットします。
- storage.load(fileobj)
- storage の内容をファイル(あるいは read メソッドをサポートしている他のオブジェクト)の内容で置き換えます。
- storage.save(fileobj)
- storage オブジェクトの内容をファイル(あるいは write メソッドをサポートしている他のオブジェクト)に書き出します。
説明description- ビュー名が指定されていない場合は storage 全体の定義文字列を返します。指定されたときはそのトップレベルのビューのものだけを返します。
getas- 副作用: ビューの構造も変更されます。
注意: 通常新たなビューを作成するときか、既存のビューの構造変更をするときに使用します。
定義文字列は以下のような形式となります。
"people[name:S,addr:S,city:S,state:S,zip:S]"
上記は "<ビュー名>[<プロパティ名>:<プロパティの型>...]" という形式になっています。
プロパティに指定できる型は以下うち一つだけです。
| I | | adaptive integer (Python の int に変換される) |
| L | | 64 ビット integer (Python の long に変換される) |
| F | | C 言語の float (Python float に変換される) |
| D | | C 言語の double (Python の float と同じ) |
| S | | C 言語のヌルを終端とした文字列 (Python の文字列に変換される) |
| B | | C 言語のバイト配列 (Python の文字列に変換される) |
注意: 定義文字列の中に空白文字を入れてはいけません。
Python バインディングにおいて S と B の違い、つまりゼロを終端とする文字列には S を使用することは C/C++ の場合ほど重要ではありません。Python における両者の主な違いは、長さ 0 バイトのデータを扱う場合には B を使わなければならないこととソート順が S (stricmp) と B (memcmp) では異なることです。今後 Unicode/UTF-8 が S プロパティで重要な役割を果すことになるため、テキストデータの保存には S を使うことを推奨します。
3. View オブジェクト
View オブジェクトはスライシングや連結など、シーケンス (list) のメソッドを実装しています。View オブジェクトは順序化された"プロパティ"を持つ"行"のシーケンスのようにして扱うことが可能です。なおインデクシング (getitem) を使うと行のコピーではなく参照を返すようになっています。
r = view[0]
r.name = 'Julius Caesar'
view[0].name # 'Julius Caesar' を返す
スライスでは元のビューと連動する変更可能なビューを返します。特別な使い方として、スライスを使って元のビューを同じ構造の新たな空のビューを作ることもできます。
v2 = v[0:0]
スライスを使ってビューを変更する。
v[:] = [] # empties the view
View オブジェクトは getattr もサポートしており、プロパティを返すようになっています(例: view.shoesize で shoesize カラムの内容を参照できます)。ビューは view = db.view('inventory') のようにして storage から作成するほか、他のビューから生成することも可能です(select、sort、flatten、join、project 等を参照)。カラムのない空のビューを作りたいときは vw = metakit.view() を実行します。
書式
- view.insert(index, obj)
- オブジェクトを行に強制変換し、ビューの index で指定した位置に挿入します。
- ix = view.append(obj)
- オブジェクトを行に強制変換し、ビューに追加します。
- view.delete(index)
- index で指定した行をビューから削除します。
- lp = view.structure()
- property オブジェクトのリストを返します。
- cn = view.addproperty(fileobj)
- 新たなプロパティを定義します。戻り値はカラムの位置です。
- str = view.access(byteprop, rownum, offset, length=0)
- バイト・プロパティのコンテンツ全体(あるいは一部)を取得します。
- view.modify(byteprop, rownum, string, offset, diff=0)
- バイト・プロパティのコンテンツ全体(あるいは一部)を保存します。diff の値が 0 より小さいとき (<0) は削除、0 より大きいとき (>0) はインサートをおこないます。
- n = view.itemsize(prop, rownum=0)
- アイテムのサイズを返します(S および B 型の場合 rownum の指定が必要)。integer 型のプロパティで結果として返される値 -1/-2/-4 はそれぞれ 1/2/4 ビットであることを意味します。
- view.map(func, subset=None)
- func で指定された関数をビューの各行に適用します。subset の指定がある場合はそれに一致する行に対してのみ処理をおこないます。関数は "func(row)" の形式で行の変更処理をおこなうようになっている必要があります。また subset にはビューのサブセットを指定しなければなりません。たとえば次の例のように使います。
"customers.map(func, customers.select(...))".
- rview = view.filter(func)
- func で指定した関数を満足する行をインデックス付きのビューとして返します。関数は "func(row)" の形式で、除外すべき行の場合は false を返すようになっていなければなりません。
- obj = view.reduce(func, start=0)
- ビューの各行に対し、func(row, lastresult) 形式の関数を適用した結果を返します。
- view.remove(indices)
- indices で指定したサブセットのインデックスに一致するすべての行をビューから削除します。インデックスがユニークである必要のないこと、ビューがソートされていなくても良いという点が minus とは異なります。
- rview = view.indices(subset)
- subset で指定した内容に一致する行をインデックスを含むビューとして返します。
- rview = view.copy()
- ビューのコピーを返します。
説明addproperty- このメソッドを使って追加したプロパティはコミットしても保存されません。永続的なプロパティを作成するにはビューを定義(または再定義)する際 storage.getas(...) を使う必要があります。
append- (colname=value...) 形式のキーワード引数をサポートしています。
insert- オブジェクトから行への強制変換は次のようにビューのカラム名を中心におこなわれます。
| 辞書 |
| (カラム名 -> キー) |
| インスタンス |
| (カラム名 -> 属性名) |
| リスト |
| (カラム番号 -> リストのインデックス) - 注意! |
4. 派生ビュー
書式
- vw = view.select(criteria...)
- criteria として与えられた条件に一致するフィールドを持つ行をビューにして返します。
- vw = view.select(low, high)
- 指定範囲の行をビューにして返します(inclusive)。
- vw = view.sort()
- "ネイティブ"なオーダーでビューをソートします。例: キーのオーダー定義
- vw = view.sort(property...)
- 指定したプロパティでソートを実行します。
- vw = view.sortrev((propall...), (proprev...))
- ビューを降順にソートします。オプションでソートのキーにするプロパティを指定できます。
- vw = view.project(property...)
- 名前付きカラムのビューを返します。
説明
select- 条件に一致する内容のサブセットを返す検索の例。
result = inventory.select(shoesize=44)
result = inventory.select({'shoesize':40},{'shoesize':43})
result = inventory.select({},{'shoesize':43})派生ビューは元のビューと"連動"しています。派生ビューに対してほどこした変更内容は元のビューにも反映されます。
sort- ソート済みの列を返す例。
result = inventory.sort(inventory.shoesize)
ソート済みのビューに対する変更に関しては上記 select の説明を参照してください。
5. ビュー操作
書式
- vw = view.flatten(subprop, outer=0)
- 入れ子状のビューからひとつの'フラットな'ビューを生成します。
- vw = view.join(view, property...,outer=0)
- join するビューは両方とも同じプロパティ(カラム)名を同じ型で持っているものでなければなりません。
- ix = view.find(criteria..., start=0)
- 見つかった行のインデックスを返します。見つからなかったときは -1 を返します。
- ix = view.search(criteria...)
- (ネイティブ・ビューのオーダーで)二分探索を実行し、一致部分または挿入ポイントを返します。
- ix, cnt = view.locate(criteria...)
- 二分探索を実行し、位置とカウントをタプルで返します(カウントは 0 のこともあり得ます)。
- vw = view.unique()
- 重複する行を除いた行の複製を新たなビューとして返します。
- vw = view.union(view2)
- view と view2 の和集合を新たなビューとして返します。
- vw = view.intersect(view2)
- view と view2 の積集合を新たなビューとして返します。
- vw = view.different(view2)
- view と view2 の排他的論理和を新たなビューとして返します。
- vw = view.minus(view2)
- 集合演算で view - view.intersect(view2) と表記される差集合を新たなビューとして返します。
- vw = view.remapwith(view2)
- 行を view2 の先頭のプロパティとして割当て直します。
- vw = view.pair(view2)
- 各行を平行に二つ一組に連結します。
- vw = view.rename('oldname', 'newname')
- 一つのプロパティの名前を変更し、派生ビューとして返します。
- vw = view.product(view)
- 二つのビューのデカルト積を返します。
- vw = view.groupby(property..., 'subname')
- 指定したプロパティでグループ化し、subname に指定したサブビューを作成します。
- vw = view.counts(property..., 'name')
- 指定したプロパティでグループ化し、残りはカウント・フィールドにします。
説明
find- view[view.find(firstname='Joe')] は view.select(firstname='Joe')[0] と同等ですが、次のように "start" キーワードを使うともっと速くなります。 view.find(firstname='Joe', start=3)
6. ビューのマッピング
書式
- vw = view.hash(mapview, numkeys=1)
- 先頭からの N フィールドを使いハッシュ・マッピングを生成します。
- vw = view.blocked(blockview)
- すべてのセグメントが単一の大きなビューに見える"ブロック"ビューを生成します。
- vw = view.ordered(numkeys=1)
- 先頭からの N フィールドをソート・キーとみなし、維持することを定義します。ブロック・ビューと合わせてこれを定義した場合、二段階の btree が実現されることになります。
説明
blocked- このビューはたとえ行が複数のブロックに保存されていたとしても、単一で大きなフラットなビューのようにふるまいます。ブロック・サイズとブロック数のトレードオフ関係が自動的に再計算され、最適な状態になります。
このビューはサブビューが必要な構造であっても、単一のプロパティを持つものとして定義されていなければなりません。
hash- このビューは高速なキー探索のために特別なハッシュマップのビューを生成します。常にビューの先頭プロパティがキーとなります。
最初にハッシュ・ビューを使うときそのビューにどんな行があっても問題が起きないように、引数に指定する mapview は空でなくてはなりません。その後は、ハッシュ・マッピング・レイヤーを通じてしか元のビューやマップ・ビューは変更できなくなります。マップ・ビューの構造定義は "_H:I,_R:I" となっている必要があります。
このビューは変更可能ですが、インサートやキー・フィールドの変更をおこなうと、行の再配置によりハッシュ値がユニークでなくなる場合もあります。注意: このような変更処理をして行のキーが重複してしまった場合、もう一方の行はビューから削除されます。
ordered- これは元のビューと同一のビューで、Metakit に対し、先頭から numKeys 個のプロパティをキーにしてソートすることを意味します。これにより view.find() の検索条件にキーが含まれている場合、二分探索が使われるようになります(検索結果のビューはソートされていませんが、これに対する find は効率的におこなわれます)。
このビューは変更可能ですが、インサートやキー・フィールドとなっているプロパティの変更をおこなうと、行の再配置によりソート・キーの保守の必要が生じます。注意: このような変更によりキーが他の行のキーと重複してしまった場合、もう一方の行はビューから削除されます。
このビューは view.blocked() と組み合わせて2段階の btree 構造にすることも可能です。
7. Rowref オブジェクト
RowRef オブジェクトは属性(カラム)を設定したり、それを取得することができます。
RowRef オブジェクトは (view, ndx) 形式のタプルとしてカプセル化ししたものです。
ビューから取得するには rowref = view[33] のようにします。
8. Property オブジェクト
プロパティには属性名 id と type があります。
例: p = metakit.property('I', 'shoesize')
プロパティはカラムを指すために使われますが、両者は同じものではないことに注意してください。たとえばある storage において Property('I', 'shoesize') はユニークです(複数のインスタンスを生成している場合も、そのすべてが同じ property.id を持っています)。しかしひとつのプロパティで異なるビューにある複数のカラムを指し示すことができます。このことから join がどのように動作するか、なぜ "view.sort(view.firstname)" と "view.sort(metakit.property('S','firstname'))" が同じなのかが分かるはずです。
© 2003 Jean-Claude Wippler <
jcw@equi4.com>