diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/ManyToManyJoinTableWithObjEntityIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/ManyToManyJoinTableWithObjEntityIT.java new file mode 100644 index 0000000000..1311f2cf66 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/ManyToManyJoinTableWithObjEntityIT.java @@ -0,0 +1,114 @@ +/***************************************************************** + * 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.Cayenne; +import org.apache.cayenne.PersistenceState; +import org.apache.cayenne.di.Inject; +import org.apache.cayenne.query.ObjectSelect; +import org.apache.cayenne.test.jdbc.DBHelper; +import org.apache.cayenne.test.jdbc.TableHelper; +import org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.Enrollments; +import org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.Student; +import org.apache.cayenne.unit.di.server.CayenneProjects; +import org.apache.cayenne.unit.di.server.ServerCase; +import org.apache.cayenne.unit.di.server.UseServerRuntime; +import org.junit.Before; +import org.junit.Test; + +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; + +@UseServerRuntime(CayenneProjects.MANY_TO_MANY_JOIN_TABLE_OBJ_ENTITY_PROJECT) +public class ManyToManyJoinTableWithObjEntityIT extends ServerCase { + + @Inject + protected DataContext context; + @Inject + protected DBHelper dbHelper; + protected TableHelper tStudent; + protected TableHelper tCourse; + protected TableHelper tEnrollments; + + @Before + public void setUp() throws Exception { + tStudent = new TableHelper(dbHelper, "student"); + tStudent.setColumns("id"); + + tCourse = new TableHelper(dbHelper, "course"); + tCourse.setColumns("id"); + + tEnrollments = new TableHelper(dbHelper, "enrollments"); + tEnrollments.setColumns("student_id","course_id"); + + } + + protected void createObjectsDataSet() throws Exception { + tStudent.insert(1 ); + + tCourse.insert(1); + tCourse.insert(2); + tCourse.insert(3); + + tEnrollments.insert(1,1); + tEnrollments.insert(1,2); + tEnrollments.insert(1,3); + + } + + + @Test + public void testDeleteObjects() throws Exception { + createObjectsDataSet(); + + assertEquals(1, tStudent.getRowCount()); + assertEquals(1, tStudent.selectAll().size()); + assertEquals(3, tEnrollments.getRowCount()); + assertEquals(3, tEnrollments.selectAll().size()); + + Student student = Cayenne.objectForPK(context, Student.class, 1); + + List select = ObjectSelect.query(Enrollments.class).select(context); + assertEquals(3,select.size()); + + assertEquals(PersistenceState.COMMITTED, student.getPersistenceState()); + context.deleteObject(student); + + assertEquals(PersistenceState.DELETED, student.getPersistenceState()); + context.commitChanges(); + + for (Enrollments enrollments : select) { + assertEquals(PersistenceState.TRANSIENT, enrollments.getPersistenceState()); + assertNull(enrollments.getObjectContext()); + } + + assertEquals(PersistenceState.TRANSIENT, student.getPersistenceState()); + assertNull(student.getObjectContext()); + + assertEquals(0, tStudent.getRowCount()); + assertEquals(0, tStudent.selectAll().size()); + assertEquals(0, tEnrollments.getRowCount()); + assertEquals(0, tEnrollments.selectAll().size()); + } + +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/flush/DefaultDataDomainFlushActionTest.java b/cayenne-server/src/test/java/org/apache/cayenne/access/flush/DefaultDataDomainFlushActionTest.java index 9f1b0c30a6..8f0c06aa88 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/access/flush/DefaultDataDomainFlushActionTest.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/access/flush/DefaultDataDomainFlushActionTest.java @@ -22,7 +22,9 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import org.apache.cayenne.ObjectId; import org.apache.cayenne.PersistenceState; @@ -119,6 +121,56 @@ public void mergeSameObjectsId_ReplacementId() { assertThat(merged, not(hasItem(sameInstance(op[1])))); } + @Test + public void mergeSameObjectIdCompoundId() { + Map keyMap1_1 = new LinkedHashMap<>(); + keyMap1_1.put("student_id", 1); + keyMap1_1.put("course_id", 1); + + Map keyMap1_2 = new LinkedHashMap<>(); + keyMap1_2.put("student_id", 1); + keyMap1_2.put("course_id", 2); + + Map keyMap1_3 = new LinkedHashMap<>(); + keyMap1_3.put("student_id", 1); + keyMap1_3.put("course_id", 3); + + ObjectId studentObjectId1 = ObjectId.of("student", "id", 1); + + ObjectId enrollmentObjectId1_1 = ObjectId.of("enrollment", keyMap1_1); + enrollmentObjectId1_1.getReplacementIdMap().put("student_id",1); + + ObjectId enrollmentObjectId1_2 = ObjectId.of("enrollment", keyMap1_2); + enrollmentObjectId1_2.getReplacementIdMap().put("student_id",1); + + ObjectId enrollmentObjectId1_3 = ObjectId.of("enrollment", keyMap1_3); + enrollmentObjectId1_3.getReplacementIdMap().put("student_id",1); + + DbEntity student = mockEntity("student"); + DbEntity enrollment = mockEntity("enrollment"); + + List ops = new ArrayList<>(); + + ops.add(new DeleteDbRowOp(mockObject(studentObjectId1), student, studentObjectId1)); + ops.add(new UpdateDbRowOp(mockObject(enrollmentObjectId1_1), enrollment, enrollmentObjectId1_1)); + ops.add(new UpdateDbRowOp(mockObject(enrollmentObjectId1_3), enrollment, enrollmentObjectId1_3)); + ops.add(new UpdateDbRowOp(mockObject(enrollmentObjectId1_2), enrollment, enrollmentObjectId1_2)); + + ops.add(new DeleteDbRowOp(mockObject(enrollmentObjectId1_3), enrollment, enrollmentObjectId1_3)); + ops.add(new DeleteDbRowOp(mockObject(enrollmentObjectId1_2), enrollment, enrollmentObjectId1_2)); + ops.add(new DeleteDbRowOp(mockObject(enrollmentObjectId1_1), enrollment, enrollmentObjectId1_1)); + + + DefaultDataDomainFlushAction action = mock(DefaultDataDomainFlushAction.class); + when(action.mergeSameObjectIds((List) any(List.class))).thenCallRealMethod(); + + Collection merged = action.mergeSameObjectIds(ops); + assertEquals(4, merged.size()); + + merged.forEach(dbRowOp -> assertThat(dbRowOp, instanceOf(DeleteDbRowOp.class))); + } + + @Test public void createQueries() { ObjectId id1 = ObjectId.of("test", "id", 1); diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/Course.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/Course.java new file mode 100644 index 0000000000..553b12c725 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/Course.java @@ -0,0 +1,9 @@ +package org.apache.cayenne.testdo.many_to_many_joinTable_objEntity; + +import org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.auto._Course; + +public class Course extends _Course { + + private static final long serialVersionUID = 1L; + +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/Enrollments.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/Enrollments.java new file mode 100644 index 0000000000..f3cc8f5d4b --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/Enrollments.java @@ -0,0 +1,9 @@ +package org.apache.cayenne.testdo.many_to_many_joinTable_objEntity; + +import org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.auto._Enrollments; + +public class Enrollments extends _Enrollments { + + private static final long serialVersionUID = 1L; + +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/Student.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/Student.java new file mode 100644 index 0000000000..651978a680 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/Student.java @@ -0,0 +1,9 @@ +package org.apache.cayenne.testdo.many_to_many_joinTable_objEntity; + +import org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.auto._Student; + +public class Student extends _Student { + + private static final long serialVersionUID = 1L; + +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/auto/_Course.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/auto/_Course.java new file mode 100644 index 0000000000..bc3c7cf09d --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/auto/_Course.java @@ -0,0 +1,92 @@ +package org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; + +import org.apache.cayenne.BaseDataObject; +import org.apache.cayenne.exp.property.ListProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.Enrollments; + +/** + * Class _Course 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 _Course extends BaseDataObject { + + private static final long serialVersionUID = 1L; + + public static final String ID_PK_COLUMN = "id"; + + public static final ListProperty ENROLLMENTS = PropertyFactory.createList("enrollments", Enrollments.class); + + + protected Object enrollments; + + public void addToEnrollments(Enrollments obj) { + addToManyTarget("enrollments", obj, true); + } + + public void removeFromEnrollments(Enrollments obj) { + removeToManyTarget("enrollments", obj, true); + } + + @SuppressWarnings("unchecked") + public List getEnrollments() { + return (List)readProperty("enrollments"); + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "enrollments": + return this.enrollments; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "enrollments": + this.enrollments = 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.enrollments); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.enrollments = in.readObject(); + } + +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/auto/_Enrollments.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/auto/_Enrollments.java new file mode 100644 index 0000000000..2e6ceddef3 --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/auto/_Enrollments.java @@ -0,0 +1,105 @@ +package org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.cayenne.BaseDataObject; +import org.apache.cayenne.exp.property.EntityProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.Course; +import org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.Student; + +/** + * Class _Enrollments 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 _Enrollments extends BaseDataObject { + + private static final long serialVersionUID = 1L; + + public static final String STUDENT_ID_PK_COLUMN = "student_id"; + public static final String COURSE_ID_PK_COLUMN = "course_id"; + + public static final EntityProperty ENROLLMENTS_TO_COURSE = PropertyFactory.createEntity("enrollments_to_course", Course.class); + public static final EntityProperty ENROLLMENTS_TO_STUDENT = PropertyFactory.createEntity("enrollments_to_student", Student.class); + + + protected Object enrollments_to_course; + protected Object enrollments_to_student; + + public void setEnrollments_to_course(Course enrollments_to_course) { + setToOneTarget("enrollments_to_course", enrollments_to_course, true); + } + + public Course getEnrollments_to_course() { + return (Course)readProperty("enrollments_to_course"); + } + + public void setEnrollments_to_student(Student enrollments_to_student) { + setToOneTarget("enrollments_to_student", enrollments_to_student, true); + } + + public Student getEnrollments_to_student() { + return (Student)readProperty("enrollments_to_student"); + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "enrollments_to_course": + return this.enrollments_to_course; + case "enrollments_to_student": + return this.enrollments_to_student; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "enrollments_to_course": + this.enrollments_to_course = val; + break; + case "enrollments_to_student": + this.enrollments_to_student = 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.enrollments_to_course); + out.writeObject(this.enrollments_to_student); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.enrollments_to_course = in.readObject(); + this.enrollments_to_student = in.readObject(); + } + +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/auto/_Student.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/auto/_Student.java new file mode 100644 index 0000000000..e7d0eb7c0d --- /dev/null +++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/many_to_many_joinTable_objEntity/auto/_Student.java @@ -0,0 +1,92 @@ +package org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.util.List; + +import org.apache.cayenne.BaseDataObject; +import org.apache.cayenne.exp.property.ListProperty; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.testdo.many_to_many_joinTable_objEntity.Enrollments; + +/** + * Class _Student 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 _Student extends BaseDataObject { + + private static final long serialVersionUID = 1L; + + public static final String ID_PK_COLUMN = "id"; + + public static final ListProperty ENROLLMENTS = PropertyFactory.createList("enrollments", Enrollments.class); + + + protected Object enrollments; + + public void addToEnrollments(Enrollments obj) { + addToManyTarget("enrollments", obj, true); + } + + public void removeFromEnrollments(Enrollments obj) { + removeToManyTarget("enrollments", obj, true); + } + + @SuppressWarnings("unchecked") + public List getEnrollments() { + return (List)readProperty("enrollments"); + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "enrollments": + return this.enrollments; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "enrollments": + this.enrollments = 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.enrollments); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.enrollments = in.readObject(); + } + +} diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java index ccb392ed82..96fc7fb70d 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/CayenneProjects.java @@ -75,6 +75,7 @@ public class CayenneProjects { public static final String SUS_PROJECT = "cayenne-sus.xml"; public static final String TABLE_PRIMITIVES_PROJECT = "cayenne-table-primitives.xml"; public static final String TESTMAP_PROJECT = "cayenne-testmap.xml"; + public static final String MANY_TO_MANY_JOIN_TABLE_OBJ_ENTITY_PROJECT = "cayenne-many-to-many-joinTable-objEntity.xml"; public static final String THINGS_PROJECT = "cayenne-things.xml"; public static final String TOONE_PROJECT = "cayenne-toone.xml"; public static final String UNSUPPORTED_DISTINCT_TYPES_PROJECT = "cayenne-unsupported-distinct-types.xml"; diff --git a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java index bc154c6e7d..cd0e4d0ed0 100644 --- a/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java +++ b/cayenne-server/src/test/java/org/apache/cayenne/unit/di/server/SchemaBuilder.java @@ -83,7 +83,7 @@ public class SchemaBuilder { "inheritance-vertical.map.xml", "oneway-rels.map.xml", "unsupported-distinct-types.map.xml", "array-type.map.xml", "cay-2032.map.xml", "weighted-sort.map.xml", "hybrid-data-object.map.xml", "java8.map.xml", "inheritance-with-enum.map.xml", "lazy-attributes.map.xml", "cay2666/datamap.map.xml", "cay2641/datamapLazy.map.xml", - "annotation/datamapAnnotation.map.xml" }; + "annotation/datamapAnnotation.map.xml", "many-to-many-joinTable-objEntity.map.xml" }; // hardcoded dependent entities that should be excluded // if LOBs are not supported diff --git a/cayenne-server/src/test/resources/cayenne-many-to-many-joinTable-objEntity.xml b/cayenne-server/src/test/resources/cayenne-many-to-many-joinTable-objEntity.xml new file mode 100644 index 0000000000..5f3d926547 --- /dev/null +++ b/cayenne-server/src/test/resources/cayenne-many-to-many-joinTable-objEntity.xml @@ -0,0 +1,7 @@ + + + + diff --git a/cayenne-server/src/test/resources/many-to-many-joinTable-objEntity.map.xml b/cayenne-server/src/test/resources/many-to-many-joinTable-objEntity.map.xml new file mode 100644 index 0000000000..fffc4e5811 --- /dev/null +++ b/cayenne-server/src/test/resources/many-to-many-joinTable-objEntity.map.xml @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Enrollments,Student,Course + ../java + entity + + templates/v4_1/superclass.vm + templates/v4_1/embeddable-subclass.vm + templates/v4_1/embeddable-superclass.vm + templates/v4_1/datamap-subclass.vm + templates/v4_1/datamap-superclass.vm + *.java + true + true + false + false + false + false + +