db4o その5(Native Query)

前回はdb4oのクエリの一つであるQBEについて言及したが、今回はそのQBEの弱点を補うことのできるNative Query(以降NQ)について言及する。
使用するサンプルデータと実体化されるクラス、ユーティリティコードは前回と同じであり、動作確認も同様にSun Java JDK 6を使用しているので.NET 2.0 C#にポーティングするのは簡単だろう。

・Native Query (NQ)
NQは簡単に言うと使用言語がサポートする述語論理(Predicate Logic)のコールバックを使用して検索対象を絞り込む方法だ。例えばQBEのサンプル同様にIPlayerインタフェースにおけるnameフィールドが"D.Jeter"であるオブジェクトを検索するNQは、queryメソッド(.NET C#の場合はQueryメソッド)の第一パラメタに抽象クラスPredicateを用いて以下のように書く。

for java

List roster = db.query(new Predicate(){
    @Override
    public boolean match(IPlayer candidate) {
        return ( candidate.getName().equals("D.Jeter") );
    }
});
printResult(roster, "NQ for Name = 'D.Jeter'");

.NET C#の場合は.NET Framework 2.0にデフォルトで実装されているPredicateデリゲートを使う、と思いきやObjectContainer#QueryメソッドがGenericsに対応しており、Predicateは通常のデリゲートを使用して書く。(Java同様にPredicate抽象クラスを使うこともできる)
for .NET C# 2.0 ※

IList roster = db.Query(
    delegate(IPlayer candidate)
    {
        return ( candidate.Name == "D.Jeter" );
    });
PrintResult(roster, "NQ for Name = 'D.Jeter'");

このクエリの実行結果

NQ for Name = 'D.Jeter'
result count = 1
  PlayerImpl[ name=D.Jeter, team=NYY, position=SS, hits=14, average=0.318]

NQであればQBEでは難しかった範囲値による絞込みも簡単だ。例として打率(Average)が3割3分3厘以上の選手だけを絞り込んでみよう。

List roster = db.query(new Predicate(){
    public boolean match(IPlayer candidate) {
        return ( candidate.getAverage() >= 0.333 );
    }
});
printResult(rosters, "NQ for Average >= 0.333");

このクエリの実行結果

NQ for Average >= .333
result count = 4
  PlayerImpl[ name=K.Thompson, team=NYY, position=OF, hits=1, average=0.5]
  PlayerImpl[ name=R.Cano, team=NYY, position=2B, hits=14, average=0.333]
  PlayerImpl[ name=A.Rodriguez, team=NYY, position=3B, hits=14, average=0.35]
  PlayerImpl[ name=J.Posada, team=NYY, position=C, hits=13, average=0.342]

queryメソッドはこの他に第二パラメタにコンパレータ(comparator)を指定するバリエーションがあり、これを使うことでワンパスでクエリで絞り込んだ結果セットを同時に並び替えることもできる。

public  ObjectSet  query(Predicate predicate,QueryComparator comparator);

上記メソッドを使った例として、安打(Hits)が1本を超える選手を安打数の昇順に整列させるクエリをNQで書いてみよう。

List rosters = db.query(new Predicate(){
    @Override
    public boolean match(IPlayer candidate) {
        return candidate.getHits() > 1;
    }, new QueryComparator(){
        @Override
        public int compare(IPlayer player1, IPlayer player2) {
            return (player1.getHits() == player2.getHits())
                ? 0
                : (player1.getHits() < player2.getHits())
                    ? -1
                    : +1;
        }
    }
);

このようにNQはそのクエリを言語のコンテキストで静的に記述するのが最大の特徴だ。普段使っている言語で、尚且つ開発環境の支援を受けつつクエリを記述できるので敷居が低くく間違え難く、型セーフでありコンパイル時の型チェックが有効になる。
記述が静的だという特徴は逆に言えば動的にクエリを構成することができないことを意味し、それがNQの弱点である。また、NQは構文解析後はSODA Query APIに置換されて実行される。SODAに関しては次回言及する予定だがdb4Objects coreチームはNQからSODAへの最適化が完璧ではないことは認めており、複雑なクエリはNQではなく、次回言及するSimple Data Access(SODA)を使うよう推奨している。

※.NET C#用のIPlayerインタフェースは以下のように、全てアクセサの代わりにプロパティが宣言されていることを前提にしている。

interface IPlayer {
    string Name { get; set; }
    string Team { get; set;}
    string Position { get; set;}
    int Hits { get; set; }
    double Average { get; set; }
}

.NET C#はプロパティをサポートしているので当たり前だが、Javaに比べると幾分すっきりしている。