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

commit e9df41a835becf2d5d3f66267e2b2ad58007529f
Author: Nikita Timofeev <stari...@gmail.com>
AuthorDate: Mon May 27 14:35:12 2024 +0400

    CAY-2848 Vertical Inheritance: Updating one-to-many with inverse nullifies 
other columns
---
 .../access/flush/ArcValuesCreationHandler.java     | 56 ++++++++++++++++++----
 .../access/flush/ValuesCreationHandler.java        |  6 ++-
 .../cayenne/access/VerticalInheritanceIT.java      | 35 ++++++++++++++
 .../testdo/inheritance_vertical/auto/_IvImpl.java  | 17 +++++++
 .../testdo/inheritance_vertical/auto/_IvOther.java | 22 +++++++++
 .../test/resources/inheritance-vertical.map.xml    | 11 ++++-
 6 files changed, 137 insertions(+), 10 deletions(-)

diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/flush/ArcValuesCreationHandler.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/flush/ArcValuesCreationHandler.java
index 881e0de11..8e6d1e500 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/flush/ArcValuesCreationHandler.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/flush/ArcValuesCreationHandler.java
@@ -89,23 +89,29 @@ class ArcValuesCreationHandler implements 
GraphChangeHandler {
         }
 
         if(objRelationship.isFlattened()) {
-            processFlattenedPath(arcTarget.getSourceId(), 
arcTarget.getTargetId(), entity.getDbEntity(),
+            FlattenedPathProcessingResult result = 
processFlattenedPath(arcTarget.getSourceId(), arcTarget.getTargetId(), 
entity.getDbEntity(),
                     objRelationship.getDbRelationshipPath(), created);
+            if(result.isProcessed()) {
+                factory.getProcessedArcs().add(arcTarget);
+            }
         } else {
             DbRelationship dbRelationship = 
objRelationship.getDbRelationships().get(0);
             processRelationship(dbRelationship, arcTarget.getSourceId(), 
arcTarget.getTargetId(), created);
+            factory.getProcessedArcs().add(arcTarget);
         }
-
-        factory.getProcessedArcs().add(arcTarget);
     }
 
-    ObjectId processFlattenedPath(ObjectId id, ObjectId finalTargetId, 
DbEntity entity, CayennePath dbPath, boolean add) {
-        Iterator<CayenneMapEntry> dbPathIterator = 
entity.resolvePathComponents(dbPath);
+    FlattenedPathProcessingResult processFlattenedPath(ObjectId id, ObjectId 
finalTargetId, DbEntity entity, CayennePath dbPath, boolean add) {
+        if(shouldSkipFlattenedOp(id, finalTargetId)) {
+            return flattenedResultNotProcessed();
+        }
+
         CayennePath flattenedPath = CayennePath.EMPTY_PATH;
 
         ObjectId srcId = id;
         ObjectId targetId = null;
 
+        Iterator<CayenneMapEntry> dbPathIterator = 
entity.resolvePathComponents(dbPath);
         while(dbPathIterator.hasNext()) {
             CayenneMapEntry entry = dbPathIterator.next();
             flattenedPath = flattenedPath.dot(entry.getName());
@@ -139,8 +145,8 @@ class ArcValuesCreationHandler implements 
GraphChangeHandler {
                     } else {
                         type = add ? DbRowOpType.INSERT : DbRowOpType.UPDATE;
                         factory.<DbRowOpWithValues>getOrCreate(target, 
targetId, type)
-                            .getValues()
-                            .addFlattenedId(flattenedPath, targetId);
+                                .getValues()
+                                .addFlattenedId(flattenedPath, targetId);
                     }
                 } else if(dbPathIterator.hasNext()) {
                     // should update existing DB row
@@ -151,7 +157,15 @@ class ArcValuesCreationHandler implements 
GraphChangeHandler {
             }
         }
 
-        return targetId;
+        return flattenedResultId(targetId);
+    }
+
+    private boolean shouldSkipFlattenedOp(ObjectId id, ObjectId finalTargetId) 
{
+        // as we get two sides of the relationship processed,
+        // check if we got more information for a reverse operation
+        return finalTargetId != null
+                && factory.getStore().getFlattenedIds(id).isEmpty()
+                && 
!factory.getStore().getFlattenedIds(finalTargetId).isEmpty();
     }
 
     private boolean shouldProcessAsAddition(DbRelationship relationship, 
boolean add) {
@@ -281,4 +295,30 @@ class ArcValuesCreationHandler implements 
GraphChangeHandler {
             return null;
         }
     }
+
+    static FlattenedPathProcessingResult flattenedResultId(ObjectId id) {
+        return new FlattenedPathProcessingResult(true, id);
+    }
+
+    static FlattenedPathProcessingResult flattenedResultNotProcessed() {
+        return new FlattenedPathProcessingResult(false, null);
+    }
+
+    final static class FlattenedPathProcessingResult {
+        private final boolean processed;
+        private final ObjectId id;
+
+        private FlattenedPathProcessingResult(boolean processed, ObjectId id) {
+            this.processed = processed;
+            this.id = id;
+        }
+
+        public boolean isProcessed() {
+            return processed;
+        }
+
+        public ObjectId getId() {
+            return id;
+        }
+    }
 }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/flush/ValuesCreationHandler.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/flush/ValuesCreationHandler.java
