JDOQL is a powerful, easy-to-use query language, but you may occasionally find it limiting in some way. To circumvent the limitations of JDOQL, Kodo provides alternatives and extensions to standard JDO queries. This section discusses JDOQL query extensions. For a discussion of subqueries, direct SQL queries and custom query execution, see Chapter 13, Enterprise Edition.
JDOQL extensions are custom methods that you can use in your query filter, having, ordering, and result strings. Kodo JDO provides some built-in JDOQL extensions, and you can develop your own custom extensions as needed. You can optionally preface all JDOQL extensions with ext: in your JDOQL query. For example, the following example uses a hypothetical firstThreeChars extension to search for cities whose name begins with the 3 characters 'H', 'a', 'r'.
Example 10.1. Basic JDOQL Extension
Query query = pm.newQuery (City.class); queryq.setFilter ("name.ext:firstThreeChars () == 'Har')"); Collection results = (Collection) query.execute ();
Note that it is perfectly OK to chain together extensions. For example, let's modify our search above to be case-insensitive using another hypothetical extension, equalsIgnoreCase:
Example 10.2. Chaining JDOQL Extensions
Query query = pm.newQuery (City.class); query.setFilter ("name.ext:firstThreeChars ().ext:equalsIgnoreCase ('Har')"); Collection c = (Collection) query.execute ();
Finally, when using JDOQL extensions you must be aware that any SQL-specific extensions can only execute against the database, and cannot be used for in-memory queries (recall that JDO executes queries in-memory when you supply a candidate collection rather than an extent/class, or when you set the IgnoreCache and FlushBeforeQueries properties to false and you execute a query within a transaction in which you've modified some persistent objects).
Kodo includes two default JDOQL extensions to enhance the power of JDOQL. Note that Kodo natively supports all of the JDOQL methods defined in the JDO 2 early draft specification, so those methods are not listed here as extensions. See Chapter 11, Query for a primer on JDO 2 queries.
getColumn: Places the proper alias for the given column name into the SELECT statement that is issued. This filter cannot be used for in-memory queries. When traversing relations, the column is assumed to be in the primary table of the related type. To get a column of the candidate class, use this as the extension target, as shown in the second example below.
query.setFilter ("company.address.ext:getColumn ('JDOIDX') == 5"); query.setFilter ("this.ext:getColumn ('LEGACY_DATA') == 'foo'");
sql: Embeds the given SQL argument into the SELECT statement that is issued. This filter cannot be used for in-memory queries.
query.setFilter ("price < ext:sql ('(SELECT AVG(PRICE) FROM PRODUCTS)')");
This extension replaces sqlVal and sqlExp, which are now deprecated.
You can write your own extensions by implementing the kodo.jdbc.query.JDBCFilterListener interface. View the Javadoc documentation for details. Additionally, the source for all of Kodo's built-in query extensions is included in your Kodo download to get you started. The built-in extensions reside in the kodo.query and kodo.jdbc.query packages.
There are two ways to register your custom JDOQL extensions with Kodo:
Registration by properties: You can register custom JDOQL extensions by setting the kodo.FilterListeners configuration property to a comma-separated list of plugin strings describing your extensions classes. Extensions registered in this fashion must be able to be instantiated via reflection (they must have a public no-args constructor). They must also be thread safe, because they will be shared across all queries.
Per-query registration: You can register JDOQL extensions for an individual query through the KodoQuery.addFilterListener method. You might use per-query registration for very specific extensions that do not apply globally.
Just as you can write your own JDOQL methods, you can write your own query aggregates by implementing the kodo.jdbc.query.JDBCAggregateListener interface. View the Javadoc documentation for details. When using your custom aggregates in result or having strings, you can optinally prefix the function name with ext: to identify it as an extension.
There are two ways to register your custom query aggregates with Kodo:
Registration by properties: You can register custom query aggregates by setting the kodo.AggregateListeners configuration property to a comma-separated list of plugin strings describing your aggregate implementation. Aggregates registered in this fashion must be able to be instantiated via reflection (they must have a public no-args constructor). They must also be thread safe, because they will be shared across all queries.
Per-query registration: You can register query aggregates for an individual query through the KodoQuery.addAggregateListener method. You might use per-query registration for very specific aggregates that do not apply globally.