#989 - ENH: Support @DbArray with Set ... (addition to the existing List support)

This commit is contained in:
Rob Bygrave
2017-03-10 20:50:07 +13:00
parent a22eb555d0
commit 3051412d90
10 changed files with 507 additions and 12 deletions
@@ -9,6 +9,7 @@ import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -159,4 +160,11 @@ public class EJson {
}
return ((ModifyAwareList) list).asSet();
}
/**
* Parse the json returning as a Set taking into account the current token.
*/
public static Set<Object> parseSet(JsonParser parser, JsonToken currentToken) throws IOException {
return new LinkedHashSet<>(parseList(parser, currentToken));
}
}
@@ -17,7 +17,7 @@ import io.ebean.config.dbplatform.DbPlatformType;
import io.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import io.ebeaninternal.server.type.DataEncryptSupport;
import io.ebeaninternal.server.type.ScalarType;
import io.ebeaninternal.server.type.ScalarTypeArrayList;
import io.ebeaninternal.server.type.ScalarTypeArray;
import io.ebeaninternal.server.type.ScalarTypeEnumStandard;
import io.ebeaninternal.server.type.SimpleAesEncryptor;
import io.ebeaninternal.server.type.TypeManager;
@@ -244,8 +244,8 @@ public class DeployUtil {
int dbType = scalarType.getJdbcType();
prop.setDbType(dbType);
prop.setScalarType(scalarType);
if (scalarType instanceof ScalarTypeArrayList) {
prop.setDbColumnDefn(((ScalarTypeArrayList) scalarType).getDbColumnDefn());
if (scalarType instanceof ScalarTypeArray) {
prop.setDbColumnDefn(((ScalarTypeArray) scalarType).getDbColumnDefn());
}
if (dbType == Types.VARCHAR) {
// determine the db column size
@@ -165,7 +165,8 @@ public final class DefaultTypeManager implements TypeManager {
*/
private ScalarType<?> jsonNodeJsonb;
private final PlatformArrayTypeFactory arrayTypeFactory;
private final PlatformArrayTypeFactory arrayTypeListFactory;
private final PlatformArrayTypeFactory arrayTypeSetFactory;
/**
* Create the DefaultTypeManager.
@@ -182,7 +183,8 @@ public final class DefaultTypeManager implements TypeManager {
this.extraTypeFactory = new DefaultTypeFactory(config);
this.postgres = isPostgres(config.getDatabasePlatform());
this.arrayTypeFactory = arrayTypeFactory(postgres, config.getDatabasePlatform());
this.arrayTypeListFactory = arrayTypeListFactory(postgres, config.getDatabasePlatform());
this.arrayTypeSetFactory = arrayTypeSetFactory(postgres, config.getDatabasePlatform());
this.offlineMigrationGeneration = DbOffline.isGenerateMigration();
@@ -203,7 +205,7 @@ public final class DefaultTypeManager implements TypeManager {
/**
* Return the factory to use to support DB ARRAY types.
*/
private PlatformArrayTypeFactory arrayTypeFactory(boolean postgres, DatabasePlatform databasePlatform) {
private PlatformArrayTypeFactory arrayTypeListFactory(boolean postgres, DatabasePlatform databasePlatform) {
if (postgres) {
return ScalarTypeArrayList.factory();
} else if (databasePlatform.isPlatform(Platform.H2)) {
@@ -213,6 +215,19 @@ public final class DefaultTypeManager implements TypeManager {
return null;
}
/**
* Return the factory to use to support DB ARRAY types.
*/
private PlatformArrayTypeFactory arrayTypeSetFactory(boolean postgres, DatabasePlatform databasePlatform) {
if (postgres) {
return ScalarTypeArraySet.factory();
} else if (databasePlatform.isPlatform(Platform.H2)) {
return ScalarTypeArraySetH2.factory();
}
// not supported for this DB platform
return null;
}
/**
* Load custom scalar types registered via ExtraTypeFactory and ServiceLoader.
*/
@@ -318,13 +333,19 @@ public final class DefaultTypeManager implements TypeManager {
@Override
public ScalarType<?> getArrayScalarType(Class<?> type, DbArray dbArray, Type genericType) {
Type valueType = getValueType(genericType);
if (type.equals(List.class)) {
if (arrayTypeFactory != null) {
Type valueType = getValueType(genericType);
return arrayTypeFactory.typeFor(valueType);
if (arrayTypeListFactory != null) {
return arrayTypeListFactory.typeFor(valueType);
}
// fallback to JSON storage in VARCHAR column
return new ScalarTypeJsonList.Varchar(getDocType(getValueType(genericType)));
return new ScalarTypeJsonList.Varchar(getDocType(valueType));
} else if (type.equals(Set.class)) {
if (arrayTypeSetFactory != null) {
return arrayTypeSetFactory.typeFor(valueType);
}
// fallback to JSON storage in VARCHAR column
return new ScalarTypeJsonSet.Varchar(getDocType(valueType));
}
throw new IllegalStateException("Type [" + type + "] not supported for @DbArray");
}
@@ -0,0 +1,13 @@
package io.ebeaninternal.server.type;
/**
* DB Array types.
*/
public interface ScalarTypeArray {
/**
* Return the underlying DB column type.
*/
String getDbColumnDefn();
}
@@ -19,7 +19,7 @@ import java.util.UUID;
/**
* Type mapped for DB ARRAY type (Postgres only effectively).
*/
public class ScalarTypeArrayList extends ScalarTypeJsonCollection<List> {
public class ScalarTypeArrayList extends ScalarTypeJsonCollection<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);
@@ -0,0 +1,142 @@
package io.ebeaninternal.server.type;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import io.ebean.text.json.EJson;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import javax.persistence.PersistenceException;
import java.io.IOException;
import java.lang.reflect.Type;
import java.sql.Array;
import java.sql.SQLException;
import java.sql.Types;
import java.util.LinkedHashSet;
import java.util.Set;
import java.util.UUID;
/**
* Type mapped for DB ARRAY type (Postgres only effectively).
*/
public class ScalarTypeArraySet extends ScalarTypeJsonCollection<Set> implements ScalarTypeArray {
private static ScalarTypeArraySet UUID = new ScalarTypeArraySet("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID);
private static ScalarTypeArraySet LONG = new ScalarTypeArraySet("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG);
private static ScalarTypeArraySet INTEGER = new ScalarTypeArraySet("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER);
private static ScalarTypeArraySet DOUBLE = new ScalarTypeArraySet("float", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE);
private static ScalarTypeArraySet STRING = new ScalarTypeArraySet("varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING);
static PlatformArrayTypeFactory factory() {
return new Factory();
}
static class Factory implements PlatformArrayTypeFactory {
/**
* 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;
}
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 set");
}
}
private final String arrayType;
private final ArrayElementConverter converter;
public ScalarTypeArraySet(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) {
super(Set.class, Types.ARRAY, docPropertyType);
this.arrayType = arrayType;
this.converter = converter;
}
@Override
public DocPropertyType getDocType() {
return docPropertyType;
}
/**
* Return the DB column definition for DDL generation.
*/
public String getDbColumnDefn() {
return arrayType + "[]";
}
@SuppressWarnings("unchecked")
private Set fromArray(Object[] array1) {
Set set = new LinkedHashSet();
for (Object element : array1) {
set.add(converter.toElement(element));
}
return new ModifyAwareSet(set);
}
protected Object[] toArray(Set value) {
return value.toArray();
}
@Override
public Set read(DataReader reader) throws SQLException {
Array array = reader.getArray();
if (array == null) {
return null;
} else {
return fromArray((Object[]) array.getArray());
}
}
@Override
public void bind(DataBind bind, Set value) throws SQLException {
if (value == null) {
bind.setNull(Types.ARRAY);
} else {
bind.setArray(arrayType, toArray(value));
}
}
@Override
public String formatValue(Set value) {
try {
return EJson.write(value);
} catch (IOException e) {
throw new PersistenceException("Failed to format List into JSON content", e);
}
}
@Override
public Set parse(String value) {
try {
return EJson.parseSet(value, false);
} catch (IOException e) {
throw new PersistenceException("Failed to parse JSON content as List: [" + value + "]", e);
}
}
@Override
public Set jsonRead(JsonParser parser) throws IOException {
return EJson.parseSet(parser, parser.getCurrentToken());
}
@Override
public void jsonWrite(JsonGenerator writer, Set value) throws IOException {
EJson.write(value, writer);
}
}
@@ -0,0 +1,68 @@
package io.ebeaninternal.server.type;
import io.ebeanservice.docstore.api.mapping.DocPropertyType;
import java.lang.reflect.Type;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Set;
/**
* H2 database support for DB ARRAY.
*/
class ScalarTypeArraySetH2 extends ScalarTypeArraySet {
private static ScalarTypeArraySetH2 UUID = new ScalarTypeArraySetH2("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID);
private static ScalarTypeArraySetH2 LONG = new ScalarTypeArraySetH2("bigint", DocPropertyType.LONG, ArrayElementConverter.LONG);
private static ScalarTypeArraySetH2 INTEGER = new ScalarTypeArraySetH2("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER);
private static ScalarTypeArraySetH2 DOUBLE = new ScalarTypeArraySetH2("double", DocPropertyType.DOUBLE, ArrayElementConverter.DOUBLE);
private static ScalarTypeArraySetH2 STRING = new ScalarTypeArraySetH2("varchar", DocPropertyType.TEXT, ArrayElementConverter.STRING);
static PlatformArrayTypeFactory factory() {
return new ScalarTypeArraySetH2.Factory();
}
static class Factory implements PlatformArrayTypeFactory {
/**
* 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;
}
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");
}
}
private ScalarTypeArraySetH2(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) {
super(arrayType, docPropertyType, converter);
}
@Override
public String getDbColumnDefn() {
return "array";
}
@Override
public void bind(DataBind bind, Set value) throws SQLException {
if (value == null) {
bind.setNull(Types.ARRAY);
} else {
bind.setObject(toArray(value));
}
}
}
@@ -37,7 +37,7 @@ public class ScalarTypeJsonSet {
/**
* List mapped to DB VARCHAR.
*/
private static class Varchar extends ScalarTypeJsonSet.Base {
public static class Varchar extends ScalarTypeJsonSet.Base {
public Varchar(DocPropertyType docPropertyType) {
super(Types.VARCHAR, docPropertyType);
}