RubyでのBerkeley DBの型

前回の日記で,少しだけウソを書きました.
Ruby with Berkeley DB - なぜか数学者にはワイン好きが多い

Rubyは,デフォルトでDBMやGDBMを利用するライブラリが添付されています.ただし,大きくただし書きされているように,それらは「ファイルに保存されるハッシュクラス」で,さらに「キーもバリューもstringクラス」しか扱えないという大きな短所があります.

Ruby/Berkeley DBは,その短所が無いので,お勧めです.

と書いてサンプルプログラムを載せたのですが,「stringクラス」以外の型を扱う方法を書いていませんでした.
サンプルプログラムだけ見ると,stringクラスも整数(Fixnum/Bignumクラス)も自在に扱えるかのように思えるのですが...
次のようなことをやるとエラーになります.

まず,Berkeley DB書き込みプログラム write.rb

#!/usr/bin/ruby
require 'bdb';
a = BDB::Btree::open("./testdb","table1","w",0777);
a["aaa"] = 123;
a.close;


読み込み+αプログラム read.rb

#!/usr/bin/ruby
require 'bdb';
a = BDB::Btree::open("./testdb","table1","r",0777);
p a["aaa"]+1;
a.close;


実行します.

# ruby write.rb
# ruby read.rb
read.rb:10:in `+': can't convert Fixnum into String (TypeError)
from read.rb:10

これ,デフォルトではdbmやgdbmライブラリと同じく数値も(他のオブジェクトも全て)string型に変換されてしまうために,「1」という整数を文字列の「"123"」(=a["aaa"]の結果,返ってくる値)に足そうとして失敗しているからです.


123+1=124を表示するには,Berkeley DB読み込みプログラムread.rbを,次のように改良します.

#!/usr/bin/ruby
require 'bdb';
s2i = Proc.new{|c|
c.to_i;
};

a = BDB::Btree::open("./testdb","table1","r",0777,{"set_fetch_value"=>s2i});
p a["aaa"]+1;
a.close;


実行すると,次のようになります.

# ruby write.rb
# ruby read.rb
124


整数よりも悲惨なのは,例えば配列のオブジェクトをDBに格納するときです.例えば何もしないと,


write.rb

#!/usr/bin/ruby
require 'bdb';
a = BDB::Btree::open("./testdb","table1","w",0777);
a["aaa"] = [1,23,4];
a.close;


read.rb

#!/usr/bin/ruby
require 'bdb';
a = BDB::Btree::open("./testdb","table1","r",0777);
p a["aaa"];
a.close;

実行します.

# ruby write.rb
# ruby read.rb
"1234"

3つの数字からなる配列のはずが,数字間の区別すらつきません...
かなりカッコ悪いのですが,区切り文字を入れて保存することをやってみました.


write.rb

#!/usr/bin/ruby
require 'bdb';
s2i = Proc.new{|c|
c.join(":");
};
a = BDB::Btree::open("./testdb","table1","w",0777,{"set_store_value"=>s2i});
a["aaa"] = [1,23,4];
a.close;


read.rb

#!/usr/bin/ruby
require 'bdb';
s2a = Proc.new{|c|
d = [];
c.split(":").each_with_index{|x,i| d[i] = x.to_i;};
d;
};

a = BDB::Btree::open("./testdb","table1","r",0777,{"set_fetch_value"=>s2a});
p a["aaa"];
a.close;


実行します.

# ruby write.rb
# ruby read.rb
[1, 23, 4]

一応,それらしく動いているようです.
が...これじゃ,string型しか扱えないdbmを使っているのと大差無い気もしますね...
Bekeley DBのC++用のAPIだと,型がstring型と違っても,強引にストアしたいオブジェクトのサイズを取得してBerkeley DB内に容量を確保し,バイト単位でコピーするというサンプルを良く見かけます.Rubyでもそうやれば良いのでしょうかねぇ...なんかオブジェクト指向っぽくない...スマートにオブジェクトそのものをDBに出し入れする方法は...