3. Querying


db4o supplies three querying systems, Query-By-Example (QBE) Native Queries (NQ), and the SODA API. In the previous chapter, you were briefly introduced to Query By Example(QBE).

Query-By-Example (QBE) is appropriate as a quick start for users who are still acclimating to storing and retrieving objects with db4o.

Native Queries (NQ) are the main db4o query interface, recommended for general use.

SODA is the underlying internal API. It is provided for backward compatibility and it can be useful for dynamic generation of queries, where NQ are too strongly typed.



    3.1. Query by Example (QBE)


    When using Query By Example (QBE) you provide db4o with a template object. db4o will return all of the objects which match all non-default field values. This is done via reflecting all of the fields and building a query expression where all non-default-value fields are combined with AND expressions. Here's an example from the previous chapter:

    // retrievePilotByName
    Pilot proto = new Pilot("Michael Schumacher", 0);
        IObjectSet result = db.Get(proto);
        ListResult(result);


    Querying this way has some obvious limitations:
    - db4o must reflect all members of your example object.
    - You cannot perform advanced query expressions. (AND, OR, NOT, etc.)
    - You cannot constrain on values like 0 (integers), "" (empty strings), or nulls (reference types) because they would be interpreted as unconstrained.
    - You need to be able to create objects without initialized fields. That means you can not initialize fields where they are declared. You can not enforce contracts that objects of a class are only allowed in a well-defined initialized state.
    - You need a constructor to create objects without initialized fields.

    To get around all of these constraints, db4o provides the Native Query (NQ) system.




    3.2. Native Queries


    Wouldn't it be nice to pose queries in the programming language that you are using? Wouldn't it be nice if all your query code was 100% typesafe, 100% compile-time checked and 100% refactorable? Wouldn't it be nice if the full power of object-orientation could be used by calling methods from within queries? Enter Native Queries.

    Native queries are the main db4o query interface and they are the recommended way to query databases from your application. Because native queries simply use the semantics of your programming language, they are perfectly standardized and a safe choice for the future.

    Native Queries are available for all platforms supported by db4o.


      3.2.1. Concept

      The concept of native queries is taken from the following two papers:

      - Cook/Rosenberger, Native Queries for Persistent Objects, A Design White Paper
      - Cook/Rai, Safe Query Objects: Statically Typed Objects as Remotely Executable Queries


      3.2.2. Principle

      Native Queries provide the ability to run one or more lines of code against all instances of a class. Native query expressions should return true to mark specific instances as part of the result set. db4o will attempt to optimize native query expressions and run them against indexes and without instantiating actual objects, where this is possible.


      3.2.3. Simple Example

      Let's look at how a simple native query will look like in some of the programming languages and dialects that db4o supports:

      C# .NET 2.0
      IList <Pilot> pilots = db.Query <Pilot> (delegate(Pilot pilot) {
          return pilot.Points == 100;
      });
       

      Java JDK 5
      List <Pilot> pilots = db.query(new Predicate<Pilot>() {
          public boolean match(Pilot pilot) {
              return pilot.getPoints() == 100;
          }
      });


      Java JDK 1.2 to 1.4
      List pilots = db.query(new Predicate() {
          public boolean match(Pilot pilot) {
              return pilot.getPoints() == 100;
          }
      });


      Java JDK 1.1
      ObjectSet pilots = db.query(new PilotHundredPoints());

      public static class PilotHundredPoints extends Predicate {
          public boolean match(Pilot pilot) {
              return pilot.getPoints() == 100;
          }
      }


      C# .NET 1.1
      IList pilots = db.Query(new PilotHundredPoints());

      public class PilotHundredPoints : Predicate {
          public boolean Match(Pilot pilot) {
              return pilot.Points == 100;
          }
      }


      VB .NET 1.1
      Dim pilots As IList = db.Query(new PilotHundredPoints())

      Public Class PilotHundredPoints
          Inherits Predicate
          Public Function Match (pilot As Pilot) as Boolean
              If pilot.Points = 100 Then
                  Return True
              Else
                  Return False
          End Function
      End Class


      A side note on the above syntax:
      For all dialects without support for generics, Native Queries work by convention. A class that extends the com.db4o.Predicate class is expected to have a boolean #Match() method with one parameter to describe the class extent:

       

      When using native queries, don't forget that modern integrated development environments (IDEs) can do all the typing work around the native query expression for you, if you use templates and autocompletion.


      Here is how to configure a Native Query template with Eclipse 3.1:
      From the menu, choose Window + Preferences + Java + Editor + Templates + New
      As the name type "nq". Make sure that "java" is selected as the context on the right. Paste the following into the pattern field:

      List <${extent}> list = db.query(new Predicate <${extent}> () {
          public boolean match(${extent} candidate){
              return true;
          }
      });


      Now you can create a native query with three keys: n + q + Control-Space.
      Similar features are available in most modern IDEs.


      3.2.4. Advanced Example

      For complex queries, the native syntax is very precise and quick to write. Let's compare to a SODA query that finds all pilots with a given name or a score within a given range:

      // storePilots
      db.Set(new Pilot("Michael Schumacher", 100));
          db.Set(new Pilot("Rubens Barrichello", 99));


      // retrieveComplexSODA
      IQuery query=db.Query();
          query.Constrain(typeof(Pilot));
          IQuery pointQuery=query.Descend("_points");
          query.Descend("_name").Constrain("Rubens Barrichello")
              .Or(pointQuery.Constrain(99).Greater()
              .And(pointQuery.Constrain(199).Smaller()));
          IObjectSet result=query.Execute();
          ListResult(result);
      OUTPUT:
      2
      Michael Schumacher/100
      Rubens Barrichello/99


