[jOOQ/jOOQ#18988] Regression: Wrong DDL exported by MetaImpl for MySQL

DEFAULT CURRENT_TIMESTAMP columns
This commit is contained in:
Lukas Eder 2025-09-08 11:44:52 +02:00
parent e4981c5f15
commit 6bc46e6645
13 changed files with 161 additions and 8 deletions

View File

@ -232,4 +232,24 @@ public interface ResultQueryDatabase extends Database {
/**
* A query that produces generator expressions for computed columns for a
* set of input schemas.
* <p>
* The resulting columns are:
* <ol>
* <li>Catalog name</li>
* <li>Schema name</li>
* <li>Table name</li>
* <li>Column name</li>
* <li>Generator expression as in {@link DataType#generatedAlwaysAs()}</li>
* <li>Generation option as in {@link DataType#generationOption()}</li>
* </ol>
*
* @return The query or <code>null</code> if this implementation doesn't
* support the query.
*/
@Internal
@Nullable
ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas);
}

View File

@ -366,6 +366,11 @@ public class ClickHouseDatabase extends AbstractDatabase implements ResultQueryD
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return null;
}
@Override
protected List<XMLSchemaCollectionDefinition> getXMLSchemaCollections0() throws SQLException {
List<XMLSchemaCollectionDefinition> result = new ArrayList<>();

View File

@ -658,6 +658,11 @@ public class DerbyDatabase extends AbstractDatabase implements ResultQueryDataba
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return null;
}
@Override
protected List<XMLSchemaCollectionDefinition> getXMLSchemaCollections0() throws SQLException {

View File

@ -418,6 +418,11 @@ public class DuckDBDatabase extends AbstractDatabase implements ResultQueryDatab
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return null;
}
@Override
public ResultQuery<Record12<String, String, String, String, Integer, Integer, Long, Long, BigDecimal, BigDecimal, Boolean, Long>> sequences(List<String> schemas) {
return create()

View File

@ -709,6 +709,11 @@ public class FirebirdDatabase extends AbstractDatabase implements ResultQueryDat
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return null;
}
@Override
protected List<XMLSchemaCollectionDefinition> getXMLSchemaCollections0() throws SQLException {

View File

@ -774,6 +774,11 @@ public class H2Database extends AbstractDatabase implements ResultQueryDatabase
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return null;
}
@Override
protected List<SequenceDefinition> getSequences0() throws SQLException {

View File

@ -713,6 +713,11 @@ public class HSQLDBDatabase extends AbstractDatabase implements ResultQueryDatab
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return null;
}
@Override
protected List<XMLSchemaCollectionDefinition> getXMLSchemaCollections0() throws SQLException {

View File

@ -112,6 +112,7 @@ import org.jooq.TableOptions.TableType;
// ...
import org.jooq.impl.DSL;
import org.jooq.impl.QOM.ForeignKeyRule;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.meta.AbstractDatabase;
import org.jooq.meta.AbstractIndexDefinition;
import org.jooq.meta.ArrayDefinition;
@ -716,6 +717,28 @@ public class MySQLDatabase extends AbstractDatabase implements ResultQueryDataba
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return create()
.select(
inline(null, VARCHAR).as("table_catalog"),
COLUMNS.TABLE_SCHEMA,
COLUMNS.TABLE_NAME,
COLUMNS.COLUMN_NAME,
generationExpression(COLUMNS.GENERATION_EXPRESSION),
when(COLUMNS.EXTRA.in(inline("VIRTUAL"), inline("VIRTUAL GENERATED")), inline(GenerationOption.VIRTUAL.name()))
.when(COLUMNS.EXTRA.in(inline("PERSISTENT"), inline("STORED GENERATED")), inline(GenerationOption.STORED.name()))
.as("generationOption"))
.from(COLUMNS)
.where(COLUMNS.TABLE_SCHEMA.in(schemas))
.and(COLUMNS.EXTRA.in(
inline("VIRTUAL"),
inline("VIRTUAL GENERATED"),
inline("PERSISTENT"),
inline("STORED GENERATED")))
.orderBy(COLUMNS.ORDINAL_POSITION);
}
@Override
protected List<XMLSchemaCollectionDefinition> getXMLSchemaCollections0() throws SQLException {

View File

@ -1082,6 +1082,11 @@ public class PostgresDatabase extends AbstractDatabase implements ResultQueryDat
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return null;
}
@Override
protected List<XMLSchemaCollectionDefinition> getXMLSchemaCollections0() throws SQLException {

View File

@ -616,6 +616,11 @@ public class SQLiteDatabase extends AbstractDatabase implements ResultQueryDatab
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return null;
}
@Override
protected List<XMLSchemaCollectionDefinition> getXMLSchemaCollections0() throws SQLException {

View File

@ -198,6 +198,11 @@ public class TrinoDatabase extends AbstractDatabase implements ResultQueryDataba
@Override
public ResultQuery<Record6<String, String, String, String, String, String>> generators(List<String> schemas) {
return null;
}
@Override
protected List<CatalogDefinition> getCatalogs0() throws SQLException {
List<CatalogDefinition> result = new ArrayList<>();

View File

@ -90,7 +90,6 @@ import static org.jooq.impl.SQLDataType.SMALLINT;
import static org.jooq.impl.SQLDataType.VARCHAR;
import static org.jooq.impl.Tools.EMPTY_OBJECT;
import static org.jooq.impl.Tools.EMPTY_SORTFIELD;
import static org.jooq.impl.Tools.anyMatch;
import static org.jooq.impl.Tools.flatMap;
import static org.jooq.impl.Tools.map;
import static org.jooq.tools.StringUtils.defaultIfEmpty;
@ -111,7 +110,6 @@ import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
@ -153,12 +151,9 @@ import org.jooq.exception.DataAccessException;
import org.jooq.exception.DataDefinitionException;
import org.jooq.exception.DataTypeException;
import org.jooq.exception.SQLDialectNotSupportedException;
import org.jooq.impl.QOM.ForeignKeyRule;
import org.jooq.impl.QOM.GenerationOption;
import org.jooq.tools.JooqLogger;
import org.jooq.tools.StringUtils;
import org.jooq.tools.jdbc.JDBCUtils;
import org.jetbrains.annotations.Nullable;
/**
* An implementation of the public {@link Meta} type.
@ -465,6 +460,8 @@ final class MetaImpl extends AbstractMeta {
}
}
static final record Generator(String expression, GenerationOption option) {}
private final class MetaSchema extends SchemaImpl {
private final boolean empty;
private transient volatile Map<Name, Result<Record>> columnCache;
@ -472,6 +469,7 @@ final class MetaImpl extends AbstractMeta {
private transient volatile Map<Name, Result<Record>> sequenceCache;
private transient volatile Map<Name, String> sourceCache;
private transient volatile Map<Name, String> commentCache;
private transient volatile Map<Name, Generator> generatorCache;
@ -1001,6 +999,38 @@ final class MetaImpl extends AbstractMeta {
return null;
}
final Generator generator(String tableName, String columnName) {
if (generatorCache == null) {
String sql = MetaSQL.M_GENERATORS(dialect());
if (sql != null) {
Result<Record> result = meta(() -> "Error while fetching sources for schema " + this, meta ->
withCatalog(getCatalog(), ctx(meta), ctx ->
ctx.resultQuery(patchSchema(sql), MetaSchema.this.getName()).fetch()
)
);
// TODO Support catalogs as well
generatorCache = new LinkedHashMap<>();
for (Record r : result) {
String e = r.get(4, String.class);
if (e != null) {
generatorCache.put(
name(r.get(1, String.class), r.get(2, String.class), r.get(3, String.class)),
new Generator(r.get(4, String.class), r.get(5, GenerationOption.class))
);
}
}
}
}
if (generatorCache != null)
return generatorCache.get(name(MetaSchema.this.getName(), tableName, columnName));
else
return null;
}
final String comment(String tableName) {
return comment(tableName, null);
}
@ -1705,6 +1735,10 @@ final class MetaImpl extends AbstractMeta {
type = new DefaultDataType(family(), Object.class, typeName);
}
// [#18988] MySQL JDBC reports DEFAULT CURRENT_TIMESTAMP columns as generated, not defaulted.
Generator g = schema.generator(getName(), columnName);
if (isGenerated && g == null && MYSQL == family())
isGenerated = false;

View File

@ -14,6 +14,7 @@ final class MetaSQL {
private static final EnumMap<SQLDialect, String> M_ENUMS = new EnumMap<>(SQLDialect.class);
private static final EnumMap<SQLDialect, String> M_SOURCES = new EnumMap<>(SQLDialect.class);
private static final EnumMap<SQLDialect, String> M_COMMENTS = new EnumMap<>(SQLDialect.class);
private static final EnumMap<SQLDialect, String> M_GENERATORS = new EnumMap<>(SQLDialect.class);
private static final EnumMap<SQLDialect, String> M_TRIGGERS = new EnumMap<>(SQLDialect.class);
private static final EnumMap<SQLDialect, String> M_SYNONYMS = new EnumMap<>(SQLDialect.class);
@ -47,6 +48,11 @@ final class MetaSQL {
return result != null ? result : M_COMMENTS.get(dialect.family());
}
static final String M_GENERATORS(SQLDialect dialect) {
String result = M_GENERATORS.get(dialect);
return result != null ? result : M_GENERATORS.get(dialect.family());
}
static final String M_TRIGGERS(SQLDialect dialect) {
String result = M_TRIGGERS.get(dialect);
return result != null ? result : M_TRIGGERS.get(dialect.family());
@ -254,8 +260,8 @@ final class MetaSQL {
M_ENUMS.put(MARIADB, "with recursive e as (select information_schema.COLUMNS.TABLE_SCHEMA, information_schema.COLUMNS.TABLE_NAME, information_schema.COLUMNS.COLUMN_NAME, regexp_replace(information_schema.COLUMNS.COLUMN_TYPE, 'enum\\((.*)\\)', '$1') as e from information_schema.COLUMNS where information_schema.COLUMNS.DATA_TYPE = 'enum'), l as (select e.TABLE_SCHEMA, e.TABLE_NAME, e.COLUMN_NAME, e, cast('' as char(32767)) as l, 0 as p from e union all select e.TABLE_SCHEMA, e.TABLE_NAME, e.COLUMN_NAME, regexp_replace(e, '''.*?''(?:,|$)(.*)', '$1', 1, 1), replace(regexp_replace(e, '''(.*?)''(?:,|$).*', '$1', 1, 1), '''''', ''''), (p + 1) from l as e where char_length(e) > 0) select l.TABLE_SCHEMA, l.TABLE_NAME, l.COLUMN_NAME, null as DATA_TYPE, l.l, l.p from l where (p > 0 and l.TABLE_SCHEMA in (?)) order by l.TABLE_SCHEMA, l.TABLE_NAME, l.COLUMN_NAME, l.p");
M_ENUMS.put(MYSQL, "with recursive e as (select information_schema.COLUMNS.TABLE_SCHEMA, information_schema.COLUMNS.TABLE_NAME, information_schema.COLUMNS.COLUMN_NAME, regexp_replace(information_schema.COLUMNS.COLUMN_TYPE, 'enum\\((.*)\\)', '$1') as e from information_schema.COLUMNS where information_schema.COLUMNS.DATA_TYPE = 'enum'), l as (select e.TABLE_SCHEMA, e.TABLE_NAME, e.COLUMN_NAME, e, cast('' as char(32767)) as l, 0 as p from e union all select e.TABLE_SCHEMA, e.TABLE_NAME, e.COLUMN_NAME, regexp_replace(e, '''.*?''(?:,|$)(.*)', '$1', 1, 1), replace(regexp_replace(e, '''(.*?)''(?:,|$).*', '$1', 1, 1), '''''', ''''), (p + 1) from l as e where char_length(e) > 0) select l.TABLE_SCHEMA, l.TABLE_NAME, l.COLUMN_NAME, null as DATA_TYPE, l.l, l.p from l where (p > 0 and l.TABLE_SCHEMA in (?)) order by l.TABLE_SCHEMA, l.TABLE_NAME, l.COLUMN_NAME, l.p");
M_ENUMS.put(MARIADB, "with recursive e as (select information_schema.COLUMNS.TABLE_SCHEMA, information_schema.COLUMNS.TABLE_NAME, information_schema.COLUMNS.COLUMN_NAME, regexp_replace(information_schema.COLUMNS.COLUMN_TYPE, 'enum\\\\((.*)\\\\)', '$1') as e from information_schema.COLUMNS where information_schema.COLUMNS.DATA_TYPE = 'enum'), l as (select e.TABLE_SCHEMA, e.TABLE_NAME, e.COLUMN_NAME, e, cast('' as char(32767)) as l, 0 as p from e union all select e.TABLE_SCHEMA, e.TABLE_NAME, e.COLUMN_NAME, regexp_replace(e, '''.*?''(?:,|$)(.*)', '$1', 1, 1), replace(regexp_replace(e, '''(.*?)''(?:,|$).*', '$1', 1, 1), '''''', ''''), (p + 1) from l as e where char_length(e) > 0) select l.TABLE_SCHEMA, l.TABLE_NAME, l.COLUMN_NAME, null as DATA_TYPE, l.l, l.p from l where (p > 0 and l.TABLE_SCHEMA in (?)) order by l.TABLE_SCHEMA, l.TABLE_NAME, l.COLUMN_NAME, l.p");
M_ENUMS.put(MYSQL, "with recursive e as (select information_schema.COLUMNS.TABLE_SCHEMA, information_schema.COLUMNS.TABLE_NAME, information_schema.COLUMNS.COLUMN_NAME, regexp_replace(information_schema.COLUMNS.COLUMN_TYPE, 'enum\\\\((.*)\\\\)', '$1') as e from information_schema.COLUMNS where information_schema.COLUMNS.DATA_TYPE = 'enum'), l as (select e.TABLE_SCHEMA, e.TABLE_NAME, e.COLUMN_NAME, e, cast('' as char(32767)) as l, 0 as p from e union all select e.TABLE_SCHEMA, e.TABLE_NAME, e.COLUMN_NAME, regexp_replace(e, '''.*?''(?:,|$)(.*)', '$1', 1, 1), replace(regexp_replace(e, '''(.*?)''(?:,|$).*', '$1', 1, 1), '''''', ''''), (p + 1) from l as e where char_length(e) > 0) select l.TABLE_SCHEMA, l.TABLE_NAME, l.COLUMN_NAME, null as DATA_TYPE, l.l, l.p from l where (p > 0 and l.TABLE_SCHEMA in (?)) order by l.TABLE_SCHEMA, l.TABLE_NAME, l.COLUMN_NAME, l.p");
@ -425,6 +431,31 @@ final class MetaSQL {
M_GENERATORS.put(MARIADB, "select null as table_catalog, information_schema.COLUMNS.TABLE_SCHEMA, information_schema.COLUMNS.TABLE_NAME, information_schema.COLUMNS.COLUMN_NAME, information_schema.COLUMNS.GENERATION_EXPRESSION, case when information_schema.COLUMNS.EXTRA in ('VIRTUAL', 'VIRTUAL GENERATED') then 'VIRTUAL' when information_schema.COLUMNS.EXTRA in ('PERSISTENT', 'STORED GENERATED') then 'STORED' end as generationOption from information_schema.COLUMNS where (information_schema.COLUMNS.TABLE_SCHEMA in (?) and information_schema.COLUMNS.EXTRA in ('VIRTUAL', 'VIRTUAL GENERATED', 'PERSISTENT', 'STORED GENERATED')) order by information_schema.COLUMNS.ORDINAL_POSITION");
M_GENERATORS.put(MYSQL, "select null as table_catalog, information_schema.COLUMNS.TABLE_SCHEMA, information_schema.COLUMNS.TABLE_NAME, information_schema.COLUMNS.COLUMN_NAME, information_schema.COLUMNS.GENERATION_EXPRESSION, case when information_schema.COLUMNS.EXTRA in ('VIRTUAL', 'VIRTUAL GENERATED') then 'VIRTUAL' when information_schema.COLUMNS.EXTRA in ('PERSISTENT', 'STORED GENERATED') then 'STORED' end as generationOption from information_schema.COLUMNS where (information_schema.COLUMNS.TABLE_SCHEMA in (?) and information_schema.COLUMNS.EXTRA in ('VIRTUAL', 'VIRTUAL GENERATED', 'PERSISTENT', 'STORED GENERATED')) order by information_schema.COLUMNS.ORDINAL_POSITION");