diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java index 023899247..70b04d03e 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/AnnotationFields.java @@ -221,6 +221,38 @@ public class AnnotationFields extends AnnotationParser { util.setLobType(prop); } + Length length = get(prop, Length.class); + if (length != null) { + prop.setDbLength(length.value()); + } + + io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class); + if (nonNull != null) { + prop.setNullable(false); + } + + if (validationAnnotations) { + NotNull notNull = get(prop, NotNull.class); + if (notNull != null && isEbeanValidationGroups(notNull.groups())) { + // Not null on all validation groups so enable + // DDL generation of Not Null Constraint + prop.setNullable(false); + } + + if (!prop.isLob()) { + // take the max size of all @Size annotations + int maxSize = -1; + for (Size size : getAll(prop, Size.class)) { + if (size.max() < Integer.MAX_VALUE) { + maxSize = Math.max(maxSize, size.max()); + } + } + if (maxSize != -1) { + prop.setDbLength(maxSize); + } + } + } + if (get(prop, TenantId.class) != null) { prop.setTenantId(); } @@ -322,41 +354,8 @@ public class AnnotationFields extends AnnotationParser { prop.setExcludedFromHistory(); } - Length length = get(prop, Length.class); - if (length != null) { - prop.setDbLength(length.value()); - } - - io.ebean.annotation.NotNull nonNull = get(prop, io.ebean.annotation.NotNull.class); - if (nonNull != null) { - prop.setNullable(false); - } - readDbMigration(prop); - - if (validationAnnotations) { - NotNull notNull = get(prop, NotNull.class); - if (notNull != null && isEbeanValidationGroups(notNull.groups())) { - // Not null on all validation groups so enable - // DDL generation of Not Null Constraint - prop.setNullable(false); - } - - if (!prop.isLob()) { - // take the max size of all @Size annotations - int maxSize = -1; - for (Size size : getAll(prop, Size.class)) { - if (size.max() < Integer.MAX_VALUE) { - maxSize = Math.max(maxSize, size.max()); - } - } - if (maxSize != -1) { - prop.setDbLength(maxSize); - } - } - } - // Want to process last so we can use with @Formula Transient t = get(prop, Transient.class); if (t != null) { diff --git a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java index e63533364..5e68bc052 100644 --- a/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java +++ b/src/main/java/io/ebeaninternal/server/deploy/parse/DeployUtil.java @@ -191,7 +191,7 @@ public class DeployUtil { void setDbArray(DeployBeanProperty prop, DbArray dbArray) { Class type = prop.getPropertyType(); - ScalarType scalarType = typeManager.getArrayScalarType(type, dbArray, prop.getGenericType()); + ScalarType scalarType = typeManager.getArrayScalarType(type, dbArray, prop.getGenericType(), prop.isNullable()); if (scalarType == null) { throw new RuntimeException("No ScalarType for @DbArray type for [" + prop.getFullBeanName() + "]"); } diff --git a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 094a78758..7a4f91cd3 100644 --- a/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -354,29 +354,42 @@ public final class DefaultTypeManager implements TypeManager { } @Override - public ScalarType getArrayScalarType(Class type, DbArray dbArray, Type genericType) { + public ScalarType getArrayScalarType(Class type, DbArray dbArray, Type genericType, boolean nullable) { Type valueType = getValueType(genericType); if (type.equals(List.class)) { - if (arrayTypeListFactory != null) { - if (isEnumType(valueType)) { - return arrayTypeListFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), null)); - } - return arrayTypeListFactory.typeFor(valueType); - } - // fallback to JSON storage in VARCHAR column - return new ScalarTypeJsonList.Varchar(getDocType(valueType)); + return getArrayScalarTypeList(valueType, nullable); + } else if (type.equals(Set.class)) { - if (arrayTypeSetFactory != null) { - if (isEnumType(valueType)) { - return arrayTypeSetFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), null)); - } - return arrayTypeSetFactory.typeFor(valueType); - } - // fallback to JSON storage in VARCHAR column - return new ScalarTypeJsonSet.Varchar(getDocType(valueType)); + return getArrayScalarTypeSet(valueType, nullable); + + } else { + throw new IllegalStateException("Type [" + type + "] not supported for @DbArray"); } - throw new IllegalStateException("Type [" + type + "] not supported for @DbArray"); + } + + @SuppressWarnings("rawtypes") + private ScalarType getArrayScalarTypeSet(Type valueType, boolean nullable) { + if (arrayTypeSetFactory != null) { + if (isEnumType(valueType)) { + return arrayTypeSetFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), null), nullable); + } + return arrayTypeSetFactory.typeFor(valueType, nullable); + } + // fallback to JSON storage in VARCHAR column + return new ScalarTypeJsonSet.Varchar(getDocType(valueType), nullable); + } + + @SuppressWarnings("rawtypes") + private ScalarType getArrayScalarTypeList(Type valueType, boolean nullable) { + if (arrayTypeListFactory != null) { + if (isEnumType(valueType)) { + return arrayTypeListFactory.typeForEnum(createEnumScalarType(asEnumClass(valueType), null), nullable); + } + return arrayTypeListFactory.typeFor(valueType, nullable); + } + // fallback to JSON storage in VARCHAR column + return new ScalarTypeJsonList.Varchar(getDocType(valueType), nullable); } private Class> asEnumClass(Type valueType) { @@ -398,7 +411,7 @@ public final class DefaultTypeManager implements TypeManager { if (type.equals(List.class)) { DocPropertyType docType = getDocType(genericType); if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) { - return ScalarTypeJsonList.typeFor(postgres, dbType, docType); + return ScalarTypeJsonList.typeFor(postgres, dbType, docType, prop.isNullable()); } else { return createJsonObjectMapperType(prop, dbType, docType); } @@ -407,7 +420,7 @@ 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); + return ScalarTypeJsonSet.typeFor(postgres, dbType, docType, prop.isNullable()); } else { return createJsonObjectMapperType(prop, dbType, docType); } @@ -432,8 +445,6 @@ public final class DefaultTypeManager implements TypeManager { return jsonNodeClob; case DbPlatformType.JSONB: return jsonNodeJsonb; - case DbPlatformType.JSON: - return jsonNodeJson; default: return jsonNodeJson; } diff --git a/src/main/java/io/ebeaninternal/server/type/PlatformArrayTypeFactory.java b/src/main/java/io/ebeaninternal/server/type/PlatformArrayTypeFactory.java index 11bcf8613..23d839fc7 100644 --- a/src/main/java/io/ebeaninternal/server/type/PlatformArrayTypeFactory.java +++ b/src/main/java/io/ebeaninternal/server/type/PlatformArrayTypeFactory.java @@ -10,10 +10,10 @@ public interface PlatformArrayTypeFactory { /** * Return the ScalarType to handle DB ARRAY for the given element type. */ - ScalarType typeFor(Type valueType); + ScalarType typeFor(Type valueType, boolean nullable); /** * Return the ScalarType to handle DB ARRAY for the given enum element type. */ - ScalarType typeForEnum(ScalarType scalarType); + ScalarType typeForEnum(ScalarType scalarType, boolean nullable); } diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayBase.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayBase.java index 8a08772ad..9706fc587 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayBase.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayBase.java @@ -4,11 +4,12 @@ import io.ebeanservice.docstore.api.mapping.DocPropertyType; import java.sql.Array; import java.sql.SQLException; +import java.sql.Types; abstract class ScalarTypeArrayBase extends ScalarTypeJsonCollection { - ScalarTypeArrayBase(Class type, int dbType, DocPropertyType docPropertyType) { - super(type, dbType, docPropertyType); + ScalarTypeArrayBase(Class type, int dbType, DocPropertyType docPropertyType, boolean nullable) { + super(type, dbType, docPropertyType, nullable); } @Override @@ -67,4 +68,14 @@ abstract class ScalarTypeArrayBase extends ScalarTypeJsonCollection { protected abstract T fromArray(Object[] array1); + static String arrayTypeFor(ScalarType scalarType) { + switch (scalarType.getJdbcType()) { + case Types.INTEGER: + return "integer"; + case Types.VARCHAR: + return "varchar"; + default: + throw new IllegalArgumentException("JdbcType [" + scalarType.getJdbcType() + "] not supported for @DbArray mapping on set."); + } + } } diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java index df0d669be..fba258d02 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayList.java @@ -14,65 +14,56 @@ import java.lang.reflect.Type; import java.sql.SQLException; import java.sql.Types; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; +import static java.util.Collections.EMPTY_LIST; + /** * Type mapped for DB ARRAY type (Postgres only effectively). */ @SuppressWarnings("rawtypes") public class ScalarTypeArrayList extends ScalarTypeArrayBase implements ScalarTypeArray { - private static ScalarTypeArrayList UUID = new ScalarTypeArrayList("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID); - private static ScalarTypeArrayList LONG = new ScalarTypeArrayList("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG); - private static ScalarTypeArrayList INTEGER = new ScalarTypeArrayList("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER); - private static ScalarTypeArrayList DOUBLE = new ScalarTypeArrayList("float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE); - private static ScalarTypeArrayList STRING = new ScalarTypeArrayList("varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING); - static PlatformArrayTypeFactory factory() { return new Factory(); } static class Factory implements PlatformArrayTypeFactory { + private final Map cache = new HashMap<>(); + /** * Return the ScalarType to use based on the List's generic parameter type. */ @Override - public ScalarTypeArrayList typeFor(Type valueType) { - if (valueType.equals(UUID.class)) { - return UUID; + public ScalarTypeArrayList typeFor(Type valueType, boolean nullable) { + synchronized (this) { + String key = valueType + ":" + nullable; + if (valueType.equals(UUID.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "uuid", DocPropertyType.UUID, ArrayElementConverter.UUID)); + } + if (valueType.equals(Long.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "bigint", DocPropertyType.LONG, ArrayElementConverter.LONG)); + } + if (valueType.equals(Integer.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER)); + } + if (valueType.equals(Double.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE)); + } + if (valueType.equals(String.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayList(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING)); + } + throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } - if (valueType.equals(Long.class)) { - return LONG; - } - if (valueType.equals(Integer.class)) { - return INTEGER; - } - if (valueType.equals(Double.class)) { - return DOUBLE; - } - if (valueType.equals(String.class)) { - return STRING; - } - throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } @Override - public ScalarTypeArrayList typeForEnum(ScalarType scalarType) { - final String arrayType; - switch (scalarType.getJdbcType()) { - case Types.INTEGER: - arrayType = "integer"; - break; - case Types.VARCHAR: - arrayType = "varchar"; - break; - default: - throw new IllegalArgumentException("JdbcType [" + scalarType.getJdbcType() + "] not supported for @DbArray mapping on set."); - } - - return new ScalarTypeArrayList(arrayType, scalarType.getDocType(), new ArrayElementConverter.EnumConverter(scalarType)); + public ScalarTypeArrayList typeForEnum(ScalarType scalarType, boolean nullable) { + return new ScalarTypeArrayList(nullable, arrayTypeFor(scalarType), scalarType.getDocType(), new ArrayElementConverter.EnumConverter(scalarType)); } } @@ -80,8 +71,8 @@ public class ScalarTypeArrayList extends ScalarTypeArrayBase implements Sc private final ArrayElementConverter converter; - public ScalarTypeArrayList(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { - super(List.class, Types.ARRAY, docPropertyType); + public ScalarTypeArrayList(boolean nullable, String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { + super(List.class, Types.ARRAY, docPropertyType, nullable); this.arrayType = arrayType; this.converter = converter; } @@ -120,12 +111,21 @@ public class ScalarTypeArrayList extends ScalarTypeArrayBase implements Sc @Override public void bind(DataBind bind, List value) throws SQLException { if (value == null) { - bind.setNull(Types.ARRAY); + bindNull(bind); } else { bind.setArray(arrayType, toArray(value)); } } + @Override + protected void bindNull(DataBind bind) throws SQLException { + if (nullable) { + bind.setNull(Types.ARRAY); + } else { + bind.setArray(arrayType, toArray(EMPTY_LIST)); + } + } + @Override public String formatValue(List value) { try { diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java index e793a86f2..22d6e932d 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2.java @@ -5,66 +5,78 @@ import io.ebeanservice.docstore.api.mapping.DocPropertyType; import java.lang.reflect.Type; import java.sql.SQLException; import java.sql.Types; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.UUID; + +import static java.util.Collections.EMPTY_LIST; /** * H2 database support for DB ARRAY. */ +@SuppressWarnings("rawtypes") class ScalarTypeArrayListH2 extends ScalarTypeArrayList { - private static ScalarTypeArrayListH2 UUID = new ScalarTypeArrayListH2("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID); - private static ScalarTypeArrayListH2 LONG = new ScalarTypeArrayListH2("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG); - private static ScalarTypeArrayListH2 INTEGER = new ScalarTypeArrayListH2("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER); - private static ScalarTypeArrayListH2 DOUBLE = new ScalarTypeArrayListH2("double", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE); - private static ScalarTypeArrayListH2 STRING = new ScalarTypeArrayListH2("varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING); - static PlatformArrayTypeFactory factory() { return new ScalarTypeArrayListH2.Factory(); } static class Factory implements PlatformArrayTypeFactory { + private final Map cache = new HashMap<>(); + /** * Return the ScalarType to use based on the List's generic parameter type. */ @Override - public ScalarTypeArrayListH2 typeFor(Type valueType) { - if (valueType.equals(java.util.UUID.class)) { - return UUID; + public ScalarType typeFor(Type valueType, boolean nullable) { + synchronized (this) { + String key = valueType + ":" + nullable; + if (valueType.equals(UUID.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "uuid", DocPropertyType.UUID, ArrayElementConverter.UUID)); + } + if (valueType.equals(Long.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "bigint", DocPropertyType.LONG, ArrayElementConverter.LONG)); + } + if (valueType.equals(Integer.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER)); + } + if (valueType.equals(Double.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE)); + } + if (valueType.equals(String.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArrayListH2(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING)); + } + throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } - if (valueType.equals(Long.class)) { - return LONG; - } - if (valueType.equals(Integer.class)) { - return INTEGER; - } - if (valueType.equals(Double.class)) { - return DOUBLE; - } - if (valueType.equals(String.class)) { - return STRING; - } - throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } @Override - public ScalarType typeForEnum(ScalarType scalarType) { - return new ScalarTypeArrayListH2("varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType)); + public ScalarType typeForEnum(ScalarType scalarType, boolean nullable) { + return new ScalarTypeArrayListH2(nullable, "varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType)); } } - @SuppressWarnings("rawtypes") - private ScalarTypeArrayListH2(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { - super(arrayType, docPropertyType, converter); + private ScalarTypeArrayListH2(boolean nullable, String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { + super(nullable, arrayType, docPropertyType, converter); } @Override - @SuppressWarnings("rawtypes") public void bind(DataBind bind, List value) throws SQLException { if (value == null) { - bind.setNull(Types.ARRAY); + bindNull(bind); } else { bind.setObject(toArray(value)); } } + + @Override + protected void bindNull(DataBind bind) throws SQLException { + if (nullable) { + bind.setNull(Types.ARRAY); + } else { + bind.setObject(toArray(EMPTY_LIST)); + } + } } diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java index 0d8745d35..10825e0b0 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySet.java @@ -13,20 +13,19 @@ import java.io.IOException; import java.lang.reflect.Type; import java.sql.SQLException; import java.sql.Types; +import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.Map; import java.util.Set; import java.util.UUID; +import static java.util.Collections.EMPTY_SET; + /** * Type mapped for DB ARRAY type (Postgres only effectively). */ -public class ScalarTypeArraySet extends ScalarTypeArrayBase> implements ScalarTypeArray { - - private static final ScalarTypeArraySet UUID = new ScalarTypeArraySet<>("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID); - private static final ScalarTypeArraySet LONG = new ScalarTypeArraySet<>("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG); - private static final ScalarTypeArraySet INTEGER = new ScalarTypeArraySet<>("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER); - private static final ScalarTypeArraySet DOUBLE = new ScalarTypeArraySet<>("float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE); - private static final ScalarTypeArraySet STRING = new ScalarTypeArraySet<>("varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING); +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ScalarTypeArraySet extends ScalarTypeArrayBase implements ScalarTypeArray { static PlatformArrayTypeFactory factory() { return new Factory(); @@ -34,54 +33,46 @@ public class ScalarTypeArraySet extends ScalarTypeArrayBase> implement static class Factory implements PlatformArrayTypeFactory { + private final Map cache = new HashMap<>(); + /** * Return the ScalarType to use based on the List's generic parameter type. */ @Override - public ScalarTypeArraySet typeFor(Type valueType) { - if (valueType.equals(UUID.class)) { - return UUID; + public ScalarType typeFor(Type valueType, boolean nullable) { + synchronized (this) { + String key = valueType + ":" + nullable; + if (valueType.equals(UUID.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySet(nullable, "uuid", DocPropertyType.UUID, ArrayElementConverter.UUID)); + } + if (valueType.equals(Long.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySet(nullable, "bigint", DocPropertyType.LONG, ArrayElementConverter.LONG)); + } + if (valueType.equals(Integer.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySet(nullable, "integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER)); + } + if (valueType.equals(Double.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySet(nullable, "float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE)); + } + if (valueType.equals(String.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySet(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING)); + } + throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } - if (valueType.equals(Long.class)) { - return LONG; - } - if (valueType.equals(Integer.class)) { - return INTEGER; - } - if (valueType.equals(Double.class)) { - return DOUBLE; - } - if (valueType.equals(String.class)) { - return STRING; - } - throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping on list"); } @Override - @SuppressWarnings({"unchecked", "rawtypes"}) - public ScalarTypeArraySet typeForEnum(ScalarType scalarType) { - final String arrayType; - switch (scalarType.getJdbcType()) { - case Types.INTEGER: - arrayType = "integer"; - break; - case Types.VARCHAR: - arrayType = "varchar"; - break; - default: - throw new IllegalArgumentException("JdbcType [" + scalarType.getJdbcType() + "] not supported for @DbArray mapping on set."); - } - return new ScalarTypeArraySet(arrayType, scalarType.getDocType(), new ArrayElementConverter.EnumConverter(scalarType)); + public ScalarType typeForEnum(ScalarType scalarType, boolean nullable) { + return new ScalarTypeArraySet(nullable, arrayTypeFor(scalarType), scalarType.getDocType(), new ArrayElementConverter.EnumConverter(scalarType)); } } private final String arrayType; - private final ArrayElementConverter converter; + private final ArrayElementConverter converter; - @SuppressWarnings({"unchecked", "rawtypes"}) - public ScalarTypeArraySet(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { - super((Class) Set.class, Types.ARRAY, docPropertyType); + public ScalarTypeArraySet(boolean nullable, String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { + super(Set.class, Types.ARRAY, docPropertyType, nullable); this.arrayType = arrayType; this.converter = converter; } @@ -100,29 +91,38 @@ public class ScalarTypeArraySet extends ScalarTypeArrayBase> implement } @Override - protected Set fromArray(Object[] array1) { - Set set = new LinkedHashSet<>(); + protected Set fromArray(Object[] array1) { + Set set = new LinkedHashSet(); for (Object element : array1) { set.add(converter.toElement(element)); } - return new ModifyAwareSet<>(set); + return new ModifyAwareSet(set); } - protected Object[] toArray(Set value) { + protected Object[] toArray(Set value) { return converter.toDbArray(value.toArray()); } @Override - public void bind(DataBind bind, Set value) throws SQLException { + public void bind(DataBind bind, Set value) throws SQLException { if (value == null) { - bind.setNull(Types.ARRAY); + bindNull(bind); } else { bind.setArray(arrayType, toArray(value)); } } @Override - public String formatValue(Set value) { + protected void bindNull(DataBind bind) throws SQLException { + if (nullable) { + bind.setNull(Types.ARRAY); + } else { + bind.setArray(arrayType, toArray(EMPTY_SET)); + } + } + + @Override + public String formatValue(Set value) { try { return EJson.write(value); } catch (IOException e) { @@ -131,7 +131,7 @@ public class ScalarTypeArraySet extends ScalarTypeArrayBase> implement } @Override - public Set parse(String value) { + public Set parse(String value) { try { return EJson.parseSet(value, false); } catch (IOException e) { @@ -140,12 +140,12 @@ public class ScalarTypeArraySet extends ScalarTypeArrayBase> implement } @Override - public Set jsonRead(JsonParser parser) throws IOException { + public Set jsonRead(JsonParser parser) throws IOException { return EJson.parseSet(parser, parser.getCurrentToken()); } @Override - public void jsonWrite(JsonGenerator writer, Set value) throws IOException { + public void jsonWrite(JsonGenerator writer, Set value) throws IOException { EJson.write(value, writer); } diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java index d311d666b..1c6e39c6d 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2.java @@ -5,19 +5,18 @@ import io.ebeanservice.docstore.api.mapping.DocPropertyType; import java.lang.reflect.Type; import java.sql.SQLException; import java.sql.Types; +import java.util.HashMap; +import java.util.Map; import java.util.Set; import java.util.UUID; +import static java.util.Collections.EMPTY_SET; + /** * H2 database support for DB ARRAY. */ -class ScalarTypeArraySetH2 extends ScalarTypeArraySet { - - private static final ScalarTypeArraySetH2 UUID = new ScalarTypeArraySetH2<>("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID); - private static final ScalarTypeArraySetH2 LONG = new ScalarTypeArraySetH2<>("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG); - private static final ScalarTypeArraySetH2 INTEGER = new ScalarTypeArraySetH2<>("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER); - private static final ScalarTypeArraySetH2 DOUBLE = new ScalarTypeArraySetH2<>("double", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE); - private static final ScalarTypeArraySetH2 STRING = new ScalarTypeArraySetH2<>("varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING); +@SuppressWarnings("rawtypes") +class ScalarTypeArraySetH2 extends ScalarTypeArraySet { static PlatformArrayTypeFactory factory() { return new ScalarTypeArraySetH2.Factory(); @@ -25,47 +24,60 @@ class ScalarTypeArraySetH2 extends ScalarTypeArraySet { static class Factory implements PlatformArrayTypeFactory { + private final Map cache = new HashMap<>(); + /** * Return the ScalarType to use based on the List's generic parameter type. */ @Override - public ScalarTypeArraySetH2 typeFor(Type valueType) { - if (valueType.equals(java.util.UUID.class)) { - return UUID; + public ScalarType typeFor(Type valueType, boolean nullable) { + synchronized (this) { + String key = valueType + ":" + nullable; + if (valueType.equals(UUID.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySetH2(nullable, "uuid", DocPropertyType.UUID, ArrayElementConverter.UUID)); + } + if (valueType.equals(Long.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySetH2(nullable, "bigint", DocPropertyType.LONG, ArrayElementConverter.LONG)); + } + if (valueType.equals(Integer.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySetH2(nullable, "integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER)); + } + if (valueType.equals(Double.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySetH2(nullable, "float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE)); + } + if (valueType.equals(String.class)) { + return cache.computeIfAbsent(key, s -> new ScalarTypeArraySetH2(nullable, "varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING)); + } + throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } - if (valueType.equals(Integer.class)) { - return INTEGER; - } - if (valueType.equals(Long.class)) { - return LONG; - } - if (valueType.equals(Double.class)) { - return DOUBLE; - } - if (valueType.equals(String.class)) { - return STRING; - } - throw new IllegalArgumentException("Type [" + valueType + "] not supported for @DbArray mapping"); } @Override - @SuppressWarnings({"rawtypes"}) - public ScalarTypeArraySetH2 typeForEnum(ScalarType scalarType) { - return new ScalarTypeArraySetH2("varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType)); + public ScalarType typeForEnum(ScalarType scalarType, boolean nullable) { + return new ScalarTypeArraySetH2(nullable, "varchar", DocPropertyType.TEXT, new ArrayElementConverter.EnumConverter(scalarType)); } } - @SuppressWarnings({"unchecked", "rawtypes"}) - private ScalarTypeArraySetH2(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { - super(arrayType, docPropertyType, converter); + @SuppressWarnings("rawtypes") + private ScalarTypeArraySetH2(boolean nullable, String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) { + super(nullable, arrayType, docPropertyType, converter); } @Override - public void bind(DataBind bind, Set value) throws SQLException { + public void bind(DataBind bind, Set value) throws SQLException { if (value == null) { - bind.setNull(Types.ARRAY); + bindNull(bind); } else { bind.setObject(toArray(value)); } } + + @Override + protected void bindNull(DataBind bind) throws SQLException { + if (nullable) { + bind.setNull(Types.ARRAY); + } else { + bind.setObject(toArray(EMPTY_SET)); + } + } } diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonCollection.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonCollection.java index 27f12b84a..a6456d446 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonCollection.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonCollection.java @@ -6,14 +6,29 @@ import io.ebeanservice.docstore.api.mapping.DocPropertyType; import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import java.sql.SQLException; +import java.sql.Types; abstract class ScalarTypeJsonCollection extends ScalarTypeBase implements ScalarTypeArray { - protected DocPropertyType docPropertyType; + protected final DocPropertyType docPropertyType; + protected final boolean nullable; - public ScalarTypeJsonCollection(Class type, int dbType, DocPropertyType docPropertyType) { + public ScalarTypeJsonCollection(Class type, int dbType, DocPropertyType docPropertyType, boolean nullable) { super(type, false, dbType); this.docPropertyType = docPropertyType; + this.nullable = nullable; + } + + /** + * Bind null or empty list (when not nullable). + */ + protected void bindNull(DataBind bind) throws SQLException { + if (nullable) { + bind.setNull(Types.VARCHAR); + } else { + bind.setString("[]"); + } } /** diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java index a4df1f732..fa4a85152 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonList.java @@ -17,27 +17,28 @@ import java.util.List; * Types for mapping List in JSON format to DB types VARCHAR, JSON and JSONB. */ public class ScalarTypeJsonList { + /** * Return the appropriate ScalarType based requested dbType and if Postgres. */ - public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docType) { + public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docType, boolean nullable) { if (postgres) { switch (dbType) { case DbPlatformType.JSONB: - return new ScalarTypeJsonList.JsonB(docType); + return new ScalarTypeJsonList.JsonB(docType, nullable); case DbPlatformType.JSON: - return new ScalarTypeJsonList.Json(docType); + return new ScalarTypeJsonList.Json(docType, nullable); } } - return new ScalarTypeJsonList.Varchar(docType); + return new ScalarTypeJsonList.Varchar(docType, nullable); } /** * List mapped to DB VARCHAR. */ public static class Varchar extends ScalarTypeJsonList.Base { - public Varchar(DocPropertyType docType) { - super(Types.VARCHAR, docType); + public Varchar(DocPropertyType docType, boolean nullable) { + super(Types.VARCHAR, docType, nullable); } } @@ -45,8 +46,8 @@ public class ScalarTypeJsonList { * List mapped to Postgres JSON. */ private static class Json extends ScalarTypeJsonList.PgBase { - public Json(DocPropertyType docType) { - super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docType); + public Json(DocPropertyType docType, boolean nullable) { + super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docType, nullable); } } @@ -54,8 +55,8 @@ public class ScalarTypeJsonList { * List mapped to Postgres JSONB. */ private static class JsonB extends ScalarTypeJsonList.PgBase { - public JsonB(DocPropertyType docType) { - super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docType); + public JsonB(DocPropertyType docType, boolean nullable) { + super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docType, nullable); } } @@ -65,8 +66,8 @@ public class ScalarTypeJsonList { @SuppressWarnings("rawtypes") private abstract static class Base extends ScalarTypeJsonCollection { - public Base(int dbType, DocPropertyType docType) { - super(List.class, dbType, docType); + public Base(int dbType, DocPropertyType docType, boolean nullable) { + super(List.class, dbType, docType, nullable); } @Override @@ -81,21 +82,29 @@ public class ScalarTypeJsonList { } @Override - public void bind(DataBind b, List value) throws SQLException { - + public void bind(DataBind bind, List value) throws SQLException { if (value == null) { - b.setNull(Types.VARCHAR); + bindNull(bind); } else if (value.isEmpty()) { - b.setString("[]"); + bind.setString("[]"); } else { try { - b.setString(EJson.write(value)); + bind.setString(EJson.write(value)); } catch (IOException e) { throw new SQLException("Failed to format List into JSON content", e); } } } + @Override + protected void bindNull(DataBind bind) throws SQLException { + if (nullable) { + bind.setNull(Types.VARCHAR); + } else { + bind.setString("[]"); + } + } + @Override public String formatValue(List value) { try { @@ -123,7 +132,6 @@ public class ScalarTypeJsonList { public void jsonWrite(JsonGenerator writer, List value) throws IOException { EJson.write(value, writer); } - } /** @@ -133,17 +141,24 @@ public class ScalarTypeJsonList { final String pgType; - PgBase(int jdbcType, String pgType, DocPropertyType docType) { - super(jdbcType, docType); + PgBase(int jdbcType, String pgType, DocPropertyType docType, boolean nullable) { + super(jdbcType, docType, nullable); this.pgType = pgType; } @SuppressWarnings("rawtypes") @Override public void bind(DataBind bind, List value) throws SQLException { + if (value == null) { + bindNull(bind); + } else { + bind.setObject(PostgresHelper.asObject(pgType, formatValue(value))); + } + } - String rawJson = (value == null) ? null : formatValue(value); - bind.setObject(PostgresHelper.asObject(pgType, rawJson)); + @Override + protected void bindNull(DataBind bind) throws SQLException { + bind.setObject(PostgresHelper.asObject(pgType, nullable ? null : "[]")); } } diff --git a/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java b/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java index 129ca28bc..69f91399b 100644 --- a/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java +++ b/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonSet.java @@ -1,11 +1,11 @@ package io.ebeaninternal.server.type; +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; import io.ebean.config.dbplatform.DbPlatformType; import io.ebean.text.TextException; import io.ebean.text.json.EJson; import io.ebeanservice.docstore.api.mapping.DocPropertyType; -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.core.JsonParser; import javax.persistence.PersistenceException; import java.io.IOException; @@ -23,24 +23,24 @@ public class ScalarTypeJsonSet { /** * Return the appropriate ScalarType for the requested dbType and Postgres. */ - public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType) { + public static ScalarType typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType, boolean nullable) { if (postgres) { switch (dbType) { case DbPlatformType.JSONB: - return new ScalarTypeJsonSet.JsonB(docPropertyType); + return new ScalarTypeJsonSet.JsonB(docPropertyType, nullable); case DbPlatformType.JSON: - return new ScalarTypeJsonSet.Json(docPropertyType); + return new ScalarTypeJsonSet.Json(docPropertyType, nullable); } } - return new ScalarTypeJsonSet.Varchar(docPropertyType); + return new ScalarTypeJsonSet.Varchar(docPropertyType, nullable); } /** * List mapped to DB VARCHAR. */ public static class Varchar extends ScalarTypeJsonSet.Base { - public Varchar(DocPropertyType docPropertyType) { - super(Types.VARCHAR, docPropertyType); + public Varchar(DocPropertyType docPropertyType, boolean nullable) { + super(Types.VARCHAR, docPropertyType, nullable); } } @@ -48,8 +48,8 @@ public class ScalarTypeJsonSet { * List mapped to Postgres JSON. */ private static class Json extends ScalarTypeJsonSet.PgBase { - public Json(DocPropertyType docPropertyType) { - super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docPropertyType); + public Json(DocPropertyType docPropertyType, boolean nullable) { + super(DbPlatformType.JSON, PostgresHelper.JSON_TYPE, docPropertyType, nullable); } } @@ -57,8 +57,8 @@ public class ScalarTypeJsonSet { * List mapped to Postgres JSONB. */ private static class JsonB extends ScalarTypeJsonSet.PgBase { - public JsonB(DocPropertyType docPropertyType) { - super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docPropertyType); + public JsonB(DocPropertyType docPropertyType, boolean nullable) { + super(DbPlatformType.JSONB, PostgresHelper.JSONB_TYPE, docPropertyType, nullable); } } @@ -68,8 +68,8 @@ public class ScalarTypeJsonSet { @SuppressWarnings("rawtypes") private abstract static class Base extends ScalarTypeJsonCollection { - public Base(int dbType, DocPropertyType docPropertyType) { - super(Set.class, dbType, docPropertyType); + public Base(int dbType, DocPropertyType docPropertyType, boolean nullable) { + super(Set.class, dbType, docPropertyType, nullable); } @Override @@ -84,21 +84,29 @@ public class ScalarTypeJsonSet { } @Override - public void bind(DataBind b, Set value) throws SQLException { - + public void bind(DataBind bind, Set value) throws SQLException { if (value == null) { - b.setNull(Types.VARCHAR); + bindNull(bind); } else if (value.isEmpty()) { - b.setString("[]"); + bind.setString("[]"); } else { try { - b.setString(EJson.write(value)); + bind.setString(EJson.write(value)); } catch (IOException e) { throw new SQLException("Failed to format Set into JSON content", e); } } } + @Override + protected void bindNull(DataBind bind) throws SQLException { + if (nullable) { + bind.setNull(Types.VARCHAR); + } else { + bind.setString("[]"); + } + } + @Override public String formatValue(Set value) { try { @@ -140,17 +148,24 @@ public class ScalarTypeJsonSet { final String pgType; - PgBase(int jdbcType, String pgType, DocPropertyType docPropertyType) { - super(jdbcType, docPropertyType); + PgBase(int jdbcType, String pgType, DocPropertyType docPropertyType, boolean nullable) { + super(jdbcType, docPropertyType, nullable); this.pgType = pgType; } @SuppressWarnings("rawtypes") @Override public void bind(DataBind bind, Set value) throws SQLException { + if (value == null) { + bindNull(bind); + } else { + bind.setObject(PostgresHelper.asObject(pgType, formatValue(value))); + } + } - String rawJson = (value == null) ? null : formatValue(value); - bind.setObject(PostgresHelper.asObject(pgType, rawJson)); + @Override + protected void bindNull(DataBind bind) throws SQLException { + bind.setObject(PostgresHelper.asObject(pgType, nullable ? null : "[]")); } } diff --git a/src/main/java/io/ebeaninternal/server/type/TypeManager.java b/src/main/java/io/ebeaninternal/server/type/TypeManager.java index 9ecfc2cc6..e4c038e79 100644 --- a/src/main/java/io/ebeaninternal/server/type/TypeManager.java +++ b/src/main/java/io/ebeaninternal/server/type/TypeManager.java @@ -63,7 +63,7 @@ public interface TypeManager { /** * Return the ScalarType used to handle DB ARRAY. */ - ScalarType getArrayScalarType(Class type, DbArray dbArray, Type genericType); + ScalarType getArrayScalarType(Class type, DbArray dbArray, Type genericType, boolean nullable); /** * Return the ScalarType used to handle HSTORE (Map). diff --git a/src/test/java/io/ebeaninternal/server/type/BasePlatformArrayTypeFactoryTest.java b/src/test/java/io/ebeaninternal/server/type/BasePlatformArrayTypeFactoryTest.java new file mode 100644 index 000000000..6efbaf710 --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/type/BasePlatformArrayTypeFactoryTest.java @@ -0,0 +1,92 @@ +package io.ebeaninternal.server.type; + +import org.postgresql.util.PGobject; + +import java.sql.SQLException; + +import static org.junit.Assert.assertTrue; + +class BasePlatformArrayTypeFactoryTest { + + void assertBindNullTo_Null(ScalarType type) throws SQLException { + + TestDoubleDataBind bind = new TestDoubleDataBind(); + type.bind(bind, null); + assertTrue(bind.wasSetNull); + } + + void assertBindNullTo_EmptyArray(ScalarType type) throws SQLException { + + TestDoubleDataBind bind = new TestDoubleDataBind(); + type.bind(bind, null); + + assertTrue(bind.wasEmptyArray); + } + + void assertBindNullTo_EmptyString(ScalarType type) throws SQLException { + + TestDoubleDataBind bind = new TestDoubleDataBind(); + type.bind(bind, null); + assertTrue(bind.setEmptyString); + } + + void assertBindNullTo_PGObjectNull(ScalarType type) throws SQLException { + + TestDoubleDataBind bind = new TestDoubleDataBind(); + type.bind(bind, null); + assertTrue(bind.wasPgoNull); + } + void assertBindNullTo_PGObjectEmpty(ScalarType type) throws SQLException { + + TestDoubleDataBind bind = new TestDoubleDataBind(); + type.bind(bind, null); + assertTrue(bind.wasPgoEmpty); + } + + static class TestDoubleDataBind extends DataBind { + + boolean setEmptyString; + boolean wasSetNull; + boolean setArray; + boolean wasNull; + boolean wasEmptyArray; + boolean wasPgoNull; + boolean wasPgoEmpty; + + TestDoubleDataBind() { + super(null, null, null); + } + + @Override + public void setNull(int jdbcType) { + wasSetNull = true; + } + + @Override + public void setString(String s) { + setEmptyString = "[]".equals(s); + } + + @Override + public void setObject(Object value) { + wasNull = value == null; + if (!wasNull) { + if (value instanceof Object[]) { + wasEmptyArray = ((Object[]) value).length == 0; + } else { + PGobject pgo = (PGobject) value; + wasPgoEmpty = "[]".equals(pgo.getValue()); + wasPgoNull = pgo.getValue() == null; + } + } + } + + @Override + public void setArray(String arrayType, Object[] elements) { + setArray = true; + wasNull = elements == null; + wasEmptyArray = (elements != null && elements.length == 0); + } + } + +} diff --git a/src/test/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2Test.java b/src/test/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2Test.java new file mode 100644 index 000000000..b23a2126d --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/type/ScalarTypeArrayListH2Test.java @@ -0,0 +1,52 @@ +package io.ebeaninternal.server.type; + +import org.junit.Test; + +import java.sql.SQLException; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ScalarTypeArrayListH2Test extends BasePlatformArrayTypeFactoryTest { + + private final PlatformArrayTypeFactory factory = ScalarTypeArrayListH2.factory(); + + @Test + public void notSameInstance() { + assertThat(factory.typeFor(UUID.class, true)) + .isNotSameAs(factory.typeFor(UUID.class, false)); + } + + @Test + public void sameInstance_when_notNull() { + assertThat(factory.typeFor(UUID.class, false)) + .isSameAs(factory.typeFor(UUID.class, false)); + } + + @Test + public void sameInstance_when_nullable() { + assertThat(factory.typeFor(UUID.class, true)) + .isSameAs(factory.typeFor(UUID.class, true)); + } + + @Test + public void bindNullToEmpty_when_nullableIsFalse() throws SQLException { + + assertBindNullTo_EmptyArray(factory.typeFor(Integer.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(Long.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(Double.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(String.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(UUID.class, false)); + } + + @Test + public void bindNullToNull_when_nullable() throws SQLException { + + assertBindNullTo_Null(factory.typeFor(Integer.class, true)); + assertBindNullTo_Null(factory.typeFor(Long.class, true)); + assertBindNullTo_Null(factory.typeFor(Double.class, true)); + assertBindNullTo_Null(factory.typeFor(String.class, true)); + assertBindNullTo_Null(factory.typeFor(UUID.class, true)); + } + +} diff --git a/src/test/java/io/ebeaninternal/server/type/ScalarTypeArrayListTest.java b/src/test/java/io/ebeaninternal/server/type/ScalarTypeArrayListTest.java index 698d45531..92ecc3fa1 100644 --- a/src/test/java/io/ebeaninternal/server/type/ScalarTypeArrayListTest.java +++ b/src/test/java/io/ebeaninternal/server/type/ScalarTypeArrayListTest.java @@ -5,8 +5,31 @@ import org.junit.Test; import org.mockito.Mockito; import java.sql.SQLException; +import java.util.UUID; -public class ScalarTypeArrayListTest { +import static org.assertj.core.api.Assertions.assertThat; + +public class ScalarTypeArrayListTest extends BasePlatformArrayTypeFactoryTest { + + private final PlatformArrayTypeFactory factory = ScalarTypeArrayList.factory(); + + @Test + public void notSameInstance() { + assertThat(factory.typeFor(UUID.class, true)) + .isSameAs(factory.typeFor(UUID.class, false)); + } + + @Test + public void sameInstance_when_notNull() { + assertThat(factory.typeFor(UUID.class, false)) + .isSameAs(factory.typeFor(UUID.class, false)); + } + + @Test + public void sameInstance_when_nullable() { + assertThat(factory.typeFor(UUID.class, true)) + .isSameAs(factory.typeFor(UUID.class, true)); + } @Test public void read_when_null() throws SQLException { @@ -14,7 +37,28 @@ public class ScalarTypeArrayListTest { DataReader mock = Mockito.mock(DataReader.class); Mockito.when(mock.getArray()).thenReturn(null); - ScalarType scalarType = ScalarTypeArrayList.factory().typeFor(Long.class); + ScalarType scalarType = ScalarTypeArrayList.factory().typeFor(Long.class, true); scalarType.read(mock); } + + @Test + public void bindNullToEmpty_when_nullableIsFalse() throws SQLException { + + assertBindNullTo_EmptyArray(factory.typeFor(Integer.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(Long.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(Double.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(String.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(UUID.class, false)); + } + + @Test + public void bindNullToNull_when_nullable() throws SQLException { + + assertBindNullTo_Null(factory.typeFor(Integer.class, true)); + assertBindNullTo_Null(factory.typeFor(Long.class, true)); + assertBindNullTo_Null(factory.typeFor(Double.class, true)); + assertBindNullTo_Null(factory.typeFor(String.class, true)); + assertBindNullTo_Null(factory.typeFor(UUID.class, true)); + } + } diff --git a/src/test/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2Test.java b/src/test/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2Test.java new file mode 100644 index 000000000..0e2e14fa9 --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/type/ScalarTypeArraySetH2Test.java @@ -0,0 +1,52 @@ +package io.ebeaninternal.server.type; + +import org.junit.Test; + +import java.sql.SQLException; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ScalarTypeArraySetH2Test extends BasePlatformArrayTypeFactoryTest { + + private final PlatformArrayTypeFactory factory = ScalarTypeArraySetH2.factory(); + + @Test + public void notSameInstance() { + assertThat(factory.typeFor(UUID.class, true)) + .isNotSameAs(factory.typeFor(UUID.class, false)); + } + + @Test + public void sameInstance_when_notNull() { + assertThat(factory.typeFor(UUID.class, false)) + .isSameAs(factory.typeFor(UUID.class, false)); + } + + @Test + public void sameInstance_when_nullable() { + assertThat(factory.typeFor(UUID.class, true)) + .isSameAs(factory.typeFor(UUID.class, true)); + } + + @Test + public void bindNullToEmpty_when_nullableIsFalse() throws SQLException { + + assertBindNullTo_EmptyArray(factory.typeFor(Integer.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(Long.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(Double.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(String.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(UUID.class, false)); + } + + @Test + public void bindNullToNull_when_nullable() throws SQLException { + + assertBindNullTo_Null(factory.typeFor(Integer.class, true)); + assertBindNullTo_Null(factory.typeFor(Long.class, true)); + assertBindNullTo_Null(factory.typeFor(Double.class, true)); + assertBindNullTo_Null(factory.typeFor(String.class, true)); + assertBindNullTo_Null(factory.typeFor(UUID.class, true)); + } + +} diff --git a/src/test/java/io/ebeaninternal/server/type/ScalarTypeArraySetTest.java b/src/test/java/io/ebeaninternal/server/type/ScalarTypeArraySetTest.java new file mode 100644 index 000000000..1fa9732d7 --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/type/ScalarTypeArraySetTest.java @@ -0,0 +1,52 @@ +package io.ebeaninternal.server.type; + +import org.junit.Test; + +import java.sql.SQLException; +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ScalarTypeArraySetTest extends BasePlatformArrayTypeFactoryTest { + + private final PlatformArrayTypeFactory factory = ScalarTypeArraySet.factory(); + + @Test + public void notSameInstance() { + assertThat(factory.typeFor(UUID.class, true)) + .isNotSameAs(factory.typeFor(UUID.class, false)); + } + + @Test + public void sameInstance_when_notNull() { + assertThat(factory.typeFor(UUID.class, false)) + .isSameAs(factory.typeFor(UUID.class, false)); + } + + @Test + public void sameInstance_when_nullable() { + assertThat(factory.typeFor(UUID.class, true)) + .isSameAs(factory.typeFor(UUID.class, true)); + } + + @Test + public void bindNullToEmpty_when_nullableIsFalse() throws SQLException { + + assertBindNullTo_EmptyArray(factory.typeFor(Integer.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(Long.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(Double.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(String.class, false)); + assertBindNullTo_EmptyArray(factory.typeFor(UUID.class, false)); + } + + @Test + public void bindNullToNull_when_nullable() throws SQLException { + + assertBindNullTo_Null(factory.typeFor(Integer.class, true)); + assertBindNullTo_Null(factory.typeFor(Long.class, true)); + assertBindNullTo_Null(factory.typeFor(Double.class, true)); + assertBindNullTo_Null(factory.typeFor(String.class, true)); + assertBindNullTo_Null(factory.typeFor(UUID.class, true)); + } + +} diff --git a/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java b/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java new file mode 100644 index 000000000..75334885e --- /dev/null +++ b/src/test/java/io/ebeaninternal/server/type/ScalarTypeJsonListTest.java @@ -0,0 +1,29 @@ +package io.ebeaninternal.server.type; + +import io.ebean.config.dbplatform.ExtraDbTypes; +import io.ebeanservice.docstore.api.mapping.DocPropertyType; +import org.junit.Test; + +import java.sql.SQLException; + +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_EmptyString(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, 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_Null(ScalarTypeJsonList.typeFor(true, ExtraDbTypes.JSONVarchar, DocPropertyType.OBJECT, true)); + } + +}