From 6ba693edc993b23b79e2720c93941684b4e46ee2 Mon Sep 17 00:00:00 2001 From: lukaseder Date: Wed, 4 Apr 2018 17:46:55 +0300 Subject: [PATCH] [#7379] Translate MySQL / PostgreSQL ENUM type to check constraint in other databases --- .../java/org/jooq/impl/CreateTableImpl.java | 21 ++++++++ .../java/org/jooq/impl/DefaultBinding.java | 20 +++---- .../main/java/org/jooq/impl/ParserImpl.java | 10 +++- jOOQ/src/main/java/org/jooq/impl/Tools.java | 53 ++++++++++++++++--- 4 files changed, 85 insertions(+), 19 deletions(-) diff --git a/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java b/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java index eeb5dfe5d3..2237dde1e6 100644 --- a/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/CreateTableImpl.java @@ -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 extends AbstractQuery implements private static final EnumSet NO_SUPPORT_IF_NOT_EXISTS = EnumSet.of(DERBY, FIREBIRD); private static final EnumSet NO_SUPPORT_WITH_DATA = EnumSet.of(H2, MARIADB, MYSQL, SQLITE); private static final EnumSet EMULATE_INDEXES_IN_BLOCK = EnumSet.of(POSTGRES); + private static final EnumSet EMULATE_ENUM_TYPES_AS_CHECK = EnumSet.of(CUBRID, DERBY, FIREBIRD, H2, HSQLDB, SQLITE); private static final EnumSet REQUIRES_WITH_DATA = EnumSet.of(HSQLDB); private static final EnumSet WRAP_SELECT_IN_PARENS = EnumSet.of(HSQLDB); private static final EnumSet SUPPORT_TEMPORARY = EnumSet.of(MARIADB, MYSQL, POSTGRES); @@ -444,6 +450,21 @@ final class CreateTableImpl 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) type.getType())))); + } + } + } + ctx.end(CREATE_TABLE_CONSTRAINTS); if (!indexes.isEmpty() && !EMULATE_INDEXES_IN_BLOCK.contains(ctx.family())) { diff --git a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java index 1286ffe353..917b7c277f 100644 --- a/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java +++ b/jOOQ/src/main/java/org/jooq/impl/DefaultBinding.java @@ -636,10 +636,13 @@ public class DefaultBinding implements Binding { + // [#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) 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 implements Binding { } // 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 ctx, T value) throws SQLException { @@ -2167,14 +2168,13 @@ public class DefaultBinding implements Binding { render.sql("[]"); } - @SuppressWarnings("unchecked") static final E getEnumType(Class 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); diff --git a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java index 41ed69890e..b3349997ca 100644 --- a/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/ParserImpl.java @@ -6732,16 +6732,22 @@ final class ParserImpl implements Parser { private static final DataType parseDataTypeEnum(ParserContext ctx) { parse(ctx, '('); List literals = new ArrayList(); + 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); } // ----------------------------------------------------------------------------------------------------------------- diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index a4a6ed9dd2..bfebd42863 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -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 = (DataType) 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 type) { + Object[] enums = type.getType().getEnumConstants(); + + if (enums == null) + throw new DataTypeException("EnumType must be a Java enum"); + + return enums; + } + + static final DataType emulateEnumType(DataType type) { + return emulateEnumType(type, enumConstants(type)); + } + + static final DataType emulateEnumType(DataType 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 EnumType[] enums(Class type) { + static String[] enumLiterals(Class 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[] enums(Class 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);