diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/ResultQueryDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/ResultQueryDatabase.java new file mode 100644 index 0000000000..80418e5b1a --- /dev/null +++ b/jOOQ-meta/src/main/java/org/jooq/meta/ResultQueryDatabase.java @@ -0,0 +1,73 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Other licenses: + * ----------------------------------------------------------------------------- + * Commercial licenses for this work are available. These replace the above + * ASL 2.0 and offer limited warranties, support, maintenance, and commercial + * database integrations. + * + * For more information, please visit: http://www.jooq.org/licenses + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + */ +package org.jooq.meta; + +import java.util.List; + +import org.jooq.Internal; +import org.jooq.Meta; +import org.jooq.Record6; +import org.jooq.ResultQuery; + +/** + * An interface for all {@link AbstractDatabase} implementations that can + * produce {@link ResultQuery} objects to query meta data. + *

+ * These queries will be used to generate some queries in the core library's + * {@link Meta} API. + * + * @author Lukas Eder + */ +@Internal +public interface ResultQueryDatabase { + + /** + * A query that produces unique keys for a set of input schemas. + *

+ * The resulting columns are: + *

    + *
  1. Catalog name
  2. + *
  3. Schema name
  4. + *
  5. Table name
  6. + *
  7. Constraint name
  8. + *
  9. Column name
  10. + *
  11. Column sequence
  12. + *
