diff --git a/RELEASE-NOTES.md b/RELEASE-NOTES.md index 9695f8be0..44ec966c7 100644 --- a/RELEASE-NOTES.md +++ b/RELEASE-NOTES.md @@ -16,6 +16,7 @@ * #679 Upgrading Jackson to 2.15.4 * #680 Upgrading SLF4J to 2.0.7 * #683 Upgrade Cayenne to 4.2.1 +* #684 GET with includes can't match children to parent when parent has a compound key ## Release 5.0.M19 diff --git a/agrest-cayenne/src/test/java/io/agrest/cayenne/GET/IncludeObjectIT.java b/agrest-cayenne/src/test/java/io/agrest/cayenne/GET/IncludeObjectIT.java index 259269056..1bbd45b08 100644 --- a/agrest-cayenne/src/test/java/io/agrest/cayenne/GET/IncludeObjectIT.java +++ b/agrest-cayenne/src/test/java/io/agrest/cayenne/GET/IncludeObjectIT.java @@ -3,6 +3,8 @@ import io.agrest.DataResponse; import io.agrest.cayenne.cayenne.main.E2; import io.agrest.cayenne.cayenne.main.E3; +import io.agrest.cayenne.cayenne.main.E32; +import io.agrest.cayenne.cayenne.main.E33; import io.agrest.cayenne.cayenne.main.E4; import io.agrest.cayenne.cayenne.main.E5; import io.agrest.cayenne.unit.main.MainDbTest; @@ -21,7 +23,7 @@ public class IncludeObjectIT extends MainDbTest { @BQTestTool static final MainModelTester tester = tester(Resource.class) - .entitiesAndDependencies(E2.class, E3.class, E4.class, E5.class) + .entitiesAndDependencies(E2.class, E3.class, E4.class, E5.class, E32.class, E33.class) .build(); @Test @@ -205,9 +207,9 @@ public void toMany_Sort() { .queryParam("include", "{\"path\":\"e3s\",\"sort\":\"name\"}") .queryParam("include", "id") .get().wasOk().bodyEquals(1, "{\"id\":1,\"e3s\":[" - + "{\"id\":7,\"name\":\"b\",\"phoneNumber\":null}," - + "{\"id\":9,\"name\":\"s\",\"phoneNumber\":null}," - + "{\"id\":8,\"name\":\"z\",\"phoneNumber\":null}]}"); + + "{\"id\":7,\"name\":\"b\",\"phoneNumber\":null}," + + "{\"id\":9,\"name\":\"s\",\"phoneNumber\":null}," + + "{\"id\":8,\"name\":\"z\",\"phoneNumber\":null}]}"); } @Test @@ -410,6 +412,25 @@ public void toMany_IncludeExtMapRelated() { + "{\"e5\":{\"name\":\"A\"},\"name\":\"m\"}]}"); } + @Test + public void testCompoundRootKey() { + + tester.e33().insertColumns("p_id", "name") + .values(11, "n33_1") + .values(12, "n33_2").exec(); + + tester.e32().insertColumns("p_id", "s_id", "t_id", "name") + .values(11, 101, 1001, "n32_1") + .values(12, 102, 1002, "n32_2").exec(); + + tester.target("/e32") + .queryParam("include", "e33") + .get() + .wasOk() + .bodyEquals(2, "{\"id\":{\"db:p_id\":11,\"db:s_id\":101,\"db:t_id\":1001},\"e33\":{\"id\":11,\"name\":\"n33_1\"},\"name\":\"n32_1\"}," + + "{\"id\":{\"db:p_id\":12,\"db:s_id\":102,\"db:t_id\":1002},\"e33\":{\"id\":12,\"name\":\"n33_2\"},\"name\":\"n32_2\"}"); + } + @Path("") public static class Resource { @@ -433,5 +454,11 @@ public DataResponse getE3(@Context UriInfo uriInfo) { public DataResponse getE4(@Context UriInfo uriInfo) { return AgJaxrs.select(E4.class, config).clientParams(uriInfo.getQueryParameters()).get(); } + + @GET + @Path("e32") + public DataResponse getE32(@Context UriInfo uriInfo) { + return AgJaxrs.select(E32.class, config).clientParams(uriInfo.getQueryParameters()).get(); + } } } diff --git a/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/E32.java b/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/E32.java new file mode 100644 index 000000000..c4dc10f12 --- /dev/null +++ b/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/E32.java @@ -0,0 +1,9 @@ +package io.agrest.cayenne.cayenne.main; + +import io.agrest.cayenne.cayenne.main.auto._E32; + +public class E32 extends _E32 { + + private static final long serialVersionUID = 1L; + +} diff --git a/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/E33.java b/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/E33.java new file mode 100644 index 000000000..690dbef12 --- /dev/null +++ b/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/E33.java @@ -0,0 +1,9 @@ +package io.agrest.cayenne.cayenne.main; + +import io.agrest.cayenne.cayenne.main.auto._E33; + +public class E33 extends _E33 { + + private static final long serialVersionUID = 1L; + +} diff --git a/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/auto/_E32.java b/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/auto/_E32.java new file mode 100644 index 000000000..2dd38a5cb --- /dev/null +++ b/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/auto/_E32.java @@ -0,0 +1,109 @@ +package io.agrest.cayenne.cayenne.main.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.exp.property.StringProperty; + +import io.agrest.cayenne.cayenne.main.E33; + +/** + * Class _E32 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 _E32 extends BaseDataObject { + + private static final long serialVersionUID = 1L; + + public static final String S_ID_PK_COLUMN = "s_id"; + public static final String P_ID_PK_COLUMN = "p_id"; + public static final String T_ID_PK_COLUMN = "t_id"; + + public static final StringProperty NAME = PropertyFactory.createString("name", String.class); + public static final EntityProperty E33 = PropertyFactory.createEntity("e33", E33.class); + + protected String name; + + protected Object e33; + + public void setName(String name) { + beforePropertyWrite("name", this.name, name); + this.name = name; + } + + public String getName() { + beforePropertyRead("name"); + return this.name; + } + + public void setE33(E33 e33) { + setToOneTarget("e33", e33, true); + } + + public E33 getE33() { + return (E33)readProperty("e33"); + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "name": + return this.name; + case "e33": + return this.e33; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "name": + this.name = (String)val; + break; + case "e33": + this.e33 = 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.name); + out.writeObject(this.e33); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.name = (String)in.readObject(); + this.e33 = in.readObject(); + } + +} diff --git a/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/auto/_E33.java b/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/auto/_E33.java new file mode 100644 index 000000000..44d197be7 --- /dev/null +++ b/agrest-cayenne/src/test/java/io/agrest/cayenne/cayenne/main/auto/_E33.java @@ -0,0 +1,87 @@ +package io.agrest.cayenne.cayenne.main.auto; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +import org.apache.cayenne.BaseDataObject; +import org.apache.cayenne.exp.property.PropertyFactory; +import org.apache.cayenne.exp.property.StringProperty; + +/** + * Class _E33 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 _E33 extends BaseDataObject { + + private static final long serialVersionUID = 1L; + + public static final String P_ID_PK_COLUMN = "p_id"; + + public static final StringProperty NAME = PropertyFactory.createString("name", String.class); + + protected String name; + + + public void setName(String name) { + beforePropertyWrite("name", this.name, name); + this.name = name; + } + + public String getName() { + beforePropertyRead("name"); + return this.name; + } + + @Override + public Object readPropertyDirectly(String propName) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch(propName) { + case "name": + return this.name; + default: + return super.readPropertyDirectly(propName); + } + } + + @Override + public void writePropertyDirectly(String propName, Object val) { + if(propName == null) { + throw new IllegalArgumentException(); + } + + switch (propName) { + case "name": + this.name = (String)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.name); + } + + @Override + protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException { + super.readState(in); + this.name = (String)in.readObject(); + } + +} diff --git a/agrest-cayenne/src/test/java/io/agrest/cayenne/unit/main/MainModelTester.java b/agrest-cayenne/src/test/java/io/agrest/cayenne/unit/main/MainModelTester.java index 7118228b7..64b9f41dd 100644 --- a/agrest-cayenne/src/test/java/io/agrest/cayenne/unit/main/MainModelTester.java +++ b/agrest-cayenne/src/test/java/io/agrest/cayenne/unit/main/MainModelTester.java @@ -136,4 +136,12 @@ public Table e30() { public Table e31() { return db.getTable("e31"); } + + public Table e32() { + return db.getTable("e32"); + } + + public Table e33() { + return db.getTable("e33"); + } } diff --git a/agrest-cayenne/src/test/resources/main/datamap.map.xml b/agrest-cayenne/src/test/resources/main/datamap.map.xml index 44a005882..dfc69c772 100644 --- a/agrest-cayenne/src/test/resources/main/datamap.map.xml +++ b/agrest-cayenne/src/test/resources/main/datamap.map.xml @@ -163,6 +163,16 @@ + + + + + + + + + + @@ -305,6 +315,12 @@ + + + + + + @@ -416,6 +432,12 @@ + + + + + + @@ -458,6 +480,7 @@ + diff --git a/agrest-cayenne/src/test/resources/main/schema-derby.sql b/agrest-cayenne/src/test/resources/main/schema-derby.sql index 08e29f831..8aef52681 100644 --- a/agrest-cayenne/src/test/resources/main/schema-derby.sql +++ b/agrest-cayenne/src/test/resources/main/schema-derby.sql @@ -102,6 +102,12 @@ CREATE TABLE "e30" ("id" INTEGER NOT NULL GENERATED BY DEFAULT AS IDENTITY, "e2 CREATE TABLE "e31" ("id" INTEGER NOT NULL, "name" VARCHAR(100) , PRIMARY KEY ("id")) ; +CREATE TABLE "e33" ("name" VARCHAR (100), "p_id" INTEGER NOT NULL, PRIMARY KEY ("p_id")) +; + +CREATE TABLE "e32" ("s_id" INTEGER NOT NULL, "p_id" INTEGER NOT NULL, "t_id" INTEGER NOT NULL, "name" VARCHAR (100), PRIMARY KEY ("s_id", "p_id", "t_id")) +; + ALTER TABLE "e14" ADD FOREIGN KEY ("e15_id") REFERENCES "e15" ("long_id") ; @@ -153,6 +159,9 @@ ALTER TABLE "e3" ADD FOREIGN KEY ("e5_id") REFERENCES "e5" ("id") ALTER TABLE "e30" ADD FOREIGN KEY ("e29_id1", "e29_id2") REFERENCES "e29" ("id1", "id2") ; +ALTER TABLE "e32" ADD FOREIGN KEY ("p_id") REFERENCES "e33" ("p_id") +; + CREATE SEQUENCE "PK_E1" AS BIGINT START WITH 200 INCREMENT BY 20 NO MAXVALUE NO CYCLE ; @@ -237,6 +246,12 @@ CREATE SEQUENCE "PK_E29" AS BIGINT START WITH 200 INCREMENT BY 20 NO MAXVALUE NO CREATE SEQUENCE "PK_E30" AS BIGINT START WITH 200 INCREMENT BY 20 NO MAXVALUE NO CYCLE ; +CREATE SEQUENCE "PK_E33" AS BIGINT START WITH 200 INCREMENT BY 20 NO MAXVALUE NO CYCLE +; + +CREATE SEQUENCE "PK_E32" AS BIGINT START WITH 200 INCREMENT BY 20 NO MAXVALUE NO CYCLE +; + -- iso CREATE TABLE "SQL_DATE_TEST" ("Date" DATE , "ID" INTEGER NOT NULL, "Time" TIME , "Timestamp" TIMESTAMP , PRIMARY KEY ("ID")) ;