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

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

commit b5be7b6e99f70cd69827b98da84f1c29c0fda259
Author: Andrus Adamchik <[email protected]>
AuthorDate: Thu Jun 11 17:57:10 2026 -0400

    CAY-2958 Empty join is saved
---
 RELEASE-NOTES.txt                                  |  3 +-
 .../org/apache/cayenne/map/DbRelationship.java     |  5 +-
 .../org/apache/cayenne/map/DbRelationshipTest.java | 96 ++++++++++++++++++++++
 .../ui/dbrelationship/DbJoinTableModel.java        |  5 +-
 .../ui/dbrelationship/DbRelationshipDialog.java    |  4 +-
 5 files changed, 109 insertions(+), 4 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index 42834e397..ede153c1b 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -14,10 +14,11 @@ Date:
 Changes/New Features:
 
 CAY-2956 Get rid of a dedicated adapter for Oracle 8
-CAY-2957 Get rid of adaper for legacy HSQLDB <= 1.8
+CAY-2957 Get rid of adapter for legacy HSQLDB <= 1.8
 
 Bug Fixes:
 
+CAY-2958 Empty join is saved
 
 ----------------------------------
 Release: 5.0-M2
diff --git a/cayenne/src/main/java/org/apache/cayenne/map/DbRelationship.java 
b/cayenne/src/main/java/org/apache/cayenne/map/DbRelationship.java
index 5db9010cb..1549afae0 100644
--- a/cayenne/src/main/java/org/apache/cayenne/map/DbRelationship.java
+++ b/cayenne/src/main/java/org/apache/cayenne/map/DbRelationship.java
@@ -80,7 +80,10 @@ public class DbRelationship extends Relationship<DbEntity, 
DbAttribute, DbRelati
         encoder.attribute("toDependentPK", isToDependentPK() && 
isValidForDepPk());
         encoder.attribute("toMany", isToMany());
 
-        encoder.nested(getJoins(), delegate);
+        // skip empty joins that would otherwise be saved as useless 
"<db-attribute-pair/>" tags
+        encoder.nested(getJoins().stream()
+                .filter(j -> j.getSourceName() != null || j.getTargetName() != 
null)
+                .toList(), delegate);
 
         delegate.visitDbRelationship(this);
         encoder.end();
diff --git 
a/cayenne/src/test/java/org/apache/cayenne/map/DbRelationshipTest.java 
b/cayenne/src/test/java/org/apache/cayenne/map/DbRelationshipTest.java
new file mode 100644
index 000000000..b981bf1ce
--- /dev/null
+++ b/cayenne/src/test/java/org/apache/cayenne/map/DbRelationshipTest.java
@@ -0,0 +1,96 @@
+/*****************************************************************
+ *   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.map;
+
+import org.apache.cayenne.configuration.BaseConfigurationNodeVisitor;
+import org.apache.cayenne.util.XMLEncoder;
+import org.junit.jupiter.api.Test;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class DbRelationshipTest {
+
+    @Test
+    public void encodeAsXMLSkipsEmptyJoins() {
+        DbRelationship relationship = relationship();
+        relationship.addJoin(new DbJoin(relationship, "A_ID", "B_ID"));
+
+        // empty join, e.g. added in the Modeler join table and never filled 
in; must not be saved
+        relationship.addJoin(new DbJoin(relationship));
+
+        String ls = System.lineSeparator();
+        assertEquals("<db-relationship name=\"r1\" source=\"A\" target=\"B\">" 
+ ls +
+                "<db-attribute-pair source=\"A_ID\" target=\"B_ID\"/>" + ls +
+                "</db-relationship>" + ls, encode(relationship));
+    }
+
+    @Test
+    public void encodeAsXMLKeepsHalfFilledJoins() {
+        DbRelationship relationship = relationship();
+
+        // a join with only one side set still carries user data and must be 
saved
+        relationship.addJoin(new DbJoin(relationship, "A_ID", null));
+
+        String ls = System.lineSeparator();
+        assertEquals("<db-relationship name=\"r1\" source=\"A\" target=\"B\">" 
+ ls +
+                "<db-attribute-pair source=\"A_ID\"/>" + ls +
+                "</db-relationship>" + ls, encode(relationship));
+    }
+
+    @Test
+    public void encodeAsXMLWithOnlyEmptyJoins() {
+        DbRelationship relationship = relationship();
+        relationship.addJoin(new DbJoin(relationship));
+
+        String ls = System.lineSeparator();
+        assertEquals("<db-relationship name=\"r1\" source=\"A\" 
target=\"B\"/>" + ls, encode(relationship));
+    }
+
+    private DbRelationship relationship() {
+        DataMap map = new DataMap("M");
+        DbEntity source = new DbEntity("A");
+        DbEntity target = new DbEntity("B");
+        map.addDbEntity(source);
+        map.addDbEntity(target);
+
+        DbRelationship relationship = new DbRelationship("r1");
+        relationship.setSourceEntity(source);
+        relationship.setTargetEntityName("B");
+        source.addRelationship(relationship);
+
+        return relationship;
+    }
+
+    private String encode(DbRelationship relationship) {
+        StringWriter out = new StringWriter();
+        relationship.encodeAsXML(new XMLEncoder(new PrintWriter(out)), new 
EncoderDummyVisitor());
+        return out.toString();
+    }
+
+    private static class EncoderDummyVisitor extends 
BaseConfigurationNodeVisitor<Object> {
+
+        @Override
+        public Object visitDbRelationship(DbRelationship relationship) {
+            return null;
+        }
+    }
+}
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbrelationship/DbJoinTableModel.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbrelationship/DbJoinTableModel.java
index fbef69781..1a8ab08b0 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbrelationship/DbJoinTableModel.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbrelationship/DbJoinTableModel.java
@@ -73,7 +73,10 @@ public class DbJoinTableModel extends CMTableModel<DbJoin> {
     }
 
     public void commit() {
-        relationship.setJoins(objectList);
+        // drop empty rows added by the user but never filled in
+        relationship.setJoins(objectList.stream()
+                .filter(j -> j.getSourceName() != null || j.getTargetName() != 
null)
+                .toList());
     }
 
     @Override
diff --git 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbrelationship/DbRelationshipDialog.java
 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbrelationship/DbRelationshipDialog.java
index b7e6de15c..804a716a8 100644
--- 
a/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbrelationship/DbRelationshipDialog.java
+++ 
b/modeler/cayenne-modeler/src/main/java/org/apache/cayenne/modeler/ui/dbrelationship/DbRelationshipDialog.java
@@ -344,12 +344,14 @@ public class DbRelationshipDialog extends ProjectDialog {
         stopEditing();
 
         DbJoinTableModel model = (DbJoinTableModel) table.getModel();
-        boolean updatingReverse = !model.getObjectList().isEmpty();
 
         handleNameUpdate(relationship, name.getText().trim());
 
         model.commit();
 
+        // check after commit, as it filters out empty joins
+        boolean updatingReverse = !relationship.getJoins().isEmpty();
+
         relationship.setToMany(toMany.isSelected());
 
         ObjectInfo.putToMetaData(app.getMetaData(), relationship, 
ObjectInfo.COMMENT, comment.getText());

Reply via email to