Hash::Compactを使って、構造をもったカラムのデータを効率よく扱う
Hash::Compactという、HashRefのkey/valueを、keyのエイリアスやデフォルトのvalueをサポートすることでコンパクトに表現できるモジュールがあります。ドキュメントにはCache::Memcached::Fastでの利用例が示されているのですが、今回はそれをDBで使う例を紹介します。必ずしもORMが必要なわけではないのですが、説明の簡単のために、Tengを使います。
テーブル
structカラムに、JSONでシリアライズしたHashRefを格納する、以下のようなテーブルがあったとします。
CREATE TABLE `data` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `struct` blob, PRIMARY KEY (`id`) );
Tengでのスキーマ定義
今回はTengを使うので、以下のようなスキーマ定義を行います。やってることは簡単で、こんな感じ。
- スキーマ定義
- structカラムに対するinflate/deflateの設定
- 格納するhashの、alias, default値の設定
要するに、inflate/deflateで、Hash::Compactを通すようにしているところがポイントです。
package My::Model::Schema; use JSON; use Hash::Compact; use Teng::Schema::Declare; my $options = { foo => { alias_for => 'f', }, bar => { alias_for => 'b', default => 'bar', } }; sub to_hash { Hash::Compact->new(decode_json(shift), $options); } sub from_hash { my $hash = shift; if ((ref $hash || '') eq 'Hash::Compact') { encode_json($hash->to_hash); } else { encode_json(Hash::Compact->new($hash, $options)->to_hash); } } table { name 'data'; pk 'id'; columns qw( id struct ); inflate 'struct' => \&to_hash; deflate 'struct' => \&from_hash; };
データをinsert/selectしてみる
これを使って、データをinsertしてみます(コード中のMy::Modelは、Tengを継承したクラス)。
my $model = My::Model->new; my $row = $model->insert( data => { struct => { foo => 'foo', bar => 'bar' } } );
structカラムは、Hash::Compactでコンパクト、かつ、デフォルト値は無視して記録されるので、上記のようにinsertしても、実際のレコードは、以下のようになります。
mysql> select * from data; +----+-------------+ | id | struct | +----+-------------+ | 1 | {"f":"foo"} | +----+-------------+ 1 row in set (0.00 sec)
つまり、
- fooは、実際にはfというkeyのaliasとして扱われます
- barは、'bar'というデフォルト値と同じなので、記録するまでもなく、データから削除されます。
selectしてみると、ちゃんとinsertした時のデータ構造が復元されます。
my $row = $model->single(data => { id => 1 }); my $struct = $row->struct; #=> Hash::Compact object print $struct->param('foo') #=> 'foo' print $struct->param('bar') #=> 'bar'
まとめ
JSONなどの構造を持ったカラムデータのinflate/deflateにHash::Compactを利用することにより、
- コード上はfooやbarといったわかりやすいkeyを使いつつも、
- 実際のレコード内にはコンパクトな値を格納することで、
- 開発効率と空間効率の両方を得ることが可能となります
とても便利ですね。また、JSONではなくData::MessagePackなどを使って圧縮してやったら、さらに空間効率がよくなって、いい感じになるでしょう。