13. SODA Evaluations


In the SODA API chapter we already mentioned Evaluations as a means of providing user-defined custom constraints and as a means to run any arbitrary code in a SODA query. Let's have a closer look.


    13.1. Evaluation API


    The evaluation API consists of two interfaces,  Evaluation  and  Candidate  . Evaluation implementations are implemented by the user and injected into a query. During a query, they will be called from db4o with a candidate instance in order to decide whether to include it into the current (sub-)result.


    The Evaluation  interface contains a single method only:

    public void evaluate(Candidate candidate);



    This will be called by db4o to check whether the object encapsulated by this candidate should be included into the current candidate set.


    The Candidate  interface provides three methods:

    public Object getObject();
    public void include(boolean flag);
    public ObjectContainer objectContainer();




    An Evaluation implementation may call #getObject()  to retrieve the actual object instance to be evaluated, it may call #include()  to instruct db4o whether or not to include this object in the current candidate set, and finally it may access the current database directly by calling #objectContainer() .



    13.2. Example


    For a simple example, let's go back to our Pilot/Car implementation from the Collections chapter. Back then, we kept a history of SensorReadout instances in a List member inside the car. Now imagine that we wanted to retrieve all cars that have assembled an even number of history entries. A quite contrived and seemingly trivial example, however, it gets us into trouble: Collections are transparent to the query API, it just 'looks through' them at their respective members.


    So how can we get this done? Let's implement an Evaluation that expects the objects passed in to be instances of type Car and checks their history size.

    package com.db4odoc.f1.chapter7;
    import com.db4o.query.*;
    import com.db4odoc.f1.chapter4.*;
    public class EvenHistoryEvaluation implements Evaluation {
      public void evaluate(Candidate candidate) {
        Car car=(Car)candidate.getObject();
        candidate.include(car.getHistory().size() % 2 == 0);
      }
    }


    To test it, let's add two cars with history sizes of one, respectively two:

    // storeCars
    Pilot pilot1 = new Pilot("Michael Schumacher", 100);
    Car car1 = new Car("Ferrari");
    car1.setPilot(pilot1);
    car1.snapshot();
    db.store(car1);
    Pilot pilot2 = new Pilot("Rubens Barrichello", 99);
    Car car2 = new Car("BMW");
    car2.setPilot(pilot2);
    car2.snapshot();
    car2.snapshot();
    db.store(car2);


    and run our evaluation against them:

    // queryWithEvaluation
    Query query = db.query();
    query.constrain(Car.class);
    query.constrain(new EvenHistoryEvaluation());
    ObjectSet result = query.execute();
    Util.listResult(result);



    13.3. Drawbacks


    While evaluations offer you another degree of freedom for assembling queries, they come at a certain cost: As you may already have noticed from the example, evaluations work on the fully instantiated objects, while 'normal' queries peek into the database file directly. So there's a certain performance penalty for the object instantiation, which is wasted if the object is not included into the candidate set.


    Another restriction is that, while 'normal' queries can bypass encapsulation and access candidates' private members directly, evaluations are bound to use their external API, just as in the language itself.



    One last hint: Evaluations are expected to be serializable for client/server operation. So be careful when implementing them as (anonymous) inner classes and keep in mind that those will carry an implicit reference to their surrounding class and everything that belongs to it. Best practice is to always implement evaluations as normal top level or static inner classes.




    13.4. Conclusion


    With the introduction of evaluations we finally completed our query toolbox. Evaluations provide a simple way of assemble arbitrary custom query building blocks, however, they come at a price.


    13.5. Full source


    package com.db4odoc.f1.chapter7;
    import java.io.*;
    import com.db4o.*;
    import com.db4o.query.*;
    import com.db4odoc.f1.*;
    import com.db4odoc.f1.chapter4.*;
    public class EvaluationExample extends Util {
        final static String DB4OFILENAME = System.getProperty("user.home") + "/formula1.db4o";
        public static void main(String[] args) {
            new File(DB4OFILENAME).delete();
            ObjectContainer db = Db4oEmbedded.openFile(Db4oEmbedded.newConfiguration(), DB4OFILENAME);
            try {
                storeCars(db);
                queryWithEvaluation(db);
            } finally {
                db.close();
            }
        }
        public static void storeCars(ObjectContainer db) {
            Pilot pilot1 = new Pilot("Michael Schumacher", 100);
            Car car1 = new Car("Ferrari");
            car1.setPilot(pilot1);
            car1.snapshot();
            db.store(car1);
            Pilot pilot2 = new Pilot("Rubens Barrichello", 99);
            Car car2 = new Car("BMW");
            car2.setPilot(pilot2);
            car2.snapshot();
            car2.snapshot();
            db.store(car2);
        }
        public static void queryWithEvaluation(ObjectContainer db) {
            Query query = db.query();
            query.constrain(Car.class);
            query.constrain(new EvenHistoryEvaluation());
            ObjectSet result = query.execute();
            Util.listResult(result);
        }
    }