index defdb1a3a..10a6d309a 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/flush/ValuesCreationHandler.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/flush/ValuesCreationHandler.java
@@ -52,7 +52,11 @@ class ValuesCreationHandler extends ArcValuesCreationHandler 
{
 
         if(attribute.isFlattened()) {
             // get target row ID
-            id = processFlattenedPath(id, null, dbEntity, 
attribute.getDbAttributePath(), newValue != null);
+            FlattenedPathProcessingResult result
+                    = processFlattenedPath(id, null, dbEntity, 
attribute.getDbAttributePath(), newValue != null);
+            if(result.isProcessed()) {
+                id = result.getId();
+            }
         }
 
         if(id == null) {
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 32a7fed05..1fd162bbb 100644
--- a/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/access/VerticalInheritanceIT.java
@@ -688,6 +688,41 @@ public class VerticalInheritanceIT extends RuntimeCase {
                }
        }
 
+       @Test
+       public void testUpdateFlattenedRelationshipWithInverse() throws 
SQLException {
+               TableHelper ivOtherTable = new TableHelper(dbHelper, 
"IV_OTHER");
+               ivOtherTable.setColumns("ID", 
"NAME").setColumnTypes(Types.INTEGER, Types.VARCHAR);
+
+               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", 
"OTHER3_ID").setColumnTypes(Types.INTEGER, Types.VARCHAR, Types.INTEGER);
+
+               ivOtherTable.insert(1, "other1");
+               ivOtherTable.insert(2, "other2");
+               ivBaseTable.insert(1, "Impl 1", "I");
+               ivImplTable.insert(1, "attr1", 1);
+
+               IvImpl impl = SelectById.query(IvImpl.class, 
1).selectOne(context);
+               IvOther other = SelectById.query(IvOther.class, 
2).selectOne(context);
+
+               impl.setOther3(other);
+               context.commitChanges();
+               assertEquals("Impl 1", impl.getName());
+               assertEquals("attr1", impl.getAttr1());
+               assertEquals(impl.getOther3(), other);
+
+               {
+                       ObjectContext cleanContext = runtime.newContext();
+                       IvImpl implFetched = SelectById.query(IvImpl.class, 
1).selectOne(cleanContext);
+                       IvOther otherFetched = SelectById.query(IvOther.class, 
2).selectOne(cleanContext);
+                       assertEquals("Impl 1", implFetched.getName());
+                       assertEquals("attr1", implFetched.getAttr1());
+                       assertEquals(implFetched.getOther3(), otherFetched);
+               }
+       }
+
        @Test
        public void testDeleteFlattenedNoValues() throws SQLException {
                ivAbstractTable.insert(1, null, "S");
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 a3214b2d6..f7b998530 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
@@ -35,6 +35,7 @@ public abstract class _IvImpl extends IvBase {
     public static final StringProperty<String> ATTR2 = 
PropertyFactory.createString("attr2", String.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);
 
     protected Date attr0;
     protected String attr1;
@@ -42,6 +43,7 @@ public abstract class _IvImpl extends IvBase {
 
     protected Object other1;
     protected Object other2;
+    protected Object other3;
 
     public void setAttr0(Date attr0) {
         beforePropertyWrite("attr0", this.attr0, attr0);
@@ -89,6 +91,14 @@ public abstract class _IvImpl extends IvBase {
         return (IvOther)readProperty("other2");
     }
 
+    public void setOther3(IvOther other3) {
+        setToOneTarget("other3", other3, true);
+    }
+
+    public IvOther getOther3() {
+        return (IvOther)readProperty("other3");
+    }
+
     @Override
     public Object readPropertyDirectly(String propName) {
         if(propName == null) {
@@ -106,6 +116,8 @@ public abstract class _IvImpl extends IvBase {
                 return this.other1;
             case "other2":
                 return this.other2;
+            case "other3":
+                return this.other3;
             default:
                 return super.readPropertyDirectly(propName);
         }
@@ -133,6 +145,9 @@ public abstract class _IvImpl extends IvBase {
             case "other2":
                 this.other2 = val;
                 break;
+            case "other3":
+                this.other3 = val;
+                break;
             default:
                 super.writePropertyDirectly(propName, val);
         }
@@ -154,6 +169,7 @@ public abstract class _IvImpl extends IvBase {
         out.writeObject(this.attr2);
         out.writeObject(this.other1);
         out.writeObject(this.other2);
+        out.writeObject(this.other3);
     }
 
     @Override
@@ -164,6 +180,7 @@ public abstract class _IvImpl extends IvBase {
         this.attr2 = (String)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 63ee46bee..fc18cbc55 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
@@ -35,12 +35,14 @@ 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 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);
 
     protected String name;
 
     protected Object base;
     protected Object impls;
+    protected Object implsWithInverse;
     protected Object implsWithLock;
 
     public void setName(String name) {
@@ -74,6 +76,19 @@ public abstract class _IvOther extends PersistentObject {
         return (List<IvImpl>)readProperty("impls");
     }
 
+    public void addToImplsWithInverse(IvImpl obj) {
+        addToManyTarget("implsWithInverse", obj, true);
+    }
+
+    public void removeFromImplsWithInverse(IvImpl obj) {
+        removeToManyTarget("implsWithInverse", obj, true);
+    }
+
+    @SuppressWarnings("unchecked")
+    public List<IvImpl> getImplsWithInverse() {
+        return (List<IvImpl>)readProperty("implsWithInverse");
+    }
+
     public void addToImplsWithLock(IvImplWithLock obj) {
         addToManyTarget("implsWithLock", obj, true);
     }
@@ -100,6 +115,8 @@ public abstract class _IvOther extends PersistentObject {
                 return this.base;
             case "impls":
                 return this.impls;
+            case "implsWithInverse":
+                return this.implsWithInverse;
             case "implsWithLock":
                 return this.implsWithLock;
             default:
@@ -123,6 +140,9 @@ public abstract class _IvOther extends PersistentObject {
             case "impls":
                 this.impls = val;
                 break;
+            case "implsWithInverse":
+                this.implsWithInverse = val;
+                break;
             case "implsWithLock":
                 this.implsWithLock = val;
                 break;
@@ -145,6 +165,7 @@ public abstract class _IvOther extends PersistentObject {
         out.writeObject(this.name);
         out.writeObject(this.base);
         out.writeObject(this.impls);
+        out.writeObject(this.implsWithInverse);
         out.writeObject(this.implsWithLock);
     }
 
@@ -154,6 +175,7 @@ public abstract class _IvOther extends PersistentObject {
         this.name = (String)in.readObject();
         this.base = 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 ff892937b..368ef9ee8 100644
--- a/cayenne/src/test/resources/inheritance-vertical.map.xml
+++ b/cayenne/src/test/resources/inheritance-vertical.map.xml
@@ -60,6 +60,7 @@
                <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" 
isMandatory="true"/>
                <db-attribute name="OTHER1_ID" type="INTEGER"/>
                <db-attribute name="OTHER2_ID" type="INTEGER"/>
+               <db-attribute name="OTHER3_ID" type="INTEGER"/>
        </db-entity>
        <db-entity name="IV_IMPL_WITH_LOCK">
                <db-attribute name="ATTR1" type="VARCHAR" isMandatory="true" 
length="100"/>
@@ -234,6 +235,9 @@
        <db-relationship name="other2" source="IV_IMPL" target="IV_OTHER">
                <db-attribute-pair source="OTHER2_ID" target="ID"/>
        </db-relationship>
+       <db-relationship name="other3" source="IV_IMPL" target="IV_OTHER">
+               <db-attribute-pair source="OTHER3_ID" target="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>
@@ -246,6 +250,9 @@
        <db-relationship name="impls" source="IV_OTHER" target="IV_IMPL" 
toMany="true">
                <db-attribute-pair source="ID" target="OTHER_ID"/>
        </db-relationship>
+       <db-relationship name="implsWithInverse" source="IV_OTHER" 
target="IV_IMPL" toMany="true">
+               <db-attribute-pair source="ID" target="OTHER3_ID"/>
+       </db-relationship>
        <db-relationship name="implsWithLock" source="IV_OTHER" 
target="IV_IMPL_WITH_LOCK" toMany="true">
                <db-attribute-pair source="ID" target="OTHER_ID"/>
        </db-relationship>
@@ -280,13 +287,15 @@
        <obj-relationship name="relatedConcrete" source="IvAbstract" 
target="IvConcrete" deleteRule="Nullify" 
db-relationship-path="relatedConcrete.abstract"/>
        <obj-relationship name="others" source="IvBase" target="IvOther" 
deleteRule="Deny" db-relationship-path="others"/>
        <obj-relationship name="children" source="IvConcrete" 
target="IvConcrete" deleteRule="Deny" db-relationship-path="children"/>
-       <obj-relationship name="relatedAbstract" source="IvConcrete" 
target="IvAbstract" deleteRule="Nullify" 
db-relationship-path="concrete.relatedAbstract"/>
        <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="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="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"/>
        <obj-relationship name="ivRoot" source="IvSub3" target="IvRoot" 
deleteRule="Nullify" db-relationship-path="sub3.ivRoot1"/>
 </data-map>

Reply via email to