[#7379] Translate MySQL / PostgreSQL ENUM type to check constraint in other databases

This commit is contained in:
lukaseder 2018-04-04 17:46:55 +03:00
parent 2ec9a789f9
commit 6ba693edc9
4 changed files with 85 additions and 19 deletions

View File

@ -46,12 +46,15 @@ import static org.jooq.Clause.CREATE_TABLE_CONSTRAINTS;
import static org.jooq.Clause.CREATE_TABLE_NAME;
// ...
// ...
import static org.jooq.SQLDialect.CUBRID;
// ...
import static org.jooq.SQLDialect.DERBY;
import static org.jooq.SQLDialect.FIREBIRD;
import static org.jooq.SQLDialect.H2;
// ...
import static org.jooq.SQLDialect.HSQLDB;
// ...
// ...
import static org.jooq.SQLDialect.MARIADB;
import static org.jooq.SQLDialect.MYSQL;
// ...
@ -59,6 +62,7 @@ import static org.jooq.SQLDialect.POSTGRES;
// ...
import static org.jooq.SQLDialect.SQLITE;
// ...
// ...
import static org.jooq.impl.DSL.commentOnTable;
import static org.jooq.impl.DSL.createIndex;
import static org.jooq.impl.DSL.field;
@ -100,6 +104,7 @@ import org.jooq.CreateTableAsStep;
import org.jooq.CreateTableColumnStep;
import org.jooq.CreateTableWithDataStep;
import org.jooq.DataType;
import org.jooq.EnumType;
import org.jooq.Field;
import org.jooq.Index;
import org.jooq.Name;
@ -127,6 +132,7 @@ final class CreateTableImpl<R extends Record> extends AbstractQuery implements
private static final EnumSet<SQLDialect> NO_SUPPORT_IF_NOT_EXISTS = EnumSet.of(DERBY, FIREBIRD);
private static final EnumSet<SQLDialect> NO_SUPPORT_WITH_DATA = EnumSet.of(H2, MARIADB, MYSQL, SQLITE);
private static final EnumSet<SQLDialect> EMULATE_INDEXES_IN_BLOCK = EnumSet.of(POSTGRES);
private static final EnumSet<SQLDialect> EMULATE_ENUM_TYPES_AS_CHECK = EnumSet.of(CUBRID, DERBY, FIREBIRD, H2, HSQLDB, SQLITE);
private static final EnumSet<SQLDialect> REQUIRES_WITH_DATA = EnumSet.of(HSQLDB);
private static final EnumSet<SQLDialect> WRAP_SELECT_IN_PARENS = EnumSet.of(HSQLDB);
private static final EnumSet<SQLDialect> SUPPORT_TEMPORARY = EnumSet.of(MARIADB, MYSQL, POSTGRES);
@ -444,6 +450,21 @@ final class CreateTableImpl<R extends Record> extends AbstractQuery implements
.formatSeparator()
.visit(constraint);
if (EMULATE_ENUM_TYPES_AS_CHECK.contains(ctx.family())) {
for (int i = 0; i < columnFields.size(); i++) {
DataType<?> type = columnTypes.get(i);
if (EnumType.class.isAssignableFrom(type.getType())) {
Field<?> field = columnFields.get(i);
ctx.sql(',')
.formatSeparator()
.visit(DSL.constraint(table.getName() + "_" + field.getName() + "_chk")
.check(((Field) field).in(Tools.enumLiterals((Class<EnumType>) type.getType()))));
}
}
}
ctx.end(CREATE_TABLE_CONSTRAINTS);
if (!indexes.isEmpty() && !EMULATE_INDEXES_IN_BLOCK.contains(ctx.family())) {

View File

@ -636,10 +636,13 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
// [#7379] Most databases cannot cast a bind variable to an enum type
else if (POSTGRES != family && EnumType.class.isAssignableFrom(type))
sqlCast(ctx, converted, Tools.emulateEnumType((DataType<EnumType>) dataType), dataType.length(), dataType.precision(), dataType.scale());
// In all other cases, the bind variable can be cast normally
else {
else
sqlCast(ctx, converted, dataType, dataType.length(), dataType.precision(), dataType.scale());
}
}
private static final int getValueLength(String string) {
@ -687,14 +690,12 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
}
// See if we "should" cast, to stay on the safe side
if (shouldCast(ctx, converted)) {
if (shouldCast(ctx, converted))
sqlCast(ctx, converted);
}
// Most RDBMS can infer types for bind values
else {
else
sql(ctx, converted);
}
}
private final void sql(BindingSQLContext<U> ctx, T value) throws SQLException {
@ -2167,14 +2168,13 @@ public class DefaultBinding<T, U> implements Binding<T, U> {
render.sql("[]");
}
@SuppressWarnings("unchecked")
static final <E extends EnumType> E getEnumType(Class<? extends E> type, String literal) {
try {
EnumType[] list = Tools.enums(type);
E[] list = Tools.enums(type);
for (EnumType e : list)
for (E e : list)
if (e.getLiteral().equals(literal))
return (E) e;
return e;
}
catch (Exception e) {
throw new DataTypeException("Unknown enum literal found : " + literal);

View File

@ -6732,16 +6732,22 @@ final class ParserImpl implements Parser {
private static final DataType<?> parseDataTypeEnum(ParserContext ctx) {
parse(ctx, '(');
List<String> literals = new ArrayList<String>();
int length = 0;
do {
literals.add(parseStringLiteral(ctx));
String literal = parseStringLiteral(ctx);
if (literal != null)
length = Math.max(length, literal.length());
literals.add(literal);
}
while (parseIf(ctx, ','));
parse(ctx, ')');
// [#7025] TODO, replace this by a dynamic enum data type encoding, once available
return SQLDataType.VARCHAR;
return SQLDataType.VARCHAR(length);
}
// -----------------------------------------------------------------------------------------------------------------

View File

@ -229,6 +229,7 @@ import org.jooq.conf.BackslashEscaping;
import org.jooq.conf.Settings;
import org.jooq.conf.ThrowExceptions;
import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataTypeException;
import org.jooq.exception.MappingException;
import org.jooq.exception.NoDataFoundException;
import org.jooq.exception.TooManyRowsException;
@ -4108,7 +4109,6 @@ final class Tools {
}
static final void toSQLDDLTypeDeclaration(Context<?> ctx, DataType<?> type) {
String typeName = type.getTypeName(ctx.configuration());
// In some databases, identity is a type, not a flag.
if (type.identity()) {
@ -4124,15 +4124,16 @@ final class Tools {
// [#5299] MySQL enum types
if (EnumType.class.isAssignableFrom(type.getType())) {
@SuppressWarnings("unchecked")
DataType<EnumType> enumType = (DataType<EnumType>) type;
Object[] enums = enumConstants(enumType);
switch (ctx.family()) {
case MARIADB:
case MYSQL: {
ctx.visit(K_ENUM).sql('(');
Object[] enums = type.getType().getEnumConstants();
if (enums == null)
throw new IllegalStateException("EnumType must be a Java enum");
String separator = "";
for (Object e : enums) {
ctx.sql(separator).visit(DSL.inline(((EnumType) e).getLiteral()));
@ -4142,6 +4143,11 @@ final class Tools {
ctx.sql(')');
return;
}
default: {
type = emulateEnumType(enumType, enums);
break;
}
}
}
@ -4156,6 +4162,7 @@ final class Tools {
}
}
String typeName = type.getTypeName(ctx.configuration());
if (type.hasLength()) {
// [#6289] [#7191] Some databases don't support lengths on binary types
@ -4194,6 +4201,28 @@ final class Tools {
ctx.sql(' ').visit(K_COLLATE).sql(' ').visit(type.collation());
}
private static Object[] enumConstants(DataType<? extends EnumType> type) {
Object[] enums = type.getType().getEnumConstants();
if (enums == null)
throw new DataTypeException("EnumType must be a Java enum");
return enums;
}
static final DataType<String> emulateEnumType(DataType<? extends EnumType> type) {
return emulateEnumType(type, enumConstants(type));
}
static final DataType<String> emulateEnumType(DataType<? extends EnumType> type, Object[] enums) {
int length = 0;
for (Object e : enums)
if (((EnumType) e).getLiteral() != null)
length = Math.max(length, ((EnumType) e).getLiteral().length());
return VARCHAR(length).nullability(type.nullability()).defaultValue((Field) type.defaultValue());
}
// -------------------------------------------------------------------------
// XXX: ForkJoinPool ManagedBlock implementation
// -------------------------------------------------------------------------
@ -4239,7 +4268,17 @@ final class Tools {
}
static <E extends EnumType> EnumType[] enums(Class<? extends E> type) {
static String[] enumLiterals(Class<? extends EnumType> type) {
EnumType[] values = enums(type);
String[] result = new String[values.length];
for (int i = 0; i < values.length; i++)
result[i] = values[i].getLiteral();
return result;
}
static <E extends EnumType> E[] enums(Class<? extends E> type) {
// Java implementation
if (Enum.class.isAssignableFrom(type)) {
@ -4255,7 +4294,7 @@ final class Tools {
Class<?> companionClass = Thread.currentThread().getContextClassLoader().loadClass(type.getName() + "$");
java.lang.reflect.Field module = companionClass.getField("MODULE$");
Object companion = module.get(companionClass);
return (EnumType[]) companionClass.getMethod("values").invoke(companion);
return (E[]) companionClass.getMethod("values").invoke(companion);
}
catch (Exception e) {
throw new MappingException("Error while looking up Scala enum", e);