diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationUtil.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationUtil.java index e5be3ca51d..f004d5e2b6 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationUtil.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/GenerationUtil.java @@ -54,6 +54,10 @@ import java.util.regex.Pattern; import org.jooq.Name; import org.jooq.SQLDialect; import org.jooq.exception.SQLDialectNotSupportedException; +import org.jooq.meta.DataTypeDefinition; +import org.jooq.meta.Database; +import org.jooq.meta.DefaultDataTypeDefinition; +import org.jooq.meta.SchemaDefinition; import org.jooq.util.h2.H2DataType; /** @@ -484,6 +488,29 @@ class GenerationUtil { return qualifiedJavaType.replaceAll(".*\\.", ""); } + static DataTypeDefinition getArrayBaseType(SQLDialect dialect, DataTypeDefinition type) { + Name name = getArrayBaseType(dialect, type.getType(), type.getQualifiedUserType()); + + return new DefaultDataTypeDefinition( + type.getDatabase(), + type.getSchema(), + name.last(), + type.getLength(), + type.getPrecision(), + type.getScale(), + type.isNullable(), + type.isReadonly(), + type.getGeneratedAlwaysAs(), + type.getDefaultValue(), + type.isIdentity(), + name, + type.getGenerator(), + type.getConverter(), + type.getBinding(), + type.getJavaType() + ); + } + /** * Gets the base type for an array type, depending on the RDBMS dialect */ diff --git a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java index 0f9b79eea3..32302dddc3 100644 --- a/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java +++ b/jOOQ-codegen/src/main/java/org/jooq/codegen/JavaGenerator.java @@ -9179,24 +9179,7 @@ public class JavaGenerator extends AbstractGenerator { // [#4388] TODO: Improve array handling if (database.isArrayType(type.getType())) { - Name baseType = GenerationUtil.getArrayBaseType(db.getDialect(), type.getType(), type.getQualifiedUserType()); - return getTypeReference( - db, - type.getSchema(), - out, - baseType.last(), - type.getPrecision(), - type.getScale(), - type.getLength(), - true, - false, - false, - null, - null, - null, - null, - baseType - ) + ".getArrayDataType()"; + return getJavaTypeReference(db, GenerationUtil.getArrayBaseType(db.getDialect(), type), out) + ".getArrayDataType()"; } else { return getTypeReference( diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java index ce3e25546b..a045470893 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/AbstractDatabase.java @@ -391,6 +391,10 @@ public abstract class AbstractDatabase implements Database { // [A-Za-z_$#][A-Za-z0-9_$#]+ in generated jOOQ-meta code. configuration.settings().setRenderQuotedNames(getRenderQuotedNames()); + // [#252] We're not quoting identifiers. Hence the default name path + // separator is illegal + configuration.settings().setNamePathSeparator("__"); + if (muteExceptions) { return DSL.using(configuration); } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2Database.java b/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2Database.java index 5ff24d7809..e8d5cf36f6 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2Database.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2Database.java @@ -56,6 +56,7 @@ import static org.jooq.impl.DSL.not; import static org.jooq.impl.DSL.nullif; import static org.jooq.impl.DSL.nvl; import static org.jooq.impl.DSL.one; +import static org.jooq.impl.DSL.row; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.upper; import static org.jooq.impl.DSL.when; @@ -87,9 +88,11 @@ import java.math.BigDecimal; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicBoolean; import org.jooq.DSLContext; import org.jooq.Field; @@ -135,6 +138,7 @@ import org.jooq.meta.hsqldb.information_schema.Tables; import org.jooq.meta.hsqldb.information_schema.tables.CheckConstraints; import org.jooq.meta.hsqldb.information_schema.tables.DomainConstraints; import org.jooq.meta.hsqldb.information_schema.tables.Domains; +import org.jooq.meta.hsqldb.information_schema.tables.ElementTypes; import org.jooq.meta.hsqldb.information_schema.tables.KeyColumnUsage; import org.jooq.tools.JooqLogger; import org.jooq.tools.StringUtils; @@ -151,6 +155,51 @@ public class H2Database extends AbstractDatabase implements ResultQueryDatabase private static final long DEFAULT_SEQUENCE_CACHE = 32; private static final long DEFAULT_SEQUENCE_MAXVALUE = Long.MAX_VALUE; + static final record ElementTypeLookupKey (String schema, String name, String identifier) {} + static final record ElementType (String dataType, Long length, Long precision, Long scale, String identifier, int dimension) {} + private Map ELEMENT_TYPE_LOOKUP; + + ElementType elementTypeLookup(ElementTypeLookupKey key) { + if (ELEMENT_TYPE_LOOKUP == null) { + ElementTypes e = ELEMENT_TYPES; + + ELEMENT_TYPE_LOOKUP = create().fetchMap( + select( + row( + e.OBJECT_SCHEMA, + e.OBJECT_NAME, + e.COLLECTION_TYPE_IDENTIFIER + ).mapping(ElementTypeLookupKey::new), + row( + e.DATA_TYPE, + e.CHARACTER_MAXIMUM_LENGTH, + coalesce(e.DATETIME_PRECISION, e.NUMERIC_PRECISION), + e.NUMERIC_SCALE, + e.DTD_IDENTIFIER, + inline(1) + ).mapping(ElementType::new) + ) + .from(e) + .where(e.OBJECT_SCHEMA.in(getInputSchemata())) + ); + + AtomicBoolean repeat = new AtomicBoolean(true); + while (repeat.getAndSet(false)) { + ELEMENT_TYPE_LOOKUP.replaceAll((k, v) -> { + ElementType et = ELEMENT_TYPE_LOOKUP.get(new ElementTypeLookupKey(k.schema(), k.name(), v.identifier())); + + if (et != null) { + repeat.set(true); + return new ElementType(et.dataType(), et.length(), et.precision(), et.scale(), et.identifier(), v.dimension() + 1); } + else + return v; + }); + } + } + + return ELEMENT_TYPE_LOOKUP.get(key); + } + @Override protected DSLContext create0() { return DSL.using(getConnection(), SQLDialect.H2); diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2TableDefinition.java b/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2TableDefinition.java index 7640248fef..f72cda5bfa 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2TableDefinition.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/h2/H2TableDefinition.java @@ -40,37 +40,34 @@ package org.jooq.meta.h2; import static org.jooq.impl.DSL.any; import static org.jooq.impl.DSL.choose; import static org.jooq.impl.DSL.coalesce; -import static org.jooq.impl.DSL.concat; import static org.jooq.impl.DSL.condition; -import static org.jooq.impl.DSL.field; import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.name; import static org.jooq.impl.DSL.noCondition; import static org.jooq.impl.DSL.nvl; import static org.jooq.impl.DSL.when; import static org.jooq.impl.SQLDataType.BOOLEAN; -import static org.jooq.impl.SQLDataType.INTEGER; import static org.jooq.meta.h2.information_schema.Tables.COLUMNS; -import static org.jooq.meta.hsqldb.information_schema.Tables.ELEMENT_TYPES; import static org.jooq.tools.StringUtils.defaultString; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.IntStream; -import org.jooq.Field; import org.jooq.Name; import org.jooq.Param; import org.jooq.Record; -import org.jooq.Table; import org.jooq.TableOptions.TableType; import org.jooq.meta.AbstractTableDefinition; import org.jooq.meta.ColumnDefinition; import org.jooq.meta.DataTypeDefinition; -import org.jooq.meta.Database; import org.jooq.meta.DefaultColumnDefinition; import org.jooq.meta.DefaultDataTypeDefinition; import org.jooq.meta.SchemaDefinition; +import org.jooq.meta.h2.H2Database.ElementType; +import org.jooq.meta.h2.H2Database.ElementTypeLookupKey; import org.jooq.meta.hsqldb.information_schema.Tables; /** @@ -106,23 +103,26 @@ public class H2TableDefinition extends AbstractTableDefinition { H2Database db = (H2Database) getDatabase(); + + // [#252] While recursing on ELEMENT_TYPES to detect multi dimensional + // arrays looks like the cleanest approach, H2's recursive SQL + // has been troubled by a ton of bugs in the past. Hard-coded + // left joins are an ugly option, which is why we opt for doing + // this calculation in Java, for once. + // See also: https://github.com/jOOQ/jOOQ/issues/252#issuecomment-1240484853 for (Record record : create().select( COLUMNS.COLUMN_NAME, COLUMNS.ORDINAL_POSITION, - // [#13302] Use explicit NULL check to prevent side-effects from H2's compatibility run modes - when(ELEMENT_TYPES.DATA_TYPE.isNotNull(), concat(ELEMENT_TYPES.DATA_TYPE, inline(" ARRAY"))) // [#2230] [#11733] Translate INTERVAL_TYPE to supported types - .when(COLUMNS.INTERVAL_TYPE.like(any(inline("%YEAR%"), inline("%MONTH%"))), inline("INTERVAL YEAR TO MONTH")) + when(COLUMNS.INTERVAL_TYPE.like(any(inline("%YEAR%"), inline("%MONTH%"))), inline("INTERVAL YEAR TO MONTH")) .when(COLUMNS.INTERVAL_TYPE.like(any(inline("%DAY%"), inline("%HOUR%"), inline("%MINUTE%"), inline("%SECOND%"))), inline("INTERVAL DAY TO SECOND")) .else_(Tables.COLUMNS.DATA_TYPE).as(COLUMNS.TYPE_NAME), - nvl(ELEMENT_TYPES.CHARACTER_MAXIMUM_LENGTH, COLUMNS.CHARACTER_MAXIMUM_LENGTH).as(COLUMNS.CHARACTER_MAXIMUM_LENGTH), + COLUMNS.CHARACTER_MAXIMUM_LENGTH, coalesce( - ELEMENT_TYPES.DATETIME_PRECISION, - ELEMENT_TYPES.NUMERIC_PRECISION, - COLUMNS.DATETIME_PRECISION, + COLUMNS.DATETIME_PRECISION.coerce(COLUMNS.NUMERIC_PRECISION), COLUMNS.NUMERIC_PRECISION).as(COLUMNS.NUMERIC_PRECISION), - nvl(ELEMENT_TYPES.NUMERIC_SCALE, COLUMNS.NUMERIC_SCALE).as(COLUMNS.NUMERIC_SCALE), + COLUMNS.NUMERIC_SCALE, COLUMNS.IS_NULLABLE, Tables.COLUMNS.IS_GENERATED.eq(inline("ALWAYS")).as(COLUMNS.IS_COMPUTED), Tables.COLUMNS.GENERATION_EXPRESSION, @@ -130,13 +130,10 @@ public class H2TableDefinition extends AbstractTableDefinition { COLUMNS.REMARKS, Tables.COLUMNS.IS_IDENTITY.eq(inline("YES")).as(Tables.COLUMNS.IS_IDENTITY), COLUMNS.DOMAIN_SCHEMA, - COLUMNS.DOMAIN_NAME + COLUMNS.DOMAIN_NAME, + Tables.COLUMNS.DTD_IDENTIFIER ) .from(COLUMNS) - .leftJoin(ELEMENT_TYPES) - .on(Tables.COLUMNS.TABLE_SCHEMA.equal(ELEMENT_TYPES.OBJECT_SCHEMA)) - .and(Tables.COLUMNS.TABLE_NAME.equal(ELEMENT_TYPES.OBJECT_NAME)) - .and(Tables.COLUMNS.DTD_IDENTIFIER.equal(ELEMENT_TYPES.COLLECTION_TYPE_IDENTIFIER)) .where(COLUMNS.TABLE_SCHEMA.equal(getSchema().getName())) .and(COLUMNS.TABLE_NAME.equal(getName())) .and(!getDatabase().getIncludeInvisibleColumns() @@ -163,13 +160,18 @@ public class H2TableDefinition extends AbstractTableDefinition { ? getDatabase().getSchema(record.get(COLUMNS.DOMAIN_SCHEMA)) : getSchema(); + ElementType et = db.elementTypeLookup(new ElementTypeLookupKey(getSchema().getName(), getName(), record.get(Tables.COLUMNS.DTD_IDENTIFIER))); + + if (et == null) + et = new ElementType(record.get(COLUMNS.TYPE_NAME), record.get(COLUMNS.CHARACTER_MAXIMUM_LENGTH), record.get(COLUMNS.NUMERIC_PRECISION), record.get(COLUMNS.NUMERIC_SCALE), null, 0); + DataTypeDefinition type = new DefaultDataTypeDefinition( getDatabase(), typeSchema == null ? getSchema() : typeSchema, - record.get(COLUMNS.TYPE_NAME), - record.get(COLUMNS.CHARACTER_MAXIMUM_LENGTH), - record.get(COLUMNS.NUMERIC_PRECISION), - record.get(COLUMNS.NUMERIC_SCALE), + et.dataType() + IntStream.range(0, et.dimension()).mapToObj(i -> " ARRAY").collect(Collectors.joining()), + et.length(), + et.precision(), + et.scale(), record.get(COLUMNS.IS_NULLABLE, boolean.class), isIdentity || isComputed ? null : record.get(COLUMNS.COLUMN_DEFAULT), userType