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 1ce7e8b7f CAY-2866 DefaultDataDomainFlushAction breaks on circular 
relationship update
1ce7e8b7f is described below

commit 1ce7e8b7fd72b0a54ac8286fa2a6ad87a99b1410
Author: Nikita Timofeev <stari...@gmail.com>
AuthorDate: Tue Aug 13 15:04:33 2024 +0400

    CAY-2866 DefaultDataDomainFlushAction breaks on circular relationship update
---
 RELEASE-NOTES.txt                                  |  1 +
 .../org/apache/cayenne/access/DataRowStore.java    |  2 +-
 .../access/flush/DefaultDataDomainFlushAction.java | 48 ++++++++++++++++++++--
 .../org/apache/cayenne/CircularDependencyIT.java   | 31 ++++++++++++++
 4 files changed, 77 insertions(+), 5 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index ce24b821f..b7f985f28 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -124,3 +124,4 @@ CAY-2850 Query using Clob comparison with empty String fails
 CAY-2851 Replace Existing OneToOne From New Object
 CAY-2853 Incorrect deletion of entities from flattened attributes
 CAY-2854 Improve delete prevention detection of flattened attribute row
+CAY-2866 DefaultDataDomainFlushAction breaks on circular relationship update
diff --git a/cayenne/src/main/java/org/apache/cayenne/access/DataRowStore.java 
b/cayenne/src/main/java/org/apache/cayenne/access/DataRowStore.java
index a8cfbcb3f..a7545ba2b 100644
--- a/cayenne/src/main/java/org/apache/cayenne/access/DataRowStore.java
+++ b/cayenne/src/main/java/org/apache/cayenne/access/DataRowStore.java
@@ -334,7 +334,7 @@ public class DataRowStore implements Serializable {
         if (deletedSnapshotIds.isEmpty()
                 && invalidatedSnapshotIds.isEmpty()
                 && updatedSnapshots.isEmpty()
-                && indirectlyModifiedIds.isEmpty()) {
+                && (indirectlyModifiedIds == null || 
indirectlyModifiedIds.isEmpty())) {
             logger.warn("postSnapshotsChangeEvent.. bogus call... no 
changes.");
             return;
         }
diff --git 
a/cayenne/src/main/java/org/apache/cayenne/access/flush/DefaultDataDomainFlushAction.java
 
b/cayenne/src/main/java/org/apache/cayenne/access/flush/DefaultDataDomainFlushAction.java
index 3d99e797f..d67ed20be 100644
--- 
a/cayenne/src/main/java/org/apache/cayenne/access/flush/DefaultDataDomainFlushAction.java
+++ 
b/cayenne/src/main/java/org/apache/cayenne/access/flush/DefaultDataDomainFlushAction.java
@@ -41,6 +41,8 @@ import 
org.apache.cayenne.access.flush.operation.DbRowOpMerger;
 import org.apache.cayenne.access.flush.operation.DbRowOpSorter;
 import org.apache.cayenne.access.flush.operation.DbRowOp;
 import org.apache.cayenne.access.flush.operation.DbRowOpVisitor;
+import org.apache.cayenne.access.flush.operation.DeleteDbRowOp;
+import org.apache.cayenne.access.flush.operation.InsertDbRowOp;
 import org.apache.cayenne.access.flush.operation.OpIdFactory;
 import org.apache.cayenne.access.flush.operation.UpdateDbRowOp;
 import org.apache.cayenne.graph.CompoundDiff;
@@ -89,7 +91,9 @@ public class DefaultDataDomainFlushAction implements 
DataDomainFlushAction {
         List<? extends Query> queries = createQueries(sortedOps);
         executeQueries(queries);
         createReplacementIds(objectStore, afterCommitDiff, sortedOps);
-        postprocess(context, objectStoreGraphDiff, afterCommitDiff, sortedOps);
+        // note: we are using here not filtered operations, but the original 
ones,
+        // as we need them all for the postprocessing
+        postprocess(context, objectStoreGraphDiff, afterCommitDiff, 
deduplicatedOps);
 
         return afterCommitDiff;
     }
@@ -139,8 +143,25 @@ public class DefaultDataDomainFlushAction implements 
DataDomainFlushAction {
     }
 
     protected List<DbRowOp> filterOps(List<DbRowOp> dbRowOps) {
-        // clear phantom update (this can be from insert/delete of arc with 
transient object)
-        dbRowOps.forEach(row -> row.accept(PhantomDbRowOpCleaner.INSTANCE));
+        List<DbRowOp> opsToRemove = null;
+        for (DbRowOp row : dbRowOps) {
+            // clear phantom update (this can be from insert/delete of arc 
with transient object)
+            row.accept(PhantomDbRowOpCleaner.INSTANCE);
+            // check for an empty update that we may need to filter out
+            if (row.accept(EmptyRowChecker.INSTANCE)) {
+                if (opsToRemove == null) {
+                    opsToRemove = new ArrayList<>();
+                }
+                opsToRemove.add(row);
+            }
+        }
+
+        if(opsToRemove != null && !opsToRemove.isEmpty()) {
+            // make a duplicate, to keep original operations intact
+            dbRowOps = new ArrayList<>(dbRowOps);
+            dbRowOps.removeAll(opsToRemove);
+        }
+
         return dbRowOps;
     }
 
