#1893 - @DbArray with @NotNull ... should persist empty db array rather than fail NOT NULL constraint

This commit is contained in:
rob bygrave
2019-12-17 20:27:53 +13:00
parent d8d35b7dd9
commit c6062fe93e
19 changed files with 671 additions and 260 deletions
@@ -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) {
@@ -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() + "]");
}
@@ -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<? extends Enum<?>> 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;
}
@@ -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);
}
@@ -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<T> extends ScalarTypeJsonCollection<T> {
ScalarTypeArrayBase(Class<T> type, int dbType, DocPropertyType docPropertyType) {
super(type, dbType, docPropertyType);
ScalarTypeArrayBase(Class<T> type, int dbType, DocPropertyType docPropertyType, boolean nullable) {
super(type, dbType, docPropertyType, nullable);
}
@Override
@@ -67,4 +68,14 @@ abstract class ScalarTypeArrayBase<T> extends ScalarTypeJsonCollection<T> {
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.");
}
}
}
@@ -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<List> 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<String, ScalarTypeArrayList> 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<List> 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<List> 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 {
@@ -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<String, ScalarTypeArrayListH2> 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));
}
}
}
@@ -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<T> extends ScalarTypeArrayBase<Set<T>> implements ScalarTypeArray {
private static final ScalarTypeArraySet<UUID> UUID = new ScalarTypeArraySet<>("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID);
private static final ScalarTypeArraySet<Long> LONG = new ScalarTypeArraySet<>("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG);
private static final ScalarTypeArraySet<Integer> INTEGER = new ScalarTypeArraySet<>("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER);
private static final ScalarTypeArraySet<Double> DOUBLE = new ScalarTypeArraySet<>("float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE);
private static final ScalarTypeArraySet<String> STRING = new ScalarTypeArraySet<>("varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING);
@SuppressWarnings({"rawtypes", "unchecked"})
public class ScalarTypeArraySet extends ScalarTypeArrayBase<Set> implements ScalarTypeArray {
static PlatformArrayTypeFactory factory() {
return new Factory();
@@ -34,54 +33,46 @@ public class ScalarTypeArraySet<T> extends ScalarTypeArrayBase<Set<T>> implement
static class Factory implements PlatformArrayTypeFactory {
private final Map<String, ScalarTypeArraySet> 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<T> converter;
private final ArrayElementConverter converter;
@SuppressWarnings({"unchecked", "rawtypes"})
public ScalarTypeArraySet(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter<T> 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<T> extends ScalarTypeArrayBase<Set<T>> implement
}
@Override
protected Set<T> fromArray(Object[] array1) {
Set<T> 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<T> value) {
protected Object[] toArray(Set value) {
return converter.toDbArray(value.toArray());
}
@Override
public void bind(DataBind bind, Set<T> 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<T> 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<T> extends ScalarTypeArrayBase<Set<T>> implement
}
@Override
public Set<T> parse(String value) {
public Set parse(String value) {
try {
return EJson.parseSet(value, false);
} catch (IOException e) {
@@ -140,12 +140,12 @@ public class ScalarTypeArraySet<T> extends ScalarTypeArrayBase<Set<T>> implement
}
@Override
public Set<T> 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<T> value) throws IOException {
public void jsonWrite(JsonGenerator writer, Set value) throws IOException {
EJson.write(value, writer);
}
@@ -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<T> extends ScalarTypeArraySet<T> {
private static final ScalarTypeArraySetH2<UUID> UUID = new ScalarTypeArraySetH2<>("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID);
private static final ScalarTypeArraySetH2<Long> LONG = new ScalarTypeArraySetH2<>("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG);
private static final ScalarTypeArraySetH2<Integer> INTEGER = new ScalarTypeArraySetH2<>("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER);
private static final ScalarTypeArraySetH2<Double> DOUBLE = new ScalarTypeArraySetH2<>("double", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE);
private static final ScalarTypeArraySetH2<String> 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<T> extends ScalarTypeArraySet<T> {
static class Factory implements PlatformArrayTypeFactory {
private final Map<String, ScalarTypeArraySetH2> 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<T> 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));
}
}
}
@@ -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<T> extends ScalarTypeBase<T> implements ScalarTypeArray {
protected DocPropertyType docPropertyType;
protected final DocPropertyType docPropertyType;
protected final boolean nullable;
public ScalarTypeJsonCollection(Class<T> type, int dbType, DocPropertyType docPropertyType) {
public ScalarTypeJsonCollection(Class<T> 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("[]");
}
}
/**
@@ -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<List> {
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 : "[]"));
}
}
@@ -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<Set> {
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 : "[]"));
}
}
@@ -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<String,String>).