Answering questions about Quaere
Quaere has generated quite a lot of buzz since its release yesterday, with discussions going on on both The Server Side and Channel 9 amongst others. I’ve been spending quite a lot of time at JavaZone giving demos of the framework and DSL, and the feedback has been good. Rather than going around answering questions on a dozen threads on various discussion groups, I’ll try to address some of the more interesting ones in this blog post.
First off all, a lot of people are wondering about the choice of name. I’ve written a separate blog post on this, so if you want to know what Quaere means read this.
Michael Bienstein wrote quite a few good questions in his comment on the Server Side. His first observation is that the results are Iterables. The reason for this is that I cannot do the kind of type inference LINQ does, because everything happens at runtime. I could have made the queries return everything as java.lang.Object, but that would require the users to use instanceof and casting to have a usable query result. The most common use cases return sequences of objects rather than single instances, and since Iterable is the common denominator for sequences this is what is returned.
Michael asks what if I wanted move back and forth in the query result? In that case the best option is to use a conversion operator. Quaere supports the same conversion operators as LINQ, the only difference is that some of the operators have different names to match the Java conventions better. For instance, if you’d like your query result to be a List you can do this:
String[] words = {"cherry", "apple", "blueberry"};
List<String> sortedWords =
asList(
from("w").in(words).orderByDescending("w.length()").select("w")
);
The asList operator is equivalent to LINQ’s ToList extension method. For reference here is the same query using LINQ:
string[] words = { “cherry”, “apple”, “blueberry” };
var sortedWords =
from w in words
orderby w
select w;
var wordList = sortedWords.ToList();
Similarly you have operators to convert iterables to arrays or hash maps. You can also select elements from a sequence based on which class the element is:
Object[] numbers = {null, 1.0, "two", 3, "four", 5, "six", 7.0};
Iterable<Double> doubles = ofClass(Double.class, numbers);
Michael also points out that the iterable is untyped, in The Server Side’s post this appears to be the case, but that is probably because the stuff inside the less and greater than characters have been lost in HTML. The queries return Iterable.
Michael’s next questions are more on the inner workings of the framework. He asks when the query plan is created and when it is executed. The query plans take the form of expression trees which are basically abstract syntax trees for the query, this tree is built by a builder object which implements the fluent interface used to define the language grammar (in the IDE). This fluent interface is also what gives you the intellisense while writing the queries. The query is executed when the iterator() method is called on the query result. This implies that in an example like this…
Customer[] customers = Customer.getAllCustomers();
Iterable<Customer> waCustomers =
from("c").in(customers).
where(eq("c.getRegion()", "WA")).
select("c");
for (Customer c : waCustomers) {
Assert.assertEquals("WA", c.getRegion());
}
…the query is performed when execution reaches the for loop.
Michael’s final question is how the query engine implementation gets chosen. This happens at the first from clause. For instance in the example above Quaere for Objects is used because the object passed to the “in” method is an array. The same thing would happen if the object implemented the Iterable interface. However, in the next sample (which I showed in my talk, but isn’t in the distributed snapshot) the object implements the org.quaere.Queryable interface.
Session session=sessionFactory.openSession();
HibernateAdapter customerAdapter=new HibernateAdapter<Customer>(session, Customer.class);
Iterable<Customer> customers=
from("c").in(customerAdapter).
where(eq("c.getRegion()","WA")).
select("c");
Here the query execution is delegated to the HibernateAdapter instance which is passed the entire expression tree for the query. The adapter then converts this expression tree to a Hibernate Criteria query and executes the query against the Hibernate session. Basically this is the same architecture as LINQ, so you should be able to implement all of the LINQ to * flavors for Quaere.