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