From ee6d52aaa899549e9f3927c0385d6204ba207413 Mon Sep 17 00:00:00 2001 From: Richard Zowalla Date: Mon, 14 Sep 2026 20:29:27 +0200 Subject: [PATCH 1/6] [OPENJPA-2961] Include subclasses of the treated class in the TREAT discriminator filter Build the TREAT condition through the discriminator strategy's class conditions, so that subclasses of the treated type are matched with an IN over their discriminator values. --- .../exps/BindVariableAndExpression.java | 2 +- .../kernel/exps/BindVariableExpression.java | 27 +++--- .../jpql/treatjoinon/TGameProduct.java | 36 +++++++ .../TestTreatSubclassDiscriminator.java | 93 +++++++++++++++++++ 4 files changed, 142 insertions(+), 16 deletions(-) create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TGameProduct.java create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/BindVariableAndExpression.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/BindVariableAndExpression.java index 5d1f338b5b..97d4687431 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/BindVariableAndExpression.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/BindVariableAndExpression.java @@ -58,7 +58,7 @@ public void appendTo(Select sel, ExpContext ctx, ExpState state, BinaryOpExpState bstate = (BinaryOpExpState) state; // Append discriminator condition from TREAT (if any) if (_bind.hasTreatDiscriminator()) { - _bind.appendTreatDiscriminator(sel, bstate.state1, buf); + _bind.appendTreatDiscriminator(sel, ctx, bstate.state1, buf); buf.append(" AND "); } boolean or = _exp instanceof OrExpression; diff --git a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/BindVariableExpression.java b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/BindVariableExpression.java index 7440f0c9fc..6a6c1885e5 100644 --- a/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/BindVariableExpression.java +++ b/openjpa-jdbc/src/main/java/org/apache/openjpa/jdbc/kernel/exps/BindVariableExpression.java @@ -80,9 +80,10 @@ boolean hasTreatDiscriminator() { Discriminator disc = sup.getDiscriminator(); if (disc != null) { Column[] cols = disc.getColumns(); - Object discVal = varMapping.getDiscriminator() != null - ? varMapping.getDiscriminator().getValue() : null; - return cols != null && cols.length > 0 && discVal != null; + return cols != null && cols.length > 0 + && varMapping.getDiscriminator() != null + && varMapping.getDiscriminator() + .hasClassConditions(varMapping, true); } } } @@ -91,26 +92,22 @@ boolean hasTreatDiscriminator() { /** * Appends the discriminator condition for TREAT-narrowed variables. + * The condition matches the treated class and all of its subclasses. */ - void appendTreatDiscriminator(Select sel, ExpState state, SQLBuffer buf) { + void appendTreatDiscriminator(Select sel, ExpContext ctx, ExpState state, + SQLBuffer buf) { ClassMapping varMapping = (ClassMapping) _var.getMetaData(); - ClassMapping sup = varMapping; - while (sup.getMappedPCSuperclassMapping() != null) { - sup = sup.getMappedPCSuperclassMapping(); - } - Discriminator disc = sup.getDiscriminator(); - Column[] cols = disc.getColumns(); - Object discVal = varMapping.getDiscriminator().getValue(); - buf.append(sel.getColumnAlias(cols[0], state.joins)); - buf.append(" = "); - buf.appendValue(discVal, cols[0]); + Discriminator disc = varMapping.getDiscriminator(); + ctx.store.loadSubclasses(varMapping); + buf.append(disc.getClassConditions(sel, state.joins, varMapping, + true)); } @Override public void appendTo(Select sel, ExpContext ctx, ExpState state, SQLBuffer buf) { if (hasTreatDiscriminator()) { - appendTreatDiscriminator(sel, state, buf); + appendTreatDiscriminator(sel, ctx, state, buf); } else { buf.append("1 = 1"); } diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TGameProduct.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TGameProduct.java new file mode 100644 index 0000000000..95b8103d09 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TGameProduct.java @@ -0,0 +1,36 @@ +/* + * 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.jpql.treatjoinon; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +/** + * Third level of the TProduct single table hierarchy. + */ +@Entity +@Table(name = "TPRODUCT") +@DiscriminatorValue("GAME") +public class TGameProduct extends TSoftwareProduct { + private String genre; + + public String getGenre() { return genre; } + public void setGenre(String genre) { this.genre = genre; } +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java new file mode 100644 index 0000000000..278b051068 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java @@ -0,0 +1,93 @@ +/* + * 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.jpql.treatjoinon; + +import java.util.Collections; +import java.util.List; + +import jakarta.persistence.EntityManager; + +import org.apache.openjpa.persistence.test.SQLListenerTestCase; + +/** + * TREAT(x AS Middle) over a three level single table hierarchy must match + * instances of Middle and of its subclasses, but not of the root class. + */ +public class TestTreatSubclassDiscriminator extends SQLListenerTestCase { + + @Override + public void setUp() { + setUp(TProduct.class, TSoftwareProduct.class, TGameProduct.class, + TLineItem.class, TOrder.class, DROP_TABLES); + + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + + TProduct hw = new TProduct(); + hw.setName("Hardware"); + em.persist(hw); + + TSoftwareProduct sw = new TSoftwareProduct(); + sw.setName("Software"); + em.persist(sw); + + TGameProduct game = new TGameProduct(); + game.setName("Game"); + em.persist(game); + + TOrder order = new TOrder(); + em.persist(order); + + for (TProduct p : new TProduct[] { hw, sw, game }) { + TLineItem li = new TLineItem(); + li.setQuantity(1); + li.setProduct(p); + li.setOrder(order); + em.persist(li); + } + + em.getTransaction().commit(); + em.close(); + } + + public void testTreatJoinIncludesSubclasses() { + EntityManager em = emf.createEntityManager(); + try { + List results = em.createQuery( + "SELECT s.name FROM TLineItem l JOIN TREAT(l.product AS TSoftwareProduct) s", + String.class).getResultList(); + Collections.sort(results); + assertEquals(List.of("Game", "Software"), results); + } finally { + em.close(); + } + } + + public void testTreatJoinLeafClass() { + EntityManager em = emf.createEntityManager(); + try { + List results = em.createQuery( + "SELECT s.name FROM TLineItem l JOIN TREAT(l.product AS TGameProduct) s", + String.class).getResultList(); + assertEquals(List.of("Game"), results); + } finally { + em.close(); + } + } +} From b717116291de48b2e53d2a228c69bf7674e2df9b Mon Sep 17 00:00:00 2001 From: Richard Zowalla Date: Tue, 15 Sep 2026 10:54:10 +0200 Subject: [PATCH 2/6] [OPENJPA-2961] Use try-with-resources for the EntityManager in the TREAT subclass test --- .../TestTreatSubclassDiscriminator.java | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java index 278b051068..d8514db325 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java @@ -36,58 +36,52 @@ public void setUp() { setUp(TProduct.class, TSoftwareProduct.class, TGameProduct.class, TLineItem.class, TOrder.class, DROP_TABLES); - EntityManager em = emf.createEntityManager(); - em.getTransaction().begin(); + try (EntityManager em = emf.createEntityManager()) { + em.getTransaction().begin(); - TProduct hw = new TProduct(); - hw.setName("Hardware"); - em.persist(hw); + TProduct hw = new TProduct(); + hw.setName("Hardware"); + em.persist(hw); - TSoftwareProduct sw = new TSoftwareProduct(); - sw.setName("Software"); - em.persist(sw); + TSoftwareProduct sw = new TSoftwareProduct(); + sw.setName("Software"); + em.persist(sw); - TGameProduct game = new TGameProduct(); - game.setName("Game"); - em.persist(game); + TGameProduct game = new TGameProduct(); + game.setName("Game"); + em.persist(game); - TOrder order = new TOrder(); - em.persist(order); + TOrder order = new TOrder(); + em.persist(order); - for (TProduct p : new TProduct[] { hw, sw, game }) { - TLineItem li = new TLineItem(); - li.setQuantity(1); - li.setProduct(p); - li.setOrder(order); - em.persist(li); - } + for (TProduct p : new TProduct[] { hw, sw, game }) { + TLineItem li = new TLineItem(); + li.setQuantity(1); + li.setProduct(p); + li.setOrder(order); + em.persist(li); + } - em.getTransaction().commit(); - em.close(); + em.getTransaction().commit(); + } } public void testTreatJoinIncludesSubclasses() { - EntityManager em = emf.createEntityManager(); - try { + try (EntityManager em = emf.createEntityManager()) { List results = em.createQuery( "SELECT s.name FROM TLineItem l JOIN TREAT(l.product AS TSoftwareProduct) s", String.class).getResultList(); Collections.sort(results); assertEquals(List.of("Game", "Software"), results); - } finally { - em.close(); } } public void testTreatJoinLeafClass() { - EntityManager em = emf.createEntityManager(); - try { + try (EntityManager em = emf.createEntityManager()) { List results = em.createQuery( "SELECT s.name FROM TLineItem l JOIN TREAT(l.product AS TGameProduct) s", String.class).getResultList(); assertEquals(List.of("Game"), results); - } finally { - em.close(); } } } From e51f03d744b40ad72e3ac9931e8ce920f2d20ba7 Mon Sep 17 00:00:00 2001 From: Richard Zowalla Date: Tue, 15 Sep 2026 14:01:23 +0200 Subject: [PATCH 3/6] [OPENJPA-2961] Restrict a predicate over a TREAT path to the treated type A TREAT(x AS Type) path in a WHERE clause only overrode the metadata of the path, so rows of other types matched a predicate on an attribute they share with the treated type. As defined by the specification, such a predicate is now false if x is not an instance of Type or one of its subclasses. The restriction is added to the predicate containing the TREAT path, so other branches of an OR are not affected. --- .../kernel/jpql/JPQLExpressionBuilder.java | 58 ++++++++++++++----- .../TestTreatSubclassDiscriminator.java | 42 ++++++++++++++ 2 files changed, 85 insertions(+), 15 deletions(-) diff --git a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java index a22f17eaee..bfd8f86dba 100644 --- a/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java +++ b/openjpa-kernel/src/main/java/org/apache/openjpa/kernel/jpql/JPQLExpressionBuilder.java @@ -98,6 +98,9 @@ public class JPQLExpressionBuilder private int aliasCount = 0; private boolean inAssignSubselectProjection = false; private boolean hasParameterizedInExpression = false; + // type restrictions of the TREAT paths of the predicate being evaluated + private List treatRestrictions; + private Context treatRestrictionsContext; /** * Constructor. @@ -2236,7 +2239,27 @@ private Value getTreatPath(JPQLNode node) { String schemaName = assemble(schemaNode); ClassMetaData treatMeta = getClassMetaData(schemaName, true); - // Resolve the base path using the variable + Path path = getTreatBasePath(node, name); + if (treatRestrictions != null && treatRestrictionsContext == ctx()) { + treatRestrictions.add(factory.isInstance(getTreatBasePath(node, name), + treatMeta.getDescribedType())); + } + + // Override the metadata to the treat target type + path.setMetaData(treatMeta); + + // Walk through the remaining children (path components after the dot) + for (int i = 2; i < node.children.length; i++) { + path = (Path) traversePath(path, node.children[i].text, false, true); + } + + return path; + } + + /** + * Resolves the identifier of a TREAT(identifier AS Type) path to a new path. + */ + private Path getTreatBasePath(JPQLNode node, String name) { Path path = null; final Value val = getVariable(name, false); @@ -2260,15 +2283,6 @@ private Value getTreatPath(JPQLNode node) { } path.setSchemaAlias(name); - - // Override the metadata to the treat target type - path.setMetaData(treatMeta); - - // Walk through the remaining children (path components after the dot) - for (int i = 2; i < node.children.length; i++) { - path = (Path) traversePath(path, node.children[i].text, false, true); - } - return path; } @@ -2345,12 +2359,26 @@ protected Class getDeclaredVariableType(String name) { * Returns an Expression for the given node by eval'ing it. */ private Expression getExpression(JPQLNode node) { - Object exp = eval(node); + List outerRestrictions = treatRestrictions; + Context outerRestrictionsContext = treatRestrictionsContext; + treatRestrictions = new ArrayList<>(); + treatRestrictionsContext = ctx(); + try { + Object exp = eval(node); + + // check for boolean values used as expressions + Expression result = exp instanceof Expression + ? (Expression) exp : factory.asExpression((Value) exp); - // check for boolean values used as expressions - if (!(exp instanceof Expression)) - return factory.asExpression((Value) exp); - return (Expression) exp; + // a predicate over TREAT(x AS Type) is false if x is not a Type + for (Expression restriction : treatRestrictions) { + result = and(restriction, result); + } + return result; + } finally { + treatRestrictions = outerRestrictions; + treatRestrictionsContext = outerRestrictionsContext; + } } /** diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java index d8514db325..8f4b8f34ea 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatSubclassDiscriminator.java @@ -45,10 +45,13 @@ public void setUp() { TSoftwareProduct sw = new TSoftwareProduct(); sw.setName("Software"); + sw.setRevisionNumber(2.0); em.persist(sw); TGameProduct game = new TGameProduct(); game.setName("Game"); + game.setRevisionNumber(2.0); + game.setGenre("FPS"); em.persist(game); TOrder order = new TOrder(); @@ -84,4 +87,43 @@ public void testTreatJoinLeafClass() { assertEquals(List.of("Game"), results); } } + + public void testTreatInWhereExclusiveProperty() { + try (EntityManager em = emf.createEntityManager()) { + List results = em.createQuery( + "SELECT p.name FROM TProduct p WHERE TREAT(p AS TGameProduct).genre = 'FPS'", + String.class).getResultList(); + assertEquals(List.of("Game"), results); + } + } + + public void testTreatInWhereCommonProperty() { + try (EntityManager em = emf.createEntityManager()) { + List results = em.createQuery( + "SELECT p.name FROM TProduct p WHERE TREAT(p AS TGameProduct).revisionNumber = 2.0", + String.class).getResultList(); + assertEquals(List.of("Game"), results); + } + } + + public void testTreatInWhereRestrictsOnlyItsPredicate() { + try (EntityManager em = emf.createEntityManager()) { + List results = em.createQuery( + "SELECT p.name FROM TProduct p " + + "WHERE TREAT(p AS TGameProduct).revisionNumber = 2.0 OR p.name = 'Hardware'", + String.class).getResultList(); + Collections.sort(results); + assertEquals(List.of("Game", "Hardware"), results); + } + } + + public void testTreatInWhereOnJoinVariable() { + try (EntityManager em = emf.createEntityManager()) { + List results = em.createQuery( + "SELECT p.name FROM TLineItem l JOIN l.product p " + + "WHERE TREAT(p AS TGameProduct).revisionNumber = 2.0", + String.class).getResultList(); + assertEquals(List.of("Game"), results); + } + } } From a21ce49269a8753c3bb82a54a5bc200a23c6a1fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Cristov=C3=A3o=20de=20Ara=C3=BAjo=20Silva=20Filho?= Date: Tue, 15 Sep 2026 13:11:55 -0300 Subject: [PATCH 4/6] [openjpa-2961] Adds other tests on TREAT impl --- .../treatjoinon/TCustomSoftwareProduct.java | 22 ++++++++ .../TestTreatJoinOnEmbeddable.java | 54 +++++++++++++++---- 2 files changed, 65 insertions(+), 11 deletions(-) create mode 100644 openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TCustomSoftwareProduct.java diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TCustomSoftwareProduct.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TCustomSoftwareProduct.java new file mode 100644 index 0000000000..28edd5df35 --- /dev/null +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TCustomSoftwareProduct.java @@ -0,0 +1,22 @@ +package org.apache.openjpa.persistence.jpql.treatjoinon; + +import jakarta.persistence.DiscriminatorValue; +import jakarta.persistence.Entity; +import jakarta.persistence.Table; + +@Entity +@Table(name = "TPRODUCT") +@DiscriminatorValue("CS") +public class TCustomSoftwareProduct extends TSoftwareProduct { + + private int customizationHours; + + public int getCustomizationHours() { + return customizationHours; + } + + public void setCustomizationHours(int customizationHours) { + this.customizationHours = customizationHours; + } + +} diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatJoinOnEmbeddable.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatJoinOnEmbeddable.java index 516a85350c..ba282d7836 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatJoinOnEmbeddable.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatJoinOnEmbeddable.java @@ -39,10 +39,10 @@ public class TestTreatJoinOnEmbeddable extends SingleEMFTestCase { @Override public void setUp() { - setUp(TProduct.class, TSoftwareProduct.class, - TLineItem.class, TOrder.class, - TCustomer.class, TCountry.class, - DROP_TABLES); + setUp(TProduct.class, TSoftwareProduct.class, + TCustomSoftwareProduct.class, TLineItem.class, + TOrder.class, TCustomer.class, TCountry.class, + DROP_TABLES); createTestData(); } @@ -65,6 +65,12 @@ private void createTestData() { sp3.setName("Software C"); sp3.setRevisionNumber(1.0); em.persist(sp3); + + TCustomSoftwareProduct cs1 = new TCustomSoftwareProduct(); + cs1.setName("Custom Software A"); + cs1.setRevisionNumber(1.0); + cs1.setCustomizationHours(3); + em.persist(cs1); // Create a hardware product (base type) TProduct hw = new TProduct(); @@ -95,6 +101,15 @@ private void createTestData() { li3.setProduct(hw); li3.setOrder(order2); em.persist(li3); + + TOrder order3 = new TOrder(); + em.persist(order3); + + TLineItem li4 = new TLineItem(); + li4.setQuantity(1); + li4.setProduct(cs1); + li4.setOrder(order3); + em.persist(li4); // Create customer with embedded country TCustomer cust = new TCustomer(); @@ -121,10 +136,25 @@ public void testTreatInWhereClause() { "SELECT p.name FROM TProduct p WHERE TREAT(p AS TSoftwareProduct).revisionNumber = 1.0", String.class).getResultList(); - Collections.sort(results); - assertEquals(2, results.size()); + assertEquals(3, results.size()); assertTrue(results.contains("Software A")); assertTrue(results.contains("Software C")); + assertTrue(results.contains("Custom Software A")); + + em.getTransaction().commit(); + em.close(); + } + + public void testTreatLeafInWhereClause() { + EntityManager em = emf.createEntityManager(); + em.getTransaction().begin(); + + List results = em.createQuery( + "SELECT p.name FROM TProduct p WHERE TREAT(p AS TCustomSoftwareProduct).revisionNumber = 1.0", + String.class).getResultList(); + + assertEquals(1, results.size()); + assertTrue(results.contains("Custom Software A")); em.getTransaction().commit(); em.close(); @@ -142,9 +172,10 @@ public void testTreatWithDoubleSuffix() { "SELECT p.name FROM TProduct p WHERE TREAT(p AS TSoftwareProduct).revisionNumber = 1.0D", String.class).getResultList(); - assertEquals(2, results.size()); + assertEquals(3, results.size()); assertTrue(results.contains("Software A")); assertTrue(results.contains("Software C")); + assertTrue(results.contains("Custom Software A")); em.getTransaction().commit(); em.close(); @@ -162,9 +193,10 @@ public void testTreatWithScientificNotation() { "SELECT p.name FROM TProduct p WHERE TREAT(p AS TSoftwareProduct).revisionNumber = 1E0", String.class).getResultList(); - assertEquals(2, results.size()); + assertEquals(3, results.size()); assertTrue(results.contains("Software A")); assertTrue(results.contains("Software C")); + assertTrue(results.contains("Custom Software A")); em.getTransaction().commit(); em.close(); @@ -183,12 +215,12 @@ public void testTreatJoinClass() { String.class).getResultList(); // TREAT join should only return software products (not hardware) - // We have 2 line items with software products (sp1, sp2) and 1 with hardware + // We have 3 line items with software products (sp1, sp2, cp1) and 1 with hardware assertNotNull(results); - Collections.sort(results); - assertEquals("TREAT join should return only software products", 2, results.size()); + assertEquals("TREAT join should return software products, including the especialized ones", 3, results.size()); assertTrue(results.contains("Software A")); assertTrue(results.contains("Software B")); + assertTrue(results.contains("Custom Software A")); // Hardware X should NOT be in the results due to TREAT filtering assertFalse(results.contains("Hardware X")); From 16b9d942e5238cfd12e387ceb289a3c05d1bf714 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paulo=20Cristov=C3=A3o=20de=20Ara=C3=BAjo=20Silva=20Filho?= Date: Tue, 15 Sep 2026 13:14:31 -0300 Subject: [PATCH 5/6] [openjpa-2961] Adds header to test entity --- .../treatjoinon/TCustomSoftwareProduct.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TCustomSoftwareProduct.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TCustomSoftwareProduct.java index 28edd5df35..effa3cb5e8 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TCustomSoftwareProduct.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TCustomSoftwareProduct.java @@ -1,3 +1,21 @@ +/* + * 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.jpql.treatjoinon; import jakarta.persistence.DiscriminatorValue; From 999ba528051d7b427457739392fbd21e6d6e400c Mon Sep 17 00:00:00 2001 From: Maxim Solodovnik Date: Wed, 16 Sep 2026 10:46:44 +0700 Subject: [PATCH 6/6] Tab/Space micsture is fixed --- .../jpql/treatjoinon/TestTreatJoinOnEmbeddable.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatJoinOnEmbeddable.java b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatJoinOnEmbeddable.java index ba282d7836..311e7550c5 100644 --- a/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatJoinOnEmbeddable.java +++ b/openjpa-persistence-jdbc/src/test/java/org/apache/openjpa/persistence/jpql/treatjoinon/TestTreatJoinOnEmbeddable.java @@ -40,9 +40,9 @@ public class TestTreatJoinOnEmbeddable extends SingleEMFTestCase { @Override public void setUp() { setUp(TProduct.class, TSoftwareProduct.class, - TCustomSoftwareProduct.class, TLineItem.class, - TOrder.class, TCustomer.class, TCountry.class, - DROP_TABLES); + TCustomSoftwareProduct.class, TLineItem.class, + TOrder.class, TCustomer.class, TCountry.class, + DROP_TABLES); createTestData(); }