In these examples we have a class Product. Consequently we have a "query" class autogenerated - QProduct. Here is the QProduct class
public class QProduct
extends PersistableExpressionImpl<Product>
implements PersistableExpression<Product>
{
public static QProduct candidate() {...} // candidate "this"
public static QProduct candidate(String name) {...}
public static QProduct parameter(String name) {...}
public static QProduct variable(String name) {...}
public NumericExpression<Long> id;
public StringExpression name;
public NumericExpression<Double> value;
...
}
So, as you can see we have access to the persistable fields/properties of Product by way of public fields in its query class. Now on to some examples
Filter only
Here we select all Products that have a value less than 40.0
TypesafeQuery<Product> tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.filter(cand.value.lt(40.0));
List<Product> results = tq.executeList();
Filter + Order
Here we select all Products that have a value less than 40.0 and ordering by their name
TypesafeQuery<Product> tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.filter(cand.value.lt(40.0)).orderBy(cand.name.asc());
List<Product> results = tq.executeList();
Filter + Order + Result
Here we select the product name and product value for all Products that have a value less than 40.0 , ordering by their name
TypesafeQuery<Product> tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.filter(cand.value.lt(40.0)).orderBy(cand.name.asc());
List<Object[]> results = tq.executeResultList(true, cand.name, cand.value);
Filter with Methods
Here we select all Products that have a value less than 40.0 and name starting with "Wal", and ordering by their name
TypesafeQuery<Product> tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.filter(cand.value.lt(40.0).and(cand.name.startsWith("Wal")));
List<Product> results = tq.executeList();
Filter, using Parameters
Here we select all Products that have a value less than 40.0, using a parameter
TypesafeQuery<Product> tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate()
tq.filter(cand.value.lt(tq.doubleParameter("param1"))).setParameter("param1", 40.0);
List<Product> results = tq.executeList();
Order + Range
Here we select Products, ordering by the name and restrict to the first two
TypesafeQuery<Product> tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
tq.orderBy(cand.name.asc()).range(0,2);
List<Product> results = tq.executeList();
Filter with Variable
Here we select Inventory (which has a collection of Products) that contain a Product with a particular name
TypesafeQuery<Inventory> tq = pm.newTypesafeQuery(Inventory.class);
QInventory cand = QInventory.candidate();
QProduct var = QProduct.variable("var");
tq.filter(cand.products.contains(var).and(var.name.startsWith("Wal")));
List<Inventory> results = tq.executeList();
Filter with Subquery
Here we select Products with a value less than the average value of any Product
TypesafeQuery<Product> tq = pm.newTypesafeQuery(Product.class);
QProduct cand = QProduct.candidate();
TypesafeSubquery tqsub = tq.subquery(Product.class, "p");
QProduct candsub = QProduct.candidate("p");
tq.filter(cand.value.lt(tqsub.select(candsub.value.avg())));
List<Inventory> results = tq.executeList();
As you can see from the above queries we have no hard-coded class or field names, providing better refactorability. We can obviously make use of all JDOQL supported methods in the above queries, and define parameters and variables just like in the current JDOQL API.
It should be noted that this JDOQL facility is much more elegant and requires less code than the equivalent queries using JPA "Criteria".
Looks quite nice. The name parameter for the subqueries feels strange though. The instance itself should be enough.
ReplyDeleteIf we don't pass in the "alias" for the subquery, how do we get the candidate of the subquery to be able to refer to it in filter/result of the subquery ? i.e candsub.value. Obviously there is TypesafeSubquery.candidate() but then the user has to cast it. If you have some proposal then I'd be interested
ReplyDeleteYou mean tq.subquery(QProduct.candidate("p")); presumably ?
ReplyDeleteOk, I think I understand now. The subquery and subquery candidate are a pair and are combined via the common alias.
ReplyDeleteIf the typesafe query and subquery would contain the candidate q-type as a generic parameter it could also be accessed as query.candidate() / subquery.candidate.
Btw, how do you handle many-to-one references in this API?
ReplyDeletee.g.
class Product { Shop shop;},
class Shop { User owner;}
would it be
QProduct.candidate().shop.user
or
QProduct.candidate().shop().user()
Querydsl supports both modes,
You mean chained relations presumably, since I don't care what type the relation is (1-1, 1-N, N-1, M-N). If a persistable class has a field (or property) then the "Q" class has a *field* of type Q{otherType}. So QProduct.candidate().shop.owner
ReplyDeleteNot convinced that we need to allow accessor methods on Q classes. Reason for doing such a thing? (obviously in the implementation of the Q class it can just return the field). Allowing accessor methods you would then have to look out for naming collisions ... what if the user has a property called "count" and there is also a method on XXXExpression called count() ? Better just to stick to field syntax IMHO
Yes, I mean chained relations. How do you treat recursions then?
ReplyDeletee.g
class User { User superior; }
-> QUser.candidate().superior.superior;
Querydsl supports method access for lazy initialization and annotated properties (QueryInit) for selective eager initialization.
Do you have something similar?
DN supports relations (recursive or otherwise), just as basic JDOQL does. In JDOQL you could do this.superior.superior, so you can here too. Infinite relations (only limited by memory) ;-)
ReplyDeleteAnnotated properties are translated into a *field* in the Q class. This is for the reason I mentioned above; you have to avoid method name clashes, and cannot restrict the field/property names a user chooses, but in the same way need to allow min, max, avg etc hence you create fields in the Q class.
Eager and lazy is defined by fetch groups in JDO, not by the query language.
I mean doesn't it go into an infinite loop with a recursive property (e.g. User.superior) in the initialization?
ReplyDeleteclass QUser {
public final QUser superior = new QUser(...);
}
How do you avoid this?
You can get recursion if not implemented right, obviously, but that is an *implementation* issue, not a specification issue. In the same way we could define a standard way of limiting such things in the specification ... and your proposal is ?
ReplyDeleteHas to go in the query class generator. Detecting simple recursion (A.a.a.a.a) is easy. Detecting complex recursion (A.a.b.a.b.a.b) is less easy.
Querydsl has two solutions.
ReplyDeletea) annotate the field to be initialized in deep form
e.g.
@QueryInit("user")
private Shop shop;
this way QProduct.candidate().shop.user can be used
b) accessors for entity fields
e.g.
QProduct.candidate().shop().user()
Most of our users prefer the second option.
a). With the first one, I'd expect to pass in a value of recursion depth, to be consistent with JDO fetch groups. So if a user annotates a field with @QueryDepth(3) then it initialises 3 levels down from the candidate of that type.
ReplyDeleteb). As above, we can't allow property accessors - naming clashes with the methods that are needed for JDOQL. If I have a field "avg" then I can't use that route. However I could call the property accessors getXxx() maybe, but definitely not something that will clash. And if allowing property accessors then I'd expect to go that way totally or via fields totally - don't know why we need both (users get confused enough).
To be precise with property name clashes, the user cannot make use of eq, ne, count, countDistinct, jdoObjectId, jdoVersion (as the current interface is). So maybe not a big restriction ... but still needs to be listed as a requirement. If going the property route, then don't provide fields at all.
ReplyDeleteDN SVN now supports both modes of access.
ReplyDeleteDefault is 'property', as in "cand.field()". User has responsibility for avoiding name clashes with API methods.
Also supports field access by compiler argument, and this works with recursion, initialising down to a level of 5 (arbitrary for now, but could be made compiler argument)
Great. Another thing that came to my mind is to support casts on the Q-class level.
ReplyDeletee.g.
QProduct.candidate().shop.as(QSpecialShop.class).specialProperty