Here is how the same query will look like with native query syntax, fully accessible to autocompletion, refactoring and other IDE features, fully checked at compile time:

C# .NET 2.0
IList <Pilot> result = db.Query<Pilot> (delegate(Pilot pilot) {
    return pilot.Points > 99
        && pilot.Points < 199
        || pilot.Name == "Rubens Barrichello";
});
 

Java JDK 5
List <Pilot> result = db.query(new Predicate<Pilot>() {
    public boolean match(Pilot pilot) {
        return pilot.getPoints() > 99
            && pilot.getPoints() < 199
            || pilot.getName().equals("Rubens Barrichello");
   }
});



3.2.5. Arbitrary Code

Basically that's all there is to know about native queries to be able to use them efficiently. In principle you can run arbitrary code as native queries, you just have to be very careful with side effects - especially those that might affect persistent objects.

Let's run an example that involves some more of the language features available.




3.2.6. Native Query Performance

One drawback of native queries has to be pointed out: Under the hood db4o tries to analyze native queries to convert them to SODA. This is not possible for all queries. For some queries it is very difficult to analyze the flowgraph. In this case db4o will have to instantiate some of the persistent objects to actually run the native query code. db4o will try to analyze parts of native query expressions to keep object instantiation to the minimum.

The development of the native query optimization processor will be an ongoing process in a close dialog with the db4o community. Feel free to contribute your results and your needs by providing feedback to our db4o forums.


With the current implementation, all above examples will run optimized, except for the "Arbitrary Code" example - we are working on it.


3.2.7. Full source


using Db4objects.Db4o;
using Db4objects.Db4o.Query;
using Db4objects.Db4o.Tutorial;
namespace Db4objects.Db4o.Tutorial.F1.Chapter1
{
    public class NQExample : Util
    {
        public static void Main(string[] args)
        {
            IObjectContainer db = Db4oFactory.OpenFile(Util.YapFileName);
            try
            {
                StorePilots(db);
                RetrieveComplexSODA(db);
                RetrieveComplexNQ(db);
                RetrieveArbitraryCodeNQ(db);
                ClearDatabase(db);
            }
            finally
            {
                db.Close();
            }
        }
    
        public static void StorePilots(IObjectContainer db)
        {
            db.Set(new Pilot("Michael Schumacher", 100));
            db.Set(new Pilot("Rubens Barrichello", 99));
        }
    
        public static void RetrieveComplexSODA(IObjectContainer db)
        {
            IQuery query=db.Query();
            query.Constrain(typeof(Pilot));
            IQuery pointQuery=query.Descend("_points");
            query.Descend("_name").Constrain("Rubens Barrichello")
                .Or(pointQuery.Constrain(99).Greater()
                .And(pointQuery.Constrain(199).Smaller()));
            IObjectSet result=query.Execute();
            ListResult(result);
        }
        public static void RetrieveComplexNQ(IObjectContainer db)
        {
            IObjectSet result = db.Query(new ComplexQuery());
            ListResult(result);
        }
        public static void RetrieveArbitraryCodeNQ(IObjectContainer db)
        {
            IObjectSet result = db.Query(new ArbitraryQuery(new int[]{1,100}));
            ListResult(result);
        }
    
        public static void ClearDatabase(IObjectContainer db)
        {
            IObjectSet result = db.Get(typeof(Pilot));
            while (result.HasNext())
            {
                db.Delete(result.Next());
            }
        }
    }
}







3.3. SODA Query API


The SODA query API is db4o's low level querying API, allowing direct access to nodes of query graphs. Since SODA uses strings to identify fields, it is neither perfectly typesafe nor compile-time checked and it also is quite verbose to write.

For most applications Native Queries will be the better querying interface.

However there can be applications where dynamic generation of queries is required, that's why SODA is explained here.