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

asf-gitbox-commits pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cayenne.git

commit bc559a78d37d9d29d55e5740e0e572b0dd778814
Author: Andrus Adamchik <[email protected]>
AuthorDate: Fri Jul 3 11:21:50 2026 -0400

    A real test for soft delete
---
 .../org/apache/cayenne/access/SoftDeleteIT.java    | 152 +++++++++++++++++++++
 .../{SoftDelete.java => HardDelete.java}           |   9 +-
 .../cayenne/testdo/soft_delete/SoftDelete.java     |   5 -
 .../auto/{_SoftDelete.java => _HardDelete.java}    |  32 +----
 .../testdo/soft_delete/auto/_SoftDelete.java       |   2 -
 cayenne/src/test/resources/soft-delete.map.xml     |  10 +-
 6 files changed, 167 insertions(+), 43 deletions(-)

diff --git a/cayenne/src/test/java/org/apache/cayenne/access/SoftDeleteIT.java 
b/cayenne/src/test/java/org/apache/cayenne/access/SoftDeleteIT.java
new file mode 100644
index 000000000..18e175d81
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/access/SoftDeleteIT.java
@@ -0,0 +1,152 @@
+/*****************************************************************
+ *   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.PersistenceState;
+import org.apache.cayenne.access.translator.batch.BatchTranslator;
+import org.apache.cayenne.access.translator.batch.SoftDeleteBatchTranslator;
+import org.apache.cayenne.di.Key;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.apache.cayenne.testdo.soft_delete.HardDelete;
+import org.apache.cayenne.testdo.soft_delete.SoftDelete;
+import org.apache.cayenne.unit.CayenneProjects;
+import org.apache.cayenne.unit.CayenneTestsEnv;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class SoftDeleteIT {
+
+    @RegisterExtension
+    static final CayenneTestsEnv env = CayenneTestsEnv
+            .forProject(CayenneProjects.SOFT_DELETE_PROJECT)
+            .withExtraModules(b -> b.bind(Key.get(BatchTranslator.class, 
BatchTranslator.DELETE)).toInstance(new SoftDeleteBatchTranslator()));
+
+    private TableHelper tSoftDelete;
+
+    @BeforeEach
+    public void setUp() {
+        tSoftDelete = env.table("SOFT_DELETE", "ID", "NAME", "DELETED");
+    }
+
+    @Test
+    public void softDeleteLeavesRowFlagged() throws Exception {
+        DataContext context = env.context();
+
+        SoftDelete object = context.newObject(SoftDelete.class);
+        object.setName("a");
+        context.commitChanges();
+
+        assertEquals(1, tSoftDelete.getRowCount());
+        assertNull(tSoftDelete.selectAll().getFirst()[2], "freshly inserted 
row should have NULL DELETED");
+
+        assertEquals(1, 
ObjectSelect.query(SoftDelete.class).selectCount(context));
+
+        context.deleteObjects(object);
+        context.commitChanges();
+
+        // object graph is unaware of the underlying UPDATE and treats the 
delete as final
+        assertEquals(PersistenceState.TRANSIENT, object.getPersistenceState());
+
+        // the row physically survives, now flagged as deleted
+        assertEquals(1, tSoftDelete.getRowCount());
+        List<Object[]> rows = tSoftDelete.selectAll();
+        assertTrue(toBoolean(rows.getFirst()[2]), "DELETED flag should be 
true");
+
+        // the entity qualifier hides the soft-deleted row
+        assertEquals(0, 
ObjectSelect.query(SoftDelete.class).selectCount(context));
+    }
+
+    @Test
+    public void batchSoftDeleteLeavesRowsFlagged() throws Exception {
+        DataContext context = env.context();
+
+        SoftDelete o1 = context.newObject(SoftDelete.class);
+        o1.setName("a");
+        SoftDelete o2 = context.newObject(SoftDelete.class);
+        o2.setName("b");
+        SoftDelete o3 = context.newObject(SoftDelete.class);
+        o3.setName("c");
+        context.commitChanges();
+
+        context.deleteObjects(o1, o2, o3);
+        context.commitChanges();
+
+        assertEquals(3, tSoftDelete.getRowCount());
+        for (Object[] row : tSoftDelete.selectAll()) {
+            assertTrue(toBoolean(row[2]), "DELETED flag should be true");
+        }
+
+        assertEquals(0, 
ObjectSelect.query(SoftDelete.class).selectCount(context));
+    }
+
+    @Test
+    public void softDeleteHidesOnlyDeletedRow() throws Exception {
+        DataContext context = env.context();
+
+        SoftDelete survivor = context.newObject(SoftDelete.class);
+        survivor.setName("survivor");
+        SoftDelete victim = context.newObject(SoftDelete.class);
+        victim.setName("victim");
+        context.commitChanges();
+
+        context.deleteObjects(victim);
+        context.commitChanges();
+
+        // both rows are still physically present
+        assertEquals(2, tSoftDelete.getRowCount());
+
+        // but the qualifier hides the soft-deleted one, leaving only the 
survivor visible
+        List<SoftDelete> visible = 
ObjectSelect.query(SoftDelete.class).select(context);
+        assertEquals(1, visible.size());
+        assertEquals("survivor", visible.getFirst().getName());
+    }
+
+    @Test
+    public void hardDeleteFallbackRemovesRow() throws Exception {
+        TableHelper tHardDelete = env.table("HARD_DELETE", "ID", "NAME");
+        DataContext context = env.context();
+
+        HardDelete object = context.newObject(HardDelete.class);
+        object.setName("a");
+        context.commitChanges();
+
+        assertEquals(1, tHardDelete.getRowCount());
+
+        context.deleteObjects(object);
+        context.commitChanges();
+
+        // no DELETED column -> the translator falls back to a real DELETE and 
the row is gone
+        assertEquals(0, tHardDelete.getRowCount());
+        assertEquals(0, 
ObjectSelect.query(HardDelete.class).selectCount(context));
+    }
+
+    private static boolean toBoolean(Object value) {
+        if (value instanceof Boolean b) {
+            return b;
+        }
+        // NULL means "not deleted"; some databases store BOOLEAN as a numeric 
flag
+        return value instanceof Number n && n.intValue() != 0;
+    }
+}
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/SoftDelete.java 
b/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/HardDelete.java
similarity index 83%
copy from 
cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/SoftDelete.java
copy to 
cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/HardDelete.java
index 17b70a811..3c856100b 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/SoftDelete.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/HardDelete.java
@@ -18,13 +18,8 @@
  ****************************************************************/
 package org.apache.cayenne.testdo.soft_delete;
 
