[#7379] Translate MySQL / PostgreSQL ENUM type to check constraint in other databases
This commit is contained in:
parent
2ec9a789f9
commit
6ba693edc9
@ -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())) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
Reference in New Issue
Block a user