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 <[email protected]>
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();
+ }
}