diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 1400b2180..6e1e1f0d7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -289,7 +289,7 @@ public final class DefaultTypeManager implements TypeManager { @Override public ScalarType getDbMapScalarType() { - return (postgres) ? hstoreType : ScalarTypeJsonMap.typeFor(false, Types.VARCHAR); + return (postgres) ? hstoreType : ScalarTypeJsonMap.typeFor(false, Types.VARCHAR, false); } @Override @@ -312,7 +312,7 @@ public final class DefaultTypeManager implements TypeManager { return arrayTypeSetFactory.typeFor(valueType, nullable); } // fallback to JSON storage in VARCHAR column - return new ScalarTypeJsonSet.Varchar(getDocType(valueType), nullable); + return new ScalarTypeJsonSet.Varchar(getDocType(valueType), nullable, false); // TODO: keepSource for @DbArray? } private ScalarType getArrayScalarTypeList(Type valueType, boolean nullable) { @@ -323,7 +323,7 @@ public final class DefaultTypeManager implements TypeManager { return arrayTypeListFactory.typeFor(valueType, nullable); } // fallback to JSON storage in VARCHAR column - return new ScalarTypeJsonList.Varchar(getDocType(valueType), nullable); + return new ScalarTypeJsonList.Varchar(getDocType(valueType), nullable, false); // TODO: keepSource for @DbArray? } private Class> asEnumClass(Type valueType) { @@ -340,10 +340,11 @@ public final class DefaultTypeManager implements TypeManager { Type genericType = prop.getGenericType(); boolean hasJacksonAnnotations = objectMapperPresent && checkJacksonAnnotations(prop); + boolean keepSource = prop.getMutationDetection() == MutationDetection.SOURCE; if (type.equals(List.class)) { DocPropertyType docType = getDocType(genericType); if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) { - return ScalarTypeJsonList.typeFor(postgres, dbType, docType, prop.isNullable()); + return ScalarTypeJsonList.typeFor(postgres, dbType, docType, prop.isNullable(), keepSource); } else { return createJsonObjectMapperType(prop, dbType, docType); } @@ -351,19 +352,19 @@ public final class DefaultTypeManager implements TypeManager { if (type.equals(Set.class)) { DocPropertyType docType = getDocType(genericType); if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) { - return ScalarTypeJsonSet.typeFor(postgres, dbType, docType, prop.isNullable()); + return ScalarTypeJsonSet.typeFor(postgres, dbType, docType, prop.isNullable(), keepSource); } else { return createJsonObjectMapperType(prop, dbType, docType); } } if (type.equals(Map.class)) { if (!hasJacksonAnnotations && isMapValueTypeObject(genericType)) { - return ScalarTypeJsonMap.typeFor(postgres, dbType); + return ScalarTypeJsonMap.typeFor(postgres, dbType, keepSource); } else { return createJsonObjectMapperType(prop, dbType, DocPropertyType.OBJECT); } } - if (objectMapperPresent) { + if (objectMapperPresent && prop.getMutationDetection() == MutationDetection.DEFAULT) { if (type.equals(JsonNode.class)) { switch (dbType) { case Types.VARCHAR: diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java index a196085a6..7ca361405 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java @@ -24,24 +24,24 @@ public class ScalarTypeJsonList { /** * Return the appropriate ScalarType based requested dbType and if Postgres. */ - public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docType, boolean nullable) { + public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) { if (postgres) { switch (dbType) { case DbPlatformType.JSONB: - return new ScalarTypeJsonList.JsonB(docType, nullable); + return new ScalarTypeJsonList.JsonB(docType, nullable, keepSource); case DbPlatformType.JSON: - return new ScalarTypeJsonList.Json(docType, nullable); + return new ScalarTypeJsonList.Json(docType, nullable, keepSource); } } - return new ScalarTypeJsonList.Varchar(docType, nullable); + return new ScalarTypeJsonList.Varchar(docType, nullable, keepSource); } /** * List mapped to DB VARCHAR. */ public static class Varchar extends ScalarTypeJsonList.Base { - public Varchar(DocPropertyType docType, boolean nullable) { - super(Types.VARCHAR, docType, nullable); + public Varchar(DocPropertyType docType, boolean nullable, boolean keepSource) { + super(Types.VARCHAR, docType, nullable, keepSource); } } @@ -49,8 +49,8 @@ public class ScalarTypeJsonList { * List mapped to Postgres JSON. */ private static class Json extends ScalarTypeJsonList.PgBase { - public Json(DocPropertyType docType, boolean nullable) { - super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docType, nullable); + public Json(DocPropertyType docType, boolean nullable, boolean keepSource) { + super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docType, nullable, keepSource); } } @@ -58,8 +58,8 @@ public class ScalarTypeJsonList { * List mapped to Postgres JSONB. */ private static class JsonB extends ScalarTypeJsonList.PgBase { - public JsonB(DocPropertyType docType, boolean nullable) { - super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docType, nullable); + public JsonB(DocPropertyType docType, boolean nullable, boolean keepSource) { + super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docType, nullable, keepSource); } } @@ -68,14 +68,24 @@ public class ScalarTypeJsonList { */ @SuppressWarnings("rawtypes") private abstract static class Base extends ScalarTypeJsonCollection { + private final boolean keepSource; - public Base(int dbType, DocPropertyType docType, boolean nullable) { + public Base(int dbType, DocPropertyType docType, boolean nullable, boolean keepSource) { super(List.class, dbType, docType, nullable); + this.keepSource = keepSource; + } + + @Override + public boolean isJsonMapper() { + return keepSource; } @Override public List read(DataReader reader) throws SQLException { String json = reader.getString(); + if (isJsonMapper()) { + reader.pushJson(json); + } try { // parse JSON into modifyAware list return EJson.parseList(json, true); @@ -85,17 +95,15 @@ public class ScalarTypeJsonList { } @Override - public void bind(DataBinder binder, List value) throws SQLException { + public final void bind(DataBinder binder, List value) throws SQLException { + String rawJson = isJsonMapper() ? binder.popJson() : null; + if (rawJson == null && value != null) { + rawJson = formatValue(value); + } if (value == null) { bindNull(binder); - } else if (value.isEmpty()) { - binder.setString("[]"); } else { - try { - binder.setString(EJson.write(value)); - } catch (IOException e) { - throw new SQLException("Failed to format List into JSON content", e); - } + bindRawJson(binder, rawJson); } } @@ -107,9 +115,16 @@ public class ScalarTypeJsonList { binder.setString("[]"); } } + + protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + binder.setString(rawJson); + } @Override public String formatValue(List value) { + if (value.isEmpty()) { + return "[]"; + } try { return EJson.write(value); } catch (IOException e) { @@ -144,19 +159,14 @@ public class ScalarTypeJsonList { final String pgType; - PgBase(int jdbcType, String pgType, DocPropertyType docType, boolean nullable) { - super(jdbcType, docType, nullable); + PgBase(int jdbcType, String pgType, DocPropertyType docType, boolean nullable, boolean keepSource) { + super(jdbcType, docType, nullable, keepSource); this.pgType = pgType; } - @SuppressWarnings("rawtypes") @Override - public void bind(DataBinder binder, List value) throws SQLException { - if (value == null) { - bindNull(binder); - } else { - binder.setObject(PostgresHelper.asObject(pgType, formatValue(value))); - } + protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + binder.setObject(PostgresHelper.asObject(pgType, rawJson)); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java index 6dd4f900b..507e7012b 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMap.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; +import java.io.StringWriter; import java.nio.charset.StandardCharsets; import java.sql.SQLException; import java.sql.Types; @@ -26,27 +27,21 @@ import java.util.Map; @SuppressWarnings("rawtypes") public abstract class ScalarTypeJsonMap extends ScalarTypeBase { - private static final ScalarTypeJsonMap CLOB = new ScalarTypeJsonMap.Clob(); - private static final ScalarTypeJsonMap BLOB = new ScalarTypeJsonMap.Blob(); - private static final ScalarTypeJsonMap VARCHAR = new ScalarTypeJsonMap.Varchar(); - private static final ScalarTypeJsonMap JSON = new ScalarTypeJsonMapPostgres.JSON(); - private static final ScalarTypeJsonMap JSONB = new ScalarTypeJsonMapPostgres.JSONB(); - /** * Return the ScalarType for the requested dbType and postgres. */ - public static ScalarTypeJsonMap typeFor(boolean postgres, int dbType) { + public static ScalarTypeJsonMap typeFor(boolean postgres, int dbType, boolean keepSource) { switch (dbType) { case Types.VARCHAR: - return VARCHAR; + return new ScalarTypeJsonMap.Varchar(keepSource); case Types.BLOB: - return BLOB; + return new ScalarTypeJsonMap.Blob(keepSource); case Types.CLOB: - return CLOB; + return new ScalarTypeJsonMap.Clob(keepSource); case DbPlatformType.JSONB: - return postgres ? JSONB : CLOB; + return postgres ? new ScalarTypeJsonMapPostgres.JSONB(keepSource) : new ScalarTypeJsonMap.Clob(keepSource); case DbPlatformType.JSON: - return postgres ? JSON : CLOB; + return postgres ? new ScalarTypeJsonMapPostgres.JSON(keepSource) : new ScalarTypeJsonMap.Clob(keepSource); default: throw new IllegalStateException("Unknown dbType " + dbType); } @@ -54,41 +49,50 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { public static class Clob extends ScalarTypeJsonMap { - public Clob() { - super(Types.CLOB); + public Clob(boolean keepSource) { + super(Types.CLOB, keepSource); } @Override - public Map read(DataReader reader) throws SQLException { - String content = reader.getStringFromStream(); - if (content == null) { - return null; - } - return parse(content); + protected String readJson(DataReader reader) throws SQLException { + return reader.getStringFromStream(); } } public static class Varchar extends ScalarTypeJsonMap { - public Varchar() { - super(Types.VARCHAR); + public Varchar(boolean keepSource) { + super(Types.VARCHAR, keepSource); } } public static class Blob extends ScalarTypeJsonMap { - public Blob() { - super(Types.BLOB); + public Blob(boolean keepSource) { + super(Types.BLOB, keepSource); } @Override public Map read(DataReader reader) throws SQLException { InputStream is = reader.getBinaryStream(); if (is == null) { + if (isJsonMapper()) { + reader.pushJson(null); + } return null; } try { - try (InputStreamReader inputStreamReader = new InputStreamReader(is)) { - return parse(inputStreamReader); + if (isJsonMapper()) { + StringWriter rawJson = new StringWriter(); + try (InputStreamReader inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + inputStreamReader.transferTo(rawJson); + } + reader.pushJson(rawJson.toString()); + return parse(rawJson.toString()); + + } else { + try (InputStreamReader inputStreamReader = new InputStreamReader(is, StandardCharsets.UTF_8)) { + return parse(inputStreamReader); + } } } catch (IOException e) { throw new SQLException("Error reading Blob stream from DB", e); @@ -96,18 +100,22 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { } @Override - public void bind(DataBinder binder, Map value) throws SQLException { - if (value == null) { - binder.setNull(Types.BLOB); - } else { - String rawJson = formatValue(value); - binder.setBytes(rawJson.getBytes(StandardCharsets.UTF_8)); - } + protected void bindNull(DataBinder binder) throws SQLException { + binder.setNull(Types.BLOB); } + + @Override + protected void bindJson(DataBinder binder, String rawJson) throws SQLException { + binder.setBytes(rawJson.getBytes(StandardCharsets.UTF_8)); + } + } - public ScalarTypeJsonMap(int jdbcType) { + private final boolean keepSource; + + public ScalarTypeJsonMap(int jdbcType, boolean keepSource) { super(Map.class, false, jdbcType); + this.keepSource = keepSource; } /** @@ -125,26 +133,56 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase { public boolean isDirty(Object value) { return TypeJsonManager.checkIsDirty(value); } + + + + @Override + public boolean isJsonMapper() { + return keepSource; + } + @Override public Map read(DataReader reader) throws SQLException { - String rawJson = reader.getString(); + String rawJson = readJson(reader); + if (isJsonMapper()) { + reader.pushJson(rawJson); + } + if (rawJson == null) { return null; } + return parse(rawJson); } + + protected String readJson(DataReader reader) throws SQLException { + return reader.getString(); + } + @Override - public void bind(DataBinder binder, Map value) throws SQLException { + public final void bind(DataBinder binder, Map value) throws SQLException { + String rawJson = isJsonMapper() ? binder.popJson() : null; + if (rawJson == null && value != null) { + rawJson = formatValue(value); + } + if (value == null) { - binder.setNull(Types.VARCHAR); + bindNull(binder); } else { - String rawJson = formatValue(value); - binder.setString(rawJson); + bindJson(binder, rawJson); } } + + protected void bindNull(DataBinder binder) throws SQLException { + binder.setNull(Types.VARCHAR); + } + protected void bindJson(DataBinder binder, String rawJson) throws SQLException { + binder.setString(rawJson); + } + @Override public Object toJdbcType(Object value) { return value; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java index 87eecc5f9..46a585ebc 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java @@ -4,7 +4,6 @@ import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.core.type.DataBinder; import java.sql.SQLException; -import java.util.Map; /** * Support for the Postgres DB types JSON and JSONB. @@ -13,15 +12,18 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { final String postgresType; - ScalarTypeJsonMapPostgres(int jdbcType, String postgresType) { - super(jdbcType); + ScalarTypeJsonMapPostgres(int jdbcType, String postgresType, boolean keepSource) { + super(jdbcType, keepSource); this.postgresType = postgresType; } - @SuppressWarnings("rawtypes") @Override - public void bind(DataBinder binder, Map value) throws SQLException { - String rawJson = (value == null) ? null : formatValue(value); + protected void bindNull(DataBinder binder) throws SQLException { + binder.setObject(PostgresHelper.asObject(postgresType, null)); + } + + @Override + protected void bindJson(DataBinder binder, String rawJson) throws SQLException { binder.setObject(PostgresHelper.asObject(postgresType, rawJson)); } @@ -30,8 +32,8 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { */ public static class JSON extends ScalarTypeJsonMapPostgres { - public JSON() { - super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE); + public JSON(boolean keepSource) { + super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, keepSource); } } @@ -40,8 +42,8 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap { */ public static class JSONB extends ScalarTypeJsonMapPostgres { - public JSONB() { - super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE); + public JSONB(boolean keepSource) { + super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, keepSource); } } } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java index d95adb572..7e728b0d3 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java @@ -26,24 +26,24 @@ public class ScalarTypeJsonSet { /** * Return the appropriate ScalarType for the requested dbType and Postgres. */ - public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType, boolean nullable) { + public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { if (postgres) { switch (dbType) { case DbPlatformType.JSONB: - return new ScalarTypeJsonSet.JsonB(docPropertyType, nullable); + return new ScalarTypeJsonSet.JsonB(docPropertyType, nullable, keepSource); case DbPlatformType.JSON: - return new ScalarTypeJsonSet.Json(docPropertyType, nullable); + return new ScalarTypeJsonSet.Json(docPropertyType, nullable, keepSource); } } - return new ScalarTypeJsonSet.Varchar(docPropertyType, nullable); + return new ScalarTypeJsonSet.Varchar(docPropertyType, nullable, keepSource); } /** * List mapped to DB VARCHAR. */ public static class Varchar extends ScalarTypeJsonSet.Base { - public Varchar(DocPropertyType docPropertyType, boolean nullable) { - super(Types.VARCHAR, docPropertyType, nullable); + public Varchar(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + super(Types.VARCHAR, docPropertyType, nullable, keepSource); } } @@ -51,8 +51,8 @@ public class ScalarTypeJsonSet { * List mapped to Postgres JSON. */ private static class Json extends ScalarTypeJsonSet.PgBase { - public Json(DocPropertyType docPropertyType, boolean nullable) { - super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docPropertyType, nullable); + public Json(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docPropertyType, nullable, keepSource); } } @@ -60,8 +60,8 @@ public class ScalarTypeJsonSet { * List mapped to Postgres JSONB. */ private static class JsonB extends ScalarTypeJsonSet.PgBase { - public JsonB(DocPropertyType docPropertyType, boolean nullable) { - super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docPropertyType, nullable); + public JsonB(DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docPropertyType, nullable, keepSource); } } @@ -71,13 +71,25 @@ public class ScalarTypeJsonSet { @SuppressWarnings("rawtypes") private abstract static class Base extends ScalarTypeJsonCollection { - public Base(int dbType, DocPropertyType docPropertyType, boolean nullable) { + private boolean keepSource; + + public Base(int dbType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { super(Set.class, dbType, docPropertyType, nullable); + this.keepSource = keepSource; } + + @Override + public boolean isJsonMapper() { + return keepSource; + } + @Override public Set read(DataReader reader) throws SQLException { String json = reader.getString(); + if (isJsonMapper()) { + reader.pushJson(json); + } try { // parse JSON into modifyAware list return EJson.parseSet(json, true); @@ -87,17 +99,15 @@ public class ScalarTypeJsonSet { } @Override - public void bind(DataBinder binder, Set value) throws SQLException { + public final void bind(DataBinder binder, Set value) throws SQLException { + String rawJson = isJsonMapper() ? binder.popJson() : null; + if (rawJson == null && value != null) { + rawJson = formatValue(value); + } if (value == null) { bindNull(binder); - } else if (value.isEmpty()) { - binder.setString("[]"); } else { - try { - binder.setString(EJson.write(value)); - } catch (IOException e) { - throw new SQLException("Failed to format Set into JSON content", e); - } + bindRawJson(binder, rawJson); } } @@ -110,8 +120,15 @@ public class ScalarTypeJsonSet { } } + protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + binder.setString(rawJson); + } + @Override public String formatValue(Set value) { + if (value.isEmpty()) { + return "[]"; + } try { return EJson.write(value); } catch (IOException e) { @@ -151,19 +168,14 @@ public class ScalarTypeJsonSet { final String pgType; - PgBase(int jdbcType, String pgType, DocPropertyType docPropertyType, boolean nullable) { - super(jdbcType, docPropertyType, nullable); + PgBase(int jdbcType, String pgType, DocPropertyType docPropertyType, boolean nullable, boolean keepSource) { + super(jdbcType, docPropertyType, nullable, keepSource); this.pgType = pgType; } - @SuppressWarnings("rawtypes") @Override - public void bind(DataBinder binder, Set value) throws SQLException { - if (value == null) { - bindNull(binder); - } else { - binder.setObject(PostgresHelper.asObject(pgType, formatValue(value))); - } + protected void bindRawJson(DataBinder binder, String rawJson) throws SQLException { + binder.setObject(PostgresHelper.asObject(pgType, rawJson)); } @Override diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java index ff329efac..43ed867ba 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java @@ -11,19 +11,19 @@ public class ScalarTypeJsonListTest extends BasePlatformArrayTypeFactoryTest { @Test public void typeFor_expect_nullToEmpty_when_postgresNonNull() throws SQLException { - assertBindNullTo_PGObjectEmpty(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONB, DocPropertyType.OBJECT, false)); - assertBindNullTo_PGObjectEmpty(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSON, DocPropertyType.OBJECT, false)); + assertBindNullTo_PGObjectEmpty(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONB, DocPropertyType.OBJECT, false, false)); + assertBindNullTo_PGObjectEmpty(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSON, DocPropertyType.OBJECT, false, false)); - assertBindNullTo_EmptyString(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, false)); + assertBindNullTo_EmptyString(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, false, false)); } @Test public void typeFor_expect_nullToNull_when_nullable() throws SQLException { - assertBindNullTo_PGObjectNull(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONB, DocPropertyType.OBJECT, true)); - assertBindNullTo_PGObjectNull(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSON, DocPropertyType.OBJECT, true)); + assertBindNullTo_PGObjectNull(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONB, DocPropertyType.OBJECT, true, false)); + assertBindNullTo_PGObjectNull(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSON, DocPropertyType.OBJECT, true, false)); - assertBindNullTo_Null(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, true)); + assertBindNullTo_Null(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, true, false)); } } diff --git a/ebean-core/src/test/java/org/tests/json/TestOldValue.java b/ebean-core/src/test/java/org/tests/json/TestOldValue.java new file mode 100644 index 000000000..f7bd391e5 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/json/TestOldValue.java @@ -0,0 +1,113 @@ +package org.tests.json; + +import io.ebean.BaseTestCase; +import io.ebean.DB; +import io.ebean.ValuePair; + +import org.assertj.core.api.SoftAssertions; +import org.junit.Ignore; +import org.junit.Test; +import org.tests.model.json.EBasicOldValue; + +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class TestOldValue extends BaseTestCase { + + + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + public void testDbJsonOldValue() throws Exception { + EBasicOldValue bean = new EBasicOldValue(); + JsonNodeFactory jnf = new JsonNodeFactory(false); + + bean.getStringList().add("sl1"); + bean.getStringSet().add("ss1"); + bean.getObjectMap().put("sk1","sm1"); + bean.getLongList().add(1L); + bean.getLongSet().add(1001L); + bean.getLongMap().put("lk1",2001L); + bean.getIntList().add(2); + bean.getIntSet().add(1002); + bean.getIntMap().put("ik1",2002); + + bean.setJsonNode(jnf.arrayNode().add("Foo")); + + DB.save(bean); + bean = DB.find(EBasicOldValue.class, bean.getId()); + + bean.getStringList().add("sl2"); + bean.getStringSet().add("ss2"); + bean.getObjectMap().put("sk2","sm2"); + bean.getLongList().add(5L); + bean.getLongSet().add(1005L); + bean.getLongMap().put("lk2",2005L); + bean.getIntList().add(6); + bean.getIntSet().add(1006); + bean.getIntMap().put("ik2",2006); + ((ArrayNode)bean.getJsonNode()).add("Bar"); + + Map dirty = DB.getBeanState(bean).getDirtyValues(); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(dirty).hasSize(10); + + softly.assertThat((List)dirty.get("stringList").getOldValue()).containsExactly("sl1"); + softly.assertThat((List)dirty.get("stringList").getNewValue()).containsExactly("sl1", "sl2"); + softly.assertThat((List)dirty.get("longList").getOldValue()).containsExactly(1L); + softly.assertThat((List)dirty.get("longList").getNewValue()).containsExactly(1L, 5L); + softly.assertThat((List)dirty.get("intList").getOldValue()).containsExactly(2); + softly.assertThat((List)dirty.get("intList").getNewValue()).containsExactly(2, 6); + + softly.assertThat((Set)dirty.get("stringSet").getOldValue()).containsExactly("ss1"); + softly.assertThat((Set)dirty.get("stringSet").getNewValue()).containsExactly("ss1", "ss2"); + softly.assertThat((Set)dirty.get("longSet").getOldValue()).containsExactly(1001L); + softly.assertThat((Set)dirty.get("longSet").getNewValue()).containsExactly(1001L, 1005L); + softly.assertThat((Set)dirty.get("intSet").getOldValue()).containsExactly(1002); + softly.assertThat((Set)dirty.get("intSet").getNewValue()).containsExactly(1002, 1006); + + softly.assertThat((Map)dirty.get("objectMap").getOldValue()).containsEntry("sk1","sm1").hasSize(1); + softly.assertThat((Map)dirty.get("objectMap").getNewValue()).containsEntry("sk1","sm1").containsEntry("sk2","sm2").hasSize(2); + softly.assertThat((Map)dirty.get("longMap").getOldValue()).containsEntry("lk1",2001L).hasSize(1); + softly.assertThat((Map)dirty.get("longMap").getNewValue()).containsEntry("lk1",2001L).containsEntry("lk2",2005L).hasSize(2); + softly.assertThat((Map)dirty.get("intMap").getOldValue()).containsEntry("ik1",2002).hasSize(1); + softly.assertThat((Map)dirty.get("intMap").getNewValue()).containsEntry("ik1",2002).containsEntry("ik2",2006).hasSize(2); + + softly.assertThat((ArrayNode)dirty.get("jsonNode").getOldValue()).hasToString("[\"Foo\"]"); + softly.assertThat((ArrayNode)dirty.get("jsonNode").getNewValue()).hasToString("[\"Foo\",\"Bar\"]"); + + softly.assertAll(); + + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Test + @Ignore("Old value detection does not work for @DbArray") + public void testDbArrayOldValue() throws Exception { + EBasicOldValue bean = new EBasicOldValue(); + + bean.getStringArr().add("sa1"); + + + DB.save(bean); + bean = DB.find(EBasicOldValue.class, bean.getId()); + + bean.getStringArr().add("sa2"); + + + + Map dirty = DB.getBeanState(bean).getDirtyValues(); + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(dirty).hasSize(1); + + softly.assertThat((List)dirty.get("stringArr").getOldValue()).containsExactly("sa1"); + softly.assertThat((List)dirty.get("stringArr").getNewValue()).containsExactly("sa1", "sa2"); + + softly.assertAll(); + + } +} diff --git a/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java b/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java new file mode 100644 index 000000000..37e805412 --- /dev/null +++ b/ebean-core/src/test/java/org/tests/model/json/EBasicOldValue.java @@ -0,0 +1,161 @@ +package org.tests.model.json; + +import io.ebean.annotation.DbArray; +import io.ebean.annotation.DbJson; + +import javax.persistence.Entity; +import javax.persistence.Id; + +import com.fasterxml.jackson.databind.JsonNode; + +import java.util.*; + +import static io.ebean.annotation.MutationDetection.SOURCE; + +@Entity +public class EBasicOldValue { + + @Id + Long id; + + String name; + + @DbJson(mutationDetection = SOURCE) + Set stringSet = new LinkedHashSet<>(); + + @DbJson(mutationDetection = SOURCE) + Set longSet = new LinkedHashSet<>(); + + @DbJson(mutationDetection = SOURCE) + Set intSet = new LinkedHashSet<>(); + + @DbJson(mutationDetection = SOURCE) + List stringList = new ArrayList<>(); + + @DbJson(mutationDetection = SOURCE) + List longList = new ArrayList<>(); + + @DbJson(mutationDetection = SOURCE) + List intList = new ArrayList<>(); + + @DbJson(mutationDetection = SOURCE) + Map objectMap = new LinkedHashMap<>(); + + @DbJson(mutationDetection = SOURCE) + Map longMap = new LinkedHashMap<>(); + + @DbJson(mutationDetection = SOURCE) + Map intMap = new LinkedHashMap<>(); + + @DbJson(mutationDetection = SOURCE) + JsonNode jsonNode; + + @DbArray() + List stringArr = new ArrayList<>(); + + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Set getStringSet() { + return stringSet; + } + + public void setStringSet(Set stringSet) { + this.stringSet = stringSet; + } + + public Set getLongSet() { + return longSet; + } + + public void setLongSet(Set longSet) { + this.longSet = longSet; + } + + public Set getIntSet() { + return intSet; + } + + public void setIntSet(Set intSet) { + this.intSet = intSet; + } + + public List getStringList() { + return stringList; + } + + public void setStringList(List stringList) { + this.stringList = stringList; + } + + public List getLongList() { + return longList; + } + + public void setLongList(List longList) { + this.longList = longList; + } + + public List getIntList() { + return intList; + } + + public void setIntList(List intList) { + this.intList = intList; + } + + public Map getObjectMap() { + return objectMap; + } + + public void setObjectMap(Map objectMap) { + this.objectMap = objectMap; + } + + public Map getLongMap() { + return longMap; + } + + public void setLongMap(Map longMap) { + this.longMap = longMap; + } + + public Map getIntMap() { + return intMap; + } + + public void setIntMap(Map intMap) { + this.intMap = intMap; + } + + public JsonNode getJsonNode() { + return jsonNode; + } + + public void setJsonNode(JsonNode jsonNode) { + this.jsonNode = jsonNode; + } + + public List getStringArr() { + return stringArr; + } + + public void setStringArr(List stringArr) { + this.stringArr = stringArr; + } + +}