Koya, a Tiny OODB.
SQLiteを使った小さなOODB
koya
SQLiteの調査のために、PRbをSQLiteで再実装した習作です。 PRbのリポジトリにつっこんであります。
<URL:http://cvs.sourceforge.jp/cgi-bin/viewcvs.cgi/prb/koya/>
- 関連ネタ
download
install
今のところSQLite3系では動きません。2.x系が必要です。rubyのバインディングもね。 (sqlite3のせいなのか、sqlite3のrubyのバインディングのせいなのか‥)
で、インストールはいつものように。
% sudo ruby install.rb
なにそれ?
- Koyaってなんですか?
- SQLite2.xをストレージとして使用する、機能が簡単なOODBです。
- 本当にOODBですか?
- たぶんちがいます。振る舞いは保存されませんし、継承も型もありません。
- 型もないの?
- 型の名前はありますが、型の情報は保存されません。
- Rubyっぽいの?
- PRbと違い、Rubyの写像を目指していません。一部のオブジェクトだけが オブジェクトとして扱われます。
- んあ?
- Rubyスクリプト上でKoyaObjectを継承したオブジェクトは、Koyaの中で オブジェクトとして扱われます。
- ほかのオブジェクトは?
- FixnumとStringはそのままSQLite風に、それ以外のオブジェクトはMarshalされて 保存されます。
- どう違うの?
- KoyaObjectは参照できますが、それ以外は参照できません。アクセスすると 新しいオブジェクトが生成されてしまいます。 dRubyでいうところの参照渡しと値渡しのように違います。
- 並列性はどうなの?
- トランザクションをサポートします。SQLiteはあんまり並列性がよくないので 気持ちよくあきらめています。
- えー?
- しかも、トランザクション待ちをブロックして待てないので、ポーリングします。
- とほほですね。
- まあね。
- 安定してますか?
- 実装は不安定です。それ以上にAPIも不安定です。SQLite3ではうまく動きません。 SQLite2.xでもまれに拡張ライブラリの何かを踏んでしまいます。
- なにか自慢してください。
- ある型名を持つオブジェクトの一覧(extent)や、逆リンクの一覧(referer)、 GC、世代別GC(実効性なし版)が用意されています。また、時刻を指定して その時の状態になること(revert_to)もできます。extent, referer, revert_toは おそらくアプリケーションのバージョンアップや不具合修正のために使うんだと 思います。
- やってなくて、やるつもりなのはなんですか?
- 複数データベースの扱い、Path、キャッシュ、アプリケーションとかとか。
最初
とりあえずrequire 'koya'が必要です。 それから、データベースの接続となるKoya::Storeを生成します。 パラメータはデータベースファイルの名前です。
% irb --simple-prompt >> require 'koya' >> s = Koya::Store.new('test.db')
Koya::Storeにはただ一つのrootオブジェクトがあります。
>> root = s.root => #<Koya::KoyaRef:0x..... >
rootオブジェクトはキーがStringに限定されたHashのような感じです。
>> root.keys => []
なにか入れてみます。
>> root['one'] = 1 => 1 >> root.keys => ["one"] >> root.delete('one') >> root.keys => []
つまんないですね。 transactionを作ってabortしてみましょう。
>> root.transaction do ?> root['one'] = 1 >> root['two'] = 2 >> raise 'abort' >> end RuntimeError: abort ..... >> root.keys => []
なにもなかったことになりました。
abortはいまいち作り込みが甘いというか、積極的に使うというよりも 不整合な状態でcommitできないようにする安全ネットみたいな緊急用に つかうものかもしれません。
正常なときはこうかな。
>> root.transaction do ?> root['one'] = 1 >> root['two'] = 2 >> end >> root.keys => ["one", "two"]
transaction中はほかのプロセスはtransactionに入れません。 新しい端末(Terminal2)を用意して実験します。
[Terminal2] % irb -r koya --simple-prompt >> s = Koya::Store.new('test.db') >> root = s.root
いままでの端末(Terminal1)で時間のかかるtransactionを実行してみます。
[Terminal1] >> root.transaction do ?> root['three'] = 3 >> sleep 10 >> end
10秒の間に[Terminal2]で問い合わせてみましょう。
[Terminal2] >> root.keys
いそいで入力できた人は、ブロックしている様子が見られると思います。 しばらくして、次のように表示されたかしらん。
[Terminal2] => ["one", "three", "two"]
次
普通のRubyのオブジェクトはMarshalされたりして保存されます。 それは取り出すたびに新しいオブジェクトが生成されることを意味します。
>> str = "String" >> root['str'] = str >> str.object_id 3067308 >> root['str'].object_id >> 1623454 >> root['str'].object_id >> 1600914
がっかりですね。
Koyaがオブジェクトとして扱えるのは、Koya::KoyaObjectを継承したクラス だけです。
class Foo < Koya::KoyaObject def initialize(name) self.name = name end koya_attr :name, :friend def greeting friend.greeting if friend puts "Hello, This is #{name}?n" end end
irbに上のクラス定義をペーストしてFooを定義してください。
- FooはKoya::KoyaObjectを継承します。
- koya_attrはattr_accessorみたいなもので、KoyaObjectの属性を定義します。
- @nameじゃなくてnameなのでself.をつけて明示的なメソッド呼び出しにしてる ところがあります。
生成してみましょう。
>> Foo.new('one') Koya::TransactionNotFound: Koya::TransactionNotFound .....
失敗しましたか? KoyaObjectはtransactionの中でしか生成できません。 これは生成してどこからも参照されていないときにGCされてしまわないようにする ためです。(さらにどのデータベースに生成するかを決定させるため)
root['foo']に生成したFooを入れてみます。
>> root.transaction do ?> root['foo'] = Foo.new('one') >> end => #<Foo:0x ... >
作れたみたい。
>> root['foo'].greeting Hello, This is one.
もう一つ作ります。root['foo'].friendから参照します。
>> root.transaction do ?> root['foo'].friend = Foo.new('two') >> end >> root['foo'].greeting Hello, This is two Hello, This is one
メンテナンス用に使おうと思っている機能を一つ紹介します。 指定の時刻の状態に戻るrevert_toです。
まず時刻を覚えておきます。
>> tag = Time.now
次にroot['foo']を忘れてみましょう。
>> root.delete('foo') >> root['foo'].greeting NoMethodError: undefined method `greeting' for nil:NilClass
覚えておいた時刻に戻ります。
>> s.revert_to(tag) >> root['foo'].greeting Hello, This is two Hello, This is one
今日はおしまい。
実装!!
みそは二つのテーブルです。 一つはオブジェクトのクラス名等を管理するmemory、 もう一つはオブジェクトの属性を管理するpropです。
PRbはやりすぎた気がするのでその反動でシンプルになってます。 SQLiteには型がない、というのもいろいろあきらめがついていい感じ。
アプリケーション向けのclass
Koya::KoyaObject
一般的なオブジェクトの元になるクラス
Koya::Dict
キーをStringに限定したHashみたいなもの
Koya::Stream
push, pop, shift, unshiftがある列。不自由な配列。