From f56cd6a62a1fc46f7d1f57f1af1e31a50747d399 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kraus?= Date: Fri, 3 Nov 2023 15:24:55 +0100 Subject: [PATCH] jakartaee/persistence#457 - add union/intersect/except to JPQL and criteria MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomáš Kraus --- .../queries/ObjectLevelReadQuery.java | 32 ++ .../persistence/queries/ReportQuery.java | 1 + .../JUnitCriteriaSimpleTestSuiteBase.java | 53 ++- .../persistence32/UnionCriteriaQueryTest.java | 416 ++++++++++++++++++ .../jpa/EntityManagerFactoryDelegate.java | 6 +- .../jpa/EntityManagerFactoryImpl.java | 6 +- .../internal/jpa/EntityManagerImpl.java | 26 +- .../querydef/CommonAbstractCriteriaImpl.java | 21 +- .../jpa/querydef/CriteriaBuilderImpl.java | 50 ++- .../jpa/querydef/CriteriaMultiSelectImpl.java | 137 ++++++ .../jpa/querydef/CriteriaQueryImpl.java | 159 +++++-- .../jpa/querydef/CriteriaSelectInternal.java | 25 ++ pom.xml | 2 +- 13 files changed, 856 insertions(+), 78 deletions(-) create mode 100644 jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java create mode 100644 jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaMultiSelectImpl.java create mode 100644 jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaSelectInternal.java diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ObjectLevelReadQuery.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ObjectLevelReadQuery.java index 09c8c9c57fa..1e646678079 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ObjectLevelReadQuery.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ObjectLevelReadQuery.java @@ -26,6 +26,8 @@ // 09/21/2010-2.2 Frank Schwarz and ailitchev - Bug 325684 - QueryHints.BATCH combined with QueryHints.FETCH_GROUP_LOAD will cause NPE // 3/13/2015 - Will Dazey // - 458301 : Added check so that aggregate results won't attempt force version lock if locking type is set +// 10/25/2023: Tomas Kraus +// - New Jakarta Persistence 3.2 Features package org.eclipse.persistence.queries; import org.eclipse.persistence.annotations.BatchFetchType; @@ -263,6 +265,16 @@ protected ObjectLevelReadQuery() { */ public void union(ReportQuery query) { addUnionExpression(getExpressionBuilder().union(query)); + query.getArguments().forEach(this::addArgument); + } + + /** + * PUBLIC: + * UnionAll the query results with the other query. + */ + public void unionAll(ReportQuery query) { + addUnionExpression(getExpressionBuilder().unionAll(query)); + query.getArguments().forEach(this::addArgument); } /** @@ -271,6 +283,16 @@ public void union(ReportQuery query) { */ public void intersect(ReportQuery query) { addUnionExpression(getExpressionBuilder().intersect(query)); + query.getArguments().forEach(this::addArgument); + } + + /** + * PUBLIC: + * IntersectAll the query results with the other query. + */ + public void intersectAll(ReportQuery query) { + addUnionExpression(getExpressionBuilder().intersectAll(query)); + query.getArguments().forEach(this::addArgument); } /** @@ -279,6 +301,16 @@ public void intersect(ReportQuery query) { */ public void except(ReportQuery query) { addUnionExpression(getExpressionBuilder().except(query)); + query.getArguments().forEach(this::addArgument); + } + + /** + * PUBLIC: + * ExceptAll the query results with the other query. + */ + public void exceptAll(ReportQuery query) { + addUnionExpression(getExpressionBuilder().exceptAll(query)); + query.getArguments().forEach(this::addArgument); } /** diff --git a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQuery.java b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQuery.java index c215e5feb61..597e0bb6ce6 100644 --- a/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQuery.java +++ b/foundation/org.eclipse.persistence.core/src/main/java/org/eclipse/persistence/queries/ReportQuery.java @@ -125,6 +125,7 @@ private enum ResultStatus { IGNORED }; * The builder should be provided. */ public ReportQuery() { + super(); this.queryMechanism = new ExpressionQueryMechanism(this); this.items = new ArrayList<>(); this.shouldRetrievePrimaryKeys = NO_PRIMARY_KEY; diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.criteria/src/test/java/org/eclipse/persistence/testing/tests/jpa/criteria/advanced/JUnitCriteriaSimpleTestSuiteBase.java b/jpa/eclipselink.jpa.testapps/jpa.test.criteria/src/test/java/org/eclipse/persistence/testing/tests/jpa/criteria/advanced/JUnitCriteriaSimpleTestSuiteBase.java index cb0a3e47a30..70c263a3e9d 100644 --- a/jpa/eclipselink.jpa.testapps/jpa.test.criteria/src/test/java/org/eclipse/persistence/testing/tests/jpa/criteria/advanced/JUnitCriteriaSimpleTestSuiteBase.java +++ b/jpa/eclipselink.jpa.testapps/jpa.test.criteria/src/test/java/org/eclipse/persistence/testing/tests/jpa/criteria/advanced/JUnitCriteriaSimpleTestSuiteBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2023 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0 which is available at @@ -27,6 +27,7 @@ import jakarta.persistence.Query; import jakarta.persistence.Tuple; import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CompoundSelection; import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaBuilder.Case; import jakarta.persistence.criteria.CriteriaBuilder.Coalesce; @@ -3008,7 +3009,7 @@ public void testCriteriaBuilderTupleValidation() { CriteriaQuery cquery = qb.createTupleQuery(); Root emp = wrapper.from(cquery, Employee.class); - Selection[] s = {wrapper.get(emp, Employee_id), wrapper.get(emp, Employee_lastName), wrapper.get(emp, Employee_firstName)}; + Selection[] s = {wrapper.get(emp, Employee_id), wrapper.get(emp, Employee_lastName), wrapper.get(emp, Employee_firstName)}; Selection item = qb.tuple(s); cquery.select(item); @@ -3018,16 +3019,56 @@ public void testCriteriaBuilderTupleValidation() { list.get(0); try { //verify tuple throws an exception when passed a tuple - Object unexpectedResult = qb.tuple(item); - fail("IllegalArgumentException expected using an invalid value to CriteriaBuilder.tuple(). Result returned:"+unexpectedResult); + CompoundSelection unexpectedResult = qb.tuple(item); + fail("IllegalArgumentException expected using an invalid value to CriteriaBuilder.tuple(). Result returned:" + + unexpectedResult.toString()); } catch (Exception iae) { assertEquals(iae.getClass(), IllegalArgumentException.class); } try { //verify array throws an exception when passed a tuple - Object unexpectedResult = qb.array(item); - fail("IllegalArgumentException expected using an invalid value to CriteriaBuilder.array(). Result returned:"+unexpectedResult); + CompoundSelection unexpectedResult = qb.array(item); + fail("IllegalArgumentException expected using an invalid value to CriteriaBuilder.array(). Result returned:" + + unexpectedResult.toString()); + } catch (Exception iae) { + assertEquals(iae.getClass(), IllegalArgumentException.class); + } + closeEntityManager(em); + } + + // Verify that CriteriaBuilder.tuple(List) does not throw IllegalArgumentException + public void testCriteriaBuilderTupleOfListValidation() { + EntityManager em = createEntityManager(); + + CriteriaBuilder qb = em.getCriteriaBuilder(); + CriteriaQuery cquery = qb.createTupleQuery(); + + Root emp = wrapper.from(cquery, Employee.class); + List> s = List.of(wrapper.get(emp, Employee_id), + wrapper.get(emp, Employee_lastName), + wrapper.get(emp, Employee_firstName)); + Selection item = qb.tuple(s); + cquery.select(item); + + TypedQuery query = em.createQuery(cquery); + //verify they work and can be used: + List list = query.getResultList(); + list.get(0); + try { + //verify tuple throws an exception when passed a tuple + CompoundSelection unexpectedResult = qb.tuple(List.of(item)); + fail("IllegalArgumentException expected using an invalid value to CriteriaBuilder.tuple(). Result returned:" + + unexpectedResult.toString()); + } catch (Exception iae) { + assertEquals(iae.getClass(), IllegalArgumentException.class); + } + + try { + //verify array throws an exception when passed a tuple + CompoundSelection unexpectedResult = qb.array(item); + fail("IllegalArgumentException expected using an invalid value to CriteriaBuilder.array(). Result returned:" + + unexpectedResult.toString()); } catch (Exception iae) { assertEquals(iae.getClass(), IllegalArgumentException.class); } diff --git a/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java new file mode 100644 index 00000000000..e5de054be88 --- /dev/null +++ b/jpa/eclipselink.jpa.testapps/jpa.test.persistence32/src/test/java/org/eclipse/persistence/testing/tests/jpa/persistence32/UnionCriteriaQueryTest.java @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +// Contributors: +// 10/25/2023: Tomas Kraus +// - New Jakarta Persistence 3.2 Features +package org.eclipse.persistence.testing.tests.jpa.persistence32; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.EntityTransaction; +import jakarta.persistence.TypedQuery; +import jakarta.persistence.criteria.CriteriaBuilder; +import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.Root; +import junit.framework.Test; +import org.eclipse.persistence.testing.models.jpa.persistence32.Pokemon; + +public class UnionCriteriaQueryTest extends AbstractPokemon { + + // Pokemons. Array index is ID value. + // Value of ID = 0 does not exist so it's array instance is set to null. + static final Pokemon[] POKEMONS = new Pokemon[] { + null, // Skip array index 0 + new Pokemon(1, "Pidgey", List.of(TYPES[1], TYPES[3])), + new Pokemon(2, "Beedrill", List.of(TYPES[7], TYPES[4])), + new Pokemon(3, "Squirtle", List.of(TYPES[11])), + new Pokemon(4, "Caterpie", List.of(TYPES[7])), + new Pokemon(5, "Charmander", List.of(TYPES[10])), + new Pokemon(6, "Rattata", List.of(TYPES[1])), + new Pokemon(7, "Rattata", List.of(TYPES[1])), + new Pokemon(8, "Spearow", List.of(TYPES[1], TYPES[3])), + new Pokemon(9, "Pikachu", List.of(TYPES[13])) + }; + + public static Test suite() { + return suite( + "QueryTest", + new UnionCriteriaQueryTest("testSetup"), + //new UnionCriteriaQueryTest("testSimple"), + new UnionCriteriaQueryTest("testUnionWithNoSelection"), + new UnionCriteriaQueryTest("testUnionAllWithNoSelection"), + new UnionCriteriaQueryTest("testIntersectWithNoSelection"), + new UnionCriteriaQueryTest("testIntersectAllWithNoSelection"), + new UnionCriteriaQueryTest("testExceptWithNoSelection"), + new UnionCriteriaQueryTest("testExceptAllWithNoSelection"), + new UnionCriteriaQueryTest("testUnionWithEntityParameterInSelection") + ); + } + + Map pokemons = null; + + public UnionCriteriaQueryTest() { + } + + public UnionCriteriaQueryTest(String name) { + super(name); + setPuName(getPersistenceUnitName()); + } + + /** + * The setup is done as a test, both to record its failure, and to allow + * execution in the server. + */ + public void testSetup() { + emf.runInTransaction(em -> { + for (int i = 1; i < POKEMONS.length; i++) { + em.persist(POKEMONS[i]); + } + }); + } + + private static void verifyValuesOnce(Set expected, List queryResult) { + Set check = new HashSet(expected); + for (Pokemon pokemon : queryResult) { + System.out.println(String.format("Pokemon %d:%s", pokemon.getId(), pokemon.getName())); + assertTrue(String.format("Pokemon %d:%s was not found in Set %s", pokemon.getId(), pokemon.getName(), expected), + check.contains(pokemon)); + check.remove(pokemon); + } + } + + private static void verifyValuesMultiple(Set expected, List queryResult) { + for (Pokemon pokemon : queryResult) { + System.out.println(String.format("Pokemon %d:%s", pokemon.getId(), pokemon.getName())); + assertTrue(String.format("Pokemon %d:%s was not found in Set %s", pokemon.getId(), pokemon.getName(), expected), + expected.contains(pokemon)); + } + } + + // Test UNION: Shall return distinct values from both queries: 1x Pokemon[1..3] + // Pokemon[1] matches WHERE clause in both selects + public void testUnionWithNoSelection() { + System.out.println("testUnionWithNoSelection"); + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction et = em.getTransaction(); + try { + et.begin(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + + CriteriaQuery q1 = cb.createQuery(Pokemon.class); + Root root1 = q1.from(Pokemon.class); + q1.where(cb.or( + cb.equal(root1.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root1.get("name"), cb.parameter(String.class, "name2")))); + + CriteriaQuery q2 = cb.createQuery(Pokemon.class); + Root root2 = q2.from(Pokemon.class); + q2.where(cb.or( + cb.equal(root2.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root2.get("name"), cb.parameter(String.class, "name3")))); + + TypedQuery query = em.createQuery(cb.union(q1, q2)); + query.setParameter("name1", POKEMONS[1].getName()); + query.setParameter("name2", POKEMONS[2].getName()); + query.setParameter("name3", POKEMONS[3].getName()); + List pokemons = query.getResultList(); + assertEquals(3, pokemons.size()); + verifyValuesOnce(Set.of(POKEMONS[1], POKEMONS[2], POKEMONS[3]), pokemons); + } catch (Exception e) { + et.rollback(); + throw e; + } + } + } + + // Test UNION ALL: Shall return all values from both queries: 2x Pokemon[1], 1x Pokemon[2..3] + // Pokemon[1] matches WHERE clause in both selects + public void testUnionAllWithNoSelection() { + System.out.println("testUnionAllWithNoSelection"); + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction et = em.getTransaction(); + try { + et.begin(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + + CriteriaQuery q1 = cb.createQuery(Pokemon.class); + Root root1 = q1.from(Pokemon.class); + q1.where(cb.or( + cb.equal(root1.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root1.get("name"), cb.parameter(String.class, "name2")))); + + CriteriaQuery q2 = cb.createQuery(Pokemon.class); + Root root2 = q2.from(Pokemon.class); + q2.where(cb.or( + cb.equal(root2.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root2.get("name"), cb.parameter(String.class, "name3")))); + + TypedQuery query = em.createQuery(cb.unionAll(q1, q2)); + query.setParameter("name1", POKEMONS[1].getName()); + query.setParameter("name2", POKEMONS[2].getName()); + query.setParameter("name3", POKEMONS[3].getName()); + List pokemons = query.getResultList(); + assertEquals(4, pokemons.size()); + verifyValuesMultiple(Set.of(POKEMONS[1], POKEMONS[2], POKEMONS[3]), pokemons); + } catch (Exception e) { + et.rollback(); + throw e; + } + } + } + + // Test INTERSECT: Shall return the same distinct values from both queries: 1x Pokemon[1] + // Pokemon[1] matches WHERE clause in both selects + public void testIntersectWithNoSelection() { + System.out.println("testIntersectWithNoSelection"); + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction et = em.getTransaction(); + try { + et.begin(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + + CriteriaQuery q1 = cb.createQuery(Pokemon.class); + Root root1 = q1.from(Pokemon.class); + q1.where(cb.or( + cb.equal(root1.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root1.get("name"), cb.parameter(String.class, "name2")))); + + CriteriaQuery q2 = cb.createQuery(Pokemon.class); + Root root2 = q2.from(Pokemon.class); + q2.where(cb.or( + cb.equal(root2.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root2.get("name"), cb.parameter(String.class, "name3")))); + + TypedQuery query = em.createQuery(cb.intersect(q1, q2)); + query.setParameter("name1", POKEMONS[1].getName()); + query.setParameter("name2", POKEMONS[2].getName()); + query.setParameter("name3", POKEMONS[3].getName()); + List pokemons = query.getResultList(); + assertEquals(1, pokemons.size()); + verifyValuesOnce(Set.of(POKEMONS[1]), pokemons); + } catch (Exception e) { + et.rollback(); + throw e; + } + } + } + + // Test INTERSECT ALL: Shall return the same values from both queries including duplicates: 2x Pokemon[2..3] + // q1 and q2 are UNION ALL to produce duplicated records 2x Pokemon[1..3] + // q3 and q4 are UNION ALL to produce duplicated records 2x Pokemon[1, 4] + // (q1 UNION ALL q2) INTERSECT ALL (q3 UNION ALL q4) shall produce 2x Pokemon[2..3] - q1, q2 values not in q3, q4 + // ( + // ( + // SELECT t0.ID, t0.NAME FROM PERSISTENCE32_POKEMON t0 WHERE (t0.NAME IN (?, ?, ?)) + // UNION ALL (SELECT t1.ID, t1.NAME FROM PERSISTENCE32_POKEMON t1 WHERE (t1.NAME IN (?, ?, ?))) + // ) + // INTERSECT ALL ( + // (SELECT t2.ID, t2.NAME FROM PERSISTENCE32_POKEMON t2 WHERE ((t2.NAME = ?) OR (t2.NAME = ?)) + // UNION ALL (SELECT t3.ID, t3.NAME FROM PERSISTENCE32_POKEMON t3 WHERE ((t3.NAME = ?) OR (t3.NAME = ?)))) + // ) + // ) + public void testIntersectAllWithNoSelection() { + System.out.println("testIntersectAllWithNoSelection"); + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction et = em.getTransaction(); + try { + et.begin(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + + CriteriaQuery q1 = cb.createQuery(Pokemon.class); + Root root1 = q1.from(Pokemon.class); + q1.where(root1.get("name") + .in(cb.parameter(String.class, "name1"), + cb.parameter(String.class, "name2"), + cb.parameter(String.class, "name3"))); + + CriteriaQuery q2 = cb.createQuery(Pokemon.class); + Root root2 = q2.from(Pokemon.class); + q2.where(root2.get("name") + .in(cb.parameter(String.class, "name1"), + cb.parameter(String.class, "name2"), + cb.parameter(String.class, "name3"))); + + CriteriaQuery q3 = cb.createQuery(Pokemon.class); + Root root3 = q3.from(Pokemon.class); + q3.where(cb.or( + cb.equal(root3.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root3.get("name"), cb.parameter(String.class, "name4")))); + + CriteriaQuery q4 = cb.createQuery(Pokemon.class); + Root root4 = q4.from(Pokemon.class); + q4.where(cb.or( + cb.equal(root4.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root4.get("name"), cb.parameter(String.class, "name4")))); + + TypedQuery query = em.createQuery(cb.intersectAll(cb.unionAll(q1, q2), cb.unionAll(q3, q4))); + query.setParameter("name1", POKEMONS[1].getName()); + query.setParameter("name2", POKEMONS[2].getName()); + query.setParameter("name3", POKEMONS[3].getName()); + query.setParameter("name4", POKEMONS[4].getName()); + List pokemons = query.getResultList(); + assertEquals(2, pokemons.size()); + verifyValuesMultiple(Set.of(POKEMONS[1]), pokemons); + } catch (Exception e) { + et.rollback(); + throw e; + } + } + } + + // Test EXCEPT: Shall return distinct values from q1 not in q2: 1x Pokemon[2..3] + // Pokemon[1] matches WHERE clause in both selects + public void testExceptWithNoSelection() { + System.out.println("testExceptAllWithNoSelection"); + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction et = em.getTransaction(); + try { + et.begin(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + + CriteriaQuery q1 = cb.createQuery(Pokemon.class); + Root root1 = q1.from(Pokemon.class); + q1.where(root1.get("name") + .in(cb.parameter(String.class, "name1"), + cb.parameter(String.class, "name2"), + cb.parameter(String.class, "name3"))); + + CriteriaQuery q2 = cb.createQuery(Pokemon.class); + Root root2 = q2.from(Pokemon.class); + q2.where(cb.or( + cb.equal(root2.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root2.get("name"), cb.parameter(String.class, "name4")))); + + TypedQuery query = em.createQuery(cb.except(q1, q2)); + query.setParameter("name1", POKEMONS[1].getName()); + query.setParameter("name2", POKEMONS[2].getName()); + query.setParameter("name3", POKEMONS[3].getName()); + query.setParameter("name4", POKEMONS[4].getName()); + List pokemons = query.getResultList(); + assertEquals(2, pokemons.size()); + verifyValuesOnce(Set.of(POKEMONS[2], POKEMONS[3]), pokemons); + } catch (Exception e) { + et.rollback(); + throw e; + } + } + } + + // Test EXCEPT: Shall return values from q1 not in q2 including duplicates: 2x Pokemon[1..3] + // q1 and q2 are UNION ALL to produce duplicated records 2x Pokemon[1..3] + // q3 produces 1x Pokemon[1, 4] + // ( + // ( + // SELECT t0.ID, t0.NAME FROM PERSISTENCE32_POKEMON t0 WHERE (t0.NAME IN (?, ?, ?)) + // UNION ALL (SELECT t1.ID, t1.NAME FROM PERSISTENCE32_POKEMON t1 WHERE (t1.NAME IN (?, ?, ?))) + // ) + // EXCEPT ALL (SELECT t2.ID, t2.NAME FROM PERSISTENCE32_POKEMON t2 WHERE ((t2.NAME = ?) OR (t2.NAME = ?))) + // ) + public void testExceptAllWithNoSelection() { + System.out.println("testExceptAllWithNoSelection"); + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction et = em.getTransaction(); + try { + et.begin(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + + CriteriaQuery q1 = cb.createQuery(Pokemon.class); + Root root1 = q1.from(Pokemon.class); + q1.where(root1.get("name") + .in(cb.parameter(String.class, "name1"), + cb.parameter(String.class, "name2"), + cb.parameter(String.class, "name3"))); + + CriteriaQuery q2 = cb.createQuery(Pokemon.class); + Root root2 = q2.from(Pokemon.class); + q2.where(root2.get("name") + .in(cb.parameter(String.class, "name1"), + cb.parameter(String.class, "name2"), + cb.parameter(String.class, "name3"))); + + CriteriaQuery q3 = cb.createQuery(Pokemon.class); + Root root3 = q3.from(Pokemon.class); + q3.where(cb.or( + cb.equal(root3.get("name"), cb.parameter(String.class, "name1")), + cb.equal(root3.get("name"), cb.parameter(String.class, "name4")))); + + TypedQuery query = em.createQuery(cb.exceptAll(cb.unionAll(q1, q2), q3)); + query.setParameter("name1", POKEMONS[1].getName()); + query.setParameter("name2", POKEMONS[2].getName()); + query.setParameter("name3", POKEMONS[3].getName()); + query.setParameter("name4", POKEMONS[4].getName()); + List pokemons = query.getResultList(); + assertEquals(5, pokemons.size()); + verifyValuesMultiple(Set.of(POKEMONS[1], POKEMONS[2], POKEMONS[3]), pokemons); + } catch (Exception e) { + et.rollback(); + throw e; + } + } + } + + public void testSimple() { + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction et = em.getTransaction(); + try { + et.begin(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + CriteriaQuery q1 = cb.createQuery(Pokemon.class); + Root root1 = q1.from(Pokemon.class); + q1.select(root1.get("name")); + q1.where(cb.equal(root1.get("name"), cb.parameter(String.class, "name"))); + TypedQuery query = em.createQuery(q1); + query.setParameter("name", POKEMONS[1].getName()); + List pokemons = query.getResultList(); + assertEquals(1, pokemons.size()); + } catch (Exception e) { + et.rollback(); + throw e; + } + } + } + + public void testUnionWithEntityParameterInSelection() { + try (EntityManager em = emf.createEntityManager()) { + EntityTransaction et = em.getTransaction(); + try { + et.begin(); + CriteriaBuilder cb = em.getCriteriaBuilder(); + + CriteriaQuery q1 = cb.createQuery(Pokemon.class); + Root root1 = q1.from(Pokemon.class); + q1.select(root1.get("name")); + q1.where(cb.equal(root1.get("name"), cb.parameter(String.class, "name1"))); + + CriteriaQuery q2 = cb.createQuery(Pokemon.class); + Root root2 = q2.from(Pokemon.class); + q2.select(root2.get("name")); + q2.where(cb.equal(root2.get("name"), cb.parameter(String.class, "name2"))); + + TypedQuery query = em.createQuery(cb.union(q1, q2)); + query.setParameter("name1", POKEMONS[1].getName()); + query.setParameter("name2", POKEMONS[2].getName()); + List pokemons = query.getResultList(); + assertEquals(2, pokemons.size()); + } catch (Exception e) { + et.rollback(); + throw e; + } + } + } + +} diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java index b262663f6dc..0a6ae3e60e8 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryDelegate.java @@ -56,7 +56,6 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.Metamodel; - import org.eclipse.persistence.config.EntityManagerProperties; import org.eclipse.persistence.config.FlushClearCache; import org.eclipse.persistence.config.PersistenceUnitProperties; @@ -764,6 +763,11 @@ public boolean isInstance(Object entity, Class entityClass) { throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); } + @Override + public String getName() { + return setupImpl.getPersistenceUnitUniqueName(); + } + // TODO-API-3.2 @Override public Class getClass(T entity) { diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryImpl.java index 85fec80a38a..7d624f5d791 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerFactoryImpl.java @@ -45,7 +45,6 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.metamodel.Attribute; import jakarta.persistence.metamodel.Metamodel; - import org.eclipse.persistence.config.ReferenceMode; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.exceptions.PersistenceUnitLoadingException; @@ -649,6 +648,11 @@ public boolean isInstance(Object entity, Class entityClass) { return delegate.isInstance(entity, entityClass); } + @Override + public String getName() { + return delegate.getName(); + } + // TODO-API-3.2 @Override public Class getClass(T entity) { diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerImpl.java index 9eaae4575f5..f6367a33a54 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/EntityManagerImpl.java @@ -58,6 +58,8 @@ import java.util.Set; import java.util.WeakHashMap; +import javax.sql.DataSource; + import jakarta.persistence.CacheRetrieveMode; import jakarta.persistence.CacheStoreMode; import jakarta.persistence.ConnectionConsumer; @@ -84,10 +86,9 @@ import jakarta.persistence.criteria.CriteriaBuilder; import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaSelect; import jakarta.persistence.criteria.CriteriaUpdate; import jakarta.persistence.metamodel.Metamodel; -import javax.sql.DataSource; - import org.eclipse.persistence.annotations.CacheKeyType; import org.eclipse.persistence.config.EntityManagerProperties; import org.eclipse.persistence.config.QueryHints; @@ -106,6 +107,7 @@ import org.eclipse.persistence.internal.identitymaps.CacheId; import org.eclipse.persistence.internal.jpa.querydef.CriteriaDeleteImpl; import org.eclipse.persistence.internal.jpa.querydef.CriteriaQueryImpl; +import org.eclipse.persistence.internal.jpa.querydef.CriteriaSelectInternal; import org.eclipse.persistence.internal.jpa.querydef.CriteriaUpdateImpl; import org.eclipse.persistence.internal.jpa.transaction.EntityTransactionImpl; import org.eclipse.persistence.internal.jpa.transaction.EntityTransactionWrapper; @@ -1721,17 +1723,23 @@ public jakarta.persistence.Query createQuery(DatabaseQuery databaseQuery) { } } - - /** - * @see EntityManager#createQuery(jakarta.persistence.criteria.CriteriaQuery) - * @since Java Persistence 2.0 - */ @Override public TypedQuery createQuery(CriteriaQuery criteriaQuery) { - try{ + try { verifyOpen(); return new EJBQueryImpl(((CriteriaQueryImpl)criteriaQuery).translate(), this); - }catch (RuntimeException e){ + } catch (RuntimeException e) { + setRollbackOnly(); + throw e; + } + } + + @Override + public TypedQuery createQuery(CriteriaSelect selectQuery) { + try { + verifyOpen(); + return new EJBQueryImpl(((CriteriaSelectInternal)selectQuery).translate(), this); + } catch (RuntimeException e) { setRollbackOnly(); throw e; } diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CommonAbstractCriteriaImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CommonAbstractCriteriaImpl.java index 5c9cf5083dd..3492dfb1550 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CommonAbstractCriteriaImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CommonAbstractCriteriaImpl.java @@ -52,7 +52,8 @@ * @author Chris Delahunt * @since EclipseLink 2.5 */ -public abstract class CommonAbstractCriteriaImpl implements CommonAbstractCriteria, Serializable { +public abstract class CommonAbstractCriteriaImpl + implements CommonAbstractCriteria, Serializable, CriteriaSelectInternal { @Serial private static final long serialVersionUID = -2729946665208116620L; @@ -95,7 +96,8 @@ public Predicate getRestriction(){ * Otherwise, the result type is Object. * @return result type */ - public Class getResultType(){ + @Override + public Class getResultType() { return this.queryType; } @@ -232,9 +234,22 @@ public Set> getParameters() { /** * Translates from the criteria query to a EclipseLink Database Query. + * + * @return EclipseLink {@link DatabaseQuery} */ + @Override public DatabaseQuery translate() { - DatabaseQuery query = getDatabaseQuery(); + return translate(getDatabaseQuery()); + } + + /** + * Translates from the criteria query to a EclipseLink Database Query. + * Target {@link DatabaseQuery} instance is supplied. + * + * @param query target {@link DatabaseQuery} instance + * @return EclipseLink {@link DatabaseQuery} + */ + protected DatabaseQuery translate(DatabaseQuery query) { for (ParameterExpression parameter : getParameters()) { query.addArgument(((ParameterExpressionImpl)parameter).getInternalName(), parameter.getJavaType()); } diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaBuilderImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaBuilderImpl.java index db254a238c2..1b9957f346a 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaBuilderImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaBuilderImpl.java @@ -37,6 +37,7 @@ import jakarta.persistence.criteria.CompoundSelection; import jakarta.persistence.criteria.CriteriaDelete; import jakarta.persistence.criteria.CriteriaQuery; +import jakarta.persistence.criteria.CriteriaSelect; import jakarta.persistence.criteria.CriteriaUpdate; import jakarta.persistence.criteria.Expression; import jakarta.persistence.criteria.Join; @@ -68,6 +69,7 @@ import org.eclipse.persistence.internal.jpa.metamodel.MetamodelImpl; import org.eclipse.persistence.internal.jpa.metamodel.TypeImpl; import org.eclipse.persistence.internal.jpa.querydef.AbstractQueryImpl.ResultType; +import org.eclipse.persistence.internal.jpa.querydef.CriteriaMultiSelectImpl.Union; import org.eclipse.persistence.internal.localization.ExceptionLocalization; import org.eclipse.persistence.jpa.JpaCriteriaBuilder; import org.eclipse.persistence.queries.ReportQuery; @@ -159,6 +161,13 @@ public CompoundSelection tuple(Selection... selections){ return new CompoundSelectionImpl<>(Tuple.class, selections, true); } + @Override + public CompoundSelection tuple(List> selections) { + return tuple(selections != null + ? selections.toArray(new Selection[selections.size()]) + : null); + } + /** * Create an array-valued selection item * @param selections selection items @@ -171,6 +180,13 @@ public CompoundSelection array(Selection... selections){ return new CompoundSelectionImpl<>((Class) ClassConstants.AOBJECT, selections, true); } + @Override + public CompoundSelection array(List> selections) { + return array(selections != null + ? selections.toArray(new Selection[selections.size()]) + : null); + } + @Override public Order asc(Expression expression) { return asc(expression, Nulls.NONE); @@ -3041,40 +3057,38 @@ public Root treat(Root root, Class type) { return new RootImpl(entity, this.metamodel, type, parentRoot.currentNode.treat(type), entity); } - // TODO-API-3.2 @Override - public CriteriaQuery union(CriteriaQuery first, CriteriaQuery second) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + @SuppressWarnings("unchecked") + public CriteriaSelect union(CriteriaSelect first, CriteriaSelect second) { + return new CriteriaMultiSelectImpl<>((CriteriaSelect) first, second, Union.UNION); } - // TODO-API-3.2 @Override - public CriteriaQuery unionAll(CriteriaQuery first, CriteriaQuery second) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + @SuppressWarnings("unchecked") + public CriteriaSelect unionAll(CriteriaSelect first, CriteriaSelect second) { + return new CriteriaMultiSelectImpl<>((CriteriaSelect) first, second, Union.UNION_ALL); } - // TODO-API-3.2 @Override - public CriteriaQuery intersect(CriteriaQuery first, CriteriaQuery second) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + @SuppressWarnings("unchecked") + public CriteriaSelect intersect(CriteriaSelect first, CriteriaSelect second) { + return new CriteriaMultiSelectImpl<>((CriteriaSelect) first, second, Union.INTERSECT); } - // TODO-API-3.2 @Override - public CriteriaQuery intersectAll(CriteriaQuery first, CriteriaQuery second) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + @SuppressWarnings("unchecked") + public CriteriaSelect intersectAll(CriteriaSelect first, CriteriaSelect second) { + return new CriteriaMultiSelectImpl<>((CriteriaSelect) first, second, Union.INTERSECT_ALL); } - // TODO-API-3.2 @Override - public CriteriaQuery except(CriteriaQuery first, CriteriaQuery second) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + public CriteriaSelect except(CriteriaSelect first, CriteriaSelect second) { + return new CriteriaMultiSelectImpl<>(first, second, Union.EXCEPT); } - // TODO-API-3.2 @Override - public CriteriaQuery exceptAll(CriteriaQuery first, CriteriaQuery second) { - throw new UnsupportedOperationException("Jakarta Persistence 3.2 API was not implemented yet"); + public CriteriaSelect exceptAll(CriteriaSelect first, CriteriaSelect second) { + return new CriteriaMultiSelectImpl<>(first, second, Union.EXCEPT_ALL); } } diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaMultiSelectImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaMultiSelectImpl.java new file mode 100644 index 00000000000..b990fde7c1c --- /dev/null +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaMultiSelectImpl.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2011, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2022 IBM Corporation. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, + * or the Eclipse Distribution License v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +// Contributors: +// 10/25/2023: Tomas Kraus +// - New Jakarta Persistence 3.2 Features +package org.eclipse.persistence.internal.jpa.querydef; + +import java.util.Objects; +import java.util.function.BiFunction; + +import jakarta.persistence.criteria.CriteriaSelect; +import org.eclipse.persistence.queries.DatabaseQuery; +import org.eclipse.persistence.queries.ReadAllQuery; +import org.eclipse.persistence.queries.ReportQuery; + +public class CriteriaMultiSelectImpl implements CriteriaSelect, CriteriaSelectInternal { + + // Union operator with query builder + enum Union { + + UNION((first, second) -> { + first.union(second); + return first; + }), + UNION_ALL((first, second) -> { + first.unionAll(second); + return first; + }), + INTERSECT((first, second) -> { + first.intersect(second); + return first; + }), + INTERSECT_ALL((first, second) -> { + first.intersectAll(second); + return first; + }), + EXCEPT((first, second) -> { + first.except(second); + return first; + }), + EXCEPT_ALL((first, second) -> { + first.exceptAll(second); + return first; + }); + + // Builder for union/intersect/except query + private final BiFunction builder; + + Union(BiFunction builder) { + this.builder = builder; + } + + } + + private final CriteriaSelectInternal first; + // 2nd query + private final CriteriaSelectInternal second; + // Union operator + private final Union union; + protected Class queryType; + + public CriteriaMultiSelectImpl(CriteriaSelect first, + CriteriaSelect second, + Union union) { + Objects.requireNonNull(first, "First component of union expression is null"); + Objects.requireNonNull(second, "Second component of union expression is null"); + this.first = (CriteriaSelectInternal) first; + this.second = (CriteriaSelectInternal) second; + this.union = union; + this.queryType = this.first.getResultType(); + } + + @Override + public Class getResultType() { + return queryType; + } + + /** + * Translates from the criteria query to a EclipseLink Database Query. + */ + @Override + public DatabaseQuery translate() { + return translate(new BooleanValue()); + } + + private DatabaseQuery translate(BooleanValue haveTop) { + DatabaseQuery firstQuery; + if (first instanceof CriteriaQueryImpl) { + // Only the top level DatabaseQuery instance may not be the ReportQuery. + // This is the lowest level of all first node path + if (haveTop.get()) { + firstQuery = ((CriteriaQueryImpl) first).transalteToReportQuery(); + } else { + firstQuery = first.translate(); + haveTop.setTrue(); + } + } else { + firstQuery = ((CriteriaMultiSelectImpl) first).translate(haveTop); + } + DatabaseQuery secondQuery = (second instanceof CriteriaQueryImpl) + ? ((CriteriaQueryImpl) second).transalteToReportQuery() + : ((CriteriaMultiSelectImpl) second).translate(haveTop); + return union.builder.apply((ReadAllQuery) firstQuery, (ReportQuery) secondQuery); + } + + // Boolean value holder used in criteria query to DatabaseQuery translation + // Helps to find top level DatabaseQuery instance + private static final class BooleanValue { + + private static boolean value; + + private BooleanValue() { + value = false; + } + + private boolean get() { + return value; + } + + private void setTrue() { + value = true; + } + + } + +} diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaQueryImpl.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaQueryImpl.java index eb1a46eebcc..fd3c95523aa 100644 --- a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaQueryImpl.java +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaQueryImpl.java @@ -15,6 +15,8 @@ // Gordon Yorke - Initial development // 10/01/2018: Will Dazey // - #253: Add support for embedded constructor results with CriteriaBuilder +// 10/25/2023: Tomas Kraus +// - New Jakarta Persistence 3.2 Features package org.eclipse.persistence.internal.jpa.querydef; import java.lang.reflect.Constructor; @@ -424,17 +426,36 @@ public void addJoin(FromImpl from) { this.joins.add(from); } - @Override - protected DatabaseQuery getDatabaseQuery() { - ObjectLevelReadQuery query = null; + /** + * Create {@link ReadAllQuery} or {@link ReportQuery} from content of this instance. + * May be forced to always return {@link ReportQuery}, even when {@link ReadAllQuery} is sufficient. + * This is required for complex queries with {@code UNION}/{@code EXCEPT}/{@code INTERSECT}, where + * only {@link ReportQuery} instance is accepted as part of the query. + * + * @param toReportQuery force {@link ReportQuery} creation. + * @return {@link ReadAllQuery} or {@link ReportQuery} from content of this instance + */ + protected ReadAllQuery getDatabaseQuery(boolean toReportQuery) { + ReadAllQuery query = null; if (this.selection == null || !this.selection.isCompoundSelection()) { - query = createSimpleQuery(); + query = createSimpleQuery(toReportQuery); } else { - query = createCompoundQuery(); + query = createCompoundQuery(toReportQuery); } return query; } + /** + * Create {@link ReadAllQuery} or {@link ReportQuery} from content of this instance. + * Returned class depends on current query complexity. + * + * @return {@link ReadAllQuery} or {@link ReportQuery} from content of this instance + */ + @Override + protected DatabaseQuery getDatabaseQuery() { + return getDatabaseQuery(false); + } + /** * Return the ordering expressions in order of precedence. * @@ -460,8 +481,9 @@ public Selection getSelection() { * Translates from the criteria query to a EclipseLink Database Query. */ @SuppressWarnings("deprecation") - protected ObjectLevelReadQuery createCompoundQuery() { - ObjectLevelReadQuery query = null; + protected ReadAllQuery createCompoundQuery(boolean toReportQuery) { + ReadAllQuery query = null; + if (this.queryResult == ResultType.UNKNOWN) { if (this.selection.isConstructor()) { this.queryResult = ResultType.CONSTRUCTOR; @@ -473,6 +495,7 @@ protected ObjectLevelReadQuery createCompoundQuery() { } if (this.queryResult.equals(ResultType.PARTIAL)) { + // TODO: allow force of ReportQuery creation for usage in UNION ReadAllQuery raq = new ReadAllQuery(this.queryType); for (Selection selection : this.selection.getCompoundSelectionItems()) { raq.addPartialAttribute(((SelectionImpl) selection).currentNode); @@ -563,15 +586,20 @@ protected ObjectLevelReadQuery createCompoundQuery() { return query; } - protected ObjectLevelReadQuery createSimpleQuery() { - ObjectLevelReadQuery query = null; + protected ReadAllQuery createSimpleQuery(boolean toReportQuery) { + ReadAllQuery query = null; if (this.queryResult == ResultType.UNKNOWN) { // unknown type so let's figure this out. if (selection == null) { if (this.roots != null && !this.roots.isEmpty()) { this.selection = (SelectionImpl) this.roots.iterator().next(); - query = new ReadAllQuery(((FromImpl) this.selection).getJavaType()); + if (toReportQuery) { + // TODO: Test and fix + query = createReportQueryWithItem(((FromImpl) this.selection).getJavaType()); + } else { + query = new ReadAllQuery(((FromImpl) this.selection).getJavaType()); + } List list = ((FromImpl) this.roots.iterator().next()).findJoinFetches(); for (org.eclipse.persistence.expressions.Expression fetch : list) { query.addJoinedAttribute(fetch); @@ -582,50 +610,42 @@ protected ObjectLevelReadQuery createSimpleQuery() { } else if (this.roots == null || this.roots.isEmpty()) { throw new IllegalArgumentException(ExceptionLocalization.buildMessage("CRITERIA_NO_ROOT_FOR_COMPOUND_QUERY")); } - } else { // Selection is not null set type to selection - TypeImpl type = ((MetamodelImpl)this.metamodel).getType(selection.getJavaType()); + TypeImpl type = ((MetamodelImpl)this.metamodel).getType(selection.getJavaType()); if (type != null && type.getPersistenceType().equals(PersistenceType.ENTITY)) { - query = new ReadAllQuery(type.getJavaType()); - List list = ((FromImpl) this.roots.iterator().next()).findJoinFetches(); + if (toReportQuery) { + query = createReportQueryWithItem(type.getJavaType()); + ((ReportQuery) query).setShouldReturnWithoutReportQueryResult(true); + } else { + query = new ReadAllQuery(type.getJavaType()); + } + List list = ((FromImpl) this.roots.iterator().next()).findJoinFetches(); for (org.eclipse.persistence.expressions.Expression fetch : list) { query.addJoinedAttribute(fetch); } query.setExpressionBuilder(((InternalSelection)selection).getCurrentNode().getBuilder()); - } else { - query = new ReportQuery(); - query.setReferenceClass(this.selection.getCurrentNode().getBuilder().getQueryClass()); - if (!this.selection.isCompoundSelection() && ((InternalExpression) this.selection).isCompoundExpression()) { - if (((FunctionExpressionImpl) this.selection).getOperation() == CriteriaBuilderImpl.SIZE) { - //selecting size not all databases support subselect in select clause so convert to count/groupby - PathImpl collectionExpression = (PathImpl) ((FunctionExpressionImpl) this.selection).getChildExpressions().get(0); - ExpressionImpl fromExpression = (ExpressionImpl) collectionExpression.getParentPath(); - ((ReportQuery) query).addAttribute(this.selection.getAlias(), collectionExpression.getCurrentNode().count(), ClassConstants.INTEGER); - ((ReportQuery) query).addGrouping(fromExpression.getCurrentNode()); - } - ((ReportQuery) query).addAttribute(this.selection.getAlias(), this.selection.getCurrentNode(), this.selection.getJavaType()); - - } else { - ((ReportQuery) query).addItem(this.selection.getAlias(), this.selection.getCurrentNode()); - ((ReportQuery) query).setShouldReturnSingleAttribute(true); - } + query = createReportQueryWithSelection(this.selection.getCurrentNode().getBuilder().getQueryClass()); } } } else if (this.queryResult.equals(ResultType.ENTITY)) { - if (this.selection != null && (!((InternalSelection) this.selection).isRoot())) { - query = new ReportQuery(); - query.setReferenceClass(this.queryType); - ((ReportQuery) query).addItem(this.selection.getAlias(), this.selection.getCurrentNode(), ((FromImpl) this.selection).findJoinFetches()); + query = createReportQueryWithItem(this.queryType); ((ReportQuery) query).setShouldReturnSingleAttribute(true); } else { - query = new ReadAllQuery(this.queryType); + // Tested by testUnionAllWithNoSelection + if (toReportQuery) { + query = createReportQueryWithItem(this.queryType); + ((ReportQuery) query).setShouldReturnWithoutReportQueryResult(true); + } else { + query = new ReadAllQuery(this.queryType); + } if (this.roots != null && !this.roots.isEmpty()) { - List list = ((FromImpl) this.roots.iterator().next()).findJoinFetches(); + List list = ((FromImpl) this.roots.iterator().next()).findJoinFetches(); if (!list.isEmpty()) { - query.setExpressionBuilder(list.get(0).getBuilder()); // set the builder to one of the fetches bases. + // set the builder to one of the fetches bases. + query.setExpressionBuilder(list.get(0).getBuilder()); } for (org.eclipse.persistence.expressions.Expression fetch : list) { query.addJoinedAttribute(fetch); @@ -642,6 +662,7 @@ protected ObjectLevelReadQuery createSimpleQuery() { list.add(this.selection); reportQuery = new TupleQuery(list); } else { + // Tested by testUnionWithEntityParameterInSelection reportQuery = new ReportQuery(); reportQuery.setShouldReturnWithoutReportQueryResult(true); } @@ -699,12 +720,72 @@ protected ObjectLevelReadQuery createSimpleQuery() { return query; } + // Create ReportQuery with provided reference class + private static ReportQuery createReportQuery(Class classToRead) { + ReportQuery query = new ReportQuery(); + query.setReferenceClass(classToRead); + return query; + } + + // Create ReportQuery with provided reference class + // Add single result expression value + private ReportQuery createReportQueryWithItem(Class classToRead) { + ReportQuery query = createReportQuery(classToRead); + if (selection != null) { + if (((InternalSelection) selection).isFrom()) { + query.addItem(selection.getAlias(), selection.getCurrentNode(), ((FromImpl) selection).findJoinFetches()); + } else { + query.addAttribute(selection.getAlias(), selection.getCurrentNode(), selection.getJavaType()); + } + } else { + FromImpl root = ((FromImpl) roots.iterator().next()); + query.addItem(root.getAlias(), root.getCurrentNode()); + } + return query; + } + + // Create ReportQuery with provided reference class + // Selection exists (selection != null) and may be compound or simple + private ReportQuery createReportQueryWithSelection(Class classToRead) { + ReportQuery query = createReportQuery(classToRead); + // Just to blame for invalid usage + if (selection == null) { + throw new IllegalStateException("Called createReportQueryWithSelection with no selection set"); + } + if (!selection.isCompoundSelection() && ((InternalExpression) selection).isCompoundExpression()) { + // noinspection StringEquality - using String constants so instances will match + if (((FunctionExpressionImpl) selection).getOperation() == CriteriaBuilderImpl.SIZE) { + // Selecting size not all databases support sub-select in select clause so convert to count/groupby + PathImpl collectionExpression = (PathImpl) ((FunctionExpressionImpl) this.selection).getChildExpressions().get(0); + ExpressionImpl fromExpression = (ExpressionImpl) collectionExpression.getParentPath(); + query.addAttribute(this.selection.getAlias(), collectionExpression.getCurrentNode().count(), ClassConstants.INTEGER); + query.addGrouping(fromExpression.getCurrentNode()); + } + query.addAttribute(this.selection.getAlias(), this.selection.getCurrentNode(), this.selection.getJavaType()); + } else { + query.addItem(this.selection.getAlias(), this.selection.getCurrentNode()); + query.setShouldReturnSingleAttribute(true); + } + return query; + } + /** * Translates from the criteria query to a EclipseLink Database Query. */ @Override public DatabaseQuery translate() { - ObjectLevelReadQuery query = (ObjectLevelReadQuery)super.translate(); + return translate(false); + } + + /** + * Translate always to {@link ReportQuery}. + */ + public ReportQuery transalteToReportQuery() { + return (ReportQuery) translate(true); + } + + private ReadAllQuery translate(boolean toReportQuery) { + ReadAllQuery query = (ReadAllQuery) translate(getDatabaseQuery(toReportQuery)); for (Iterator> iterator = this.getRoots().iterator(); iterator.hasNext();) { findJoins((FromImpl) iterator.next()); diff --git a/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaSelectInternal.java b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaSelectInternal.java new file mode 100644 index 00000000000..9d36423d9fd --- /dev/null +++ b/jpa/org.eclipse.persistence.jpa/src/main/java/org/eclipse/persistence/internal/jpa/querydef/CriteriaSelectInternal.java @@ -0,0 +1,25 @@ +package org.eclipse.persistence.internal.jpa.querydef; + +import jakarta.persistence.criteria.CriteriaSelect; +import org.eclipse.persistence.queries.DatabaseQuery; + +/** + * Internal interface to access CriteriaQuery result type. + * + * @param the type of the result + */ +public interface CriteriaSelectInternal extends CriteriaSelect { + + /** + * Get the type of the result. + * + * @return the type of the result + */ + Class getResultType(); + + /** + * Translates from the criteria query to a EclipseLink Database Query. + */ + DatabaseQuery translate(); + +} diff --git a/pom.xml b/pom.xml index 81b7f10c403..db351c99794 100644 --- a/pom.xml +++ b/pom.xml @@ -203,7 +203,7 @@ 3.1.3 3.1.0 2.1.3 - 3.2.0-B01 + 3.2.0-SNAPSHOT 2.1.2 2.0.2 3.0.1