diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Item.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Item.java new file mode 100644 index 000000000..8516ead7b --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Item.java @@ -0,0 +1,56 @@ +/* + * 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.results; + +import javax.persistence.*; + +@Entity +@Table(name="CRIT_RES_ITEM") +public class Item { + @Id + @GeneratedValue + private int id; + + @ManyToOne + private Order order; + + @OneToOne(cascade=CascadeType.PERSIST) + private Producer producer; + + public Producer getProduct() { + return producer; + } + + public void setProduct(Producer product) { + this.producer = product; + } + + public int getId() { + return id; + } + + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Item_.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Item_.java new file mode 100644 index 000000000..53f107ab2 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Item_.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.results; + +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +@StaticMetamodel(value=Item.class) +public class Item_ { + public static volatile SingularAttribute order; + public static volatile SingularAttribute producer; +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Order.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Order.java new file mode 100644 index 000000000..5f942db7d --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Order.java @@ -0,0 +1,89 @@ +/* + * 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.results; + +import java.util.Date; +import java.util.Set; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.ManyToOne; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +@Entity +@Table(name="CRIT_RES_ORD") +public class Order { + @Id + long id; + boolean filled; + Date date; + + @OneToMany(cascade=CascadeType.PERSIST) + Set items; + + @ManyToOne + Shop shop; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public boolean isFilled() { + return filled; + } + + public void setFilled(boolean filled) { + this.filled = filled; + } + + public Date getDate() { + return date; + } + + public void setDate(Date date) { + this.date = date; + } + + public Set getItems() { + return items; + } + + public void setItems(Set items) { + this.items = items; + } + + public Shop getShop() { + return shop; + } + + public void setShop(Shop shop) { + this.shop = shop; + } + + public String toPrettyString() { + return String.format("Order: %s date=%s filled=%s",getId(), getDate(), isFilled()); + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/OrderProducer.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/OrderProducer.java new file mode 100644 index 000000000..a3d2a738b --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/OrderProducer.java @@ -0,0 +1,51 @@ +/* + * 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.results; + +public class OrderProducer { + + private Order order; + private Producer producer; + + public OrderProducer(Order o, Producer p) { + order = o; + producer = p; + } + + public String toString() { + return order.toPrettyString() + " " + producer.toString(); + } + + public Order getOrder() { + return order; + } + + public void setOrder(Order order) { + this.order = order; + } + + public Producer getProducer() { + return producer; + } + + public void setProduct(Producer producer) { + this.producer = producer; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Order_.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Order_.java new file mode 100644 index 000000000..3cd1b1201 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Order_.java @@ -0,0 +1,34 @@ +/* + * 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.results; + +import java.util.Date; + +import javax.persistence.metamodel.SetAttribute; +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +@StaticMetamodel(value = Order.class) +public class Order_ { + public static volatile SetAttribute items; + public static volatile SingularAttribute filled; + public static volatile SingularAttribute date; + public static volatile SingularAttribute shop; +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Producer.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Producer.java new file mode 100644 index 000000000..ef79d4474 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Producer.java @@ -0,0 +1,61 @@ +/* + * 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.results; + +import javax.persistence.Entity; +import javax.persistence.OneToOne; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +@Entity +public class Producer { + @Id + @GeneratedValue + private int id; + + @OneToOne + Item item; + + private String name; + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public Item getItem() { + return item; + } + + public void setItem(Item item) { + this.item = item; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Producer_.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Producer_.java new file mode 100644 index 000000000..1c55d815b --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Producer_.java @@ -0,0 +1,28 @@ +/* + * 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.results; + +import javax.persistence.metamodel.StaticMetamodel; +import javax.persistence.metamodel.SingularAttribute; + +@StaticMetamodel(value=Producer.class) +public class Producer_ { + public static volatile SingularAttribute item; +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Shop.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Shop.java new file mode 100644 index 000000000..698e925d6 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Shop.java @@ -0,0 +1,64 @@ +/* + * 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.results; + +import javax.persistence.CascadeType; +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.OneToMany; +import javax.persistence.Table; + +import java.util.Set; + +@Entity +@Table(name="CRIT_RES_SHOP") +public class Shop { + @Id + long id; + + String name; + + @OneToMany(cascade=CascadeType.PERSIST) + Set orders; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getOrders() { + return orders; + } + + public void setOrders(Set orders) { + this.orders = orders; + } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Shop_.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Shop_.java new file mode 100644 index 000000000..6c585b428 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/Shop_.java @@ -0,0 +1,30 @@ +/* + * 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.results; + +import javax.persistence.metamodel.SetAttribute; +import javax.persistence.metamodel.SingularAttribute; +import javax.persistence.metamodel.StaticMetamodel; + +@StaticMetamodel(value=Shop.class) +public class Shop_ { + public static volatile SingularAttribute name; + public static volatile SetAttributeorders; +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/TestTypedResults.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/TestTypedResults.java new file mode 100644 index 000000000..328278274 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/criteria/results/TestTypedResults.java @@ -0,0 +1,234 @@ +/* + * 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.results; + +import java.text.DateFormat; +import java.text.ParseException; +import java.util.HashSet; +import java.util.List; + +import javax.persistence.EntityManager; +import javax.persistence.Query; +import javax.persistence.Tuple; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.CriteriaQuery; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.QueryBuilder; +import javax.persistence.criteria.Root; + +import org.apache.openjpa.persistence.test.SingleEMFTestCase; + +public class TestTypedResults extends SingleEMFTestCase { + + private static final int N_ORDERS = 15; + private static final int N_ITEMS_PER_ORDER = 3; + + // use short data format + private static final String[] ORDER_DATES = + { "3/12/2008 1:00 PM", "10/01/2008 1:51 AM", "12/12/2008 10:01 AM", "5/21/2009 3:23 PM" }; + + DateFormat df = DateFormat.getInstance(); // uses SHORT dateformat by default + + public void setUp() throws Exception { + setUp(Order.class, Item.class, Shop.class, Producer.class); + populate(); + } + + public void populate() throws ParseException { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + Shop s = new Shop(); + Order order; + Item item; + Producer p; + + s.setId(1); + s.setName("eBay.com"); + s.setOrders(new HashSet()); + + for (int i = 1; i <= N_ORDERS; i++) { + order = new Order(); + order.setId(i); + order.setDate(df.parse(ORDER_DATES[i % ORDER_DATES.length])); + order.setFilled(i % 2 == 0 ? true : false); + order.setShop(s); + order.setItems(new HashSet()); + s.getOrders().add(order); + for (int j = 1; j <= N_ITEMS_PER_ORDER; j++) { + item = new Item(); + item.setOrder(order); + order.getItems().add(item); + p = new Producer(); + p.setName("filler"); + p.setItem(item); + item.setProduct(p); + } + } + em.persist(s); + em.getTransaction().commit(); + em.close(); + } + + public void tearDown() throws Exception { + super.tearDown(); + } + + /** + * Verify that a query using a date field works the same with JPQL, JPQL (typed), Criteria (typed), and via a + * NativeQuery + * + * @throws Exception + */ + public void testTypedJPQLQuery() { + EntityManager em = emf.createEntityManager(); + + Query jpqlQuery = em.createQuery("Select o from Order o where o.filled = true"); + // Don't suppress warnings. + List jpqlResults = jpqlQuery.getResultList(); + assertEquals(N_ORDERS / 2, jpqlResults.size()); + + TypedQuery jpqlTypedQuery = em.createQuery("Select o from Order o where o.filled = true", Order.class); + List jpqlTypedResults = jpqlTypedQuery.getResultList(); + assertEquals(N_ORDERS / 2, jpqlTypedResults.size()); + + // create the same query and get typed results. + QueryBuilder qb = em.getQueryBuilder(); + CriteriaQuery cq = qb.createQuery(Order.class); + Root order = cq.from(Order.class); + cq.select(order).where(qb.equal(order.get(Order_.filled), Boolean.TRUE)); + + TypedQuery typedCriteriaQuery = em.createQuery(cq); + List typedCriteriaResults = typedCriteriaQuery.getResultList(); + assertEquals(N_ORDERS / 2, typedCriteriaResults.size()); + + Query nativeQ = em.createNativeQuery("SELECT * FROM CRIT_RES_ORD o WHERE (o.filled = 1)", Order.class); + // Don't suppress warnings. + List typedNativeResults = nativeQ.getResultList(); + assertEquals(N_ORDERS / 2, typedNativeResults.size()); + + for (Order o : jpqlResults) { + assertTrue(jpqlTypedResults.contains(o)); + assertTrue(typedCriteriaResults.contains(o)); + assertTrue(typedNativeResults.contains(o)); + } + em.close(); + } + + /** + * Verify that a query using a date field works the same with JPQL, JPQL (typed), Criteria (typed), and via a + * NativeQuery + * + * @throws Exception + */ + public void testDateQuery() throws Exception { + EntityManager em = emf.createEntityManager(); + + Query jpqlQuery = em.createQuery("Select o from Order o where o.date < :maxDate"); + jpqlQuery.setParameter("maxDate", df.parse(ORDER_DATES[2])); + List jpqlResults = jpqlQuery.getResultList(); + assertEquals(N_ORDERS / 2, jpqlResults.size()); + + TypedQuery typedJpqlQuery = em.createQuery("Select o from Order o where o.date < :maxDate", Order.class); + typedJpqlQuery.setParameter("maxDate", df.parse(ORDER_DATES[2])); + List typedJpqlResults = typedJpqlQuery.getResultList(); + assertEquals(N_ORDERS / 2, typedJpqlResults.size()); + + QueryBuilder qb = em.getQueryBuilder(); + CriteriaQuery criteriaQuery = qb.createQuery(Order.class); + Root order = criteriaQuery.from(Order.class); + criteriaQuery.select(order).where(qb.lessThan(order.get(Order_.date), df.parse(ORDER_DATES[2]))); + TypedQuery tq = em.createQuery(criteriaQuery); + List criteriaResults = tq.getResultList(); + assertEquals(N_ORDERS / 2, criteriaResults.size()); + + String parm = new java.sql.Timestamp(df.parse(ORDER_DATES[2]).getTime()).toString(); + Query nativeQuery = + em.createNativeQuery("Select * from CRIT_RES_ORD o WHERE (o.date < '" + parm + "')", Order.class); + List nativeResults = nativeQuery.getResultList(); + assertEquals(N_ORDERS / 2, nativeResults.size()); + + for (Order o : jpqlResults) { + assertTrue(typedJpqlResults.contains(o)); + assertTrue(criteriaResults.contains(o)); + assertTrue(nativeResults.contains(o)); + } + em.close(); + } + + /** + * Testcase to verify that selecting multiple results in a variety of ways returns the same results. Results are + * returned via a normal Object [] (JPQL), Tuple (Criteria), and a custom tuple (Criteria.construct) + * + * @throws Exception + */ + public void testMultiSelect() throws Exception { + // get results from traditional JPQL + EntityManager em = emf.createEntityManager(); + Query jpqlQuery = + em.createQuery("SELECT o, p from Order o JOIN o.items i JOIN i.producer p WHERE o.filled = true"); + // don't suppress warnings. + List jpqlResults = jpqlQuery.getResultList(); + + // Get results using Tuple + QueryBuilder qb = em.getQueryBuilder(); + CriteriaQuery criteriaQuery = qb.createTupleQuery(); + Root order = criteriaQuery.from(Order.class); + Join producer = order.join(Order_.items).join(Item_.producer); + criteriaQuery.select(qb.tuple(order, producer)); + criteriaQuery.where(qb.equal(order.get(Order_.filled), Boolean.TRUE)); + TypedQuery eq = em.createQuery(criteriaQuery); + List criteriaResults = eq.getResultList(); + + // Get results using custom class + CriteriaQuery constructQuery = qb.createQuery(OrderProducer.class); + Root order2 = constructQuery.from(Order.class); + Join producer2 = order.join(Order_.items).join(Item_.producer); + constructQuery.select(qb.construct(OrderProducer.class, order2, producer2)); + constructQuery.where(qb.equal(order2.get(Order_.filled), Boolean.TRUE)); + TypedQuery typedQuery = em.createQuery(constructQuery); + List constructResults = typedQuery.getResultList(); + + assertEquals(N_ORDERS / 2 * N_ITEMS_PER_ORDER, jpqlResults.size()); + assertEquals(N_ORDERS / 2 * N_ITEMS_PER_ORDER, criteriaResults.size()); + assertEquals(N_ORDERS / 2 * N_ITEMS_PER_ORDER, constructResults.size()); + + for (Object[] os : jpqlResults) { + assertEquals(2, os.length); + assertTrue(os[0] instanceof Order); + assertTrue(os[1] instanceof Producer); + } + + // cheap way to ensure that we have the same contents. + // if needed an orderBy clause can be added to make this more robust. + Object[] jpqlTuple; + Tuple criteriaTuple; + OrderProducer constructTuple; + for (int i = 0; i < jpqlResults.size(); i++) { + jpqlTuple = jpqlResults.get(i); + criteriaTuple = criteriaResults.get(i); + constructTuple = constructResults.get(i); + assertEquals(jpqlTuple[0], criteriaTuple.get(0)); + assertEquals(jpqlTuple[1], criteriaTuple.get(1)); + assertEquals(jpqlTuple[0], constructTuple.getOrder()); + assertEquals(jpqlTuple[1], constructTuple.getProducer()); + } + em.close(); + } +} 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 45c270711..5b0162fa6 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 @@ -945,7 +945,8 @@ public class EntityManagerImpl } public TypedQuery createQuery(String query, Class resultClass) { - throw new UnsupportedOperationException(); + return new QueryImpl(this, _ret, + _broker.newQuery(JPQLParser.LANG_JPQL, query)); } public OpenJPAQuery createQuery(String query) { diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/TupleElementImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/TupleElementImpl.java index 931648bee..714f80ad6 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/TupleElementImpl.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/TupleElementImpl.java @@ -16,6 +16,7 @@ * specific language governing permissions and limitations * under the License. */ + package org.apache.openjpa.persistence; import javax.persistence.TupleElement; @@ -30,6 +31,7 @@ import javax.persistence.TupleElement; public class TupleElementImpl implements TupleElement { private String _alias; protected final Class _cls; + private X _value; protected TupleElementImpl(Class cls) { _cls = cls; @@ -46,4 +48,15 @@ public class TupleElementImpl implements TupleElement { public Class getJavaType() { return _cls; } + + public X getValue() { + return _value; + } + + @SuppressWarnings("unchecked") + public void setValue(Object x) { + // X is unknown at compile time in TupleImpl when we construct a new Tuple. + // so we're stuck with this ugly cast. + _value = (X) x; + } } diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/TupleImpl.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/TupleImpl.java new file mode 100644 index 000000000..86e86d2a0 --- /dev/null +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/TupleImpl.java @@ -0,0 +1,173 @@ +/* + * 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; + +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.Tuple; +import javax.persistence.TupleElement; + +public class TupleImpl implements Tuple { + List> elements = new ArrayList>(); + + /** + * Get the value of the specified tuple element. + * + * @param tupleElement + * tuple element + * @return value of tuple element + * @throws IllegalArgumentException + * if tuple element does not correspond to an element in the query result tuple + */ + public X get(TupleElement tupleElement) { + if (!elements.contains(tupleElement)) { + throw new IllegalArgumentException("tupleElement was not found in this tuple"); // TODO MDD improve + } + + TupleElementImpl impl = (TupleElementImpl) tupleElement; + return impl.getValue(); + } + + /** + * Get the value of the tuple element to which the specified alias has been assigned. + * + * @param alias + * alias assigned to tuple element + * @param type + * of the tuple element + * @return value of the tuple element + * @throws IllegalArgumentException + * if alias does not correspond to an element in the query result tuple or element cannot be assigned to + * the specified type + */ + @SuppressWarnings("unchecked") + public X get(String alias, Class type) { + if (type == null) { + throw new IllegalArgumentException("Type was null"); + } + Object rval = get(alias); + if (!type.isAssignableFrom(rval.getClass())) { + throw new IllegalArgumentException(String.format( + "TupleElement type did not match for alias: %s. Provided type: %s actual type: %s", alias, type, rval + .getClass().toString())); + } + return (X) rval; + } + + /** + * Get the value of the tuple element to which the specified alias has been assigned. + * + * @param alias + * alias assigned to tuple element + * @return value of the tuple element + * @throws IllegalArgumentException + * if alias does not correspond to an element in the query result tuple + */ + public Object get(String alias) { + if (alias == null) { + throw new IllegalArgumentException(String.format("Alias was null.")); + } + for (TupleElement te : elements) { + if (alias.equals(te.getAlias())) { + return ((TupleElementImpl) te).getValue(); + } + } + throw new IllegalArgumentException(String.format("Alias %s was not found.", alias)); + } + + /** + * Get the value of the element at the specified position in the result tuple. The first position is 0. + * + * @param i + * position in result tuple + * @param type + * type of the tuple element + * @return value of the tuple element + * @throws IllegalArgumentException + * if i exceeds length of result tuple or element cannot be assigned to the specified type + */ + @SuppressWarnings("unchecked") + public X get(int i, Class type) { + if (type == null) { + throw new IllegalArgumentException("Type was null"); + } + Object rval = get(i); + if(! type.isAssignableFrom(rval.getClass())) { + throw new IllegalArgumentException(String.format( + "Type did not match for position: %d. Provided type: %s actual type: %s", i, type.getClass(), + rval.getClass())); + } + return (X) rval; + } + + /** + * Get the value of the element at the specified position in the result tuple. The first position is 0. + * + * @param i + * position in result tuple + * @return value of the tuple element + * @throws IllegalArgumentException + * if i exceeds length of result tuple + */ + public Object get(int i) { + if (i > elements.size()) { + throw new IllegalArgumentException(String.format( + "Attempt to read TupleElement %d when there are only %d elements available", i, elements.size())); + } + if (i == -1) { + throw new IllegalArgumentException("Cannot obtain the -1th element in this tuple. Thank you for playing"); + } + return toArray()[i]; + } + + /** + * Return the values of the result tuple elements as an array. + * + * @return tuple element values + */ + public Object[] toArray() { + Object[] rval = new Object[elements.size()]; + int i = 0; + for (TupleElement tupleElement : elements) { + rval[i] = ((TupleElementImpl) tupleElement).getValue(); + i++; + } + return rval; + } + + /** + * Return the tuple elements + * + * @return tuple elements + */ + public List> getElements() { + return elements; + } + + @SuppressWarnings("unchecked") + public void put(Object key, Object value) { + // TODO check for duplicate aliases? + TupleElementImpl element = new TupleElementImpl(value.getClass()); + element.setAlias((String) key); + element.setValue(value); + elements.add(element); + } +} diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java index 2ab0a215d..d757c6090 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaBuilder.java @@ -678,8 +678,7 @@ public class CriteriaBuilder implements QueryBuilder, ExpressionParser { return new Expressions.IsNull((ExpressionImpl )x); } - public CompoundSelection tuple(Selection... arg0) { - // TODO Auto-generated method stub - return null; + public CompoundSelection tuple(Selection... selections) { + return new TupleSelection(Tuple.class, selections); } } diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaExpressionBuilder.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaExpressionBuilder.java index aa8bd8095..6a5d25007 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaExpressionBuilder.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/CriteriaExpressionBuilder.java @@ -40,6 +40,7 @@ import org.apache.openjpa.kernel.exps.ExpressionFactory; import org.apache.openjpa.kernel.exps.QueryExpressions; import org.apache.openjpa.kernel.exps.Value; import org.apache.openjpa.meta.ClassMetaData; +import org.apache.openjpa.persistence.TupleImpl; import org.apache.openjpa.persistence.meta.AbstractManagedType; import org.apache.openjpa.persistence.meta.Members; import org.apache.openjpa.persistence.meta.MetamodelImpl; @@ -242,7 +243,12 @@ public class CriteriaExpressionBuilder { ExpressionFactory factory, CriteriaQueryImpl q, MetamodelImpl model, Map, Value> exp2Vals) { for (Selection s : selections) { - if (s instanceof NewInstanceSelection) { + if(s instanceof TupleSelection ) { + exps.resultClass = TupleImpl.class; + getProjections(exps, ((TupleSelection)s).getSelectionItems(), projections, aliases, + clauses, factory, q, model, exp2Vals); + } + else if (s instanceof NewInstanceSelection) { exps.resultClass = s.getJavaType(); getProjections(exps, ((NewInstanceSelection)s).getSelectionItems(), projections, aliases, clauses, factory, q, model, exp2Vals); diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/NewInstanceSelection.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/NewInstanceSelection.java index d4bb4f4f2..35ce09942 100644 --- a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/NewInstanceSelection.java +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/NewInstanceSelection.java @@ -24,8 +24,6 @@ import java.util.List; import javax.persistence.criteria.CompoundSelection; import javax.persistence.criteria.Selection; -import org.apache.openjpa.persistence.TupleElementImpl; - /** * A selection item that constructs new instance of a user-defined class with arguments specified as other selected * items. diff --git a/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/TupleSelection.java b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/TupleSelection.java new file mode 100644 index 000000000..d772986b7 --- /dev/null +++ b/openjpa-persistence/src/main/java/org/apache/openjpa/persistence/criteria/TupleSelection.java @@ -0,0 +1,10 @@ +package org.apache.openjpa.persistence.criteria; + +import javax.persistence.criteria.Selection; + +public class TupleSelection extends NewInstanceSelection { + + public TupleSelection(final Class cls, final Selection[] selections) { + super(cls, selections); + } +} diff --git a/openjpa-persistence/src/test/java/org/apache/openjpa/persistence/TestTupleImpl.java b/openjpa-persistence/src/test/java/org/apache/openjpa/persistence/TestTupleImpl.java new file mode 100644 index 000000000..bbad26a18 --- /dev/null +++ b/openjpa-persistence/src/test/java/org/apache/openjpa/persistence/TestTupleImpl.java @@ -0,0 +1,269 @@ +/* + * 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; + +import java.util.List; + +import javax.persistence.Entity; +import javax.persistence.Tuple; +import javax.persistence.TupleElement; + +import junit.framework.TestCase; + +/** + * Test the following methods in TupleImpl + *
    + *
  • Tuple
  • + *
      + *
    • get(int)
    • + *
    • get(int, Class)
    • + *
    • get(String)
    • + *
    • get(String, Class)
    • + *
    • get(TupleElement)
    • + *
    • getElements()
    • + *
    • toArray()
    • + *
    + *
  • TupleImpl
  • + *
      + *
    • get(Object)
    • + *
    • getValues()
    • + *
    • put(Object, Object)
    • + *
    + *
+ */ +public class TestTupleImpl extends TestCase { + protected Order _order = new Order(); + protected Product _product = new Product(); + protected Item _item = new Item(); + protected Store _store = new Store(); + protected UrgentOrder _urgentOrder = new UrgentOrder(); + protected Tuple tuple = getTuple(); + + public void setUp() { + } + + /** + * Default tuple with some arbitrary pseudo entities + * + * @return + */ + protected TupleImpl getTuple() { + TupleImpl tuple = new TupleImpl(); + tuple.put("alias1", _order); + tuple.put("alias2", _product); + tuple.put("alias3", _item); + tuple.put("alias4", _store); + tuple.put("alias5", _urgentOrder); + return tuple; + } + + public void testGetInt() { + assertEquals(_order, tuple.get(0)); + assertEquals(_product, tuple.get(1)); + assertEquals(_item, tuple.get(2)); + assertEquals(_store, tuple.get(3)); + assertEquals(_urgentOrder, tuple.get(4)); + // TODO MDD more tests + } + + public void testGetIntNegativeValueThrowsException() { + try { + Object o = tuple.get(-1); + fail("tuple.get(-1) should throw IllegalArgumentException"); + o.toString(); // clean up meaningless compiler warning + } catch (IllegalArgumentException iae) { + // expected + } + } + + public void testGetIntOutOfRangeThrowsException() { + try { + Object o = tuple.get(10); + fail("tuple.get(i) where i > size of TupleElements should throw IllegalArgumentException"); + o.toString(); // clean up meaningless compiler warning + } catch (IllegalArgumentException iae) { + // expected + } + try { + Object o = tuple.get(Integer.MAX_VALUE); + fail("tuple.get(i) where i > size of TupleElements should throw IllegalArgumentException"); + o.toString(); // clean up meaningless compiler warning + } catch (IllegalArgumentException iae) { + // expected + } + } + + public void testGetIntClass() { + assertEquals(_order, tuple.get(0, Order.class)); + assertEquals(_product, tuple.get(1, Product.class)); + assertEquals(_item, tuple.get(2, Item.class)); + assertEquals(_store, tuple.get(3, Store.class)); + assertEquals(_urgentOrder, tuple.get(4, UrgentOrder.class)); + + assertEquals(_urgentOrder, tuple.get(4, Order.class)); + + } + + public void testGetIntClassExceptions() { + // duplicate code, but could be useful later if impl changes. + try { + Object o = tuple.get(-1, Order.class); + fail("tuple.get(-1) should throw IllegalArgumentException"); + o.toString(); // clean up meaningless compiler warning + } catch (IllegalArgumentException iae) { + // expected + } + try { + Object o = tuple.get(200, Product.class); + fail("tuple.get(i) where i > size of TupleElements should throw IllegalArgumentException"); + o.toString(); // clean up meaningless compiler warning + } catch (IllegalArgumentException iae) { + // expected + } + + try { + Product product = (Product) tuple.get(0, Product.class); + fail("Expecting IllegalArgumentException when the wrong type is specified on Tuple.get(int, Class)"); + product.toString(); // remove compiler warning for unused variable + } catch (IllegalArgumentException iae) { + // expected + } + try { + tuple.get(0, UrgentOrder.class); + fail("Should not be able to upcast Order to UrgentOrder"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + public void testGetString() { + assertEquals(_order, tuple.get("alias1")); + assertEquals(_product, tuple.get("alias2")); + assertEquals(_item, tuple.get("alias3")); + assertEquals(_store, tuple.get("alias4")); + assertEquals(_urgentOrder, tuple.get("alias5")); + + try { + tuple.get("NotAnAlias"); + fail("Expected an IllegalArgumentException for an alias that wasn't found"); + } catch (IllegalArgumentException iae) { + // expected + } + try { + tuple.get((String) null); + fail("Expected an IllegalArgumentException for null alias"); + } catch (IllegalArgumentException iae) { + // expected + } + try { + tuple.get(""); + fail("Expected an IllegalArgumentException for null alias"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + public void testGetStringClass() { + // TODO MDD convert to equals tests + assertTrue(tuple.get("alias1", Order.class) instanceof Order); + assertTrue(tuple.get("alias2", Product.class) instanceof Product); + assertTrue(tuple.get("alias3", Item.class) instanceof Item); + assertTrue(tuple.get("alias4", Store.class) instanceof Store); + + try { + tuple.get("NotAnAlias", Product.class); + fail("Expected an IllegalArgumentException for an alias that wasn't found"); + } catch (IllegalArgumentException iae) { + // expected + } + try { + tuple.get((String) null, Item.class); + fail("Expected an IllegalArgumentException for null alias"); + } catch (IllegalArgumentException iae) { + // expected + } + try { + tuple.get("", Store.class); + fail("Expected an IllegalArgumentException for null alias"); + } catch (IllegalArgumentException iae) { + // expected + } + } + + public void testGetTupleElement() { + for (TupleElement element : tuple.getElements()) { + assertEquals(((TupleElementImpl) element).getValue(), tuple.get(element)); + } + } + + public void testToArray() { + Object[] objects = tuple.toArray(); + assertEquals(5, objects.length); + assertEquals(_order, objects[0]); + assertEquals(_product, objects[1]); + assertEquals(_item, objects[2]); + assertEquals(_store, objects[3]); + assertEquals(_urgentOrder, objects[4]); + } + + @SuppressWarnings("unchecked") + public void testGetElements() { + List> elements = tuple.getElements(); + assertEquals(5, elements.size()); + + TupleElement orderElement = (TupleElement) elements.get(0); + TupleElement productElement = (TupleElement) elements.get(1); + TupleElement itemElement = (TupleElement) elements.get(2); + TupleElement storeElement = (TupleElement) elements.get(3); + TupleElement urgentOrderElement = (TupleElement) elements.get(4); + + assertEquals("alias1", orderElement.getAlias()); + assertEquals(Order.class, orderElement.getJavaType()); + assertEquals("alias2", productElement.getAlias()); + assertEquals(Product.class, productElement.getJavaType()); + assertEquals("alias3", itemElement.getAlias()); + assertEquals(Item.class, itemElement.getJavaType()); + assertEquals("alias4", storeElement.getAlias()); + assertEquals(Store.class, storeElement.getJavaType()); + assertEquals("alias5", urgentOrderElement.getAlias()); + assertEquals(UrgentOrder.class, urgentOrderElement.getJavaType()); + } + + + + // Begin fake entities. + class Order { + // public Order() { + // new Exception().printStackTrace(); + // } + } + + class UrgentOrder extends Order { + } + + class Item { + } + + class Product { + } + + class Store { + } +}