db4o その18(自動リファクタリングの対象外なこと)

先日はdb4oにおけるスキーマ・エボリューションと同等の機能である自動リファクタリングに関して言及した。
自動リファクタリングのサンプルではIPlayerインタフェースにアクセスするフィールドを追加する例で説明したが、ではその逆にIPlayerインタフェースからアクセスするフィールドを除去する場合はどうだろう。

interface IPlayer {
    String getName();
    void setName(String name);
    String getTeam();
    void setTeam(String team);
    //Object getPosition();
    //void setPosition(Object position);
    int getAge();
    void setAge(int age);
    List getStats();
    void set(List stats);
    IBattingStats getStats(int year);
}

例として守備位置"position"フィールド(とそのアクセサ)を除去してみた。とここまで書いてすぐに解ると思うが、プログラムからは除去したフィールドには単純にアクセスができなくなる。当然だが除去したアクセッサを他のプログラムで使っている場合はコンパイルエラーになるので対処が必要だ。(実際には除去されたフィールドはデータベース上にはそのまま残っており、一度除去したフィールドを再度復活させた場合、そのフィールドの型が変更されていなければ以前に設定した際の値が取得できる)

ではフィールドの追加/除去ではなく同一のフィールド名のままでその型が変更された場合はdb4oはどのような振舞いをするのだろうか。今度は守備位置フィールドの型を当初の"String"から"Object"に変更してみよう。

interface IPlayer {
:
    Object getPosition();
    void setPosition(Object position);
:
}

当然ながらPlayerImplクラスも同様に変更する。

public class PlayerImpl implements IPlayer {
    private String name;
    private String team;
    private Object position;
    :

この状態で以前の"roster.db"を開き、元々String型だった"position"フィールドにアクセスしてみよう。

public static ObjectSet sodaForPlayer(ObjectContainer db, String playerName) {
    Query query = db.query();
    query.constrain(IPlayer.class);
    query.descend("name").constrain(playerName);
    return query.execute();
}
:
ObjectContainer oldDb = Db4o.openFile("roster.db");
String playerName = "D.Jeter";
ObjectSet resultSet = sodaForPlayer(olddb, playerName);
IPlayer jeter = (IPlayer)playerName.next();
System.out.println(playerName + " position? = " + jeter.getPosition());
:

実行結果

D.Jeter position? = null

コードを書いた時点では、守備位置フィールドである"position"の型は確かに変更したが、それはStringクラスの継承元であるObjectクラスに変更したのであって単純に元々のデータベースの内容を新たな型のフィールドにセットする際に互換性があればそのままセットされるはずだ。
しかし予想は裏切られた。db4oはフィールドの型の一致を厳密に検査しているらしく、型に相違があった場合は例え互換性があったとしても値は隠されて取得することができないようだ。

実際の運用ではこの例のようにオブジェクトの特定のフィールドの型が変更されることはままあることである。では、そういう場合は旧データにアクセスする手段は無いのだろうか。

db4oは現在のクラス構成と型情報に頼らず、データベース上に格納されているデータとメタデータに直積アクセスする手段を提供している。
例えば上と同じようにデータベースに格納されているString型の"position"フィールドにアクセスするためには以下のように記述できるのだ。

ObjectContainer oldDb = Db4o.openFile("roster.db");
StoredClass sc = oldDb.ext().storedClass("PlayerImpl");
StoredField oldField = sc.storedField("position", String.class);
String playerName = "D.Jeter";
ObjectSet resultSet = sodaForPlayer(olddb, playerName);
IPlayer jeter = (IPlayer)playerName.next();
System.out.println(playerName + " position? = " + oldField.get(jeter));

それぞれ、StoredClassはデータベースに格納されているクラス(のメタデータ)を、StoredFieldはフィールド(のメタデータ)を取得する。データベース中のオブジェクトの任意のフィールド値を取得するにはStoredField#get(onObject)メソッドを使用するのだ。
このイディオムで実行結果は以下のようになるはずである。

・Expected

D.Jeter position? = SS

しかし.....実際に実行してみると以下のようになってしまう。
・Actual

D.Jeter position? = null

う〜ん、これはおかしい。 これじゃdb4oのリファレンスとも合わない。間違いなくバグと思われる。