While there's lots of things we need to figure out to create generified queries, here is one low-hanging fruit - type-safe qualifiers. The idea is borrowed from the WebObjects EOF extensions framework called Wonder:

http://webobjects.mdimension.com/hudson/job/Wonder53/javadoc/er/extensions/eof/ERXKey.html

The way it works, is that for each String property in a Persistent object, we generate an extra parameterized "key". E.g.:

public class _Artist extends CayenneDataObject {

        public static final String DATE_OF_BIRTH_PROPERTY = "dateOfBirth";
        public static final Key<Date> DATE_OF_BIRTH = new Key<Date>(
                        DATE_OF_BIRTH_PROPERTY);

        public static final String NAME_PROPERTY = "name";
        public static final Key<String> NAME = new Key<String>(NAME_PROPERTY);

        public static final String PAINTINGS_PROPERTY = "paintings";
        public static final Key<Painting> PAINTINGS = new Key<Painting>(
                        PAINTINGS_PROPERTY);
...

Key class is a builder of type-safe key-value expressions (see the link above for the type of methods that it has). I wrote a quick API- only prototype that is a replacement of ExpressionFactory. Here is an example of building the following Expression the old way and the new way:

Expression: name='X' and (painting.name='Y' or painting.name='Z' or painting.name='A')

1. Current API:

Expression clause1 = ExpressionFactory.matchExp(Artist.NAME_PROPERTY, "X"); Expression clause2 = ExpressionFactory.matchExp(Artist.PAINTINGS_PROPERTY + "." + Painting.NAME_PROPERTY, "Y"); Expression clause3 = ExpressionFactory.matchExp(Artist.PAINTINGS_PROPERTY + "." + Painting.NAME_PROPERTY, "Z"); Expression clause4 = ExpressionFactory.matchExp(Artist.PAINTINGS_PROPERTY + "." + Painting.NAME_PROPERTY, "A");

Expression clause23 = clause2.orExp(clause3);
Expression clause234 = clause23.orExp(clause4);
Expression qualifier = clause1.andExp(clause234);

2. New API:

Expression clause1 = Artist.NAME.eq("X");
Expression clause2 = Artist.PAINTINGS.dot(Painting.NAME).eq("Y");
Expression clause3 = Artist.PAINTINGS.dot(Painting.NAME).eq("Z");
Expression clause4 = Artist.PAINTINGS.dot(Painting.NAME).eq("A");

Expression qualifier = Each.get(clause1, Any.get(clause2, clause3, clause4));

As you see the new API is much tighter, and the first part of it is completely type-safe (generated "keys" ensure that we are matching against the right type of value, even in a multi-step path). The last line uses 2 new Expression factories, "Each" and "Any", to quickly organize key-value expressions into a nested expression tree. I think this is as good as it can get within the constraints of the Java syntax.

I'd like to see if a similar approach can be extended to query building. Although that'll be a entirely different level of effort. For one thing it may force us to reconcile EJQBL and SelectQuery into a single query (SelectQuery is functionally a subset of EJBQLQuery. But SelectQuery "compilation" into SQL is fairly optimized, while EJBQLQuery-to-SQL conversion involves a much longer and slower pipeline). Plus of course there are other obstacles that we discussed before (such as a variety of possible result types).

Andrus





Reply via email to