From 532e73e1a8841bc546df6b736b3db97e23809278 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Thu, 1 Jun 2023 22:35:58 +0100 Subject: [PATCH 01/11] Prototype for dealing with nested optional fields --- .../rosetta/annotations/NestedOptional.java | 15 +++++ .../java/com/hubspot/rosetta/Rosetta.java | 1 + .../internal/NestedOptionalDeserializer.java | 55 +++++++++++++++++++ .../RosettaAnnotationIntrospector.java | 14 +++++ .../annotations/NestedOptionalTest.java | 27 +++++++++ .../beans/MoreFieldsInnerFieldBean.java | 22 ++++++++ .../rosetta/beans/NestedOptionalBean.java | 30 ++++++++++ 7 files changed, 164 insertions(+) create mode 100644 RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java create mode 100644 RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java create mode 100644 RosettaCore/src/test/java/com/hubspot/rosetta/annotations/NestedOptionalTest.java create mode 100644 RosettaCore/src/test/java/com/hubspot/rosetta/beans/MoreFieldsInnerFieldBean.java create mode 100644 RosettaCore/src/test/java/com/hubspot/rosetta/beans/NestedOptionalBean.java diff --git a/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java b/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java new file mode 100644 index 00000000..eec4e466 --- /dev/null +++ b/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java @@ -0,0 +1,15 @@ +package com.hubspot.rosetta.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicate that a field is a nested optional and serde operations will follow nested optional rules. + */ +@Target({ElementType.FIELD, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +@RosettaAnnotation +public @interface NestedOptional { +} diff --git a/RosettaCore/src/main/java/com/hubspot/rosetta/Rosetta.java b/RosettaCore/src/main/java/com/hubspot/rosetta/Rosetta.java index 484a2852..79fc90d7 100644 --- a/RosettaCore/src/main/java/com/hubspot/rosetta/Rosetta.java +++ b/RosettaCore/src/main/java/com/hubspot/rosetta/Rosetta.java @@ -48,6 +48,7 @@ public static ObjectMapper cloneAndCustomize(ObjectMapper mapper) { mapper.registerModule(module); } + return mapper; } diff --git a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java new file mode 100644 index 00000000..3a51d240 --- /dev/null +++ b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java @@ -0,0 +1,55 @@ +package com.hubspot.rosetta.internal; + +import java.io.IOException; +import java.util.Iterator; +import java.util.Optional; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; + +public class NestedOptionalDeserializer extends StdScalarDeserializer> { + + private final Class referencedClazz; + + public NestedOptionalDeserializer(Class> clazz, Class referencedClazz) { + super(clazz); + this.referencedClazz = referencedClazz; + } + + @Override + public Optional deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + + JsonNode root = mapper.readValue(jp, JsonNode.class); + + return convert(root, mapper); + } + + private Optional convert(JsonNode root, ObjectMapper mapper) throws IOException { + if (root.isNull()) { + return Optional.empty(); + } + + Iterator fieldNames = root.fieldNames(); + + if (!fieldNames.hasNext()) { + throw new IllegalArgumentException("The provided object has no fields: " + root); + } + + while (fieldNames.hasNext()) { + String fieldName = fieldNames.next(); + + if (!root.get(fieldName).isNull()) { + return Optional.of( + mapper.treeToValue(root, referencedClazz) + ); + } + } + + return Optional.empty(); + } + +} diff --git a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java index 6956c49d..80e0a4ed 100644 --- a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java +++ b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Value; import com.fasterxml.jackson.core.Version; +import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; @@ -12,7 +13,9 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector; +import com.fasterxml.jackson.databind.type.ReferenceType; import com.fasterxml.jackson.databind.util.ClassUtil; +import com.hubspot.rosetta.annotations.NestedOptional; import com.hubspot.rosetta.annotations.RosettaCreator; import com.hubspot.rosetta.annotations.RosettaDeserialize; import com.hubspot.rosetta.annotations.RosettaIgnore; @@ -65,6 +68,7 @@ public JsonSerializer findSerializer(Annotated a) { @SuppressWarnings("unchecked") public JsonDeserializer findDeserializer(Annotated a) { StoredAsJson storedAsJson = a.getAnnotation(StoredAsJson.class); + NestedOptional nestedOptional = a.getAnnotation(NestedOptional.class); RosettaDeserialize rosettaDeserialize = a.getAnnotation(RosettaDeserialize.class); if (storedAsJson != null && rosettaDeserialize != null) { throw new IllegalArgumentException("Cannot have @StoredAsJson as well as @RosettaDeserialize annotations on the same entry"); @@ -78,6 +82,16 @@ public JsonDeserializer findDeserializer(Annotated a) { return new StoredAsJsonDeserializer(a.getRawType(), a.getType(), empty, objectMapper); } + if (nestedOptional != null && a instanceof AnnotatedMethod) { + AnnotatedMethod am = (AnnotatedMethod) a; + + JavaType parameterType = am.getParameterType(0); + if (parameterType instanceof ReferenceType) { + ReferenceType refType = (ReferenceType) parameterType; + return new NestedOptionalDeserializer(refType.getRawClass(), refType.getReferencedType().getRawClass()); + } + } + if (rosettaDeserialize != null) { Class klass = rosettaDeserialize.using(); if (klass != JsonDeserializer.None.class) { diff --git a/RosettaCore/src/test/java/com/hubspot/rosetta/annotations/NestedOptionalTest.java b/RosettaCore/src/test/java/com/hubspot/rosetta/annotations/NestedOptionalTest.java new file mode 100644 index 00000000..774e84cb --- /dev/null +++ b/RosettaCore/src/test/java/com/hubspot/rosetta/annotations/NestedOptionalTest.java @@ -0,0 +1,27 @@ +package com.hubspot.rosetta.annotations; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.IOException; + +import org.junit.Test; + +import com.hubspot.rosetta.Rosetta; +import com.hubspot.rosetta.beans.InnerBean; +import com.hubspot.rosetta.beans.NestedOptionalBean; + +public class NestedOptionalTest { + + @Test + public void testAnnotatedFieldDeserialization() throws IOException { + String nestedOptionalBeanJson = "" + + "{\"firstNestedOptional\":{\"stringProperty\":\"value-1\"}, " + + "\"secondNestedOptional\":{\"firstStringProperty\": null, \"secondStringProperty\": null}}"; + + NestedOptionalBean bean = Rosetta.getMapper().readValue(nestedOptionalBeanJson, NestedOptionalBean.class); + + assertThat(bean.getFirstNestedOptional().map(InnerBean::getStringProperty)).contains("value-1"); + assertThat(bean.getSecondNestedOptional()).isEmpty(); + } + +} diff --git a/RosettaCore/src/test/java/com/hubspot/rosetta/beans/MoreFieldsInnerFieldBean.java b/RosettaCore/src/test/java/com/hubspot/rosetta/beans/MoreFieldsInnerFieldBean.java new file mode 100644 index 00000000..397ea31b --- /dev/null +++ b/RosettaCore/src/test/java/com/hubspot/rosetta/beans/MoreFieldsInnerFieldBean.java @@ -0,0 +1,22 @@ +package com.hubspot.rosetta.beans; + +public class MoreFieldsInnerFieldBean { + private String firstStringProperty; + private String secondStringProperty; + + public String getFirstStringProperty() { + return firstStringProperty; + } + + public void setFirstStringProperty(String firstStringProperty) { + this.firstStringProperty = firstStringProperty; + } + + public String getSecondStringProperty() { + return secondStringProperty; + } + + public void setSecondStringProperty(String secondStringProperty) { + this.secondStringProperty = secondStringProperty; + } +} diff --git a/RosettaCore/src/test/java/com/hubspot/rosetta/beans/NestedOptionalBean.java b/RosettaCore/src/test/java/com/hubspot/rosetta/beans/NestedOptionalBean.java new file mode 100644 index 00000000..84573211 --- /dev/null +++ b/RosettaCore/src/test/java/com/hubspot/rosetta/beans/NestedOptionalBean.java @@ -0,0 +1,30 @@ +package com.hubspot.rosetta.beans; + +import java.util.Optional; + +import com.hubspot.rosetta.annotations.NestedOptional; + +public class NestedOptionalBean { + + @NestedOptional + private Optional firstNestedOptional; + + @NestedOptional + private Optional secondNestedOptional; + + public Optional getFirstNestedOptional() { + return firstNestedOptional; + } + + public void setFirstNestedOptional(Optional firstNestedOptional) { + this.firstNestedOptional = firstNestedOptional; + } + + public Optional getSecondNestedOptional() { + return secondNestedOptional; + } + + public void setSecondNestedOptional(Optional secondNestedOptional) { + this.secondNestedOptional = secondNestedOptional; + } +} From 8ffb34a3fe393ed5b8248101cca56fa2b75fe2d4 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Fri, 2 Jun 2023 09:50:28 +0100 Subject: [PATCH 02/11] We only deal with deserialization for the nested optionals - javadoc update to reflect this --- .../java/com/hubspot/rosetta/annotations/NestedOptional.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java b/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java index eec4e466..f2f4bae2 100644 --- a/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java +++ b/RosettaAnnotations/src/main/java/com/hubspot/rosetta/annotations/NestedOptional.java @@ -6,7 +6,7 @@ import java.lang.annotation.Target; /** - * Indicate that a field is a nested optional and serde operations will follow nested optional rules. + * Indicate that a field is a nested optional and deserialization will follow the nested optional rules. */ @Target({ElementType.FIELD, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) From f567144f7e9ed611b77b2aa6ed6b52f8424a7856 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Fri, 2 Jun 2023 09:51:19 +0100 Subject: [PATCH 03/11] Rm accidental whitespace --- RosettaCore/src/main/java/com/hubspot/rosetta/Rosetta.java | 1 - 1 file changed, 1 deletion(-) diff --git a/RosettaCore/src/main/java/com/hubspot/rosetta/Rosetta.java b/RosettaCore/src/main/java/com/hubspot/rosetta/Rosetta.java index 79fc90d7..484a2852 100644 --- a/RosettaCore/src/main/java/com/hubspot/rosetta/Rosetta.java +++ b/RosettaCore/src/main/java/com/hubspot/rosetta/Rosetta.java @@ -48,7 +48,6 @@ public static ObjectMapper cloneAndCustomize(ObjectMapper mapper) { mapper.registerModule(module); } - return mapper; } From a84048c2d75085a4a13a9fddb134e7ccbabbe80e Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Fri, 2 Jun 2023 13:29:51 +0100 Subject: [PATCH 04/11] Faster iteration over fields --- .../rosetta/internal/NestedOptionalDeserializer.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java index 3a51d240..77225d7a 100644 --- a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java +++ b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java @@ -2,6 +2,7 @@ import java.io.IOException; import java.util.Iterator; +import java.util.Map.Entry; import java.util.Optional; import com.fasterxml.jackson.core.JsonParser; @@ -33,16 +34,16 @@ private Optional convert(JsonNode root, ObjectMapper mapper) throws IOExcepti return Optional.empty(); } - Iterator fieldNames = root.fieldNames(); + Iterator> it = root.fields(); - if (!fieldNames.hasNext()) { + if (!it.hasNext()) { throw new IllegalArgumentException("The provided object has no fields: " + root); } - while (fieldNames.hasNext()) { - String fieldName = fieldNames.next(); + while (it.hasNext()) { + Entry entry = it.next(); - if (!root.get(fieldName).isNull()) { + if (!entry.getValue().isNull()) { return Optional.of( mapper.treeToValue(root, referencedClazz) ); From 45f9f0705ccc8c3a702a8fc8d148a939d11fd796 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Fri, 2 Jun 2023 13:33:47 +0100 Subject: [PATCH 05/11] StdScalarDeserializer -> StdDeserializer --- .../hubspot/rosetta/internal/NestedOptionalDeserializer.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java index 77225d7a..44a61d2e 100644 --- a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java +++ b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/NestedOptionalDeserializer.java @@ -9,9 +9,9 @@ import com.fasterxml.jackson.databind.DeserializationContext; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.deser.std.StdScalarDeserializer; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; -public class NestedOptionalDeserializer extends StdScalarDeserializer> { +public class NestedOptionalDeserializer extends StdDeserializer> { private final Class referencedClazz; From 7aaed0954f8ac8172ab4be6e3d62009a7850d562 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Tue, 6 Jun 2023 17:54:40 +0100 Subject: [PATCH 06/11] Added a test case for jdbi3 showing how the nested optional is used for a view object which fetches the entire optional nested object instead two separate optional fields. This pays off even more with a greater number of fields in the nested object, and when the nested object is subtyped. --- .../RosettaAnnotationIntrospector.java | 22 ++++--- .../rosetta/jdbi3/AbstractJdbiTest.java | 2 + .../rosetta/jdbi3/NestedOptionalTest.java | 29 +++++++++ .../com/hubspot/rosetta/jdbi3/TestDao.java | 15 +++++ .../rosetta/jdbi3/TestRelatedObject.java | 60 +++++++++++++++++++ .../hubspot/rosetta/jdbi3/TestViewObject.java | 59 ++++++++++++++++++ 6 files changed, 179 insertions(+), 8 deletions(-) create mode 100644 RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java create mode 100644 RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java create mode 100644 RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestViewObject.java diff --git a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java index 80e0a4ed..63cc9412 100644 --- a/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java +++ b/RosettaCore/src/main/java/com/hubspot/rosetta/internal/RosettaAnnotationIntrospector.java @@ -3,7 +3,6 @@ import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.annotation.JsonInclude.Value; import com.fasterxml.jackson.core.Version; -import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.JsonDeserializer; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; @@ -12,6 +11,7 @@ import com.fasterxml.jackson.databind.introspect.AnnotatedClass; import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.AnnotatedMethod; +import com.fasterxml.jackson.databind.introspect.AnnotatedParameter; import com.fasterxml.jackson.databind.introspect.NopAnnotationIntrospector; import com.fasterxml.jackson.databind.type.ReferenceType; import com.fasterxml.jackson.databind.util.ClassUtil; @@ -82,14 +82,10 @@ public JsonDeserializer findDeserializer(Annotated a) { return new StoredAsJsonDeserializer(a.getRawType(), a.getType(), empty, objectMapper); } - if (nestedOptional != null && a instanceof AnnotatedMethod) { - AnnotatedMethod am = (AnnotatedMethod) a; + if (nestedOptional != null && (a instanceof AnnotatedMethod || a instanceof AnnotatedParameter)) { + ReferenceType refType = getReferenceType(a); - JavaType parameterType = am.getParameterType(0); - if (parameterType instanceof ReferenceType) { - ReferenceType refType = (ReferenceType) parameterType; - return new NestedOptionalDeserializer(refType.getRawClass(), refType.getReferencedType().getRawClass()); - } + return new NestedOptionalDeserializer(refType.getRawClass(), refType.getReferencedType().getRawClass()); } if (rosettaDeserialize != null) { @@ -184,4 +180,14 @@ private Annotated getAnnotatedTypeFromAnnotatedMethod(AnnotatedMethod a) { throw new IllegalArgumentException("Cannot have @StoredAsJson on a method with no parameters AND no arguments"); } } + + private ReferenceType getReferenceType(Annotated a) { + if (a instanceof AnnotatedMethod) { + return (ReferenceType) ((AnnotatedMethod) a).getParameterType(0); + } else if (a instanceof AnnotatedParameter) { + return ReferenceType.upgradeFrom(a.getType(), a.getType().containedType(0)); + } else { + throw new IllegalArgumentException("Could not get ReferencedType."); + } + } } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java index 9409f19d..18af59b1 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java @@ -15,8 +15,10 @@ public void setup() { jdbi.useHandle(handle -> { handle.execute("CREATE TABLE IF NOT EXISTS test_table (id INT, name VARCHAR(255) NOT NULL, PRIMARY KEY (id))"); handle.execute("CREATE TABLE IF NOT EXISTS test_list_table (id INT, value INT NOT NULL, PRIMARY KEY (id))"); + handle.execute("CREATE TABLE IF NOT EXISTS test_nested_table (id INT, relatedId INT, name VARCHAR(255), score BIGINT, PRIMARY KEY (id))"); handle.execute("TRUNCATE TABLE test_table"); handle.execute("TRUNCATE TABLE test_list_table"); + handle.execute("TRUNCATE TABLE test_nested_table"); }); } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java new file mode 100644 index 00000000..ee6462ca --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java @@ -0,0 +1,29 @@ +package com.hubspot.rosetta.jdbi3; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Optional; + +import org.junit.Test; + +public class NestedOptionalTest extends AbstractJdbiTest { + + @Test + public void itDeserializesNestedObjects() { + TestObject firstObject = new TestObject(1, "name-1"); + TestRelatedObject relatedObject = new TestRelatedObject(1, 1, "related-name-1", 12); + + TestViewObject firstExpectedView = new TestViewObject(1, "name-1", Optional.of(relatedObject)); + + TestObject secondObject = new TestObject(2, "name-2"); + + TestViewObject secondExpectedView = new TestViewObject(2, "name-2", Optional.empty()); + + getDao().insert(firstObject); + getDao().insert(relatedObject); + + getDao().insert(secondObject); + + assertThat(getDao().getAllView()).contains(firstExpectedView, secondExpectedView); + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java index c69e1d4d..89a45674 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java @@ -1,6 +1,7 @@ package com.hubspot.rosetta.jdbi3; import java.util.List; + import org.jdbi.v3.sqlobject.SqlObject; import org.jdbi.v3.sqlobject.config.RegisterRowMapperFactory; import org.jdbi.v3.sqlobject.customizer.BindList.EmptyHandling; @@ -34,9 +35,23 @@ public interface TestDao extends SqlObject { @SqlQuery("SELECT * FROM test_list_table WHERE value IN ()") List getWithObjectFieldValue(@BindListWithRosetta(value = "values", field = "objectValue") List values); + @SqlQuery("SELECT " + + "test_table.id AS id, " + + "test_table.name AS name, " + + "r.id AS \"related.id\", " + + "r.relatedId AS \"related.relatedId\", " + + "r.name AS \"related.name\", " + + "r.score AS \"related.score\" " + + "FROM test_table LEFT JOIN test_nested_table as r " + + "ON test_table.id = r.relatedId;") + List getAllView(); + @SqlUpdate("INSERT INTO test_table (id, name) VALUES (:id, :name)") int insert(@BindWithRosetta TestObject object); @SqlUpdate("INSERT INTO test_list_table (id, value) VALUES (:id, :value)") int insert(@BindWithRosetta TestListObject object); + + @SqlUpdate("INSERT INTO test_nested_table (id, relatedId, name, score) VALUES (:id, :relatedId, :name, :score);") + int insert(@BindWithRosetta TestRelatedObject object); } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java new file mode 100644 index 00000000..e744de5b --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java @@ -0,0 +1,60 @@ +package com.hubspot.rosetta.jdbi3; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Objects; + +public class TestRelatedObject { + private final int id; + private final int relatedId; + private final String name; + private final long score; + + @JsonCreator + public TestRelatedObject(@JsonProperty("id") int id, @JsonProperty("relatedId") int relatedId, @JsonProperty("name") String name, @JsonProperty("score") long score) { + this.id = id; + this.relatedId = relatedId; + this.name = name; + this.score = score; + } + + public int getId() { + return id; + } + + public int getRelatedId() { + return relatedId; + } + + public String getName() { + return name; + } + + public long getScore() { + return score; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestRelatedObject that = (TestRelatedObject) o; + return id == that.id && relatedId == that.relatedId && score == that.score && Objects.equal(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, relatedId, name, score); + } + + @Override + public String toString() { + return "TestRelatedObject{" + + "id=" + id + + ", relatedId=" + relatedId + + ", name='" + name + '\'' + + ", score=" + score + + '}'; + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestViewObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestViewObject.java new file mode 100644 index 00000000..8503f765 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestViewObject.java @@ -0,0 +1,59 @@ +package com.hubspot.rosetta.jdbi3; + +import java.util.Optional; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Objects; +import com.google.common.base.Preconditions; +import com.hubspot.rosetta.annotations.NestedOptional; + +public class TestViewObject { + + private final int id; + private final String name; + + @NestedOptional + private final Optional related; + + @JsonCreator + public TestViewObject(@JsonProperty("id") int id, @JsonProperty("name") String name, @JsonProperty("related") Optional related) { + this.id = id; + this.name = name; + this.related = Preconditions.checkNotNull(related); + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public Optional getRelated() { + return related; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestViewObject that = (TestViewObject) o; + return id == that.id && Objects.equal(name, that.name) && Objects.equal(related, that.related); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, name, related); + } + + @Override + public String toString() { + return "TestViewObject{" + + "id=" + id + + ", name='" + name + '\'' + + ", related=" + related + + '}'; + } +} From 3fe489983f3f92719ad4f8940693aee4cae04af6 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Tue, 6 Jun 2023 18:07:02 +0100 Subject: [PATCH 07/11] More understandable related object: name -> otherName --- .../rosetta/jdbi3/NestedOptionalTest.java | 7 ++++++- .../java/com/hubspot/rosetta/jdbi3/TestDao.java | 2 +- .../hubspot/rosetta/jdbi3/TestRelatedObject.java | 16 ++++++++-------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java index ee6462ca..f2568998 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java @@ -9,7 +9,7 @@ public class NestedOptionalTest extends AbstractJdbiTest { @Test - public void itDeserializesNestedObjects() { + public void itDeserializesNestedObject() { TestObject firstObject = new TestObject(1, "name-1"); TestRelatedObject relatedObject = new TestRelatedObject(1, 1, "related-name-1", 12); @@ -26,4 +26,9 @@ public void itDeserializesNestedObjects() { assertThat(getDao().getAllView()).contains(firstExpectedView, secondExpectedView); } + + @Test + public void itDeserializedSubTypedNestedObject() { + + } } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java index 89a45674..26ac6c6e 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java @@ -40,7 +40,7 @@ public interface TestDao extends SqlObject { "test_table.name AS name, " + "r.id AS \"related.id\", " + "r.relatedId AS \"related.relatedId\", " + - "r.name AS \"related.name\", " + + "r.otherName AS \"related.otherName\", " + "r.score AS \"related.score\" " + "FROM test_table LEFT JOIN test_nested_table as r " + "ON test_table.id = r.relatedId;") diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java index e744de5b..a8efc673 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRelatedObject.java @@ -7,14 +7,14 @@ public class TestRelatedObject { private final int id; private final int relatedId; - private final String name; + private final String otherName; private final long score; @JsonCreator - public TestRelatedObject(@JsonProperty("id") int id, @JsonProperty("relatedId") int relatedId, @JsonProperty("name") String name, @JsonProperty("score") long score) { + public TestRelatedObject(@JsonProperty("id") int id, @JsonProperty("relatedId") int relatedId, @JsonProperty("otherName") String otherName, @JsonProperty("score") long score) { this.id = id; this.relatedId = relatedId; - this.name = name; + this.otherName = otherName; this.score = score; } @@ -26,8 +26,8 @@ public int getRelatedId() { return relatedId; } - public String getName() { - return name; + public String getOtherName() { + return otherName; } public long getScore() { @@ -40,12 +40,12 @@ public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TestRelatedObject that = (TestRelatedObject) o; - return id == that.id && relatedId == that.relatedId && score == that.score && Objects.equal(name, that.name); + return id == that.id && relatedId == that.relatedId && score == that.score && Objects.equal(otherName, that.otherName); } @Override public int hashCode() { - return Objects.hashCode(id, relatedId, name, score); + return Objects.hashCode(id, relatedId, otherName, score); } @Override @@ -53,7 +53,7 @@ public String toString() { return "TestRelatedObject{" + "id=" + id + ", relatedId=" + relatedId + - ", name='" + name + '\'' + + ", name='" + otherName + '\'' + ", score=" + score + '}'; } From 3831b9cd0500c97993e33569e78f6f13f1ece80e Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Tue, 6 Jun 2023 18:22:37 +0100 Subject: [PATCH 08/11] Red-Green nested objects --- .../rosetta/jdbi3/TestGreenNestedObject.java | 8 +++++++ .../rosetta/jdbi3/TestRedNestedObject.java | 8 +++++++ .../jdbi3/TestSubTypedNestedObject.java | 22 +++++++++++++++++++ .../rosetta/jdbi3/TestSubTypedViewObject.java | 2 ++ 4 files changed, 40 insertions(+) create mode 100644 RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java create mode 100644 RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java create mode 100644 RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java create mode 100644 RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java new file mode 100644 index 00000000..59a36e51 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java @@ -0,0 +1,8 @@ +package com.hubspot.rosetta.jdbi3; + +public class TestGreenSubTypedNestedObject implements TestSubTypedNestedObject { + @Override + public String getColor() { + return null; + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java new file mode 100644 index 00000000..23945860 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java @@ -0,0 +1,8 @@ +package com.hubspot.rosetta.jdbi3; + +public class TestRedSubTypedNestedObject implements TestSubTypedNestedObject { + @Override + public String getColor() { + return "RED"; + } +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java new file mode 100644 index 00000000..f1ad0159 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java @@ -0,0 +1,22 @@ +package com.hubspot.rosetta.jdbi3; + +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonSubTypes.Type; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import com.fasterxml.jackson.annotation.JsonTypeInfo.As; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = As.EXISTING_PROPERTY, + property = "color", + visible = true +) +@JsonSubTypes( + { + @Type(value = TestGreenNestedObject.class, name = "GREEN"), + @Type(value = TestRedNestedObject.class, name = "RED"), + } +) +public interface TestNestedObject { + String getColor(); +} diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java new file mode 100644 index 00000000..90cbf791 --- /dev/null +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java @@ -0,0 +1,2 @@ +package com.hubspot.rosetta.jdbi3;public class TestSubTypedViewObject { +} From fb9cc943e49ed9e2165dea946481397b02c7bb11 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Tue, 6 Jun 2023 18:23:02 +0100 Subject: [PATCH 09/11] Fixes to the nested object + green-red queries --- .../java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java | 4 +++- .../src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java index 18af59b1..8d7e01b3 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/AbstractJdbiTest.java @@ -15,10 +15,12 @@ public void setup() { jdbi.useHandle(handle -> { handle.execute("CREATE TABLE IF NOT EXISTS test_table (id INT, name VARCHAR(255) NOT NULL, PRIMARY KEY (id))"); handle.execute("CREATE TABLE IF NOT EXISTS test_list_table (id INT, value INT NOT NULL, PRIMARY KEY (id))"); - handle.execute("CREATE TABLE IF NOT EXISTS test_nested_table (id INT, relatedId INT, name VARCHAR(255), score BIGINT, PRIMARY KEY (id))"); + handle.execute("CREATE TABLE IF NOT EXISTS test_nested_table (id INT, relatedId INT, otherName VARCHAR(255), score BIGINT, PRIMARY KEY (id))"); + handle.execute("CREATE TABLE IF NOT EXISTS test_subtyped_nested_table (relatedId INT, color VARCHAR(255), relaxSong VARCHAR(255) DEFAULT NULL, relaxLevel BIGINT DEFAULT NULL, dangerLevel BIGINT DEFAULT NULL, PRIMARY KEY (relatedId))"); handle.execute("TRUNCATE TABLE test_table"); handle.execute("TRUNCATE TABLE test_list_table"); handle.execute("TRUNCATE TABLE test_nested_table"); + handle.execute("TRUNCATE TABLE test_subtyped_nested_table"); }); } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java index 26ac6c6e..e0c65c04 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java @@ -52,6 +52,12 @@ public interface TestDao extends SqlObject { @SqlUpdate("INSERT INTO test_list_table (id, value) VALUES (:id, :value)") int insert(@BindWithRosetta TestListObject object); - @SqlUpdate("INSERT INTO test_nested_table (id, relatedId, name, score) VALUES (:id, :relatedId, :name, :score);") + @SqlUpdate("INSERT INTO test_nested_table (id, relatedId, otherName, score) VALUES (:id, :relatedId, :otherName, :score);") int insert(@BindWithRosetta TestRelatedObject object); + + @SqlUpdate("INSERT INTO test_subtyped_nested_table (color, relatedId, dangerLevel) VALUES (:color, :relatedId, :dangerLevel);") + int insert(@BindWithRosetta TestRedNestedObject object); + + @SqlUpdate("INSERT INTO test_subtyped_nested_table (color, relatedId, relaxSong, relaxLevel) VALUES (:color, :relatedId, :relaxSong, :relaxLevel);") + int insert(@BindWithRosetta TestGreenNestedObject object); } From 52e2c4c537ce70b36484dcb1092e0e0d12ede956 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Tue, 6 Jun 2023 18:23:14 +0100 Subject: [PATCH 10/11] Fully fleshed subtyped objects --- .../rosetta/jdbi3/TestGreenNestedObject.java | 33 +++++++++++- .../rosetta/jdbi3/TestRedNestedObject.java | 43 ++++++++++++++- .../jdbi3/TestSubTypedNestedObject.java | 3 +- .../rosetta/jdbi3/TestSubTypedViewObject.java | 54 ++++++++++++++++++- 4 files changed, 128 insertions(+), 5 deletions(-) diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java index 59a36e51..df1e1923 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java @@ -1,8 +1,37 @@ package com.hubspot.rosetta.jdbi3; -public class TestGreenSubTypedNestedObject implements TestSubTypedNestedObject { +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class TestGreenNestedObject implements TestSubTypedNestedObject { + + private final int relatedId; + private final long relaxLevel; + private final String relaxSong; + + @JsonCreator + public TestGreenNestedObject(@JsonProperty("relatedId") int relatedId, + @JsonProperty("relaxLevel") long relaxLevel, + @JsonProperty("relaxSong") String relaxSong) { + this.relatedId = relatedId; + this.relaxLevel = relaxLevel; + this.relaxSong = relaxSong; + } + + public int getRelatedId() { + return relatedId; + } + + public long getRelaxLevel() { + return relaxLevel; + } + + public String getRelaxSong() { + return relaxSong; + } + @Override public String getColor() { - return null; + return "GREEN"; } } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java index 23945860..85698596 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestRedNestedObject.java @@ -1,8 +1,49 @@ package com.hubspot.rosetta.jdbi3; -public class TestRedSubTypedNestedObject implements TestSubTypedNestedObject { +import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Objects; + +public class TestRedNestedObject implements TestSubTypedNestedObject { + + private final int relatedId; + private final long dangerLevel; + + public TestRedNestedObject(@JsonProperty("relatedId") int relatedId, @JsonProperty("dangerLevel") long dangerLevel) { + this.relatedId = relatedId; + this.dangerLevel = dangerLevel; + } + + public int getRelatedId() { + return relatedId; + } + + public long getDangerLevel() { + return dangerLevel; + } + @Override public String getColor() { return "RED"; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestRedNestedObject that = (TestRedNestedObject) o; + return relatedId == that.relatedId && dangerLevel == that.dangerLevel; + } + + @Override + public int hashCode() { + return Objects.hashCode(relatedId, dangerLevel); + } + + @Override + public String toString() { + return "TestRedNestedObject{" + + "relatedId=" + relatedId + + ", dangerLevel=" + dangerLevel + + '}'; + } } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java index f1ad0159..7dbe4e17 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedNestedObject.java @@ -17,6 +17,7 @@ @Type(value = TestRedNestedObject.class, name = "RED"), } ) -public interface TestNestedObject { +public interface TestSubTypedNestedObject { + int getRelatedId(); String getColor(); } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java index 90cbf791..2fc54019 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java @@ -1,2 +1,54 @@ -package com.hubspot.rosetta.jdbi3;public class TestSubTypedViewObject { +package com.hubspot.rosetta.jdbi3; + +import java.util.Optional; + +import com.google.common.base.Objects; +import com.hubspot.rosetta.annotations.NestedOptional; + +public class TestSubTypedViewObject { + private final int id; + private final String name; + + @NestedOptional + private final Optional related; + + public TestSubTypedViewObject(int id, String name, Optional related) { + this.id = id; + this.name = name; + this.related = related; + } + + public int getId() { + return id; + } + + public String getName() { + return name; + } + + public Optional getRelated() { + return related; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestSubTypedViewObject that = (TestSubTypedViewObject) o; + return id == that.id && Objects.equal(name, that.name) && Objects.equal(related, that.related); + } + + @Override + public int hashCode() { + return Objects.hashCode(id, name, related); + } + + @Override + public String toString() { + return "TestSubTypedViewObject{" + + "id=" + id + + ", name='" + name + '\'' + + ", related=" + related + + '}'; + } } From bad29f54591ecf1719ef1150bca27ae32d9c0d24 Mon Sep 17 00:00:00 2001 From: Przemyslaw Gliniecki Date: Tue, 6 Jun 2023 18:31:41 +0100 Subject: [PATCH 11/11] Working demo of subtyped & optional nested objects as a field for a view object --- .../rosetta/jdbi3/NestedOptionalTest.java | 20 ++++++++++++++++ .../com/hubspot/rosetta/jdbi3/TestDao.java | 12 ++++++++++ .../rosetta/jdbi3/TestGreenNestedObject.java | 23 +++++++++++++++++++ .../rosetta/jdbi3/TestSubTypedViewObject.java | 5 +++- 4 files changed, 59 insertions(+), 1 deletion(-) diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java index f2568998..fe822497 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/NestedOptionalTest.java @@ -29,6 +29,26 @@ public void itDeserializesNestedObject() { @Test public void itDeserializedSubTypedNestedObject() { + TestObject firstObject = new TestObject(1, "name-1"); + TestGreenNestedObject firstRelatedObject = new TestGreenNestedObject(1, 1000L, "relax-song-1"); + + TestObject secondObject = new TestObject(2, "name-2"); + TestRedNestedObject secondRelatedObject = new TestRedNestedObject(2, 300L); + + TestObject thirdObject = new TestObject(3, "name-3"); + + TestSubTypedViewObject firstExpectedView = new TestSubTypedViewObject(1, "name-1", Optional.of(firstRelatedObject)); + TestSubTypedViewObject secondExpectedView = new TestSubTypedViewObject(2, "name-2", Optional.of(secondRelatedObject)); + TestSubTypedViewObject thirdExpectedView = new TestSubTypedViewObject(3, "name-3", Optional.empty()); + + getDao().insert(firstObject); + getDao().insert(firstRelatedObject); + + getDao().insert(secondObject); + getDao().insert(secondRelatedObject); + + getDao().insert(thirdObject); + assertThat(getDao().getAllSubTypedNestedView()).contains(firstExpectedView, secondExpectedView, thirdExpectedView); } } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java index e0c65c04..8bf78acd 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestDao.java @@ -46,6 +46,18 @@ public interface TestDao extends SqlObject { "ON test_table.id = r.relatedId;") List getAllView(); + @SqlQuery("SELECT " + + "test_table.id AS id, " + + "test_table.name AS name, " + + "subtyped.relatedId AS \"related.relatedId\", " + + "subtyped.color AS \"related.color\", " + + "subtyped.dangerLevel AS \"related.dangerLevel\", " + + "subtyped.relaxSong AS \"related.relaxSong\", " + + "subtyped.relaxLevel AS \"related.relaxLevel\" " + + "FROM test_table LEFT JOIN test_subtyped_nested_table as subtyped " + + "ON test_table.id = subtyped.relatedId;") + List getAllSubTypedNestedView(); + @SqlUpdate("INSERT INTO test_table (id, name) VALUES (:id, :name)") int insert(@BindWithRosetta TestObject object); diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java index df1e1923..72138e29 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestGreenNestedObject.java @@ -2,6 +2,7 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; +import com.google.common.base.Objects; public class TestGreenNestedObject implements TestSubTypedNestedObject { @@ -34,4 +35,26 @@ public String getRelaxSong() { public String getColor() { return "GREEN"; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + TestGreenNestedObject that = (TestGreenNestedObject) o; + return relatedId == that.relatedId && relaxLevel == that.relaxLevel && Objects.equal(relaxSong, that.relaxSong); + } + + @Override + public int hashCode() { + return Objects.hashCode(relatedId, relaxLevel, relaxSong); + } + + @Override + public String toString() { + return "TestGreenNestedObject{" + + "relatedId=" + relatedId + + ", relaxLevel=" + relaxLevel + + ", relaxSong='" + relaxSong + '\'' + + '}'; + } } diff --git a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java index 2fc54019..0b59c879 100644 --- a/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java +++ b/RosettaJdbi3/src/test/java/com/hubspot/rosetta/jdbi3/TestSubTypedViewObject.java @@ -2,6 +2,8 @@ import java.util.Optional; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.base.Objects; import com.hubspot.rosetta.annotations.NestedOptional; @@ -12,7 +14,8 @@ public class TestSubTypedViewObject { @NestedOptional private final Optional related; - public TestSubTypedViewObject(int id, String name, Optional related) { + @JsonCreator + public TestSubTypedViewObject(@JsonProperty("id") int id, @JsonProperty("name") String name, @JsonProperty("related") Optional related) { this.id = id; this.name = name; this.related = related;