This is an automated email from the ASF dual-hosted git repository.

ntimofeev 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 b625eaea1 CAY-2855 Vertical Inheritance: Fix Disjoint By Id Prefetch 
On Sub-Entity
b625eaea1 is described below

commit b625eaea19417f9ed7787450a253712e969bed3c
Author: Nikita Timofeev <[email protected]>
AuthorDate: Mon Jun 10 16:36:01 2024 +0400

    CAY-2855 Vertical Inheritance: Fix Disjoint By Id Prefetch On Sub-Entity
---
 .../cayenne/access/HierarchicalObjectResolver.java |  59 +++++---
 ...tyWithMeaningfulPKAndCustomDbRowOpSorterIT.java |  12 +-
 .../apache/cayenne/access/GraphSorterModule.java   |  35 +++++
 .../cayenne/access/VerticalInheritanceIT.java      | 156 ++++++++++++++++++++-
 .../testdo/inheritance_vertical/auto/_IvImpl.java  |  24 ++++
 .../testdo/inheritance_vertical/auto/_IvOther.java |  17 +++
 .../test/resources/inheritance-vertical.map.xml    |   9 ++
 7 files changed, 284 insertions(+), 28 deletions(-)

diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java
index b87baf6ba..c57c4ba11 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/HierarchicalObjectResolver.java
@@ -33,8 +33,10 @@ import org.apache.cayenne.query.PrefetchSelectQuery;
 import org.apache.cayenne.query.PrefetchTreeNode;
 import org.apache.cayenne.query.QueryMetadata;
 import org.apache.cayenne.reflect.ClassDescriptor;
