You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2026/09/11 12:03:33 UTC
(cayenne) branch master updated: CAY-2911 Vertical Inheritance: Column Select Sometimes Null With Vertically Inherited Entity
This is an automated email from the ASF dual-hosted git repository.
stariy95 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/master by this push:
new 9e36902fd CAY-2911 Vertical Inheritance: Column Select Sometimes Null With Vertically Inherited Entity
9e36902fd is described below
commit 9e36902fd840b148ca2b9e8c71ef39d8faf7fafe
Author: ntimofeev <st...@gmail.com>
AuthorDate: Fri Sep 11 15:03:23 2026 +0300
CAY-2911 Vertical Inheritance: Column Select Sometimes Null With Vertically Inherited Entity
---
RELEASE-NOTES.txt | 1 +
.../select/DescriptorColumnExtractor.java | 13 ++-
.../cayenne/access/VerticalInheritanceIT.java | 93 +++++++++++++---------
...riptorColumnExtractorVerticalInheritanceIT.java | 90 +++++++++++++++++++++
4 files changed, 156 insertions(+), 41 deletions(-)
diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 454e0bd68..13c1f3e40 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -29,6 +29,7 @@ CAY-3017 ObjectContext should not be a DataChannel
Bug Fixes:
CAY-2832 SelectActionIT is failing on GitHub Actions
+CAY-2911 Vertical Inheritance: Column Select Sometimes Null With Vertically Inherited Entity
CAY-2996 Javadocs are missing from the central artifacts
CAY-2999 Modeler: A restricted method in java.lang.System has been called
CAY-3001 Modeler: Auto-increment attribute is not picked up
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
index 86094a32a..cd445e076 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractor.java
@@ -151,8 +151,17 @@ class DescriptorColumnExtractor extends BaseColumnExtractor implements PropertyV
int count = result.getDbAttributes().size();
for(int i=0; i<count; i++) {
- processTranslationResult(result, i);
- addEntityResultField(result.getDbAttributes().get(i));
+ if(processTranslationResult(result, i) == null) {
+ // the column was already emitted for an earlier property, and its result field with it
+ continue;
+ }
+ DbAttribute dbAttribute = result.getDbAttributes().get(i);
+ if(count > 1) {
+ // See CAY-2911, and CAY-2552 for the same fix in visitAttribute().
+ addEntityResultField(result.getAttributePaths().get(i).dot(dbAttribute.getName()));
+ } else {
+ addEntityResultField(dbAttribute);
+ }
}
return true;
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java b/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java
index a0216d5fb..91ef5f292 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java
@@ -35,6 +35,7 @@ import org.apache.cayenne.unit.CayenneTestsEnv;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.sql.SQLException;
@@ -61,6 +62,8 @@ public class VerticalInheritanceIT {
TableHelper ivConcreteTable;
+ TableHelper ivRootTable;
+
@BeforeEach
public void setup() {
runtime = env.runtime();
@@ -68,6 +71,8 @@ public class VerticalInheritanceIT {
.setColumnTypes(Types.INTEGER, Types.INTEGER, Types.CHAR);
ivConcreteTable = env.table("IV_CONCRETE").setColumns("ID", "NAME", "RELATED_ABSTRACT_ID")
.setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER);
+ ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR")
+ .setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.VARCHAR);
}
@AfterEach
@@ -81,9 +86,6 @@ public class VerticalInheritanceIT {
@Test
public void insertRoot() throws Exception {
-
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR");
-
assertEquals(0, ivRootTable.getRowCount());
IvRoot root = env.context().newObject(IvRoot.class);
@@ -103,8 +105,6 @@ public class VerticalInheritanceIT {
@Test
public void insertSub1() throws Exception {
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR");
-
TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME");
IvSub1 sub1 = env.context().newObject(IvSub1.class);
@@ -144,9 +144,6 @@ public class VerticalInheritanceIT {
@Test
public void insertSub2() throws Exception {
-
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR");
-
TableHelper ivSub2Table = env.table("IV_SUB2", "ID", "SUB2_NAME", "SUB2_ATTR");
IvSub2 sub2 = env.context().newObject(IvSub2.class);
@@ -216,9 +213,6 @@ public class VerticalInheritanceIT {
*/
@Test
public void validationOnInsertSub3Ok() throws Exception {
-
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR");
-
TableHelper ivSub3Table = env.table("IV_SUB3", "ID", "IV_ROOT_ID");
IvSub3 sub3 = env.context().newObject(IvSub3.class);
@@ -235,7 +229,6 @@ public class VerticalInheritanceIT {
*/
@Test
public void updateRelationSub3() throws Exception {
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR");
ivRootTable.insert(1, "root1", null);
ivRootTable.insert(2, "root2", null);
ivRootTable.insert(3, "name", "IvSub3");
@@ -266,9 +259,6 @@ public class VerticalInheritanceIT {
@Test
public void insertSub1Sub1() throws Exception {
-
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR");
-
TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME", "SUB1_PRICE");
TableHelper ivSub1Sub1Table = env.table("IV_SUB1_SUB1", "ID", "SUB1_SUB1_NAME", "SUB1_SUB1_PRICE");
@@ -306,10 +296,6 @@ public class VerticalInheritanceIT {
@Test
public void selectQuerySuperSub() throws Exception {
-
- TableHelper ivRootTable = env.table("IV_ROOT").setColumns("ID", "NAME", "DISCRIMINATOR").setColumnTypes(
- Types.INTEGER, Types.VARCHAR, Types.VARCHAR);
-
TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME");
// insert
@@ -346,10 +332,6 @@ public class VerticalInheritanceIT {
@Test
public void selectQueryDeepAndWide() throws Exception {
-
- TableHelper ivRootTable = env.table("IV_ROOT").setColumns("ID", "NAME", "DISCRIMINATOR").setColumnTypes(
- Types.INTEGER, Types.VARCHAR, Types.VARCHAR);
-
TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME");
TableHelper ivSub2Table = env.table("IV_SUB2", "ID", "SUB2_NAME");
@@ -412,10 +394,6 @@ public class VerticalInheritanceIT {
@Test
public void selectQueryMiddleLeaf() throws Exception {
-
- TableHelper ivRootTable = env.table("IV_ROOT").setColumns("ID", "NAME", "DISCRIMINATOR").setColumnTypes(
- Types.INTEGER, Types.VARCHAR, Types.VARCHAR);
-
TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME");
TableHelper ivSub2Table = env.table("IV_SUB2", "ID", "SUB2_NAME");
@@ -467,10 +445,6 @@ public class VerticalInheritanceIT {
@Test
public void deleteMix() throws Exception {
-
- TableHelper ivRootTable = env.table("IV_ROOT").setColumns("ID", "NAME", "DISCRIMINATOR").setColumnTypes(
- Types.INTEGER, Types.VARCHAR, Types.VARCHAR);
-
TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME");
TableHelper ivSub2Table = env.table("IV_SUB2", "ID", "SUB2_NAME");
@@ -1036,8 +1010,6 @@ public class VerticalInheritanceIT {
@Test
public void countEjbqlQuery() throws Exception {
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR");
-
TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME");
TableHelper ivSub2Table = env.table("IV_SUB2", "ID", "SUB2_ATTR", "SUB2_NAME");
@@ -1075,8 +1047,6 @@ public class VerticalInheritanceIT {
@Test
public void columnSelectVerticalInheritanceSub1() throws SQLException {
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR");
-
TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME", "SUB1_PRICE");
TableHelper ivSub1Sub1Table = env.table("IV_SUB1_SUB1", "ID", "SUB1_SUB1_NAME", "SUB1_SUB1_PRICE");
@@ -1120,8 +1090,6 @@ public class VerticalInheritanceIT {
@Test
public void columnSelectVerticalInheritanceSub1Sub1() throws SQLException {
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "NAME", "DISCRIMINATOR");
-
TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME", "SUB1_PRICE");
TableHelper ivSub1Sub1Table = env.table("IV_SUB1_SUB1", "ID", "SUB1_SUB1_NAME", "SUB1_SUB1_PRICE");
@@ -1167,7 +1135,7 @@ public class VerticalInheritanceIT {
@Test
public void insertTwoGenericVerticalInheritanceObjects() {
// Generic DataObjects play nicer with a DataContext
- final DataContext dataContext = (DataContext) env.context();
+ final DataContext dataContext = env.context();
final Persistent girlEmma = dataContext.newObject("GenGirl");
final Persistent boyLuke = dataContext.newObject("GenBoy");
@@ -1271,7 +1239,6 @@ public class VerticalInheritanceIT {
@Test
public void updateFlattenedAttributeOfThreeLevelInheritanceChild() throws SQLException {
- TableHelper ivRootTable = env.table("IV_ROOT", "ID", "DISCRIMINATOR");
TableHelper ivSub1Table = env.table("IV_SUB1", "ID");
TableHelper ivSub1Sub1Table = env.table("IV_SUB1_SUB1", "ID", "SUB1_SUB1_NAME");
@@ -1290,4 +1257,52 @@ public class VerticalInheritanceIT {
IvSub1Sub1 reread = ObjectSelect.query(IvSub1Sub1.class).where(IvSub1Sub1.SELF.eqId(1)).selectOne(cleanContext);
assertEquals("sub1sub1name-updated", reread.getSub1Sub1Name());
}
+
+ /**
+ * See CAY-2911.
+ */
+ @Test
+ public void selectColumnsSelf() throws SQLException {
+ TableHelper ivSub1Table = env.table("IV_SUB1", "ID", "SUB1_NAME", "SUB1_PRICE");
+ TableHelper ivSub2Table = env.table("IV_SUB2", "ID", "SUB2_ATTR", "SUB2_NAME");
+
+ ivRootTable.insert(1, "root", null);
+ ivRootTable.insert(2, "s1", "IvSub1");
+ ivSub1Table.insert(2, "sub1name", 42.0);
+ ivRootTable.insert(3, "s2", "IvSub2");
+ ivSub2Table.insert(3, "sub2attr", "sub2name");
+
+ ObjectContext freshContext = runtime.newContext();
+ List<IvRoot> results = ObjectSelect.query(IvRoot.class)
+ .column(IvRoot.SELF)
+ .orderBy(IvRoot.NAME.asc())
+ .select(freshContext);
+
+ assertEquals(3, results.size());
+ assertFalse(results.contains(null), () -> "column select returned a null object: " + results);
+
+ assertEquals(IvRoot.class, results.get(0).getClass());
+ assertEquals("root", results.get(0).getName());
+ assertInstanceOf(IvSub1.class, results.get(1));
+ assertInstanceOf(IvSub2.class, results.get(2));
+ }
+
+ /**
+ * See CAY-2911.
+ */
+ @Test
+ public void selectColumnsSelfAndScalar() throws SQLException {
+ TableHelper ivSub3Table = env.table("IV_SUB3", "ID", "IV_ROOT_ID");
+
+ ivRootTable.insert(1, "sub3name", "IvSub3");
+ ivSub3Table.insert(1, 1);
+
+ ObjectContext freshContext = runtime.newContext();
+ Object[] row = ObjectSelect.query(IvSub3.class)
+ .columns(IvSub3.SELF, IvSub3.NAME)
+ .selectOne(freshContext);
+
+ assertInstanceOf(IvSub3.class, row[0]);
+ assertEquals("sub3name", row[1], () -> "scalar slot held " + row[1] + ", segment offset is wrong");
+ }
}
diff --git a/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractorVerticalInheritanceIT.java b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractorVerticalInheritanceIT.java
new file mode 100644
index 000000000..464208a75
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/access/translator/select/DescriptorColumnExtractorVerticalInheritanceIT.java
@@ -0,0 +1,90 @@
+/*****************************************************************
+ * 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
+ *
+ * https://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.cayenne.access.translator.select;
+
+import java.util.Set;
+
+import org.apache.cayenne.dba.DbAdapter;
+import org.apache.cayenne.map.EntityResolver;
+import org.apache.cayenne.query.FluentSelect;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.testdo.inheritance_vertical.IvRoot;
+import org.apache.cayenne.testdo.inheritance_vertical.IvSub3;
+import org.apache.cayenne.unit.CayenneProjects;
+import org.apache.cayenne.unit.CayenneTestsEnv;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+import org.mockito.Mockito;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Verifies that {@link DescriptorColumnExtractor} registers exactly one {@code EntityResult} field per column,
+ * under a unique key.
+ * <p>
+ * See CAY-2911.
+ */
+public class DescriptorColumnExtractorVerticalInheritanceIT {
+
+ @RegisterExtension
+ static final CayenneTestsEnv env = CayenneTestsEnv.forProject(CayenneProjects.INHERITANCE_VERTICAL_PROJECT);
+
+ private SelectTranslatorContext extract(FluentSelect<?, ?> query, String entityName) {
+ EntityResolver resolver = env.context().getEntityResolver();
+ ClassDescriptor classDescriptor = resolver.getClassDescriptor(entityName);
+
+ SelectTranslatorContext translatorContext =
+ new SelectTranslatorContext(query, Mockito.mock(DbAdapter.class), resolver, null);
+
+ new DescriptorColumnExtractor(translatorContext, classDescriptor).extract();
+ return translatorContext;
+ }
+
+ private void assertFieldCountMatchesColumnCount(FluentSelect<?, ?> query, String entityName) {
+ SelectTranslatorContext context = extract(query, entityName);
+ EntityResolver resolver = env.context().getEntityResolver();
+
+ assertEquals(context.getResultNodeList().size(),
+ context.getRootEntityResult().getDbFields(resolver).size(),
+ "entity result field count must match the number of emitted columns");
+ }
+
+ @Test
+ public void fieldCountMatchesColumnCountForInheritanceRoot() {
+ assertFieldCountMatchesColumnCount(ObjectSelect.query(IvRoot.class).column(IvRoot.SELF), "IvRoot");
+ }
+
+ @Test
+ public void fieldCountMatchesColumnCountForSub3() {
+ assertFieldCountMatchesColumnCount(ObjectSelect.query(IvSub3.class).column(IvSub3.SELF), "IvSub3");
+ }
+
+ @Test
+ public void flattenedToOneFieldsArePathQualified() {
+ SelectTranslatorContext context = extract(ObjectSelect.query(IvSub3.class).column(IvSub3.SELF), "IvSub3");
+ EntityResolver resolver = env.context().getEntityResolver();
+
+ Set<String> keys = context.getRootEntityResult().getDbFields(resolver).keySet();
+
+ assertTrue(keys.contains("sub3.ID"), () -> "expected path-qualified 'sub3.ID' in " + keys);
+ assertTrue(keys.contains("sub3.IV_ROOT_ID"), () -> "expected path-qualified 'sub3.IV_ROOT_ID' in " + keys);
+ }
+}