TCK20: JDO2 Query Test Cases
JDOQL 2.0
JDO 2.0 adds the following methods to the Query API:
- setResult
- setGrouping
- setUnique
- setResultClass
- setRange
- setUnique
- setUnmodifiable
- isUnmodifiable
- setExtensions
- addExtension
JDO 2.0 extensions of the JDO query language JDOQL:
- Single string JDOQL
- Result specification
- Projections of fields and relationships
- One or more result expressions
- Distinct results
- Unique query result
- Default result class for one or more result expressions
- User defined result class
- Naming of result expressions
- Aggregate functions MIN, MAX, SUM, AVG, and COUNT
- Grouping of query result
- One or more grouing expressions
- Having clause
- New methods in Query filters:
- Map support: get(Object), containsKey(Object), containsValue(Object), isEmpty()
- Additional string methods: toLowerCase(), toUpperCase(), indexOf(String), indexOf(String, int), matches(String), substring(int), substring(int, int)
- Support for other methods: Math.abs(numeric), Math.sqrt(numeric), JDOHelper.getObjectId(Object)
- New operators %(modulo) and instanceof
- Support for implicit parameters
- Support for implicit variables
- Deletion by query
NewQueryTests New TCK Query Tests
Package names of all query test classes start with org.apache.jdo.query..
Package names of all pc classes start with org.apache.jdo.tck.pc..
Package names of all result classes start with org.apache.jdo.tck.query.result.classes..
Language Extensions
Keywords
Assertion |
JDOQL |
Testclass |
Comment |
A14.4-6: Keywords must not be used as package names, class names, parameter names, or variable names in queries. |
SELECT INTO range.PersonResult company.Person |
jdoql.keywords. |
Negative test. |
SELECT INTO range FROM company.Person |
|||
SELECT FROM select.Person |
|||
SELECT FROM select |
|||
SELECT FROM company.Person PARAMETERS int this |
|||
SELECT FROM company.Person VARIABLES long this |
|||
A14.4-7: Keywords are permitted as field names only if they are on the right side of the "." in field access expressions |
valid: |
jdoql.keywords. |
Positive and negative test. |
invalid: |
|||
A14.6.13-1: The String version of Query represents all query elements using a single string. The string contains the following structure: |
SELECT firstname AS firstName, lastname AS lastName INTO FullName FROM company.FullTimeEmployee WHERE salary > 1000 & projects.contains(project) & project.budget > limit VARIABLES Project project PARAMETERS BigDecimal limit IMPORTS IMPORT company.Project; IMPORT java.math.BigDecimal GROUP BY firstname, lastname HAVING lastname.startsWith('emp') ORDER BY personid RANGE 1 TO 5 |
jdoql.keywords. |
Positive test. |
A14.6.13-2: Keywords, identified above in bold, are either all upper-case or all lower-case. Keywords cannot be mixed case. |
valid: |
jdoql.keywords. |
Positive and negative test. |
valid: |
|||
valid: |
|||
invalid: |
New Operators
Assertion |
JDOQL |
Testclass |
Comment |
A14.6.2-40: modulo operator |
SELECT FROM company.Person WHERE personid % 2 == 0 |
jdoql.operators. |
Positive test. |
A14.6.2-41: instanceof operator |
SELECT FROM company.Employee WHERE mentor instanceof company.PartTimeEmployee |
jdoql.operators. |
Positive test. |
New Supported Methods
Assertion |
JDOQL |
Testclass |
Comment |
A14.6.2-46: Supported Map methods: |
get: |
jdoql.methods. |
Positive Test. |
containsKey: |
|||
containsValue: |
|||
A14.6.2-47: New supported String methods: |
toLowerCase: |
jdoql.methods. |
Positive test. |
toUpperCase: |
|||
indexOf: |
|||
indexOf: |
|||
matches: |
|||
substring: |
|||
substring: |
|||
startsWith: |
|||
endsWith: |
|||
A14.6.2-48: Supported Math methods: |
SELECT FROM company.FullTimeEmployee WHERE Math.abs(salary) > 10000 |
jdoql.methods. |
Positive test. |
SELECT FROM company.FullTimeEmployee WHERE Math.sqrt(salary) > 100 |
|||
A14.6.2-49: Supported JDOHelper methods: |
SELECT JDOHelper.getObjectId(this) FROM company.Person |
jdoql.methods. |
Positive test. |
Parameters
Assertion |
JDOQL |
Testclass |
Comment |
A14.6.3-2: Parameters must all be declared explicitly via declareParameters or all be declared implicitly in the filter. |
valid: |
jdoql.parameters. |
Positive and negative test. |
valid: |
|||
invalid: |
|||
A14.6.3-3: Parameters implicitly declared (in the result, filter, grouping, ordering, or range) are identified by prepending a ":" to the parameter everywhere it appears. All parameter types can be determined by one of the following techniques: |
result: |
jdoql.parameters. |
Positive test. |
filter: |
|||
grouping: |
|||
ordering: |
|||
range: |
|||
A14.6.13-3: If implicit parameters are used, their order of appearance in the query determines their order for binding to positional parameters for execution. |
SELECT FROM company.Person WHERE firstname == :param1 & lastname == :param2 |
jdoql.parameters. |
Positive test. |
Variables
Assertion |
JDOQL |
Testclass |
Comment |
A14.6.5-1: ?? A variable that is not constrained with an explicit contains clause is constrained by the extent of the persistence capable class (including subclasses). |
SELECT department FROM company.Person WHERE firstname.endsWith('First') VARIABLES Department department |
jdoql.variables. |
Posistive test. |
A14.6.5-2: ?? If the class does not manage an Extent, then no results will satisfy the query. |
(javax.jdo.option.UnconstrainedQueryVariables) |
jdoql.variables. |
Positive test. |
A14.6.5-3: All variables must be explicitly declared, or all variables must be implicitly declared. |
explicit: |
jdoql.variables. |
Positive and negative test. |
implicit: |
|||
invalid: |
|||
A14.6.5-4: Names are treated as variable names if they are explicitly declared via declareVariables. Otherwise, names are treated as field names if they are members of the candidate class. Finally, names are treated as implicitly defined variable names. |
explicit: |
jdoql.variables. |
Positive test. |
implicit: |
|||
field name: |
Other Language Changes
Assertion |
JDOQL |
Testclass |
Comment |
A14.6.2-42: There is no distinction made between character literals and String literals. Single character String literals can be used wherever character literals are permitted. String literals are allowed to be delimited by single quote marks or double quote marks. This allows String literal filters to use single quote marks instead of escaped double quote marks. |
valid: |
jdoql. |
Positive and negative test. |
invalid: |
|||
A14.6.2-43: Identifiers that are persistent field names or public final static field names are required to be supported by JDO implementations. |
field names: |
jdoql. |
Positive test. |
static field names: |
|||
A14.6.8-1: setRange(long fromIncl, long toExcl) |
SELECT lastname FROM company.Person RANGE 1 TO 10 |
jdoql. |
Positive test. |
A14.6.8-2: If ((toExcl - fromIncl) <= 0) evaluates to true, if the result of the query execution is a List, the returned List contains no instances, and an Iterator obtained from the List returns false to hasNext(). If the result of the query execution is a single instance (setUnique(true)), it will have a value of null. |
SELECT lastname FROM company.Person RANGE 10 TO 1 |
jdoql. |
Positive test. |
A14.6.8-3: setRange(String range); |
SELECT lastname FROM company.Person RANGE 1 TO 10 |
jdoql. |
Positive test. |
Query API Extensions
Assertion |
JDOQL |
Testclass |
Comment |
A14.5-11: Construct a new query instance using the specified String as the single-string representation of the query. |
SELECT FROM company.Person |
api. |
Positive test. |
A14.5-12: Construct a new query instance with the given candidate class from a named query. |
valid, unique is false, unmodifiable is false: |
api. |
Positive test and negative test. |
valid, unique is true, unmodifiable is false: |
|||
invalid, unique is true, unmodifiable is false: |
|||
invalid, unique is false, unmodifiable is true: |
|||
A14.5-13: If the named query is not found in already-loaded metadata, the query is searched for using an algorithm. Files containing metadata are examined in turn until the query is found. The order is based on the metadata search order for class metadata, but includes files named based on the query name. |
SELECT FROM company.Person |
api. |
Positive test. |
A14.5-14: If the metadata is not found in the above, a JDOUserException is thrown. |
|
api. |
Negative test. |
A14.5-15: The Query instance returned from this method can be modified by the application, just like any other Query instance. |
SELECT FROM company.Person WHERE firstname == 'emp1First' |
api. |
Positive test. |
A14.5-16: Named queries must be compilable. Attempts to get a named query that cannot be compiled result in JDOUserException. |
SeLeCt FrOm company.Person |
api. |
Negative test. |
A14.6-21: This method retrieves the fetch plan associated with the Query. It always returns the identical instance for the same Query instance. Any change made to the fetch plan affects subsequent query execution. |
SELECT FROM company.Person |
api. |
Positive test. |
A14.6-16: void setResult (String result); Specify the results of the query if not instances of the candidate class. |
valid: |
api. |
Positive test and negative test. |
invalid: |
|||
A14.6-17: void setGrouping (String grouping); Specify the grouping of results for aggregates. |
SELECT lastname FROM company.Person GROUP BY lastname |
api. |
Positive test. |
A14.6-18: void setUnique (boolean unique); Specify that there is a single result of the query. |
SELECT UNIQUE firstname FROM company.Person WHERE lastname == emp1Last' |
api. |
Positive test. |
A14.6-19: void setResultClass (Class resultClass); Specify the class to be used to return result instances. |
SELECT firstname, lastname INTO ...FullName FROM company.Person |
api. |
Positive test. |
A14.6-20: setRange(int fromIncl, int toExcl); Specify the number of instances to skip over and the maximum number of result instances to return. |
SELECT FROM company.Person RANGE 1 TO 10 |
api. |
Positive test. |
A14.6-22: The Unmodifiable option, when set to true, disallows further modification of the query, except for specifying the range and result class and ignoreCache option. |
SELECT FROM company.Person |
api. |
Negative test. |
A14.6-23: The single string query is first parsed to yield the result, result class, filter, variable list, parameter list, import list, grouping, ordering, and range. Then, the values specified in APIs setResult, setResultClass, setFilter, declareVariables, declareParamters, declareImports, setGrouping, setOrdering, and setRange override the corresponding settings from the single string query. |
SELECT firstname AS firstName, lastname AS lastName INTO ...FullName FROM company.FullTimeEmployee WHERE salary > 1000 & projects.contains(project) & project.budget > limit VARIABLES Project project PARAMETERS BigDecimal limit ORDER BY salary GROUP BY firstname, lastname HAVING lastname.startsWith('R') RANGE 1 TO 10 |
api. |
Positive test. |
A14.9-1: Some JDO vendors provide extensions to the query, and these extensions must be set in the query instance prior to execution. |
SELECT FROM company.Person |
api. |
Positive test. |
Result Handling
Assertion |
JDOQL |
Testclass |
Comment |
A14.6.9-1: If distinct is specified, the query result does not include any duplicates. If the result parameter specifies more than one result expression, duplicates are those with matching values for each result expression. |
SELECT DISTINCT FROM company.Person |
result. |
Positive test. |
A14.6.9-2: Queries against an extent always consider only distinct candidate instances, regardless of whether distinct is specified. Queries against a collection might contain duplicate candidate instances; the distinct keyword removes duplicates from the candidate collection in this case. |
(javax.jdo.option.UnconstrainedQueryVariables) SELECT FROM company.Person VARIABLES Project project |
jdoql. |
Positive test. |
A14.6.9-3: If a variable or a field of a variable is included in the result, either directly or via navigation through the variable, then the semantics of the contains clause that include the variable change. In this case, all values of the variable that satisfy the filter are included in the result. |
variable: |
result. |
Positive test. |
field of variable: |
|||
A14.6.9-4: If any result is a navigational expression, and a non-terminal field or variable has a null value for a particular set of conditions (the result calculation would throw NullPointerException), then the result is null for that result expression. |
field: |
result. |
Positive test. |
variable: |
|||
A14.6.9-5: The result expressions include: ... The result expression can be explicitly cast using the (cast) operator. |
SELECT DISTINCT (FullTimeEmployee)manager FROM company.Employee |
result. |
Positive test. |
A14.6.9-6: Count returns Long. Sum returns Long for integral types and the field's type for other Number types (BigDecimal, BigInteger, Float, and Double). Sum is invalid if applied to non-Number types. Avg, min, and max return the type of the expression. |
Count: |
result. |
Positive and negative test. |
Sum: |
|||
invalid Sum: |
|||
Avg: |
|||
invalid Avg: |
|||
Min: |
|||
Max: |
|||
A14.6.9-7: If the returned value from a query specifying a result is null, this indicates that the expression specified as the result was null. |
valid: |
result. |
Positive test. |
A14.6.9-8: If not specified, the result defaults to distinct this as C |
SELECT FROM company.Department |
result. |
Positive test. |
A14.6.10-1: When grouping is specified, each result expression must be one of: an expression contained in the grouping expression; or, an aggregate expression evaluated once per group. The query groups all elements where all expressions specified in setGrouping have the same values. The query result consists of one element per group. |
valid: |
result. |
Positive and negative test. |
invalid: |
|||
A14.6.10-2: When having is specified, the having expression consists of arithmetic and boolean expressions containing aggregate expressions. |
valid: |
result. |
Positive and negative test. |
invalid: |
|||
A14.6.11-1: When the value of the Unique flag is true, then the result of a query is a single value, with null used to indicate that none of the instances in the candidates satisfied the filter. If more than one instance satisfies the filter, and the range is not limited to one result, then execute throws a JDOUserException. |
valid, result is non-null: |
result. |
Positive and negative test. |
valid, result is null: |
|||
invalid: |
|||
A14.6.11-2: The default Unique setting is true for aggregate results without a grouping expression, and false otherwise. |
true: |
result. |
Positive test. |
false grouping: |
|||
false: |
|||
A14.6.12-1:The result class may be one of the java.lang classes Character, Boolea, Byte, Short, Integer, Long, Float, Double, String, or Object[]; or one of the java.math classes BigInteger or BigDecimal; or the java.util class Date; or the java.util interface Map; or one of the java.sql classes Date, Time, or Timestamp; or a user-defined class. |
valid, result class is String: |
result. |
Positive and negative test. |
valid, result class is TCK class: |
|||
invalid, result class is TCK class not having Long property: |
|||
invalid, result class is JDK class: |
|||
invalid, result class is JDK class and not assignment compatible: |
|||
invalid, result class is TCK class and not assignment compatible: |
|||
valid, specifying a constructor: |
|||
valid, specifying a non-existent constructor with AS: |
|||
invalid, specifying a non-existent constructor without AS: |
|||
invalid, result class is TCK class not having an no-arg constructor: |
|||
invalid, result class is TCK class not having public fields and methods: |
|||
valid, result class is TCK class having public fields and set methods: |
|||
valid, result class is TCK class having public fields and a put method: |
|||
valid, result class is TCK class having put method: |
|||
A14.6.12-2: Table 6: Shape of Result (C is the candidate class) |
valid: |
result. |
Positive test. |
valid, this as C: |
|||
valid, unique: |
|||
valid, unique, this as C: |
|||
valid, firstname: |
|||
valid, unique, firstname: |
|||
valid, firstname, lastname: |
|||
valid, unique, firstname, lastname: |
|||
valid, JDK result class, firstname: |
|||
valid, unique, JDK result class, firstname: |
|||
valid, TCK result class: |
|||
valid, unique, TCK result class: |
|||
valid, TCK result class, firstname: |
|||
valid, unique, TCK result class, firstname: |
|||
valid, TCK result class, firstname, lastname: |
|||
valid, unique, TCK result class, firstname, lastname: |
SQL Queries
Assertion |
JDOQL |
Testclass |
Comment |
A14.7-1: In this case, the factory method that takes the language string and Object is used: newQuery (String language, Object query). The language parameter is javax.jdo.query.SQL and the query parameter is the SQL query string. |
SELECT PERSONID FROM persons |
sql. |
Positive test. |
A14.7-2: The only methods that can be used are setClass to establish the candidate class, setUnique to declare that there is only one result row, and setResultClass to establish the result class. |
setClass: |
sql. |
Positive and negative test. |
setUnique(true): |
|||
setResultClass(...FullName): |
|||
invalid: |
|||
parameter binding: |
|||
A14.7-3: SQL queries can be defined without a candidate class. These queries can be found by name using the factory method newNamedQuery, specifying the class as null, or can be constructed without a candidate class. |
named query: |
sql. |
Positive test. |
non-named query: |
|||
A14.7-4: Table 7: Shape of Result of SQL Query |
valid, candidate class, unique is false: |
sql. |
Positive test and negative test. |
valid, candidate class, unique is true: |
|||
invalid, candidate class, unique is true: |
|||
valid, single column, unique is false: |
|||
valid, multiple columns, unique is false: |
|||
valid, multiple columns, unique is true: |
|||
valid, candidate class, result class, unique is false: |
|||
valid, candidate class, result class, unique is true: |
|||
valid, result class, unique is false: |
|||
valid, result class, unique is true: |
|||
valid, result class (binding using put method), unique is false: |
|||
valid, result class (binding using put method), unique is true: |
Deletion by Query
Assertion |
JDOQL |
Testclass |
Comment |
A14.8-1: These methods delete the instances of affected classes that pass the filter, and all dependent instances. Affected classes are the candidate class and its persistence-capable subclasses. |
SELECT * FROM company.Person |
delete. |
Positive test. |
A14.8-2: The number of instances of affected classes that were deleted is returned. Embedded instances and dependent instances are not counted in the return value. |
SELECT * FROM company.Person |
delete. |
Positive test. |
A14.8-3: Query elements filter, parameters, imports, variables, and unique are valid in queries used for delete. Elements result, result class, range, grouping, and ordering are invalid. If any of these elements is set to its non-default value when one of the deletePersistentAll methods is called, a JDOUserException is thrown and no instances are deleted. |
valid: |
delete. |
Positive and negative test. |
invalid result: |
|||
invalid order by: |
|||
invalid group by: |
|||
invalid having: |
|||
invalid range: |
|||
A14.8-4: Dirty instances of affected classes are first flushed to the datastore. Instances already in the cache when deleted via these methods or brought into the cache as a result of these methods undergo the life cycle transitions as if deletePersistent had been called on them. That is, if an affected class implements the DeleteCallback interface, the instances to be deleted are instantiated in memory and the jdoPreDelete method is called prior to deleting the instance in the datastore. If any LifecycleListener instances are registered with affected classes, these listeners are called for each deleted instance. Before returning control to the application, instances of affected classes in the cache are refreshed by the implementation so their status in the cache reflects whether they were deleted from the datastore. |
SELECT * FROM company.Person |
delete. |
Positive test. |
Testcase Pattern
Positive test
A positive test expects that the query compiles and executes w/o exception and returns the expected result:
Query query = pm.newQuery();
...
// define query
Object results = query.execute(...);
// check query result
List expected = new ArrayList();
expected.add(...);
checkQueryResultWithoutOrder(assertion, results, expected);
Negative test
A negative test case uses an invalid JDOQL query and expects an exception to be thrown by compile or execute:
try {
Query query = pm.newQuery();
...
// define query
Object results = query.execute();
fail(ASSERTION_FAILED, text);
} catch (JDOException e) {
if (debug) logger.debug("Caught expected " + e);
}