+ */ + ResultQuery> uniqueKeysQuery(List schemas); +} diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/firebird/FirebirdDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/firebird/FirebirdDatabase.java index 8900b70ef7..bf6720ffd2 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/firebird/FirebirdDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/firebird/FirebirdDatabase.java @@ -50,6 +50,8 @@ import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.trim; import static org.jooq.impl.DSL.when; import static org.jooq.impl.DSL.zero; +import static org.jooq.impl.SQLDataType.INTEGER; +import static org.jooq.impl.SQLDataType.VARCHAR; import static org.jooq.meta.firebird.rdb.Tables.RDB$CHECK_CONSTRAINTS; import static org.jooq.meta.firebird.rdb.Tables.RDB$FIELDS; import static org.jooq.meta.firebird.rdb.Tables.RDB$GENERATORS; @@ -70,9 +72,10 @@ import java.util.Map.Entry; import org.jooq.DSLContext; import org.jooq.Field; import org.jooq.Record; -import org.jooq.Record3; import org.jooq.Record4; +import org.jooq.Record6; import org.jooq.Result; +import org.jooq.ResultQuery; import org.jooq.SQLDialect; import org.jooq.SortOrder; import org.jooq.TableOptions.TableType; @@ -93,6 +96,7 @@ import org.jooq.meta.EnumDefinition; import org.jooq.meta.IndexColumnDefinition; import org.jooq.meta.IndexDefinition; import org.jooq.meta.PackageDefinition; +import org.jooq.meta.ResultQueryDatabase; import org.jooq.meta.RoutineDefinition; import org.jooq.meta.SchemaDefinition; import org.jooq.meta.SequenceDefinition; @@ -112,7 +116,7 @@ import org.jooq.util.firebird.FirebirdDataType; /** * @author Sugiharto Lim - Initial contribution */ -public class FirebirdDatabase extends AbstractDatabase { +public class FirebirdDatabase extends AbstractDatabase implements ResultQueryDatabase { private static Boolean is30; @@ -131,10 +135,10 @@ public class FirebirdDatabase extends AbstractDatabase { @Override protected void loadPrimaryKeys(DefaultRelations r) throws SQLException { - for (Record record : fetchKeys("PRIMARY KEY")) { - String tableName = record.get(RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME.trim()); - String fieldName = record.get(RDB$INDEX_SEGMENTS.RDB$FIELD_NAME.trim()); - String key = record.get(RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME.trim()); + for (Record record : keysQuery("PRIMARY KEY")) { + String tableName = record.get(RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME); + String fieldName = record.get(RDB$INDEX_SEGMENTS.RDB$FIELD_NAME); + String key = record.get(RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME); TableDefinition td = getTable(this.getSchemata().get(0), tableName); if (td != null) @@ -144,10 +148,10 @@ public class FirebirdDatabase extends AbstractDatabase { @Override protected void loadUniqueKeys(DefaultRelations r) throws SQLException { - for (Record record : fetchKeys("UNIQUE")) { - String tableName = record.get(RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME.trim()); - String fieldName = record.get(RDB$INDEX_SEGMENTS.RDB$FIELD_NAME.trim()); - String key = record.get(RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME.trim()); + for (Record record : keysQuery("UNIQUE")) { + String tableName = record.get(RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME); + String fieldName = record.get(RDB$INDEX_SEGMENTS.RDB$FIELD_NAME); + String key = record.get(RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME); TableDefinition td = getTable(this.getSchemata().get(0), tableName); if (td != null) @@ -155,20 +159,27 @@ public class FirebirdDatabase extends AbstractDatabase { } } - private Result> fetchKeys(String constraintType) { + @Override + public ResultQuery> uniqueKeysQuery(List schemas) { + return keysQuery("UNIQUE"); + } + + private ResultQuery> keysQuery(String constraintType) { return create() .select( - RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME.trim(), - RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME.trim(), - RDB$INDEX_SEGMENTS.RDB$FIELD_NAME.trim()) + inline(null, VARCHAR).as("catalog"), + inline(null, VARCHAR).as("schema"), + RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME.trim().as(RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME), + RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME.trim().as(RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME), + RDB$INDEX_SEGMENTS.RDB$FIELD_NAME.trim().as(RDB$INDEX_SEGMENTS.RDB$FIELD_NAME), + RDB$INDEX_SEGMENTS.RDB$FIELD_POSITION.coerce(INTEGER)) .from(RDB$RELATION_CONSTRAINTS) .join(RDB$INDEX_SEGMENTS) .on(RDB$INDEX_SEGMENTS.RDB$INDEX_NAME.eq(RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME)) - .where(RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE.eq(constraintType)) + .where(RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE.eq(inline(constraintType))) .orderBy( RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME.asc(), - RDB$INDEX_SEGMENTS.RDB$FIELD_POSITION.asc()) - .fetch(); + RDB$INDEX_SEGMENTS.RDB$FIELD_POSITION.asc()); } @Override 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 f1286611c6..1159c2b81f 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 @@ -48,6 +48,7 @@ import static org.jooq.impl.DSL.nullif; import static org.jooq.impl.DSL.one; import static org.jooq.impl.DSL.select; import static org.jooq.impl.DSL.when; +import static org.jooq.impl.SQLDataType.INTEGER; import static org.jooq.meta.h2.information_schema.Tables.COLUMNS; import static org.jooq.meta.h2.information_schema.Tables.CONSTRAINTS; import static org.jooq.meta.h2.information_schema.Tables.CROSS_REFERENCES; @@ -63,6 +64,7 @@ import static org.jooq.meta.h2.information_schema.Tables.VIEWS; import java.io.StringReader; import java.sql.SQLException; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -71,7 +73,9 @@ import org.jooq.DSLContext; import org.jooq.Field; import org.jooq.Record; import org.jooq.Record4; +import org.jooq.Record6; import org.jooq.Result; +import org.jooq.ResultQuery; import org.jooq.SQLDialect; import org.jooq.Select; import org.jooq.SortOrder; @@ -97,6 +101,7 @@ import org.jooq.meta.EnumDefinition; import org.jooq.meta.IndexColumnDefinition; import org.jooq.meta.IndexDefinition; import org.jooq.meta.PackageDefinition; +import org.jooq.meta.ResultQueryDatabase; import org.jooq.meta.RoutineDefinition; import org.jooq.meta.SchemaDefinition; import org.jooq.meta.SequenceDefinition; @@ -111,7 +116,7 @@ import org.jooq.util.h2.H2DataType; * * @author Espen Stromsnes */ -public class H2Database extends AbstractDatabase { +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; @@ -217,7 +222,7 @@ public class H2Database extends AbstractDatabase { protected void loadPrimaryKeys(DefaultRelations relations) throws SQLException { // Workaround for https://github.com/h2database/h2database/issues/1000 - for (Record record : fetchKeys("PRIMARY KEY", "PRIMARY_KEY")) { + for (Record record : keysQuery(getInputSchemata(), Arrays.>asList(inline("PRIMARY KEY"), inline("PRIMARY_KEY")))) { SchemaDefinition schema = getSchema(record.get(CONSTRAINTS.TABLE_SCHEMA)); if (schema != null) { @@ -234,7 +239,7 @@ public class H2Database extends AbstractDatabase { @Override protected void loadUniqueKeys(DefaultRelations relations) throws SQLException { - for (Record record : fetchKeys("UNIQUE")) { + for (Record record : uniqueKeysQuery(getInputSchemata())) { SchemaDefinition schema = getSchema(record.get(CONSTRAINTS.TABLE_SCHEMA)); if (schema != null) { @@ -249,24 +254,30 @@ public class H2Database extends AbstractDatabase { } } - private Result> fetchKeys(String... constraintTypes) { + @Override + public ResultQuery> uniqueKeysQuery(List schemas) { + return keysQuery(schemas, Arrays.>asList(inline("UNIQUE"))); + } + + private ResultQuery> keysQuery(List schemas, List> constraintTypes) { return create().select( + CONSTRAINTS.TABLE_CATALOG, CONSTRAINTS.TABLE_SCHEMA, CONSTRAINTS.TABLE_NAME, CONSTRAINTS.CONSTRAINT_NAME, - INDEXES.COLUMN_NAME) + INDEXES.COLUMN_NAME, + INDEXES.ORDINAL_POSITION.coerce(INTEGER)) .from(CONSTRAINTS) .join(INDEXES) .on(CONSTRAINTS.TABLE_SCHEMA.eq(INDEXES.TABLE_SCHEMA)) .and(CONSTRAINTS.TABLE_NAME.eq(INDEXES.TABLE_NAME)) .and(CONSTRAINTS.UNIQUE_INDEX_NAME.eq(INDEXES.INDEX_NAME)) - .where(CONSTRAINTS.TABLE_SCHEMA.in(getInputSchemata())) + .where(CONSTRAINTS.TABLE_SCHEMA.in(schemas)) .and(CONSTRAINTS.CONSTRAINT_TYPE.in(constraintTypes)) .orderBy( CONSTRAINTS.TABLE_SCHEMA, CONSTRAINTS.CONSTRAINT_NAME, - INDEXES.ORDINAL_POSITION) - .fetch(); + INDEXES.ORDINAL_POSITION); } @Override diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/mysql/MySQLDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/mysql/MySQLDatabase.java index 2582ed2615..018aa1f3ba 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/mysql/MySQLDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/mysql/MySQLDatabase.java @@ -42,6 +42,7 @@ import static org.jooq.impl.DSL.falseCondition; import static org.jooq.impl.DSL.inline; import static org.jooq.impl.DSL.noCondition; import static org.jooq.impl.DSL.when; +import static org.jooq.impl.SQLDataType.INTEGER; import static org.jooq.meta.mysql.information_schema.Tables.CHECK_CONSTRAINTS; import static org.jooq.meta.mysql.information_schema.Tables.COLUMNS; import static org.jooq.meta.mysql.information_schema.Tables.KEY_COLUMN_USAGE; @@ -59,6 +60,7 @@ import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.Map.Entry; import org.jooq.DSLContext; @@ -67,12 +69,14 @@ import org.jooq.Record; import org.jooq.Record5; import org.jooq.Record6; import org.jooq.Result; +import org.jooq.ResultQuery; import org.jooq.SQLDialect; import org.jooq.SortOrder; import org.jooq.Table; import org.jooq.TableField; import org.jooq.TableOptions.TableType; import org.jooq.impl.DSL; +import org.jooq.impl.SQLDataType; import org.jooq.meta.AbstractDatabase; import org.jooq.meta.AbstractIndexDefinition; import org.jooq.meta.ArrayDefinition; @@ -87,6 +91,7 @@ import org.jooq.meta.EnumDefinition; import org.jooq.meta.IndexColumnDefinition; import org.jooq.meta.IndexDefinition; import org.jooq.meta.PackageDefinition; +import org.jooq.meta.ResultQueryDatabase; import org.jooq.meta.RoutineDefinition; import org.jooq.meta.SchemaDefinition; import org.jooq.meta.SequenceDefinition; @@ -99,7 +104,7 @@ import org.jooq.tools.csv.CSVReader; /** * @author Lukas Eder */ -public class MySQLDatabase extends AbstractDatabase { +public class MySQLDatabase extends AbstractDatabase implements ResultQueryDatabase { private static Boolean is8; private static Boolean is8_0_16; @@ -129,11 +134,7 @@ public class MySQLDatabase extends AbstractDatabase { STATISTICS.COLUMN_NAME, STATISTICS.SEQ_IN_INDEX) .from(from) - // [#5213] Duplicate schema value to work around MySQL issue https://bugs.mysql.com/bug.php?id=86022 - .where(STATISTICS.TABLE_SCHEMA.in(getInputSchemata()).or( - getInputSchemata().size() == 1 - ? STATISTICS.TABLE_SCHEMA.in(getInputSchemata()) - : falseCondition())) + .where(STATISTICS.TABLE_SCHEMA.in(workaroundFor5213(getInputSchemata()))) .and(getIncludeSystemIndexes() ? noCondition() : TABLE_CONSTRAINTS.CONSTRAINT_NAME.isNull() @@ -203,7 +204,7 @@ public class MySQLDatabase extends AbstractDatabase { @Override protected void loadPrimaryKeys(DefaultRelations relations) throws SQLException { - for (Record record : fetchKeys(true)) { + for (Record record : keysQuery(getInputSchemata(), true)) { SchemaDefinition schema = getSchema(record.get(STATISTICS.TABLE_SCHEMA)); String constraintName = record.get(STATISTICS.INDEX_NAME); String tableName = record.get(STATISTICS.TABLE_NAME); @@ -219,7 +220,7 @@ public class MySQLDatabase extends AbstractDatabase { @Override protected void loadUniqueKeys(DefaultRelations relations) throws SQLException { - for (Record record : fetchKeys(false)) { + for (Record record : uniqueKeysQuery(getInputSchemata())) { SchemaDefinition schema = getSchema(record.get(STATISTICS.TABLE_SCHEMA)); String constraintName = record.get(STATISTICS.INDEX_NAME); String tableName = record.get(STATISTICS.TABLE_NAME); @@ -255,24 +256,29 @@ public class MySQLDatabase extends AbstractDatabase { return is8_0_16; } - private Result fetchKeys(boolean primary) { + @Override + public ResultQuery> uniqueKeysQuery(List schemas) { + return keysQuery(schemas, false); + } + + private ResultQuery> keysQuery(List inputSchemata, boolean primary) { // [#3560] It has been shown that querying the STATISTICS table is much faster on // very large databases than going through TABLE_CONSTRAINTS and KEY_COLUMN_USAGE // [#2059] In MemSQL primary key indexes are typically duplicated // (once with INDEX_TYPE = 'SHARD' and once with INDEX_TYPE = 'BTREE) return create().selectDistinct( + + // Don't use the actual catalog value, which is meaningless. + // Besides, MetaImpl will rely on the TABLE_SCHEMA acting as the catalog. + inline(null, STATISTICS.TABLE_CATALOG).as(STATISTICS.TABLE_CATALOG), STATISTICS.TABLE_SCHEMA, STATISTICS.TABLE_NAME, - STATISTICS.COLUMN_NAME, STATISTICS.INDEX_NAME, - STATISTICS.SEQ_IN_INDEX) + STATISTICS.COLUMN_NAME, + STATISTICS.SEQ_IN_INDEX.coerce(INTEGER)) .from(STATISTICS) - // [#5213] Duplicate schema value to work around MySQL issue https://bugs.mysql.com/bug.php?id=86022 - .where(STATISTICS.TABLE_SCHEMA.in(getInputSchemata()).or( - getInputSchemata().size() == 1 - ? STATISTICS.TABLE_SCHEMA.in(getInputSchemata()) - : falseCondition())) + .where(STATISTICS.TABLE_SCHEMA.in(workaroundFor5213(inputSchemata))) .and(primary ? STATISTICS.INDEX_NAME.eq(inline("PRIMARY")) : STATISTICS.INDEX_NAME.ne(inline("PRIMARY")).and(STATISTICS.NON_UNIQUE.eq(inline(0)))) @@ -280,8 +286,7 @@ public class MySQLDatabase extends AbstractDatabase { STATISTICS.TABLE_SCHEMA, STATISTICS.TABLE_NAME, STATISTICS.INDEX_NAME, - STATISTICS.SEQ_IN_INDEX) - .fetch(); + STATISTICS.SEQ_IN_INDEX); } @Override @@ -299,11 +304,7 @@ public class MySQLDatabase extends AbstractDatabase { .on(REFERENTIAL_CONSTRAINTS.CONSTRAINT_SCHEMA.equal(KEY_COLUMN_USAGE.CONSTRAINT_SCHEMA)) .and(REFERENTIAL_CONSTRAINTS.CONSTRAINT_NAME.equal(KEY_COLUMN_USAGE.CONSTRAINT_NAME)) .and(REFERENTIAL_CONSTRAINTS.TABLE_NAME.equal(KEY_COLUMN_USAGE.TABLE_NAME)) - // [#5213] Duplicate schema value to work around MySQL issue https://bugs.mysql.com/bug.php?id=86022 - .where(REFERENTIAL_CONSTRAINTS.CONSTRAINT_SCHEMA.in(getInputSchemata()).or( - getInputSchemata().size() == 1 - ? REFERENTIAL_CONSTRAINTS.CONSTRAINT_SCHEMA.in(getInputSchemata()) - : falseCondition())) + .where(REFERENTIAL_CONSTRAINTS.CONSTRAINT_SCHEMA.in(workaroundFor5213(getInputSchemata()))) .orderBy( KEY_COLUMN_USAGE.CONSTRAINT_SCHEMA.asc(), KEY_COLUMN_USAGE.CONSTRAINT_NAME.asc(), @@ -429,12 +430,7 @@ public class MySQLDatabase extends AbstractDatabase { .leftJoin(VIEWS) .on(TABLES.TABLE_SCHEMA.eq(VIEWS.TABLE_SCHEMA)) .and(TABLES.TABLE_NAME.eq(VIEWS.TABLE_NAME)) - - // [#5213] Duplicate schema value to work around MySQL issue https://bugs.mysql.com/bug.php?id=86022 - .where(TABLES.TABLE_SCHEMA.in(getInputSchemata()).or( - getInputSchemata().size() == 1 - ? TABLES.TABLE_SCHEMA.in(getInputSchemata()) - : falseCondition())) + .where(TABLES.TABLE_SCHEMA.in(workaroundFor5213(getInputSchemata()))) // [#9291] MariaDB treats sequences as tables .and(TABLES.TABLE_TYPE.ne(inline("SEQUENCE"))) @@ -469,11 +465,7 @@ public class MySQLDatabase extends AbstractDatabase { .from(COLUMNS) .where( COLUMNS.COLUMN_TYPE.like("enum(%)").and( - // [#5213] Duplicate schema value to work around MySQL issue https://bugs.mysql.com/bug.php?id=86022 - COLUMNS.TABLE_SCHEMA.in(getInputSchemata()).or( - getInputSchemata().size() == 1 - ? COLUMNS.TABLE_SCHEMA.in(getInputSchemata()) - : falseCondition()))) + COLUMNS.TABLE_SCHEMA.in(workaroundFor5213(getInputSchemata())))) .orderBy( COLUMNS.TABLE_SCHEMA.asc(), COLUMNS.TABLE_NAME.asc(), @@ -618,4 +610,19 @@ public class MySQLDatabase extends AbstractDatabase { protected boolean exists0(Table table) { return exists1(table, TABLES.TABLES, TABLES.TABLE_SCHEMA, TABLES.TABLE_NAME); } + + private List> workaroundFor5213(List inputSchemata) { + // [#5213] Add a dummy schema to single element lists to work around MySQL issue https://bugs.mysql.com/bug.php?id=86022 + + List> schemas = new ArrayList<>(); + + for (String schema : inputSchemata) + schemas.add(DSL.val(schema)); + + // Random UUID generated by fair dice roll + if (schemas.size() == 1) + schemas.add(DSL.inline("ee7f6174-34f2-484b-8d81-20a4d9fc866d")); + + return schemas; + } } diff --git a/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresDatabase.java b/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresDatabase.java index 7cc69b785c..0db4ab249d 100644 --- a/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresDatabase.java +++ b/jOOQ-meta/src/main/java/org/jooq/meta/postgres/PostgresDatabase.java @@ -102,10 +102,10 @@ import org.jooq.Name; // ... import org.jooq.Record; import org.jooq.Record2; -import org.jooq.Record4; import org.jooq.Record5; import org.jooq.Record6; import org.jooq.Result; +import org.jooq.ResultQuery; import org.jooq.SQLDialect; import org.jooq.Select; import org.jooq.SortOrder; @@ -133,6 +133,7 @@ import org.jooq.meta.EnumDefinition; import org.jooq.meta.IndexColumnDefinition; import org.jooq.meta.IndexDefinition; import org.jooq.meta.PackageDefinition; +import org.jooq.meta.ResultQueryDatabase; import org.jooq.meta.RoutineDefinition; import org.jooq.meta.SchemaDefinition; import org.jooq.meta.SequenceDefinition; @@ -157,7 +158,7 @@ import org.jooq.tools.JooqLogger; * * @author Lukas Eder */ -public class PostgresDatabase extends AbstractDatabase { +public class PostgresDatabase extends AbstractDatabase implements ResultQueryDatabase { private static final JooqLogger log = JooqLogger.getLogger(PostgresDatabase.class); @@ -259,7 +260,7 @@ public class PostgresDatabase extends AbstractDatabase { @Override protected void loadPrimaryKeys(DefaultRelations relations) throws SQLException { - for (Record record : fetchKeys("PRIMARY KEY")) { + for (Record record : keysQuery(getInputSchemata(), inline("PRIMARY KEY"))) { SchemaDefinition schema = getSchema(record.get(KEY_COLUMN_USAGE.TABLE_SCHEMA)); String key = record.get(KEY_COLUMN_USAGE.CONSTRAINT_NAME); String tableName = record.get(KEY_COLUMN_USAGE.TABLE_NAME); @@ -273,7 +274,7 @@ public class PostgresDatabase extends AbstractDatabase { @Override protected void loadUniqueKeys(DefaultRelations relations) throws SQLException { - for (Record record : fetchKeys("UNIQUE")) { + for (Record record : uniqueKeysQuery(getInputSchemata())) { SchemaDefinition schema = getSchema(record.get(KEY_COLUMN_USAGE.TABLE_SCHEMA)); String key = record.get(KEY_COLUMN_USAGE.CONSTRAINT_NAME); String tableName = record.get(KEY_COLUMN_USAGE.TABLE_NAME); @@ -285,22 +286,28 @@ public class PostgresDatabase extends AbstractDatabase { } } - private Result> fetchKeys(String constraintType) { + @Override + public ResultQuery> uniqueKeysQuery(List schemas) { + return keysQuery(schemas, inline("UNIQUE")); + } + + private ResultQuery> keysQuery(List schemas, Field constraintType) { return create() .select( - KEY_COLUMN_USAGE.CONSTRAINT_NAME, + KEY_COLUMN_USAGE.TABLE_CATALOG, KEY_COLUMN_USAGE.TABLE_SCHEMA, KEY_COLUMN_USAGE.TABLE_NAME, - KEY_COLUMN_USAGE.COLUMN_NAME) + KEY_COLUMN_USAGE.CONSTRAINT_NAME, + KEY_COLUMN_USAGE.COLUMN_NAME, + KEY_COLUMN_USAGE.ORDINAL_POSITION) .from(KEY_COLUMN_USAGE) - .where(KEY_COLUMN_USAGE.tableConstraints().CONSTRAINT_TYPE.equal(constraintType)) - .and(KEY_COLUMN_USAGE.tableConstraints().TABLE_SCHEMA.in(getInputSchemata())) + .where(KEY_COLUMN_USAGE.tableConstraints().CONSTRAINT_TYPE.eq(constraintType)) + .and(KEY_COLUMN_USAGE.tableConstraints().TABLE_SCHEMA.in(schemas)) .orderBy( KEY_COLUMN_USAGE.TABLE_SCHEMA.asc(), KEY_COLUMN_USAGE.TABLE_NAME.asc(), KEY_COLUMN_USAGE.CONSTRAINT_NAME.asc(), - KEY_COLUMN_USAGE.ORDINAL_POSITION.asc()) - .fetch(); + KEY_COLUMN_USAGE.ORDINAL_POSITION.asc()); } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/Diff.java b/jOOQ/src/main/java/org/jooq/impl/Diff.java index 00dfda879d..fc9962293b 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Diff.java +++ b/jOOQ/src/main/java/org/jooq/impl/Diff.java @@ -89,6 +89,7 @@ import org.jooq.Sequence; import org.jooq.Table; import org.jooq.TableOptions.TableType; import org.jooq.UniqueKey; +import org.jooq.tools.StringUtils; /** * A class producing a diff between two {@link Meta} objects. @@ -147,7 +148,8 @@ final class Diff { @Override public void drop(DiffResult r, Schema s) { if (s.getTables().isEmpty() && s.getSequences().isEmpty()) { - r.queries.add(ctx.dropSchema(s)); + if (!StringUtils.isEmpty(s.getName())) + r.queries.add(ctx.dropSchema(s)); } else if (migrateConf.dropSchemaCascade()) { @@ -157,7 +159,8 @@ final class Diff { for (ForeignKey fk : uk.getReferences()) r.droppedFks.add(fk); - r.queries.add(ctx.dropSchema(s).cascade()); + if (!StringUtils.isEmpty(s.getName())) + r.queries.add(ctx.dropSchema(s).cascade()); } else { for (Table t : s.getTables()) @@ -166,7 +169,8 @@ final class Diff { for (Sequence seq : s.getSequences()) DROP_SEQUENCE.drop(r, seq); - r.queries.add(ctx.dropSchema(s)); + if (!StringUtils.isEmpty(s.getName())) + r.queries.add(ctx.dropSchema(s)); } } }; diff --git a/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java b/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java index 731dd54cd5..c7960444b7 100644 --- a/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java +++ b/jOOQ/src/main/java/org/jooq/impl/MetaImpl.java @@ -43,6 +43,7 @@ import static java.lang.Boolean.TRUE; // ... // ... // ... +import static org.jooq.SQLDialect.FIREBIRD; import static org.jooq.SQLDialect.H2; import static org.jooq.SQLDialect.HSQLDB; // ... @@ -55,16 +56,17 @@ import static org.jooq.SQLDialect.SQLITE; import static org.jooq.impl.AbstractNamed.findIgnoreCase; import static org.jooq.impl.DSL.condition; import static org.jooq.impl.DSL.name; +import static org.jooq.impl.Tools.EMPTY_OBJECT; import static org.jooq.impl.Tools.EMPTY_SORTFIELD; import static org.jooq.tools.StringUtils.defaultString; +import java.io.IOException; import java.io.Serializable; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -72,6 +74,8 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.regex.Pattern; +import java.util.Properties; import java.util.Set; import org.jooq.Catalog; @@ -103,6 +107,8 @@ import org.jooq.exception.SQLDialectNotSupportedException; import org.jooq.tools.JooqLogger; import org.jooq.tools.StringUtils; +import org.jetbrains.annotations.NotNull; + /** * An implementation of the public {@link Meta} type. *

@@ -120,14 +126,28 @@ final class MetaImpl extends AbstractMeta { private static final Set EXPRESSION_COLUMN_DEFAULT = SQLDialect.supportedBy(H2, POSTGRES); private static final Set ENCODED_TIMESTAMP_PRECISION = SQLDialect.supportedBy(HSQLDB, MARIADB); private static final Set NO_SUPPORT_TIMESTAMP_PRECISION = SQLDialect.supportedBy(MYSQL, SQLITE); + private static final Set NO_SUPPORT_SCHEMAS = SQLDialect.supportedBy(FIREBIRD, SQLITE); + private static final Pattern P_DERBY_SYSINDEX = Pattern.compile("^(?:SQL\\d{14,}).*$"); + private static final Pattern P_H2_SYSINDEX = Pattern.compile("^(?:PRIMARY_KEY_|UK_INDEX_|FK_INDEX_).*$"); private final DatabaseMetaData databaseMetaData; private final boolean inverseSchemaCatalog; + private static final Properties META_SQL = new Properties(); + + static { + try { + META_SQL.load(MetaImpl.class.getResourceAsStream("/meta/metasql.properties")); + } + catch (IOException e) { + log.error("Cannot load metasql.properties", e); + } + } + MetaImpl(Configuration configuration, DatabaseMetaData databaseMetaData) { super(configuration); @@ -326,6 +346,7 @@ final class MetaImpl extends AbstractMeta { */ private static final long serialVersionUID = -2621899850912554198L; private transient volatile Map> columnCache; + private transient volatile Map> ukCache; MetaSchema(String name, Catalog catalog) { super(name, catalog); @@ -423,7 +444,7 @@ final class MetaImpl extends AbstractMeta { : TableType.TABLE; - result.add(new MetaTable(name, this, getColumns(catalog, schema, name), tableType)); + result.add(new MetaTable(name, this, getColumns(catalog, schema, name), getUks(catalog, schema, name), tableType)); // TODO: Find a more efficient way to do this // Result pkColumns = executor.fetch(meta().getPrimaryKeys(catalog, schema, name)) @@ -435,6 +456,47 @@ final class MetaImpl extends AbstractMeta { return result; } + private final Result getUks(final String catalog, final String schema, final String table) { + if (ukCache == null) { + final String sql = META_SQL.getProperty("uniqueKeysQuery." + family()); + + if (sql != null) { + Result result = meta(new MetaFunction() { + @Override + public Result run(DatabaseMetaData meta) throws SQLException { + return DSL.using(meta.getConnection(), family()).resultQuery( + sql, + NO_SUPPORT_SCHEMAS.contains(dialect()) + ? EMPTY_OBJECT + : inverseSchemaCatalog + ? new Object[] { catalog } + : new Object[] { schema } + ).fetch(); + } + }); + + // TODO Support catalogs as well + Map> groups = result.intoGroups(new Field[] { result.field(0), result.field(1), result.field(2) }); + ukCache = new LinkedHashMap<>(); + + for (Entry> entry : groups.entrySet()) { + Record key = entry.getKey(); + Result value = entry.getValue(); + ukCache.put(name( + catalog == null ? null : key.get(0, String.class), + key.get(1, String.class), + key.get(2, String.class) + ), value); + } + } + } + + if (ukCache != null) + return ukCache.get(name(catalog, schema, table)); + else + return null; + } + @SuppressWarnings("unchecked") private final Result getColumns(String catalog, String schema, String table) { @@ -554,15 +616,18 @@ final class MetaImpl extends AbstractMeta { /** * Generated UID */ - private static final long serialVersionUID = 4843841667753000233L; + private static final long serialVersionUID = 4843841667753000233L; + private final Result uks; - MetaTable(String name, Schema schema, Result columns, TableType tableType) { + MetaTable(String name, Schema schema, Result columns, Result uks, TableType tableType) { super(name(name), schema, null, null, null, null, null, TableOptions.of(tableType)); // Possible scenarios for columns being null: // - The "table" is in fact a SYNONYM if (columns != null) - init(columns); + initColumns(columns); + + this.uks = uks; } @Override @@ -629,10 +694,16 @@ final class MetaImpl extends AbstractMeta { if (constraints.contains(indexName)) it.remove(); - // In H2, system indexes are called PRIMARY_KEY_xx_y + // else switch (family()) { + case DERBY: + if (P_DERBY_SYSINDEX.matcher(indexName).matches()) + it.remove(); + + break; + case H2: - if (indexName.startsWith("PRIMARY_KEY_") || indexName.startsWith("FK_INDEX_")) + if (P_H2_SYSINDEX.matcher(indexName).matches()) it.remove(); break; @@ -642,10 +713,26 @@ final class MetaImpl extends AbstractMeta { return result; } + @SuppressWarnings("unchecked") @Override public final List> getKeys() { + List> result = new ArrayList<>(); + UniqueKey pk = getPrimaryKey(); - return pk == null ? Collections.>emptyList() : Collections.>singletonList(pk); + if (pk != null) + result.add(pk); + + if (uks != null) { + Map> groups = uks.intoGroups((Field) uks.field(3)); + + for (Entry> group : groups.entrySet()) { + Result columns = group.getValue(); + columns.sortAsc(5); + result.add(createUniqueKey(columns, 4, 3, false)); + } + } + + return result; } @Override @@ -689,12 +776,12 @@ final class MetaImpl extends AbstractMeta { // Sort by KEY_SEQ result.sortAsc(4); - return createPrimaryKey(result, 3); + return createUniqueKey(result, 3, 5, true); } @Override @SuppressWarnings("unchecked") - public List> getReferences() { + public final List> getReferences() { Result result = meta(new MetaFunction() { @Override public Result run(DatabaseMetaData meta) throws SQLException { @@ -754,7 +841,7 @@ final class MetaImpl extends AbstractMeta { this, name(fkName), fkFields, - new MetaPrimaryKey(pkTable, pkName, pkFields), + new MetaUniqueKey(pkTable, pkName, pkFields, true), // TODO: Can we know whether it is a PK or UK? pkFields, true )); @@ -808,7 +895,7 @@ final class MetaImpl extends AbstractMeta { @SuppressWarnings("unchecked") - private final UniqueKey createPrimaryKey(Result result, int columnName) { + private final UniqueKey createUniqueKey(Result result, int columnName, int keyName, boolean isPrimary) { if (result.size() > 0) { TableField[] f = new TableField[result.size()]; @@ -826,8 +913,8 @@ final class MetaImpl extends AbstractMeta { f[i] = (TableField) field; } - String indexName = result.get(0).get(5, String.class); - return new MetaPrimaryKey(this, indexName, f); + String indexName = result.get(0).get(keyName, String.class); + return new MetaUniqueKey(this, indexName, f, isPrimary); } else { return null; @@ -880,7 +967,7 @@ final class MetaImpl extends AbstractMeta { } @SuppressWarnings({ "rawtypes", "unchecked" }) - private final void init(Result columns) { + private final void initColumns(Result columns) { boolean hasAutoIncrement = false; for (Record column : columns) { @@ -989,20 +1076,23 @@ final class MetaImpl extends AbstractMeta { } } - private final class MetaPrimaryKey extends AbstractKey implements UniqueKey { + private final class MetaUniqueKey extends AbstractKey implements UniqueKey { /** * Generated UID */ - private static final long serialVersionUID = 6997258619475953490L; + private static final long serialVersionUID = 6997258619475953490L; + private final boolean isPrimary; - MetaPrimaryKey(Table table, String pkName, TableField[] fields) { - super(table, pkName == null ? null : name(pkName), fields, true); + MetaUniqueKey(Table table, String name, TableField[] fields, boolean isPrimary) { + super(table, name == null ? null : name(name), fields, true); + + this.isPrimary = isPrimary; } @Override public final boolean isPrimary() { - return true; + return isPrimary; } @Override diff --git a/jOOQ/src/main/java/org/jooq/impl/Tools.java b/jOOQ/src/main/java/org/jooq/impl/Tools.java index 77611968f2..df88d9dfdc 100644 --- a/jOOQ/src/main/java/org/jooq/impl/Tools.java +++ b/jOOQ/src/main/java/org/jooq/impl/Tools.java @@ -321,6 +321,7 @@ final class Tools { static final int[] EMPTY_INT = {}; static final JSONEntry[] EMPTY_JSONENTRY = {}; static final Name[] EMPTY_NAME = {}; + static final Object[] EMPTY_OBJECT = {}; static final Param[] EMPTY_PARAM = {}; static final OrderField[] EMPTY_ORDERFIELD = {}; static final Query[] EMPTY_QUERY = {}; diff --git a/jOOQ/src/main/resources/meta/metasql.properties b/jOOQ/src/main/resources/meta/metasql.properties new file mode 100644 index 0000000000..308a3cbbcd --- /dev/null +++ b/jOOQ/src/main/resources/meta/metasql.properties @@ -0,0 +1,11 @@ +# The queries generated from the various jOOQ-meta ResultQueryDatabase types. +uniqueKeysQuery.FIREBIRD=select null catalog, null schema, trim(RDB$RELATION_CONSTRAINTS.RDB$RELATION_NAME) RDB$RELATION_NAME, trim(RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME) RDB$CONSTRAINT_NAME, trim(RDB$INDEX_SEGMENTS.RDB$FIELD_NAME) RDB$FIELD_NAME, RDB$INDEX_SEGMENTS.RDB$FIELD_POSITION from RDB$RELATION_CONSTRAINTS join RDB$INDEX_SEGMENTS on RDB$INDEX_SEGMENTS.RDB$INDEX_NAME = RDB$RELATION_CONSTRAINTS.RDB$INDEX_NAME where RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_TYPE = 'UNIQUE' order by RDB$RELATION_CONSTRAINTS.RDB$CONSTRAINT_NAME asc, RDB$INDEX_SEGMENTS.RDB$FIELD_POSITION asc +uniqueKeysQuery.H2=select INFORMATION_SCHEMA.CONSTRAINTS.TABLE_CATALOG, INFORMATION_SCHEMA.CONSTRAINTS.TABLE_SCHEMA, INFORMATION_SCHEMA.CONSTRAINTS.TABLE_NAME, INFORMATION_SCHEMA.CONSTRAINTS.CONSTRAINT_NAME, INFORMATION_SCHEMA.INDEXES.COLUMN_NAME, INFORMATION_SCHEMA.INDEXES.ORDINAL_POSITION from INFORMATION_SCHEMA.CONSTRAINTS join INFORMATION_SCHEMA.INDEXES on (INFORMATION_SCHEMA.CONSTRAINTS.TABLE_SCHEMA = INFORMATION_SCHEMA.INDEXES.TABLE_SCHEMA and INFORMATION_SCHEMA.CONSTRAINTS.TABLE_NAME = INFORMATION_SCHEMA.INDEXES.TABLE_NAME and INFORMATION_SCHEMA.CONSTRAINTS.UNIQUE_INDEX_NAME = INFORMATION_SCHEMA.INDEXES.INDEX_NAME) where (INFORMATION_SCHEMA.CONSTRAINTS.TABLE_SCHEMA in (cast(? as varchar(2147483647))) and INFORMATION_SCHEMA.CONSTRAINTS.CONSTRAINT_TYPE in ('UNIQUE')) order by INFORMATION_SCHEMA.CONSTRAINTS.TABLE_SCHEMA, INFORMATION_SCHEMA.CONSTRAINTS.CONSTRAINT_NAME, INFORMATION_SCHEMA.INDEXES.ORDINAL_POSITION +uniqueKeysQuery.MARIADB=select distinct null as TABLE_CATALOG, information_schema.STATISTICS.TABLE_SCHEMA, information_schema.STATISTICS.TABLE_NAME, information_schema.STATISTICS.INDEX_NAME, information_schema.STATISTICS.COLUMN_NAME, information_schema.STATISTICS.SEQ_IN_INDEX from information_schema.STATISTICS where (information_schema.STATISTICS.TABLE_SCHEMA in (?, 'ee7f6174-34f2-484b-8d81-20a4d9fc866d') and information_schema.STATISTICS.INDEX_NAME <> 'PRIMARY' and information_schema.STATISTICS.NON_UNIQUE = 0) order by information_schema.STATISTICS.TABLE_SCHEMA, information_schema.STATISTICS.TABLE_NAME, information_schema.STATISTICS.INDEX_NAME, information_schema.STATISTICS.SEQ_IN_INDEX +uniqueKeysQuery.MYSQL=select distinct null as TABLE_CATALOG, information_schema.STATISTICS.TABLE_SCHEMA, information_schema.STATISTICS.TABLE_NAME, information_schema.STATISTICS.INDEX_NAME, information_schema.STATISTICS.COLUMN_NAME, information_schema.STATISTICS.SEQ_IN_INDEX from information_schema.STATISTICS where (information_schema.STATISTICS.TABLE_SCHEMA in (?, 'ee7f6174-34f2-484b-8d81-20a4d9fc866d') and information_schema.STATISTICS.INDEX_NAME <> 'PRIMARY' and information_schema.STATISTICS.NON_UNIQUE = 0) order by information_schema.STATISTICS.TABLE_SCHEMA, information_schema.STATISTICS.TABLE_NAME, information_schema.STATISTICS.INDEX_NAME, information_schema.STATISTICS.SEQ_IN_INDEX +uniqueKeysQuery.POSTGRES=select information_schema.key_column_usage.table_catalog, information_schema.key_column_usage.table_schema, information_schema.key_column_usage.table_name, information_schema.key_column_usage.constraint_name, information_schema.key_column_usage.column_name, information_schema.key_column_usage.ordinal_position from (information_schema.key_column_usage left outer join information_schema.table_constraints as alias_99043051 on (information_schema.key_column_usage.constraint_catalog = alias_99043051.constraint_catalog and information_schema.key_column_usage.constraint_schema = alias_99043051.constraint_schema and information_schema.key_column_usage.constraint_name = alias_99043051.constraint_name)) where (alias_99043051.constraint_type = 'UNIQUE' and alias_99043051.table_schema in (?)) order by information_schema.key_column_usage.table_schema asc, information_schema.key_column_usage.table_name asc, information_schema.key_column_usage.constraint_name asc, information_schema.key_column_usage.ordinal_position asc + + + + + diff --git a/pom.xml b/pom.xml index b5c71a9acf..9b1e480a9e 100644 --- a/pom.xml +++ b/pom.xml @@ -647,6 +647,7 @@ jOOQ-migrations + jOOQ-kotlin jOOQ-scala_2.13 @@ -671,6 +672,10 @@ + + + +