[jOOQ/jOOQ#252] Added H2 code generation support

This commit is contained in:
Lukas Eder 2022-09-08 14:09:35 +02:00
parent 196bc03ee5
commit 79ea77c896
5 changed files with 107 additions and 42 deletions

View File

@ -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
*/

View File

@ -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(

View File

@ -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);
}

View File

@ -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<ElementTypeLookupKey, ElementType> 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);

View File

@ -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