This is an automated email from the ASF dual-hosted git repository.
aadamchik pushed a commit to branch STABLE-4.1
in repository https://gitbox.apache.org/repos/asf/cayenne.git
The following commit(s) were added to refs/heads/STABLE-4.1 by this push:
new 1eb5e89 CommitLog does not include FKs for deleted objects CAY-2670
1eb5e89 is described below
commit 1eb5e8927d0b21819ccb2b4fa2ab5a983af2d508
Author: Andrus Adamchik <[email protected]>
AuthorDate: Thu Aug 6 14:26:51 2020 +0300
CommitLog does not include FKs for deleted objects CAY-2670
... reproduced and fixed
---
.../cayenne/commitlog/DeletedDiffProcessor.java | 223 +++++++++++----------
.../cayenne/commitlog/CommitLogFilterIT.java | 32 +++
.../cayenne/commitlog/db/AuditableChild1x.java | 9 +
.../commitlog/db/auto/_AuditableChild1x.java | 104 ++++++++++
.../commitlog/unit/AuditableServerCase.java | 4 +
.../src/test/resources/cayenne-lifecycle.xml | 2 +
.../src/test/resources/lifecycle-map.map.xml | 17 +-
7 files changed, 284 insertions(+), 107 deletions(-)
diff --git
a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DeletedDiffProcessor.java
b/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DeletedDiffProcessor.java
index 976ca85..9257281 100644
---
a/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DeletedDiffProcessor.java
+++
b/cayenne-commitlog/src/main/java/org/apache/cayenne/commitlog/DeletedDiffProcessor.java
@@ -18,123 +18,134 @@
****************************************************************/
package org.apache.cayenne.commitlog;
-import java.util.List;
-
import org.apache.cayenne.DataChannel;
import org.apache.cayenne.DataRow;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.QueryResponse;
-import org.apache.cayenne.graph.GraphChangeHandler;
+import org.apache.cayenne.commitlog.meta.CommitLogEntity;
+import org.apache.cayenne.commitlog.meta.CommitLogEntityFactory;
import org.apache.cayenne.commitlog.model.MutableChangeMap;
import org.apache.cayenne.commitlog.model.MutableObjectChange;
import org.apache.cayenne.commitlog.model.ObjectChangeType;
-import org.apache.cayenne.commitlog.meta.CommitLogEntity;
-import org.apache.cayenne.commitlog.meta.CommitLogEntityFactory;
+import org.apache.cayenne.graph.GraphChangeHandler;
+import org.apache.cayenne.map.DbRelationship;
import org.apache.cayenne.query.ObjectIdQuery;
-import org.apache.cayenne.reflect.AttributeProperty;
-import org.apache.cayenne.reflect.ClassDescriptor;
-import org.apache.cayenne.reflect.PropertyVisitor;
-import org.apache.cayenne.reflect.ToManyProperty;
-import org.apache.cayenne.reflect.ToOneProperty;
+import org.apache.cayenne.reflect.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.util.List;
+
class DeletedDiffProcessor implements GraphChangeHandler {
- private static final Logger LOGGER =
LoggerFactory.getLogger(DeletedDiffProcessor.class);
-
- private CommitLogEntityFactory entityFactory;
- private MutableChangeMap changeSet;
- private DataChannel channel;
-
- DeletedDiffProcessor(MutableChangeMap changeSet, DataChannel channel,
CommitLogEntityFactory entityFactory) {
- this.changeSet = changeSet;
- this.channel = channel;
- this.entityFactory = entityFactory;
- }
-
- @Override
- public void nodeRemoved(Object nodeId) {
- ObjectId id = (ObjectId) nodeId;
-
- final MutableObjectChange objectChangeSet =
changeSet.getOrCreate(id, ObjectChangeType.DELETE);
-
- // TODO: rewrite with SelectById query after Cayenne upgrade
- ObjectIdQuery query = new ObjectIdQuery(id, true,
ObjectIdQuery.CACHE);
- QueryResponse result = channel.onQuery(null, query);
-
- @SuppressWarnings("unchecked")
- List<DataRow> rows = result.firstList();
-
- if (rows.isEmpty()) {
- LOGGER.warn("No DB snapshot for object to be deleted,
no changes will be recorded. ID: " + id);
- return;
- }
-
- final DataRow row = rows.get(0);
-
- ClassDescriptor descriptor =
channel.getEntityResolver().getClassDescriptor(id.getEntityName());
- final CommitLogEntity entity = entityFactory.getEntity(id);
-
- descriptor.visitProperties(new PropertyVisitor() {
-
- @Override
- public boolean visitAttribute(AttributeProperty
property) {
-
- if (!entity.isIncluded(property.getName())) {
- return true;
- }
-
- Object value;
- if (entity.isConfidential(property.getName())) {
- value = Confidential.getInstance();
- } else {
- String key =
property.getAttribute().getDbAttributeName();
- value = row.get(key);
- }
-
- if (value != null) {
-
objectChangeSet.attributeChanged(property.getName(), value, null);
- }
- return true;
- }
-
- @Override
- public boolean visitToOne(ToOneProperty property) {
- // TODO record FK changes?
- return true;
- }
-
- @Override
- public boolean visitToMany(ToManyProperty property) {
- return true;
- }
-
- });
- }
-
- @Override
- public void nodeIdChanged(Object nodeId, Object newId) {
- // do nothing
- }
-
- @Override
- public void nodeCreated(Object nodeId) {
- // do nothing
- }
-
- @Override
- public void nodePropertyChanged(Object nodeId, String property, Object
oldValue, Object newValue) {
- // do nothing
- }
-
- @Override
- public void arcCreated(Object nodeId, Object targetNodeId, Object
arcId) {
- // do nothing
- }
-
- @Override
- public void arcDeleted(Object nodeId, Object targetNodeId, Object
arcId) {
- // do nothing
- }
+ private static final Logger LOGGER =
LoggerFactory.getLogger(DeletedDiffProcessor.class);
+
+ private CommitLogEntityFactory entityFactory;
+ private MutableChangeMap changeSet;
+ private DataChannel channel;
+
+ DeletedDiffProcessor(MutableChangeMap changeSet, DataChannel channel,
CommitLogEntityFactory entityFactory) {
+ this.changeSet = changeSet;
+ this.channel = channel;
+ this.entityFactory = entityFactory;
+ }
+
+ @Override
+ public void nodeRemoved(Object nodeId) {
+ ObjectId id = (ObjectId) nodeId;
+
+ final MutableObjectChange objectChangeSet = changeSet.getOrCreate(id,
ObjectChangeType.DELETE);
+
+ // TODO: rewrite with SelectById query after Cayenne upgrade
+ ObjectIdQuery query = new ObjectIdQuery(id, true, ObjectIdQuery.CACHE);
+ QueryResponse result = channel.onQuery(null, query);
+
+ @SuppressWarnings("unchecked")
+ List<DataRow> rows = result.firstList();
+
+ if (rows.isEmpty()) {
+ LOGGER.warn("No DB snapshot for object to be deleted, no changes
will be recorded. ID: " + id);
+ return;
+ }
+
+ final DataRow row = rows.get(0);
+
+ ClassDescriptor descriptor =
channel.getEntityResolver().getClassDescriptor(id.getEntityName());
+ final CommitLogEntity entity = entityFactory.getEntity(id);
+
+ descriptor.visitProperties(new PropertyVisitor() {
+
+ @Override
+ public boolean visitAttribute(AttributeProperty property) {
+
+ if (!entity.isIncluded(property.getName())) {
+ return true;
+ }
+
+ Object value;
+ if (entity.isConfidential(property.getName())) {
+ value = Confidential.getInstance();
+ } else {
+ String key = property.getAttribute().getDbAttributeName();
+ value = row.get(key);
+ }
+
+ if (value != null) {
+ objectChangeSet.attributeChanged(property.getName(),
value, null);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visitToOne(ToOneProperty property) {
+ if (!entity.isIncluded(property.getName())) {
+ return true;
+ }
+
+ // TODO: is there such a thing as "confidential" relationship
that we need to hide?
+
+ DbRelationship dbRelationship =
property.getRelationship().getDbRelationships().get(0);
+
+ ObjectId value = row.createTargetObjectId(
+ property.getTargetDescriptor().getEntity().getName(),
+ dbRelationship);
+
+ if (value != null) {
+
objectChangeSet.toOneRelationshipDisconnected(property.getName(), value);
+ }
+ return true;
+ }
+
+ @Override
+ public boolean visitToMany(ToManyProperty property) {
+ return true;
+ }
+
+ });
+ }
+
+ @Override
+ public void nodeIdChanged(Object nodeId, Object newId) {
+ // do nothing
+ }
+
+ @Override
+ public void nodeCreated(Object nodeId) {
+ // do nothing
+ }
+
+ @Override
+ public void nodePropertyChanged(Object nodeId, String property, Object
oldValue, Object newValue) {
+ // do nothing
+ }
+
+ @Override
+ public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
+ // do nothing
+ }
+
+ @Override
+ public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
+ // do nothing
+ }
}
diff --git
a/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/CommitLogFilterIT.java
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/CommitLogFilterIT.java
index 78fba95..3a41c07 100644
---
a/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/CommitLogFilterIT.java
+++
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/CommitLogFilterIT.java
@@ -22,6 +22,7 @@ import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.commitlog.db.Auditable1;
import org.apache.cayenne.commitlog.db.AuditableChild1;
+import org.apache.cayenne.commitlog.db.AuditableChild1x;
import org.apache.cayenne.commitlog.model.*;
import org.apache.cayenne.commitlog.unit.AuditableServerCase;
import org.apache.cayenne.configuration.server.ServerRuntimeBuilder;
@@ -186,6 +187,37 @@ public class CommitLogFilterIT extends AuditableServerCase
{
}
@Test
+ public void testPostCommit_Delete_ToOne_OneWay() throws SQLException {
+ auditable1.insert(1, "xx");
+ auditableChild1x.insert(1, 1, "cc1");
+ auditableChild1x.insert(2, 1, "cc2");
+
+ AuditableChild1x ac1 = SelectById.query(AuditableChild1x.class,
2).selectOne(context);
+ context.deleteObject(ac1);
+ context.commitChanges();
+
+ ArgumentCaptor<ChangeMap> changeMap =
ArgumentCaptor.forClass(ChangeMap.class);
+ verify(mockListener).onPostCommit(any(ObjectContext.class),
changeMap.capture());
+
+ assertNotNull(changeMap.getValue());
+ assertEquals(1, changeMap.getValue().getUniqueChanges().size());
+
+ ObjectChange change = changeMap.getValue().getChanges().get(new
ObjectId("AuditableChild1x", AuditableChild1x.ID_PK_COLUMN, 2));
+ assertNotNull(change);
+ assertEquals(ObjectChangeType.DELETE, change.getType());
+
+ assertEquals(1, change.getAttributeChanges().size());
+ assertEquals("cc2",
change.getAttributeChanges().get(AuditableChild1x.CHAR_PROPERTY1.getName()).getOldValue());
+
assertNull(change.getAttributeChanges().get(AuditableChild1x.CHAR_PROPERTY1.getName()).getNewValue());
+
+ assertTrue("No 1..N relationships in the entity",
change.getToManyRelationshipChanges().isEmpty());
+ assertEquals("N..1 state was not captured", 1,
change.getToOneRelationshipChanges().size());
+ assertEquals(new ObjectId("Auditable1", Auditable1.ID_PK_COLUMN, 1),
+
change.getToOneRelationshipChanges().get(AuditableChild1x.PARENT.getName()).getOldValue());
+ }
+
+
+ @Test
public void testPostCommit_UpdateToOne() throws SQLException {
auditable1.insert(1, "xx");
auditable1.insert(2, "yy");
diff --git
a/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild1x.java
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild1x.java
new file mode 100644
index 0000000..7d8c4a6
--- /dev/null
+++
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/db/AuditableChild1x.java
@@ -0,0 +1,9 @@
+package org.apache.cayenne.commitlog.db;
+
+import org.apache.cayenne.commitlog.db.auto._AuditableChild1x;
+
+public class AuditableChild1x extends _AuditableChild1x {
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git
a/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild1x.java
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild1x.java
new file mode 100644
index 0000000..5203591
--- /dev/null
+++
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/db/auto/_AuditableChild1x.java
@@ -0,0 +1,104 @@
+package org.apache.cayenne.commitlog.db.auto;
+
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
+import org.apache.cayenne.commitlog.db.Auditable1;
+import org.apache.cayenne.exp.Property;
+
+/**
+ * Class _AuditableChild1x was generated by Cayenne.
+ * It is probably a good idea to avoid changing this class manually,
+ * since it may be overwritten next time code is regenerated.
+ * If you need to make any customizations, please use subclass.
+ */
+public abstract class _AuditableChild1x extends BaseDataObject {
+
+ private static final long serialVersionUID = 1L;
+
+ public static final String ID_PK_COLUMN = "ID";
+
+ public static final Property<String> CHAR_PROPERTY1 =
Property.create("charProperty1", String.class);
+ public static final Property<Auditable1> PARENT =
Property.create("parent", Auditable1.class);
+
+ protected String charProperty1;
+
+ protected Object parent;
+
+ public void setCharProperty1(String charProperty1) {
+ beforePropertyWrite("charProperty1", this.charProperty1,
charProperty1);
+ this.charProperty1 = charProperty1;
+ }
+
+ public String getCharProperty1() {
+ beforePropertyRead("charProperty1");
+ return this.charProperty1;
+ }
+
+ public void setParent(Auditable1 parent) {
+ setToOneTarget("parent", parent, true);
+ }
+
+ public Auditable1 getParent() {
+ return (Auditable1)readProperty("parent");
+ }
+
+ @Override
+ public Object readPropertyDirectly(String propName) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch(propName) {
+ case "charProperty1":
+ return this.charProperty1;
+ case "parent":
+ return this.parent;
+ default:
+ return super.readPropertyDirectly(propName);
+ }
+ }
+
+ @Override
+ public void writePropertyDirectly(String propName, Object val) {
+ if(propName == null) {
+ throw new IllegalArgumentException();
+ }
+
+ switch (propName) {
+ case "charProperty1":
+ this.charProperty1 = (String)val;
+ break;
+ case "parent":
+ this.parent = val;
+ break;
+ default:
+ super.writePropertyDirectly(propName, val);
+ }
+ }
+
+ private void writeObject(ObjectOutputStream out) throws IOException {
+ writeSerialized(out);
+ }
+
+ private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
+ readSerialized(in);
+ }
+
+ @Override
+ protected void writeState(ObjectOutputStream out) throws IOException {
+ super.writeState(out);
+ out.writeObject(this.charProperty1);
+ out.writeObject(this.parent);
+ }
+
+ @Override
+ protected void readState(ObjectInputStream in) throws IOException,
ClassNotFoundException {
+ super.readState(in);
+ this.charProperty1 = (String)in.readObject();
+ this.parent = in.readObject();
+ }
+
+}
diff --git
a/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/unit/AuditableServerCase.java
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/unit/AuditableServerCase.java
index 570a54f..67ee30d 100644
---
a/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/unit/AuditableServerCase.java
+++
b/cayenne-commitlog/src/test/java/org/apache/cayenne/commitlog/unit/AuditableServerCase.java
@@ -34,6 +34,7 @@ public abstract class AuditableServerCase {
protected TableHelper auditable1;
protected TableHelper auditableChild1;
+ protected TableHelper auditableChild1x;
protected TableHelper auditable2;
protected TableHelper auditableChild3;
@@ -55,6 +56,8 @@ public abstract class AuditableServerCase {
this.auditableChild1 = new TableHelper(dbHelper,
"AUDITABLE_CHILD1").setColumns("ID", "AUDITABLE1_ID",
"CHAR_PROPERTY1");
+ this.auditableChild1x = new TableHelper(dbHelper,
"AUDITABLE_CHILD1X").setColumns("ID", "AUDITABLE1_ID",
+ "CHAR_PROPERTY1");
this.auditable2 = new TableHelper(dbHelper,
"AUDITABLE2").setColumns("ID", "CHAR_PROPERTY1", "CHAR_PROPERTY2");
@@ -66,6 +69,7 @@ public abstract class AuditableServerCase {
"AUDITABLE3_ID");
this.auditableChild1.deleteAll();
+ this.auditableChild1x.deleteAll();
this.auditable1.deleteAll();
this.auditableChild3.deleteAll();
this.auditable2.deleteAll();
diff --git a/cayenne-commitlog/src/test/resources/cayenne-lifecycle.xml
b/cayenne-commitlog/src/test/resources/cayenne-lifecycle.xml
index 4144737..0858b03 100644
--- a/cayenne-commitlog/src/test/resources/cayenne-lifecycle.xml
+++ b/cayenne-commitlog/src/test/resources/cayenne-lifecycle.xml
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<domain xmlns="http://cayenne.apache.org/schema/10/domain"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://cayenne.apache.org/schema/10/domain
https://cayenne.apache.org/schema/10/domain.xsd"
project-version="10">
<map name="lifecycle-map"/>
<node name="lifecycle-db"
diff --git a/cayenne-commitlog/src/test/resources/lifecycle-map.map.xml
b/cayenne-commitlog/src/test/resources/lifecycle-map.map.xml
index db86a62..2f65c88 100644
--- a/cayenne-commitlog/src/test/resources/lifecycle-map.map.xml
+++ b/cayenne-commitlog/src/test/resources/lifecycle-map.map.xml
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<data-map xmlns="http://cayenne.apache.org/schema/10/modelMap"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap
http://cayenne.apache.org/schema/10/modelMap.xsd"
+ xsi:schemaLocation="http://cayenne.apache.org/schema/10/modelMap
https://cayenne.apache.org/schema/10/modelMap.xsd"
project-version="10">
<property name="defaultPackage"
value="org.apache.cayenne.commitlog.db"/>
<db-entity name="AUDITABLE1">
@@ -29,6 +29,11 @@
<db-attribute name="CHAR_PROPERTY1" type="VARCHAR"
length="200"/>
<db-attribute name="ID" type="INTEGER" isPrimaryKey="true"
isMandatory="true"/>
</db-entity>
+ <db-entity name="AUDITABLE_CHILD1X">
+ <db-attribute name="AUDITABLE1_ID" type="INTEGER"/>
+ <db-attribute name="CHAR_PROPERTY1" type="VARCHAR"
length="200"/>
+ <db-attribute name="ID" type="INTEGER" isPrimaryKey="true"
isMandatory="true"/>
+ </db-entity>
<db-entity name="AUDITABLE_CHILD3">
<db-attribute name="AUDITABLE2_ID" type="INTEGER"/>
<db-attribute name="CHAR_PROPERTY1" type="VARCHAR"
length="200"/>
@@ -76,6 +81,9 @@
<obj-entity name="AuditableChild1"
className="org.apache.cayenne.commitlog.db.AuditableChild1"
dbEntityName="AUDITABLE_CHILD1">
<obj-attribute name="charProperty1" type="java.lang.String"
db-attribute-path="CHAR_PROPERTY1"/>
</obj-entity>
+ <obj-entity name="AuditableChild1x"
className="org.apache.cayenne.commitlog.db.AuditableChild1x"
dbEntityName="AUDITABLE_CHILD1X">
+ <obj-attribute name="charProperty1" type="java.lang.String"
db-attribute-path="CHAR_PROPERTY1"/>
+ </obj-entity>
<obj-entity name="AuditableChild3"
className="org.apache.cayenne.commitlog.db.AuditableChild3"
dbEntityName="AUDITABLE_CHILD3">
<obj-attribute name="charProperty1" type="java.lang.String"
db-attribute-path="CHAR_PROPERTY1"/>
<obj-attribute name="charProperty2" type="java.lang.String"
db-attribute-path="CHAR_PROPERTY2"/>
@@ -87,6 +95,9 @@
<db-relationship name="children1" source="AUDITABLE1"
target="AUDITABLE_CHILD1" toMany="true">
<db-attribute-pair source="ID" target="AUDITABLE1_ID"/>
</db-relationship>
+ <db-relationship name="children1x" source="AUDITABLE1"
target="AUDITABLE_CHILD1X" toMany="true">
+ <db-attribute-pair source="ID" target="AUDITABLE1_ID"/>
+ </db-relationship>
<db-relationship name="children" source="AUDITABLE2"
target="AUDITABLE_CHILD3" toMany="true">
<db-attribute-pair source="ID" target="AUDITABLE2_ID"/>
</db-relationship>
@@ -99,6 +110,9 @@
<db-relationship name="parent" source="AUDITABLE_CHILD1"
target="AUDITABLE1">
<db-attribute-pair source="AUDITABLE1_ID" target="ID"/>
</db-relationship>
+ <db-relationship name="parent" source="AUDITABLE_CHILD1X"
target="AUDITABLE1">
+ <db-attribute-pair source="AUDITABLE1_ID" target="ID"/>
+ </db-relationship>
<db-relationship name="parent" source="AUDITABLE_CHILD3"
target="AUDITABLE2">
<db-attribute-pair source="AUDITABLE2_ID" target="ID"/>
</db-relationship>
@@ -119,6 +133,7 @@
<obj-relationship name="auditable4s" source="Auditable3"
target="Auditable4" deleteRule="Deny" db-relationship-path="auditable4s"/>
<obj-relationship name="auditable3" source="Auditable4"
target="Auditable3" deleteRule="Nullify" db-relationship-path="auditable3"/>
<obj-relationship name="parent" source="AuditableChild1"
target="Auditable1" deleteRule="Nullify" db-relationship-path="parent"/>
+ <obj-relationship name="parent" source="AuditableChild1x"
target="Auditable1" deleteRule="Nullify" db-relationship-path="parent"/>
<obj-relationship name="parent" source="AuditableChild3"
target="Auditable2" deleteRule="Nullify" db-relationship-path="parent"/>
<obj-relationship name="e4s" source="E3" target="E4" deleteRule="Deny"
db-relationship-path="e34s.e4"/>
<obj-relationship name="e3s" source="E4" target="E3" deleteRule="Deny"
db-relationship-path="e34s.e3"/>