@@ -224,11 +245,30 @@ public class DefaultDataDomainFlushAction implements 
DataDomainFlushAction {
 
         @Override
         public Void visitUpdate(UpdateDbRowOp dbRow) {
-            //
             if(dbRow.getChangeId().isTemporary() && 
!dbRow.getChangeId().isReplacementIdAttached()) {
                 dbRow.getValues().clear();
             }
             return null;
         }
     }
+
+    protected static class EmptyRowChecker implements DbRowOpVisitor<Boolean> {
+
+        protected static final EmptyRowChecker INSTANCE = new 
EmptyRowChecker();
+
+        @Override
+        public Boolean visitDelete(DeleteDbRowOp dbRow) {
+            return false;
+        }
+
+        @Override
+        public Boolean visitInsert(InsertDbRowOp dbRow) {
+            return false;
+        }
+
+        @Override
+        public Boolean visitUpdate(UpdateDbRowOp dbRow) {
+            return dbRow.getValues().isEmpty();
+        }
+    }
 }
diff --git a/cayenne/src/test/java/org/apache/cayenne/CircularDependencyIT.java 
b/cayenne/src/test/java/org/apache/cayenne/CircularDependencyIT.java
index f4ae2c5b8..f65cf7e4e 100644
--- a/cayenne/src/test/java/org/apache/cayenne/CircularDependencyIT.java
+++ b/cayenne/src/test/java/org/apache/cayenne/CircularDependencyIT.java
@@ -22,6 +22,7 @@ package org.apache.cayenne;
 import org.apache.cayenne.di.Inject;
 import org.apache.cayenne.testdo.relationships.E1;
 import org.apache.cayenne.testdo.relationships.E2;
+import org.apache.cayenne.testdo.relationships.ReflexiveAndToOne;
 import org.apache.cayenne.unit.OracleUnitDbAdapter;
 import org.apache.cayenne.unit.UnitDbAdapter;
 import org.apache.cayenne.unit.di.runtime.CayenneProjects;
@@ -67,4 +68,34 @@ public class CircularDependencyIT extends RuntimeCase {
         }
 
     }
+
+    @Test
+    public void testUpdate() {
+        E1 e1 = context.newObject(E1.class);
+        E2 e2 = context.newObject(E2.class);
+
+        e1.setText("e1 #" + 1);
+        e2.setText("e2 #" + 2);
+
+        e1.setE2(e2);
+        context.commitChanges();
+
+        e2.setE1(e1);
+        context.commitChanges();
+    }
+
+    @Test
+    public void testUpdateSelfRelationship() {
+        ReflexiveAndToOne e1 = context.newObject(ReflexiveAndToOne.class);
+        ReflexiveAndToOne e2 = context.newObject(ReflexiveAndToOne.class);
+
+        e1.setName("e1 #" + 1);
+        e2.setName("e2 #" + 2);
+
+        e1.setToParent(e2);
+        context.commitChanges();
+
+        e2.setToParent(e1);
+        context.commitChanges();
+    }
 }

Reply via email to