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がある列。不自由な配列。