From d1f498b59e7c8747911703f4255ab4014ab3061c Mon Sep 17 00:00:00 2001 From: Pinaki Poddar Date: Thu, 11 Dec 2008 01:29:10 +0000 Subject: [PATCH] OPENJPA-806: Testing Criteria API by executing against the database. Persistent test classes declare fields as appropriate to the tested queries. Criteria that use new JPQL constructs such as KEY(), CASE etc. are compared literally with handcrafted JPQL string. The supported ones are executed against the database. git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@725522 13f79535-47bb-0310-9956-ffa450edef68 --- .../openjpa/kernel/jpql/JPQLParser.java | 7 +- .../openjpa/kernel/jpql/ParseException.java | 4 + .../openjpa/kernel/jpql/localizer.properties | 2 + .../openjpa/persistence/criteria/Account.java | 40 +++ .../openjpa/persistence/criteria/Address.java | 17 +- .../openjpa/persistence/criteria/Contact.java | 8 + .../persistence/criteria/Contractor.java | 2 + .../openjpa/persistence/criteria/Course.java | 13 +- .../persistence/criteria/CreditCard.java | 11 +- .../persistence/criteria/Customer.java | 30 +++ .../persistence/criteria/Department.java | 3 + .../persistence/criteria/Employee.java | 19 +- .../openjpa/persistence/criteria/Exempt.java | 3 + .../openjpa/persistence/criteria/Item.java | 11 +- .../persistence/criteria/LineItem.java | 32 +++ .../openjpa/persistence/criteria/Manager.java | 3 + .../openjpa/persistence/criteria/Order.java | 12 +- .../openjpa/persistence/criteria/Person.java | 11 +- .../openjpa/persistence/criteria/Phone.java | 3 + .../openjpa/persistence/criteria/Photo.java | 29 +++ .../openjpa/persistence/criteria/Student.java | 29 +++ .../persistence/criteria/TestCriteria.java | 239 +++++++++++++----- .../persistence/criteria/VideoStore.java | 7 + .../persistence/EntityManagerImpl.java | 7 +- .../query/QueryDefinitionImpl.java | 5 +- .../persistence/query/QueryExpression.java | 6 - 26 files changed, 464 insertions(+), 89 deletions(-) create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/LineItem.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Photo.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Student.java diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLParser.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLParser.java index db08d32a9..1f87850cb 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLParser.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLParser.java @@ -45,7 +45,12 @@ public class JPQLParser if (query.getContext().getParameterDeclaration() != null) throw new UserException(_loc.get("param-decs-invalid")); - return new JPQLExpressionBuilder.ParsedJPQL(ql); + try { + return new JPQLExpressionBuilder.ParsedJPQL(ql); + } catch (ParseException e) { + throw new ParseException(_loc.get("jpql-parse-error", + ql, e.getMessage()).getMessage(), e); + } } public void populate(Object parsed, ExpressionStoreQuery query) { diff --git a/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/ParseException.java b/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/ParseException.java index 0943ab0b1..5ccd23fde 100644 --- a/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/ParseException.java +++ b/openjpa-kernel/src/main/jjtree/org/apache/openjpa/kernel/jpql/ParseException.java @@ -75,6 +75,10 @@ public class ParseException public ParseException(String message) { super(message); } + + public ParseException(String message, Throwable t) { + super(message, t); + } /** * This method has the standard behavior when this object has been diff --git a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties index 4dd1b3e61..ca7321b7c 100644 --- a/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties +++ b/openjpa-kernel/src/main/resources/org/apache/openjpa/kernel/jpql/localizer.properties @@ -70,3 +70,5 @@ query-extensions-error: This JPQL query uses non-standard OpenJPA \ extensions in the {0} clause. JPQL string: "{1}". The \ openjpa.Compatibility configuration setting is configured to disallow \ JPQL extensions. +jpql-parse-error: "{1}" while parsing JPQL "{0}". See nested stack trace for \ + original parse error. \ No newline at end of file diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account.java new file mode 100644 index 000000000..82a11b309 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Account.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.criteria; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; + +/** + * Persistent class used in testing QueryDefinition API. + * + * @author Pinaki Poddar + * + */ +@Entity +@Table(name="CR_ACCOUNT") +public class Account { + @Id + @GeneratedValue + private long id; + + private int balance; +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Address.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Address.java index b1d969d5a..87375f0f1 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Address.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Address.java @@ -18,10 +18,11 @@ */ package org.apache.openjpa.persistence.criteria; -import java.util.List; - +import javax.persistence.Embeddable; import javax.persistence.Entity; -import javax.persistence.OneToMany; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.Table; /** * Used for testing Criteria API. @@ -32,8 +33,14 @@ import javax.persistence.OneToMany; * */ @Entity +@Table(name="CR_ADDRESS") +@Embeddable public class Address { + @Id + @GeneratedValue + private long id; + + private String state; + private String county; private String zipCode; - @OneToMany - private List phones; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Contact.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Contact.java index 1d6f80937..ede3998ee 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Contact.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Contact.java @@ -18,9 +18,17 @@ */ package org.apache.openjpa.persistence.criteria; +import java.util.List; + +import javax.persistence.Embeddable; +import javax.persistence.OneToMany; import javax.persistence.OneToOne; +@Embeddable public class Contact { @OneToOne private Address address; + + @OneToMany + private List phones; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Contractor.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Contractor.java index a3a259281..ea3a34822 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Contractor.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Contractor.java @@ -19,8 +19,10 @@ package org.apache.openjpa.persistence.criteria; import javax.persistence.Entity; +import javax.persistence.Table; @Entity +@Table(name="CR_CONTRACTOR") public class Contractor { } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Course.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Course.java index 589ce73d7..616b8911a 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Course.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Course.java @@ -18,9 +18,20 @@ */ package org.apache.openjpa.persistence.criteria; +import java.util.List; + import javax.persistence.Entity; +import javax.persistence.OneToMany; +import javax.persistence.OrderBy; +import javax.persistence.Table; @Entity -public class Course { +@Table(name="CR_COURSE") +public class Course { + private String name; + + @OneToMany + @OrderBy + private List studentWaitList; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/CreditCard.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/CreditCard.java index 78609c60d..b63c23b48 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/CreditCard.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/CreditCard.java @@ -18,9 +18,18 @@ */ package org.apache.openjpa.persistence.criteria; +import java.util.List; + import javax.persistence.Entity; +import javax.persistence.OrderBy; +import javax.persistence.Table; @Entity -public class CreditCard { +@Table(name="CR_CREDITCARD") +public class CreditCard { + private String holdr; + + @OrderBy + private List transactionHistory; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Customer.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Customer.java index 4a2df9556..6dd10afbd 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Customer.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Customer.java @@ -18,10 +18,40 @@ */ package org.apache.openjpa.persistence.criteria; +import java.util.List; +import java.util.Set; + import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; @Entity +@Table(name="CR_CUSTOMER") + public class Customer { + private long id; private String firstName; private String lastName; + @OneToMany + private Set orders; + private int status; + private int balanceOwned; + @OneToOne + private Address address; + + private int filledOrderCount; + private String country; + + @OneToMany + private List accounts; + + public Customer() { + + } + + public Customer(long id, int status, int count) { + + } } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Department.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Department.java index e45b4aa01..e63a12717 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Department.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Department.java @@ -22,8 +22,11 @@ import java.util.Set; import javax.persistence.Entity; import javax.persistence.OneToMany; +import javax.persistence.Table; @Entity +@Table(name="CR_DEPARTMENT") + public class Department { private int deptNo; private String name; diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Employee.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Employee.java index dad98ac28..91a12183b 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Employee.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Employee.java @@ -18,9 +18,26 @@ */ package org.apache.openjpa.persistence.criteria; +import javax.persistence.Embedded; import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.OneToOne; +import javax.persistence.Table; @Entity +@Table(name="CR_EMPLOYEE") + public class Employee { - private Address contactInfo; + private String name; + @Embedded + private Contact contactInfo; + @ManyToOne + private Department department; + private int rating; + private float salary; + @OneToOne + private Employee spouse; + @ManyToOne + private Manager manager; + } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Exempt.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Exempt.java index 63ccfe53d..8b55577a6 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Exempt.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Exempt.java @@ -19,8 +19,11 @@ package org.apache.openjpa.persistence.criteria; import javax.persistence.Entity; +import javax.persistence.Table; @Entity +@Table(name="CR_EXEMPT") + public class Exempt { } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Item.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Item.java index b6ccfb894..d2629becd 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Item.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Item.java @@ -18,9 +18,18 @@ */ package org.apache.openjpa.persistence.criteria; +import java.util.Map; + import javax.persistence.Entity; +import javax.persistence.OneToMany; +import javax.persistence.OneToOne; +import javax.persistence.Table; @Entity -public class Item { +@Table(name="CR_ITEM") +public class Item { + private String name; + @OneToMany + private Map photos; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/LineItem.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/LineItem.java new file mode 100644 index 000000000..3d54bf622 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/LineItem.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.criteria; + +import javax.persistence.Entity; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +@Entity +@Table(name="CR_LINEITEM") + +public class LineItem { + private int price; + @ManyToOne + private Order order; +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Manager.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Manager.java index 99cf404f5..2f4485788 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Manager.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Manager.java @@ -19,8 +19,11 @@ package org.apache.openjpa.persistence.criteria; import javax.persistence.Entity; +import javax.persistence.Table; @Entity +@Table(name="CR_MANAGER") + public class Manager { } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Order.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Order.java index 2d76301f2..f0fd9144a 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Order.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Order.java @@ -18,16 +18,24 @@ */ package org.apache.openjpa.persistence.criteria; +import java.util.List; + import javax.persistence.Entity; import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; import javax.persistence.Table; @Entity -@Table(name="C_ORDER") +@Table(name="CR_ORDER") public class Order { private int quantity; - + private int cost; + private int totalcost; + private int count; @ManyToOne private Customer customer; + + @OneToMany(mappedBy="order") + private List lineItems; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Person.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Person.java index 3fd1e6ab8..c1facd2a3 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Person.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Person.java @@ -18,9 +18,18 @@ */ package org.apache.openjpa.persistence.criteria; +import java.util.List; + import javax.persistence.Entity; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import org.apache.openjpa.persistence.PersistentCollection; @Entity -public class Person { +@Table(name="CR_PERSON") +public class Person { + @PersistentCollection + private List nicknames; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Phone.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Phone.java index 1f9e106b4..ef79c27bd 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Phone.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Phone.java @@ -19,8 +19,11 @@ package org.apache.openjpa.persistence.criteria; import javax.persistence.Entity; +import javax.persistence.Table; @Entity +@Table(name="CR_PHONE") + public class Phone { private String vendor; } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Photo.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Photo.java new file mode 100644 index 000000000..aa94e0e91 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Photo.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.criteria; + +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name="CR_PHOTO") + +public class Photo { + private String label; +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Student.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Student.java new file mode 100644 index 000000000..308635f3f --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/Student.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.openjpa.persistence.criteria; + +import javax.persistence.Entity; +import javax.persistence.Table; + +@Entity +@Table(name="CR_STUDENT") + +public class Student { + +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestCriteria.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestCriteria.java index 1123fc57c..b4b3809cb 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestCriteria.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/TestCriteria.java @@ -19,13 +19,19 @@ package org.apache.openjpa.persistence.criteria; +import java.util.List; + import javax.persistence.CaseExpression; import javax.persistence.DomainObject; +import javax.persistence.EntityManager; +import javax.persistence.EntityManagerFactory; import javax.persistence.Expression; +import javax.persistence.Query; import javax.persistence.QueryBuilder; import javax.persistence.QueryDefinition; import javax.persistence.SelectItem; +import org.apache.openjpa.kernel.jpql.ParseException; import org.apache.openjpa.persistence.query.AbstractDomainObject; import org.apache.openjpa.persistence.query.QueryBuilderImpl; import org.apache.openjpa.persistence.query.QueryDefinitionImpl; @@ -33,11 +39,9 @@ import org.apache.openjpa.persistence.test.SingleEMFTestCase; /** - * Tests by stringifying QueryDefinition and comparing the resultant string + * Tests QueryDefinition and comparing the resultant string * with an equivalent JPQL. * - * Does not execute the query. - * * The examples are taken from Criteria API Section of Java Persistence API * Version 2.0 [1]. * @@ -49,25 +53,39 @@ import org.apache.openjpa.persistence.test.SingleEMFTestCase; */ public class TestCriteria extends SingleEMFTestCase { protected QueryBuilderImpl qb; + private static EntityManagerFactory emf = null; protected StringComparison comparator = new StringComparison(); public void setUp() { - super.setUp(Contractor.class, Course.class, CreditCard.class, - Department.class, Employee.class, Exempt.class, Item.class, - Manager.class, Person.class, VideoStore.class, Order.class, - Customer.class); + if (emf == null) { + super.setUp(DROP_TABLES, + Account.class, + Address.class, + Contact.class, + Contractor.class, + Course.class, + CreditCard.class, + Customer.class, + Department.class, + Employee.class, + Exempt.class, + Item.class, + LineItem.class, + Manager.class, + Person.class, + Order.class, + Phone.class, + Photo.class, + Student.class, + VideoStore.class); + emf = super.emf; + } qb = (QueryBuilderImpl)emf.getQueryBuilder(); + emf.createEntityManager(); } public void tearDown() { - // do nothing as you do not have a database connection - } - - void compare(String s, QueryDefinition q) { - String actual = qb.toJPQL(q); - if (!comparator.compare(s,actual)) { - fail("\r\nExpected: [" + s + "]\r\nActual : [" + actual + "]"); - } + // do nothing as we may not have a database connection } public void testMultipleDomainOfSameClass() { @@ -121,7 +139,7 @@ public class TestCriteria extends SingleEMFTestCase { String jpql = "select i.name, VALUE(p)" + " from Item i join i.photos p" + " where KEY(p) like 'egret'"; - compare(jpql, qdef); + compare(jpql, qdef, "VALUE(p) not supported"); } public void testLiteral() { @@ -145,7 +163,7 @@ public class TestCriteria extends SingleEMFTestCase { String jpql = "select TYPE(e)" + " from Employee e" + " where TYPE(e) <> Exempt"; - compare(jpql, e); + compare(jpql, e, "Type() not supported"); } public void testIndex() { @@ -157,7 +175,7 @@ public class TestCriteria extends SingleEMFTestCase { String jpql = "select s.name" + " from Course c join c.studentWaitList s" + " where c.name = 'Calculus' and INDEX(s) = 0"; - compare(jpql, c); + compare(jpql, c, "Index() not supported"); } public void testSum() { @@ -203,7 +221,7 @@ public class TestCriteria extends SingleEMFTestCase { + " FROM Employee e" + " WHERE e.department.name = 'Engineering'"; - compare(jpql, e); + compare(jpql, e, "Case not supported"); } public void testMemberOf() { @@ -222,7 +240,7 @@ public class TestCriteria extends SingleEMFTestCase { String jpql = "select c from Customer c " + " where c.status = :status"; - compare(jpql, qdef); + compare(jpql, qdef, null, "status", 1); } public void testBetween() { @@ -235,7 +253,8 @@ public class TestCriteria extends SingleEMFTestCase { String jpql = "select t from CreditCard c JOIN c.transactionHistory t" + " where c.holder.name = 'John Doe' AND INDEX(t) " + " BETWEEN 0 AND 9"; - compare(jpql, c); + + compare(jpql, c, "Index() not supported"); } public void testIsEmpty() { @@ -275,7 +294,8 @@ public class TestCriteria extends SingleEMFTestCase { order.get("count"))); - String jpql = "SELECT NEW org.apache.openjpa.persistence.criteria.Customer(c.id, c.status, o.count)" + String jpql = "SELECT NEW org.apache.openjpa.persistence.criteria.Customer" + + "(c.id, c.status, o.count)" + " FROM Customer c JOIN c.orders o" + " WHERE o.count > 100"; compare(jpql, q); @@ -285,7 +305,7 @@ public class TestCriteria extends SingleEMFTestCase { QueryDefinition q = qb.createQueryDefinition(); DomainObject v = q.addRoot(VideoStore.class); DomainObject i = v.join("videoInventory"); - q.where(v.get("location").get("zipcode").equal("94301") + q.where(v.get("location").get("zipCode").equal("94301") .and(i.value().greaterThan(0))); q.select(v.get("location").get("street"), i.key().get("title"), @@ -293,8 +313,9 @@ public class TestCriteria extends SingleEMFTestCase { String jpql = "SELECT v.location.street, KEY(v2).title, VALUE(v2)" + " FROM VideoStore v JOIN v.videoInventory v2" - + " WHERE v.location.zipcode = '94301' AND VALUE(v2) > 0"; - compare(jpql, q); + + " WHERE v.location.zipCode = '94301' AND VALUE(v2) > 0"; + + compare(jpql, q, "KEY() and/or VALUE() not supported"); } public void testGroupByHaving() { @@ -345,35 +366,35 @@ public class TestCriteria extends SingleEMFTestCase { } public void testOrderBy2() { - QueryDefinition q = qb.createQueryDefinition(); - DomainObject customer = q.addRoot(Customer.class); - DomainObject order = customer.join("orders"); - DomainObject address = customer.join("address"); - q.where(address.get("state").equal("CA")) - .select(order.get("quantity"), address.get("zipcode")) - .orderBy(order.get("quantity").desc(), address.get("zipcode")); - String jpql = "SELECT o.quantity, a.zipcode" - + " FROM Customer c JOIN c.orders o JOIN c.address a" - + " WHERE a.state = 'CA'" - + " ORDER BY o.quantity DESC, a.zipcode"; - compare(jpql, q); + QueryDefinition q = qb.createQueryDefinition(); + DomainObject customer = q.addRoot(Customer.class); + DomainObject order = customer.join("orders"); + DomainObject address = customer.join("address"); + q.where(address.get("state").equal("CA")) + .select(order.get("quantity"), address.get("zipCode")) + .orderBy(order.get("quantity").desc(), address.get("zipCode")); + String jpql = "SELECT o.quantity, a.zipCode" + + " FROM Customer c JOIN c.orders o JOIN c.address a" + + " WHERE a.state = 'CA'" + + " ORDER BY o.quantity DESC, a.zipCode"; + compare(jpql, q); } public void testOrderByExpression() { - DomainObject o = qb.createQueryDefinition(Order.class); - DomainObject a = o.join("customer").join("address"); - SelectItem taxedCost = o.get("cost").times(1.08); - o.select(o.get("quantity"), taxedCost, a.get("zipcode")) - .where(a.get("state").equal("CA") - .and(a.get("county").equal("Santa Clara"))) - .orderBy(o.get("quantity"), taxedCost, a.get("zipcode")); - - String jpql = "SELECT o.quantity, o.cost*1.08 as o2, a.zipcode" - + " FROM Order o JOIN o.customer c JOIN c.address a" - + " WHERE a.state = 'CA' AND a.county = 'Santa Clara'" - + " ORDER BY o.quantity, o2, a.zipcode"; - - compare(jpql, o); + DomainObject o = qb.createQueryDefinition(Order.class); + DomainObject a = o.join("customer").join("address"); + SelectItem taxedCost = o.get("cost").times(1.08); + o.select(o.get("quantity"), taxedCost, a.get("zipCode")) + .where(a.get("state").equal("CA") + .and(a.get("county").equal("Santa Clara"))) + .orderBy(o.get("quantity"), taxedCost, a.get("zipCode")); + + String jpql = "SELECT o.quantity, o.cost*1.08 as o2, a.zipCode" + + " FROM Order o JOIN o.customer c JOIN c.address a" + + " WHERE a.state = 'CA' AND a.county = 'Santa Clara'" + + " ORDER BY o.quantity, o2, a.zipCode"; + + compare(jpql, o); } public void testCorrelatedSubquery() { @@ -399,11 +420,11 @@ public class TestCriteria extends SingleEMFTestCase { public void testCreateSubquery() { DomainObject customer = qb.createQueryDefinition(Customer.class); DomainObject order = qb.createSubqueryDefinition(customer.get("orders")); - customer.where(order.select(order.get("price").avg()).greaterThan(100)); + customer.where(order.select(order.get("cost").avg()).greaterThan(100)); String jpql = "SELECT c " + " FROM Customer c" - + " WHERE (SELECT AVG(o.price) FROM c.orders o) > 100"; + + " WHERE (SELECT AVG(o.cost) FROM c.orders o) > 100"; compare(jpql, customer); } @@ -415,16 +436,17 @@ public class TestCriteria extends SingleEMFTestCase { String jpql = "SELECT e " + " FROM Employee e" + " WHERE TYPE(e) IN (Exempt, Contractor)"; - compare(jpql, q); + + compare(jpql, q, "Type() not supported"); } public void testStringList() { - DomainObject q = qb.createQueryDefinition(Employee.class); + DomainObject q = qb.createQueryDefinition(Customer.class); q.where(q.get("country").in("USA", "UK", "France")); - String jpql = "SELECT e " - + " FROM Employee e" - + " WHERE e.country IN ('USA', 'UK', 'France')"; + String jpql = "SELECT c " + + " FROM Customer c" + + " WHERE c.country IN ('USA', 'UK', 'France')"; compare(jpql, q); } @@ -439,11 +461,11 @@ public class TestCriteria extends SingleEMFTestCase { String jpql = "SELECT e.name, f.name, CONCAT(" + " CASE WHEN f.annualMiles > 50000 THEN 'Platinum'" - + " WHEN f.annualMiles > 25000 THEN 'Gold'" + + " WHEN f.annualMiles > 25000 THEN 'Gold'" + " ELSE '' END, 'Frequent Flyer')" - + "FROM Employee e JOIN e.frequentFlierPlan f"; + + " FROM Employee e JOIN e.frequentFlierPlan f"; - compare(jpql, e); + compare(jpql, e, "Case not supported"); } public void testCorrelatedSubquerySpecialCase1() { @@ -456,7 +478,7 @@ public class TestCriteria extends SingleEMFTestCase { + " where 10000 < ALL " + " (select a.balance from o.customer c join o.customer.accounts a)"; - compare(jpql, o); + compare(jpql, o, "SubQuery generates invalid SQL on Derby"); } public void testCorrelatedSubquerySpecialCase2() { @@ -470,7 +492,7 @@ public class TestCriteria extends SingleEMFTestCase { + " where 10000 < ALL " + " (select a.balance from c.accounts a)"; - compare(jpql, o); + compare(jpql, o, "SubQuery generates invalid SQL on Derby"); } public void testRecursiveDefinitionIsNotAllowed() { @@ -478,11 +500,100 @@ public class TestCriteria extends SingleEMFTestCase { q.where(q.exists().and(q.get("name").equal("wrong"))); try { - compare("?", q); + qb.toJPQL(q); fail(); } catch (RuntimeException e) { // good } } + // --------------------------------------------------------------------- + // verification methods + // --------------------------------------------------------------------- + + /** + * Compare by executing the queries generated from the given JPQL and + * QueryDefinition. + */ + void compare(String jpql, QueryDefinition q) { + compare(jpql, q, null, (Object[])null); + } + + /** + * Compare hand crafted JPQL and QueryDefinition. + * If skip is null then execute both queries against the database, otherwise + * compare them literally. + */ + void compare(String jpql, QueryDefinition q, String skip, Object...p) { + boolean execute = (skip == null); + if (execute) { + executeActually(jpql, q, p); + } else { + System.err.println("***WARN: " + this.getName() + + ": skips executing ["+ jpql + "] because " + skip); + compareLiterally(jpql, q); + } + } + + /** + * Compare the string version of QueryDefinition and given JPQL string with + * some flexibility of case-insensitive reserved words. + */ + private void compareLiterally(String jpql, QueryDefinition q) { + String actual = qb.toJPQL(q); + if (!comparator.compare(jpql,actual)) + fail("\r\nExpected: [" + jpql + "]\r\nActual : [" + actual + "]"); + } + + /** + * Executes the given JPQL and QueryDefinition independently and compare + * their results. + */ + private void executeActually(String jpql, QueryDefinition q, Object...p) { + EntityManager em = emf.createEntityManager(); + List criteriaResult = null; + List jpqlResult = null; + Throwable criteriaError = null; + Throwable jpqlError = null; + + try { + Query cq = em.createQuery(q); + setParameters(cq, p); + criteriaResult = cq.getResultList(); + } catch (Exception e) { + criteriaError = e; + } + try { + Query nq = em.createQuery(jpql); + setParameters(nq, p); + jpqlResult = nq.getResultList(); + } catch (Exception e) { + jpqlError = e; + } + + if (criteriaError == null && jpqlError == null) { + assertEquals(criteriaResult.size(), jpqlResult.size()); + } else if (criteriaError != null && jpqlError == null) { + fail("QueryDefinition generated invalid JPQL\r\n" + + "Criteria [" + qb.toJPQL(q) + "]\r\n" + + "error : " + criteriaError.getMessage()); + } else if (criteriaError == null && jpqlError != null) { + fail("Handcrafted JPQL is invalid \r\n" + + "JPQL [" + jpql + "]\r\n" + + "error : " + jpqlError.getMessage()); + } else { + fail("Both JPQL and QueryDefinition failed to execute.\r\n" + + "JPQL " + jpql + "\r\n" + + "error :" + jpqlError.getMessage() + "\r\n" + + "Criteria " + qb.toJPQL(q) + "\r\n" + + "error : " + criteriaError.getMessage()); + } + } + void setParameters(Query q, Object...p) { + if (p == null) + return; + for (int i = 0; i < p.length; i += 2) { + q.setParameter(p[i].toString(), p[i+1]); + } + } } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/VideoStore.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/VideoStore.java index b440abbc2..9bda91f12 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/VideoStore.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/VideoStore.java @@ -19,8 +19,15 @@ package org.apache.openjpa.persistence.criteria; import javax.persistence.Entity; +import javax.persistence.Table; +/** + * + * @author Pinaki Poddar + * + */ @Entity +@Table(name="CR_VIDEOSTORE") public class VideoStore { } diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java index d3c3979ed..8b667ccb4 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/EntityManagerImpl.java @@ -60,6 +60,7 @@ import org.apache.openjpa.kernel.QueryLanguages; import org.apache.openjpa.kernel.Seq; import org.apache.openjpa.kernel.FetchConfiguration; import org.apache.openjpa.kernel.jpql.JPQLParser; +import org.apache.openjpa.kernel.jpql.ParseException; import org.apache.openjpa.lib.util.Closeable; import org.apache.openjpa.lib.util.Localizer; import org.apache.openjpa.meta.ClassMetaData; @@ -1392,8 +1393,8 @@ public class EntityManagerImpl } public Query createQuery(QueryDefinition qdef) { - throw new UnsupportedOperationException( - "JPA 2.0 - Method not yet implemented"); + String jpql = getQueryBuilder().toJPQL(qdef); + return createQuery(jpql); } public T find(Class entityClass, Object primaryKey, @@ -1413,7 +1414,7 @@ public class EntityManagerImpl "JPA 2.0 - Method not yet implemented"); } - public QueryBuilder getQueryBuilder() { + public QueryBuilderImpl getQueryBuilder() { return new QueryBuilderImpl(_emf); } diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/query/QueryDefinitionImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/query/QueryDefinitionImpl.java index f21d10e9f..7b4cb17e1 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/query/QueryDefinitionImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/query/QueryDefinitionImpl.java @@ -395,9 +395,8 @@ public class QueryDefinitionImpl extends ExpressionImpl case EXPRESSION : buffer.append(v.asExpression(ctx)) .append(i != list.size()-1 ? ", " : " "); break; - case JOINABLE : buffer.append(v.asJoinable(ctx)) - .append(i > 0 && v instanceof RootPath ? - "," : " "); + case JOINABLE : buffer.append(i > 0 && v instanceof RootPath ? + ", " : " ").append(v.asJoinable(ctx)); break; } } diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/query/QueryExpression.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/query/QueryExpression.java index c4e0da4ef..f25e50b81 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/query/QueryExpression.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/query/QueryExpression.java @@ -32,10 +32,4 @@ abstract class QueryExpression extends UnaryOperatorExpression public QueryExpression(QueryDefinitionImpl q, UnaryFunctionalOperator op) { super(q, op); } - - @Override - public String asExpression(AliasContext ctx) { - return "(" + super.asExpression(ctx) + ")"; - } - }