+import org.apache.cayenne.util.SingleEntryMap;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -117,21 +119,12 @@ class HierarchicalObjectResolver {
             ObjRelationship relationship = 
processorNode.getIncoming().getRelationship();
 
             List<DbRelationship> dbRelationships = 
relationship.getDbRelationships();
-            DbRelationship lastDbRelationship = dbRelationships.get(0);
-
-            String pathPrefix = "";
+            CayennePath pathPrefix = CayennePath.EMPTY_PATH;
             if (dbRelationships.size() > 1) {
                 // we need path prefix for flattened relationships
-                StringBuilder buffer = new StringBuilder();
                 for (int i = dbRelationships.size() - 1; i >= 1; i--) {
-                    if (buffer.length() > 0) {
-                        buffer.append(".");
-                    }
-
-                    
buffer.append(dbRelationships.get(i).getReverseRelationship().getName());
+                    pathPrefix = 
pathPrefix.dot(dbRelationships.get(i).getReverseRelationship().getName());
                 }
-
-                pathPrefix = buffer.append(".").toString();
             }
 
             List<DataRow> parentDataRows;
@@ -145,7 +138,8 @@ class HierarchicalObjectResolver {
             }
 
             int maxIdQualifierSize = 
context.getParentDataDomain().getMaxIdQualifierSize();
-            List<DbJoin> joins = lastDbRelationship.getJoins();
+            List<DbJoin> joins = getDbJoins(relationship);
+            Map<DbJoin, String> joinToDataRowKey = getDataRowKeys(joins, 
pathPrefix);
 
             List<PrefetchSelectQuery<DataRow>> queries = new ArrayList<>();
             PrefetchSelectQuery<DataRow> currentQuery = null;
@@ -168,7 +162,8 @@ class HierarchicalObjectResolver {
 
                 List<Object> joinValues = new ArrayList<>(joins.size());
                 for (DbJoin join : joins) {
-                    Object targetValue = dataRow.get(join.getSourceName());
+                    String dataRowKey = joinToDataRowKey.get(join);
+                    Object targetValue = dataRow.get(dataRowKey);
                     joinValues.add(targetValue);
                 }
 
@@ -205,7 +200,39 @@ class HierarchicalObjectResolver {
             return true;
         }
 
-        private void createDisjointByIdPrefetchQualifier(String pathPrefix, 
PrefetchSelectQuery<?> currentQuery,
+        private List<DbJoin> getDbJoins(ObjRelationship relationship) {
+            // we get the part of the relationship path that contains FK
+            List<DbRelationship> dbRelationships = 
relationship.getDbRelationships();
+            if(relationship.isToMany() || !relationship.isToPK()) {
+                return dbRelationships.get(0).getJoins();
+            } else {
+                return dbRelationships.get(dbRelationships.size() - 
1).getJoins();
+            }
+        }
+
+        /**
+         * precalculate join to a DataRow key
+         * @param joins to get key from
+         * @param pathPrefix for the flattened path
+         * @return mapping of DbJoin to DataRow key to use
+         */
+        private Map<DbJoin, String> getDataRowKeys(List<DbJoin> joins, 
CayennePath pathPrefix) {
+            Map<DbJoin, String> joinToDataRowKey = joins.size() == 1
+                    ? new SingleEntryMap<>(joins.get(0))
+                    : new HashMap<>(joins.size());
+            for (DbJoin join : joins) {
+                String dataRowKey;
+                if(join.getRelationship().isToMany() || 
!join.getRelationship().isToPK()) {
+                    dataRowKey = join.getSourceName();
+                } else {
+                    dataRowKey = pathPrefix.dot(join.getSourceName()).value();
+                }
+                joinToDataRowKey.put(join, dataRowKey);
+            }
+            return joinToDataRowKey;
+        }
+
+        private void createDisjointByIdPrefetchQualifier(CayennePath 
pathPrefix, PrefetchSelectQuery<?> currentQuery,
                                                          List<DbJoin> joins, 
Set<List<Object>> values) {
             if (currentQuery == null) return;
 
@@ -214,7 +241,7 @@ class HierarchicalObjectResolver {
              // Results in SQL:  ... targetField IN ( ?, ?, ?, .... )
             if (joins.size() == 1 && values.size() > 1) {
                 currentQuery.and( ExpressionFactory.inDbExp(
-                    pathPrefix + joins.get(0).getTargetName(),
+                    pathPrefix.dot(joins.get(0).getTargetName()).value(),
                     
values.stream().flatMap(List::stream).collect(Collectors.toSet())
                 ));
             } else { // Handle a single value or compound prefetch ID 
predicates
@@ -226,7 +253,7 @@ class HierarchicalObjectResolver {
                     allJoinsQualifier = null;
                     for(int j=0; j<joins.size(); j++) {
                         Expression joinQualifier = ExpressionFactory
-                                .matchDbExp(pathPrefix + 
joins.get(j).getTargetName(), joinValues.get(j));
+                                
.matchDbExp(pathPrefix.dot(joins.get(j).getTargetName()).value(), 
joinValues.get(j));
                         if (allJoinsQualifier == null) {
                             allJoinsQualifier = joinQualifier;
                         } else {
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/DataContextEntityWithMeaningfulPKAndCustomDbRowOpSorterIT.java
 
b/cayenne/src/test/java/org/apache/cayenne/access/DataContextEntityWithMeaningfulPKAndCustomDbRowOpSorterIT.java
index 519144a23..09bfeaf34 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/access/DataContextEntityWithMeaningfulPKAndCustomDbRowOpSorterIT.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/access/DataContextEntityWithMeaningfulPKAndCustomDbRowOpSorterIT.java
@@ -19,11 +19,7 @@
 
 package org.apache.cayenne.access;
 
-import org.apache.cayenne.access.flush.operation.DbRowOpSorter;
-import org.apache.cayenne.access.flush.operation.GraphBasedDbRowOpSorter;
-import org.apache.cayenne.di.Binder;
 import org.apache.cayenne.di.Inject;
-import org.apache.cayenne.di.Module;
 import org.apache.cayenne.testdo.meaningful_pk.MeaningfulPKDep;
 import org.apache.cayenne.testdo.meaningful_pk.MeaningfulPKTest1;
 import org.apache.cayenne.testdo.meaningful_pk.MeaningfulPk;
@@ -34,7 +30,7 @@ import org.apache.cayenne.unit.di.runtime.UseCayenneRuntime;
 import org.junit.Test;
 
 @UseCayenneRuntime(CayenneProjects.MEANINGFUL_PK_PROJECT)
-@ExtraModules(DataContextEntityWithMeaningfulPKAndCustomDbRowOpSorterIT.GraphSorterModule.class)
+@ExtraModules(GraphSorterModule.class)
 public class DataContextEntityWithMeaningfulPKAndCustomDbRowOpSorterIT extends 
RuntimeCase {
 
     @Inject
@@ -80,10 +76,4 @@ public class 
DataContextEntityWithMeaningfulPKAndCustomDbRowOpSorterIT extends R
         context.commitChanges();
     }
 
-    public static class GraphSorterModule implements Module {
-        @Override
-        public void configure(Binder binder) {
-            binder.bind(DbRowOpSorter.class).to(GraphBasedDbRowOpSorter.class);
-        }
-    }
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/access/GraphSorterModule.java 
b/cayenne/src/test/java/org/apache/cayenne/access/GraphSorterModule.java
new file mode 100644
index 000000000..d833d590b
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/access/GraphSorterModule.java
@@ -0,0 +1,35 @@
+/*****************************************************************
+ *   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;
+
+import org.apache.cayenne.access.flush.operation.DbRowOpSorter;
+import org.apache.cayenne.access.flush.operation.GraphBasedDbRowOpSorter;
+import org.apache.cayenne.di.Binder;
+import org.apache.cayenne.di.Module;
+
+/**
+ * Test module that sets up {@link GraphBasedDbRowOpSorter} instead of default 
one
+ */
+public class GraphSorterModule implements Module {
+    @Override
+    public void configure(Binder binder) {
+        binder.bind(DbRowOpSorter.class).to(GraphBasedDbRowOpSorter.class);
+    }
+}
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 1fd162bbb..6257e6bdb 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java
@@ -30,6 +30,7 @@ import org.apache.cayenne.test.jdbc.DBHelper;
 import org.apache.cayenne.test.jdbc.TableHelper;
 import org.apache.cayenne.testdo.inheritance_vertical.*;
 import org.apache.cayenne.unit.di.runtime.CayenneProjects;
+import org.apache.cayenne.unit.di.runtime.ExtraModules;
 import org.apache.cayenne.unit.di.runtime.RuntimeCase;
 import org.apache.cayenne.unit.di.runtime.UseCayenneRuntime;
 import org.junit.After;
@@ -47,6 +48,8 @@ import java.util.Map;
 import static org.junit.Assert.*;
 
 @UseCayenneRuntime(CayenneProjects.INHERITANCE_VERTICAL_PROJECT)
+// Default sorter fails to properly sort all the relationships in the test 
schema used
+@ExtraModules(GraphSorterModule.class)
 public class VerticalInheritanceIT extends RuntimeCase {
 
        @Inject
@@ -865,7 +868,7 @@ public class VerticalInheritanceIT extends RuntimeCase {
         * @link https://issues.apache.org/jira/browse/CAY-2840
         */
        @Test
-       public void testJointPrefetchBelongsTo() throws SQLException {
+       public void testBaseJointPrefetchBelongsTo() throws SQLException {
                TableHelper ivOtherTable = new TableHelper(dbHelper, 
"IV_OTHER");
                ivOtherTable.setColumns("ID", "NAME", 
"BASE_ID").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER);
 
@@ -891,6 +894,157 @@ public class VerticalInheritanceIT extends RuntimeCase {
                assertEquals("attr1", impl.getAttr1());
        }
 
+       /**
+        * @link https://issues.apache.org/jira/browse/CAY-2840
+        */
+       @Test
+       public void testBaseDisjointPrefetchBelongsTo() throws SQLException {
+               TableHelper ivOtherTable = new TableHelper(dbHelper, 
"IV_OTHER");
+               ivOtherTable.setColumns("ID", "NAME", 
"BASE_ID").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER);
+
+               TableHelper ivBaseTable = new TableHelper(dbHelper, "IV_BASE");
+               ivBaseTable.setColumns("ID", "NAME", 
"TYPE").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.CHAR);
+
+               TableHelper ivImplTable = new TableHelper(dbHelper, "IV_IMPL");
+               ivImplTable.setColumns("ID", 
"ATTR1").setColumnTypes(Types.INTEGER, Types.VARCHAR);
+
+               ivBaseTable.insert(1, "Impl 1", "I");
+               ivImplTable.insert(1, "attr1");
+               ivOtherTable.insert(1, "other1", 1);
+
+               IvOther other = 
ObjectSelect.query(IvOther.class).prefetch(IvOther.BASE.disjoint()).selectOne(context);
+               assertNotNull(other);
+               assertNotNull(other.getBase());
+               
assertTrue(IvImpl.class.isAssignableFrom(other.getBase().getClass()));
+
+               IvImpl impl = (IvImpl)other.getBase();
+               // Ensure that base attributes were prefetched correctly
+               assertEquals("Impl 1", impl.getName());
+               // Ensure that subclass attributes were prefetched correctly
+               assertEquals("attr1", impl.getAttr1());
+       }
+
+       /**
+        * @link https://issues.apache.org/jira/browse/CAY-2840
+        */
+       @Test
+       public void testBaseDisjointByIdPrefetchBelongsTo() throws SQLException 
{
+               TableHelper ivOtherTable = new TableHelper(dbHelper, 
"IV_OTHER");
+               ivOtherTable.setColumns("ID", "NAME", 
"BASE_ID").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER);
+
+               TableHelper ivBaseTable = new TableHelper(dbHelper, "IV_BASE");
+               ivBaseTable.setColumns("ID", "NAME", 
"TYPE").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.CHAR);
+
+               TableHelper ivImplTable = new TableHelper(dbHelper, "IV_IMPL");
+               ivImplTable.setColumns("ID", 
"ATTR1").setColumnTypes(Types.INTEGER, Types.VARCHAR);
+
+               ivBaseTable.insert(1, "Impl 1", "I");
+               ivImplTable.insert(1, "attr1");
+               ivOtherTable.insert(1, "other1", 1);
+
+               IvOther other = 
ObjectSelect.query(IvOther.class).prefetch(IvOther.BASE.disjointById()).selectOne(context);
+               assertNotNull(other);
+               assertNotNull(other.getBase());
+               
assertTrue(IvImpl.class.isAssignableFrom(other.getBase().getClass()));
+
+               IvImpl impl = (IvImpl)other.getBase();
+               // Ensure that base attributes were prefetched correctly
+               assertEquals("Impl 1", impl.getName());
+               // Ensure that subclass attributes were prefetched correctly
+               assertEquals("attr1", impl.getAttr1());
+       }
+
+       /**
+        * @link https://issues.apache.org/jira/browse/CAY-2855
+        */
+       @Test
+       public void testImplJointPrefetchBelongsTo() throws SQLException {
+               TableHelper ivOtherTable = new TableHelper(dbHelper, 
"IV_OTHER");
+               ivOtherTable.setColumns("ID", "NAME", 
"IMPL_ID").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER);
+
+               TableHelper ivBaseTable = new TableHelper(dbHelper, "IV_BASE");
+               ivBaseTable.setColumns("ID", "NAME", 
"TYPE").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.CHAR);
+
+               TableHelper ivImplTable = new TableHelper(dbHelper, "IV_IMPL");
+               ivImplTable.setColumns("ID", 
"ATTR1").setColumnTypes(Types.INTEGER, Types.VARCHAR);
+
+               ivBaseTable.insert(1, "Impl 1", "I");
+               ivImplTable.insert(1, "attr1");
+               ivOtherTable.insert(1, "other1", 1);
+
+               IvOther other = 
ObjectSelect.query(IvOther.class).prefetch(IvOther.IMPL.joint()).limit(1).selectOne(context);
+               assertNotNull(other);
+
+               IvImpl impl = other.getImpl();
+               assertNotNull(other.getImpl());
+               // Ensure that base attributes were prefetched correctly
+               assertEquals("Impl 1", impl.getName());
+               // Ensure that subclass attributes were prefetched correctly
+               assertEquals("attr1", impl.getAttr1());
+       }
+
+       /**
+        * @link https://issues.apache.org/jira/browse/CAY-2855
+        */
+       @Test
+       public void testImplDisjointPrefetchBelongsTo() throws SQLException {
+               TableHelper ivOtherTable = new TableHelper(dbHelper, 
"IV_OTHER");
+               ivOtherTable.setColumns("ID", "NAME", 
"IMPL_ID").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER);
+
+               TableHelper ivBaseTable = new TableHelper(dbHelper, "IV_BASE");
+               ivBaseTable.setColumns("ID", "NAME", 
"TYPE").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.CHAR);
+
+               TableHelper ivImplTable = new TableHelper(dbHelper, "IV_IMPL");
+               ivImplTable.setColumns("ID", 
"ATTR1").setColumnTypes(Types.INTEGER, Types.VARCHAR);
+
+               ivBaseTable.insert(1, "Impl 1", "I");
+               ivImplTable.insert(1, "attr1");
+               ivOtherTable.insert(1, "other1", 1);
+
+               IvOther other = ObjectSelect.query(IvOther.class)
+                               .prefetch(IvOther.IMPL.disjoint())
+                               .selectOne(context);
+               assertNotNull(other);
+
+               IvImpl impl = other.getImpl();
+               assertNotNull(other.getImpl());
+               // Ensure that base attributes were prefetched correctly
+               assertEquals("Impl 1", impl.getName());
+               // Ensure that subclass attributes were prefetched correctly
+               assertEquals("attr1", impl.getAttr1());
+       }
+
+       /**
+        * @link https://issues.apache.org/jira/browse/CAY-2855
+        */
+       @Test
+       public void testImplDisjointByIdPrefetchBelongsTo() throws SQLException 
{
+               TableHelper ivOtherTable = new TableHelper(dbHelper, 
"IV_OTHER");
+               ivOtherTable.setColumns("ID", "NAME", 
"IMPL_ID").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER);
+
+               TableHelper ivBaseTable = new TableHelper(dbHelper, "IV_BASE");
+               ivBaseTable.setColumns("ID", "NAME", 
"TYPE").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.CHAR);
+
+               TableHelper ivImplTable = new TableHelper(dbHelper, "IV_IMPL");
+               ivImplTable.setColumns("ID", 
"ATTR1").setColumnTypes(Types.INTEGER, Types.VARCHAR);
+
+               ivBaseTable.insert(1, "Impl 1", "I");
+               ivImplTable.insert(1, "attr1");
+               ivOtherTable.insert(1, "other1", 1);
+
+               IvOther other = ObjectSelect.query(IvOther.class)
+                               .prefetch(IvOther.IMPL.disjointById())
+                               .selectOne(context);
+               assertNotNull(other);
+
+               IvImpl impl = other.getImpl();
+               assertNotNull(other.getImpl());
+               // Ensure that base attributes were prefetched correctly
+               assertEquals("Impl 1", impl.getName());
+               // Ensure that subclass attributes were prefetched correctly
+               assertEquals("attr1", impl.getAttr1());
+       }
+
        /**
         * @link https://issues.apache.org/jira/browse/CAY-2282
         */
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/testdo/inheritance_vertical/auto/_IvImpl.java
 
b/cayenne/src/test/java/org/apache/cayenne/testdo/inheritance_vertical/auto/_IvImpl.java
index f7b998530..4f16d7fb3 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/testdo/inheritance_vertical/auto/_IvImpl.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/testdo/inheritance_vertical/auto/_IvImpl.java
@@ -4,9 +4,11 @@ import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 import java.util.Date;
+import java.util.List;
 
 import org.apache.cayenne.exp.property.DateProperty;
 import org.apache.cayenne.exp.property.EntityProperty;
+import org.apache.cayenne.exp.property.ListProperty;
 import org.apache.cayenne.exp.property.NumericIdProperty;
 import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.exp.property.SelfProperty;
@@ -33,6 +35,7 @@ public abstract class _IvImpl extends IvBase {
     public static final DateProperty<Date> ATTR0 = 
PropertyFactory.createDate("attr0", Date.class);
     public static final StringProperty<String> ATTR1 = 
PropertyFactory.createString("attr1", String.class);
     public static final StringProperty<String> ATTR2 = 
PropertyFactory.createString("attr2", String.class);
+    public static final ListProperty<IvOther> IMPL_OTHERS = 
PropertyFactory.createList("implOthers", IvOther.class);
     public static final EntityProperty<IvOther> OTHER1 = 
PropertyFactory.createEntity("other1", IvOther.class);
     public static final EntityProperty<IvOther> OTHER2 = 
PropertyFactory.createEntity("other2", IvOther.class);
     public static final EntityProperty<IvOther> OTHER3 = 
PropertyFactory.createEntity("other3", IvOther.class);
@@ -41,6 +44,7 @@ public abstract class _IvImpl extends IvBase {
     protected String attr1;
     protected String attr2;
 
+    protected Object implOthers;
     protected Object other1;
     protected Object other2;
     protected Object other3;
@@ -75,6 +79,19 @@ public abstract class _IvImpl extends IvBase {
         return this.attr2;
     }
 
+    public void addToImplOthers(IvOther obj) {
+        addToManyTarget("implOthers", obj, true);
+    }
+
+    public void removeFromImplOthers(IvOther obj) {
+        removeToManyTarget("implOthers", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<IvOther> getImplOthers() {
+        return (List<IvOther>)readProperty("implOthers");
+    }
+
     public void setOther1(IvOther other1) {
         setToOneTarget("other1", other1, true);
     }
@@ -112,6 +129,8 @@ public abstract class _IvImpl extends IvBase {
                 return this.attr1;
             case "attr2":
                 return this.attr2;
+            case "implOthers":
+                return this.implOthers;
             case "other1":
                 return this.other1;
             case "other2":
@@ -139,6 +158,9 @@ public abstract class _IvImpl extends IvBase {
             case "attr2":
                 this.attr2 = (String)val;
                 break;
+            case "implOthers":
+                this.implOthers = val;
+                break;
             case "other1":
                 this.other1 = val;
                 break;
@@ -167,6 +189,7 @@ public abstract class _IvImpl extends IvBase {
         out.writeObject(this.attr0);
         out.writeObject(this.attr1);
         out.writeObject(this.attr2);
+        out.writeObject(this.implOthers);
         out.writeObject(this.other1);
         out.writeObject(this.other2);
         out.writeObject(this.other3);
@@ -178,6 +201,7 @@ public abstract class _IvImpl extends IvBase {
         this.attr0 = (Date)in.readObject();
         this.attr1 = (String)in.readObject();
         this.attr2 = (String)in.readObject();
+        this.implOthers = in.readObject();
         this.other1 = in.readObject();
         this.other2 = in.readObject();
         this.other3 = in.readObject();
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/testdo/inheritance_vertical/auto/_IvOther.java
 
b/cayenne/src/test/java/org/apache/cayenne/testdo/inheritance_vertical/auto/_IvOther.java
index fc18cbc55..655a3b3bf 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/testdo/inheritance_vertical/auto/_IvOther.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/testdo/inheritance_vertical/auto/_IvOther.java
@@ -34,6 +34,7 @@ public abstract class _IvOther extends PersistentObject {
 
     public static final StringProperty<String> NAME = 
PropertyFactory.createString("name", String.class);
     public static final EntityProperty<IvBase> BASE = 
PropertyFactory.createEntity("base", IvBase.class);
+    public static final EntityProperty<IvImpl> IMPL = 
PropertyFactory.createEntity("impl", IvImpl.class);
     public static final ListProperty<IvImpl> IMPLS = 
PropertyFactory.createList("impls", IvImpl.class);
     public static final ListProperty<IvImpl> IMPLS_WITH_INVERSE = 
PropertyFactory.createList("implsWithInverse", IvImpl.class);
     public static final ListProperty<IvImplWithLock> IMPLS_WITH_LOCK = 
PropertyFactory.createList("implsWithLock", IvImplWithLock.class);
@@ -41,6 +42,7 @@ public abstract class _IvOther extends PersistentObject {
     protected String name;
 
     protected Object base;
+    protected Object impl;
     protected Object impls;
     protected Object implsWithInverse;
     protected Object implsWithLock;
@@ -63,6 +65,14 @@ public abstract class _IvOther extends PersistentObject {
         return (IvBase)readProperty("base");
     }
 
+    public void setImpl(IvImpl impl) {
+        setToOneTarget("impl", impl, true);
+    }
+
+    public IvImpl getImpl() {
+        return (IvImpl)readProperty("impl");
+    }
+
     public void addToImpls(IvImpl obj) {
         addToManyTarget("impls", obj, true);
     }
@@ -113,6 +123,8 @@ public abstract class _IvOther extends PersistentObject {
                 return this.name;
             case "base":
                 return this.base;
+            case "impl":
+                return this.impl;
             case "impls":
                 return this.impls;
             case "implsWithInverse":
@@ -137,6 +149,9 @@ public abstract class _IvOther extends PersistentObject {
             case "base":
                 this.base = val;
                 break;
+            case "impl":
+                this.impl = val;
+                break;
             case "impls":
                 this.impls = val;
                 break;
@@ -164,6 +179,7 @@ public abstract class _IvOther extends PersistentObject {
         super.writeState(out);
         out.writeObject(this.name);
         out.writeObject(this.base);
+        out.writeObject(this.impl);
         out.writeObject(this.impls);
         out.writeObject(this.implsWithInverse);
         out.writeObject(this.implsWithLock);
@@ -174,6 +190,7 @@ public abstract class _IvOther extends PersistentObject {
         super.readState(in);
         this.name = (String)in.readObject();
         this.base = in.readObject();
+        this.impl = in.readObject();
         this.impls = in.readObject();
         this.implsWithInverse = in.readObject();
         this.implsWithLock = in.readObject();
diff --git a/cayenne/src/test/resources/inheritance-vertical.map.xml 
b/cayenne/src/test/resources/inheritance-vertical.map.xml
index 368ef9ee8..142f7ee13 100644
--- a/cayenne/src/test/resources/inheritance-vertical.map.xml
+++ b/cayenne/src/test/resources/inheritance-vertical.map.xml
@@ -70,6 +70,7 @@
        <db-entity name="IV_OTHER">
                <db-attribute name="BASE_ID" type="INTEGER"/>
                <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" 
isMandatory="true"/>
+               <db-attribute name="IMPL_ID" type="INTEGER"/>
                <db-attribute name="NAME" type="VARCHAR" length="100"/>
        </db-entity>
        <db-entity name="IV_ROOT">
@@ -238,6 +239,9 @@
        <db-relationship name="other3" source="IV_IMPL" target="IV_OTHER">
                <db-attribute-pair source="OTHER3_ID" target="ID"/>
        </db-relationship>
+       <db-relationship name="others" source="IV_IMPL" target="IV_OTHER" 
toMany="true">
+               <db-attribute-pair source="ID" target="IMPL_ID"/>
+       </db-relationship>
        <db-relationship name="base" source="IV_IMPL_WITH_LOCK" 
target="IV_BASE_WITH_LOCK">
                <db-attribute-pair source="ID" target="ID"/>
        </db-relationship>
@@ -247,6 +251,9 @@
        <db-relationship name="base" source="IV_OTHER" target="IV_BASE">
                <db-attribute-pair source="BASE_ID" target="ID"/>
        </db-relationship>
+       <db-relationship name="impl" source="IV_OTHER" target="IV_IMPL">
+               <db-attribute-pair source="IMPL_ID" target="ID"/>
+       </db-relationship>
        <db-relationship name="impls" source="IV_OTHER" target="IV_IMPL" 
toMany="true">
                <db-attribute-pair source="ID" target="OTHER_ID"/>
        </db-relationship>
@@ -289,11 +296,13 @@
        <obj-relationship name="children" source="IvConcrete" 
target="IvConcrete" deleteRule="Deny" db-relationship-path="children"/>
        <obj-relationship name="parent" source="IvConcrete" target="IvConcrete" 
deleteRule="Nullify" db-relationship-path="parent"/>
        <obj-relationship name="relatedAbstract" source="IvConcrete" 
target="IvAbstract" deleteRule="Nullify" 
db-relationship-path="concrete.relatedAbstract"/>
+       <obj-relationship name="implOthers" source="IvImpl" target="IvOther" 
deleteRule="Deny" db-relationship-path="impl.others"/>
        <obj-relationship name="other1" source="IvImpl" target="IvOther" 
deleteRule="Nullify" db-relationship-path="impl.other1"/>
        <obj-relationship name="other2" source="IvImpl" target="IvOther" 
deleteRule="Nullify" db-relationship-path="impl.other2"/>
        <obj-relationship name="other3" source="IvImpl" target="IvOther" 
deleteRule="Nullify" db-relationship-path="impl.other3"/>
        <obj-relationship name="other1" source="IvImplWithLock" 
target="IvOther" deleteRule="Nullify" db-relationship-path="impl.other1"/>
        <obj-relationship name="base" source="IvOther" target="IvBase" 
deleteRule="Nullify" db-relationship-path="base"/>
+       <obj-relationship name="impl" source="IvOther" target="IvImpl" 
deleteRule="Nullify" db-relationship-path="impl.base"/>
        <obj-relationship name="impls" source="IvOther" target="IvImpl" 
deleteRule="Deny" db-relationship-path="impls.base"/>
        <obj-relationship name="implsWithInverse" source="IvOther" 
target="IvImpl" deleteRule="Deny" db-relationship-path="implsWithInverse.base"/>
        <obj-relationship name="implsWithLock" source="IvOther" 
target="IvImplWithLock" deleteRule="Deny" db-relationship-path="impls.base"/>

Reply via email to