-import org.apache.cayenne.testdo.soft_delete.auto._SoftDelete;
+import org.apache.cayenne.testdo.soft_delete.auto._HardDelete;
 
-public class SoftDelete extends _SoftDelete {
-
-    @Override
-    protected void onPrePersist() {
-        setDeleted(false);
-    }
+public class HardDelete extends _HardDelete {
 
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/SoftDelete.java 
b/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/SoftDelete.java
index 17b70a811..ca11b511a 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/SoftDelete.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/SoftDelete.java
@@ -22,9 +22,4 @@ import org.apache.cayenne.testdo.soft_delete.auto._SoftDelete;
 
 public class SoftDelete extends _SoftDelete {
 
-    @Override
-    protected void onPrePersist() {
-        setDeleted(false);
-    }
-
 }
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_SoftDelete.java
 
b/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_HardDelete.java
similarity index 69%
copy from 
cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_SoftDelete.java
copy to 
cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_HardDelete.java
index 90761f4ea..52b4f373a 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_SoftDelete.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_HardDelete.java
@@ -5,45 +5,32 @@ import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
 
 import org.apache.cayenne.PersistentObject;
-import org.apache.cayenne.exp.property.BaseProperty;
 import org.apache.cayenne.exp.property.NumericIdProperty;
 import org.apache.cayenne.exp.property.PropertyFactory;
 import org.apache.cayenne.exp.property.SelfProperty;
 import org.apache.cayenne.exp.property.StringProperty;
-import org.apache.cayenne.testdo.soft_delete.SoftDelete;
+import org.apache.cayenne.testdo.soft_delete.HardDelete;
 
 /**
- * Class _SoftDelete was generated by Cayenne.
+ * Class _HardDelete 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 _SoftDelete extends PersistentObject {
+public abstract class _HardDelete extends PersistentObject {
 
     private static final long serialVersionUID = 1L;
 
-    public static final SelfProperty<SoftDelete> SELF = 
PropertyFactory.createSelf(SoftDelete.class);
+    public static final SelfProperty<HardDelete> SELF = 
PropertyFactory.createSelf(HardDelete.class);
 
-    public static final NumericIdProperty<Integer> ID_PK_PROPERTY = 
PropertyFactory.createNumericId("ID", "SoftDelete", Integer.class);
+    public static final NumericIdProperty<Integer> ID_PK_PROPERTY = 
PropertyFactory.createNumericId("ID", "HardDelete", Integer.class);
     public static final String ID_PK_COLUMN = "ID";
 
-    public static final BaseProperty<Boolean> DELETED = 
PropertyFactory.createBase("deleted", Boolean.class);
     public static final StringProperty<String> NAME = 
PropertyFactory.createString("name", String.class);
 
-    protected Boolean deleted;
     protected String name;
 
 
-    public void setDeleted(Boolean deleted) {
-        beforePropertyWrite("deleted", this.deleted, deleted);
-        this.deleted = deleted;
-    }
-
-    public Boolean getDeleted() {
-        beforePropertyRead("deleted");
-        return this.deleted;
-    }
-
     public void setName(String name) {
         beforePropertyWrite("name", this.name, name);
         this.name = name;
@@ -54,8 +41,6 @@ public abstract class _SoftDelete extends PersistentObject {
         return this.name;
     }
 
-    protected abstract void onPrePersist();
-
     @Override
     public Object readPropertyDirectly(String propName) {
         if(propName == null) {
@@ -63,8 +48,6 @@ public abstract class _SoftDelete extends PersistentObject {
         }
 
         switch(propName) {
-            case "deleted":
-                return this.deleted;
             case "name":
                 return this.name;
             default:
@@ -79,9 +62,6 @@ public abstract class _SoftDelete extends PersistentObject {
         }
 
         switch (propName) {
-            case "deleted":
-                this.deleted = (Boolean)val;
-                break;
             case "name":
                 this.name = (String)val;
                 break;
@@ -101,14 +81,12 @@ public abstract class _SoftDelete extends PersistentObject 
{
     @Override
     protected void writeState(ObjectOutputStream out) throws IOException {
         super.writeState(out);
-        out.writeObject(this.deleted);
         out.writeObject(this.name);
     }
 
     @Override
     protected void readState(ObjectInputStream in) throws IOException, 
ClassNotFoundException {
         super.readState(in);
-        this.deleted = (Boolean)in.readObject();
         this.name = (String)in.readObject();
     }
 
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_SoftDelete.java
 
b/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_SoftDelete.java
index 90761f4ea..9272e2db9 100644
--- 
a/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_SoftDelete.java
+++ 
b/cayenne/src/test/java/org/apache/cayenne/testdo/soft_delete/auto/_SoftDelete.java
@@ -54,8 +54,6 @@ public abstract class _SoftDelete extends PersistentObject {
         return this.name;
     }
 
-    protected abstract void onPrePersist();
-
     @Override
     public Object readPropertyDirectly(String propName) {
         if(propName == null) {
diff --git a/cayenne/src/test/resources/soft-delete.map.xml 
b/cayenne/src/test/resources/soft-delete.map.xml
index 6ded7f6a5..596f417d2 100644
--- a/cayenne/src/test/resources/soft-delete.map.xml
+++ b/cayenne/src/test/resources/soft-delete.map.xml
@@ -8,11 +8,17 @@
                <db-attribute name="DELETED" type="BOOLEAN"/>
                <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" 
isMandatory="true"/>
                <db-attribute name="NAME" type="VARCHAR" length="100"/>
-               <qualifier><![CDATA[DELETED = false]]></qualifier>
+               <qualifier><![CDATA[DELETED = false or DELETED = 
null]]></qualifier>
+       </db-entity>
+       <db-entity name="HARD_DELETE">
+               <db-attribute name="ID" type="INTEGER" isPrimaryKey="true" 
isMandatory="true"/>
+               <db-attribute name="NAME" type="VARCHAR" length="100"/>
        </db-entity>
        <obj-entity name="SoftDelete" 
className="org.apache.cayenne.testdo.soft_delete.SoftDelete" 
dbEntityName="SOFT_DELETE">
                <obj-attribute name="deleted" type="java.lang.Boolean" 
db-attribute-path="DELETED"/>
                <obj-attribute name="name" type="java.lang.String" 
db-attribute-path="NAME"/>
-               <post-add method-name="onPrePersist"/>
+       </obj-entity>
+       <obj-entity name="HardDelete" 
className="org.apache.cayenne.testdo.soft_delete.HardDelete" 
dbEntityName="HARD_DELETE">
+               <obj-attribute name="name" type="java.lang.String" 
db-attribute-path="NAME"/>
        </obj-entity>
 </data-map>

Reply via email to