From 1295753816b8aeef6f5b8f0e41bb707039938eaa Mon Sep 17 00:00:00 2001 From: Mark Rotteveel Date: Wed, 3 Apr 2024 14:10:38 +0200 Subject: [PATCH] #798 Add backing index names to getImportedKeys, getExportedKeys and getCrossReference of DatabaseMetaData --- src/docs/asciidoc/release_notes.adoc | 3 + .../firebirdsql/jdbc/FBDatabaseMetaData.java | 30 ++ .../jdbc/metadata/AbstractKeysMethod.java | 10 +- .../jdbc/metadata/GetCrossReference.java | 8 +- .../jdbc/metadata/GetExportedKeys.java | 8 +- .../jdbc/metadata/GetImportedKeys.java | 8 +- .../FBDatabaseMetaDataAbstractKeysTest.java | 272 ++++++++++++++++++ .../FBDatabaseMetaDataCrossReferenceTest.java | 73 +++++ .../FBDatabaseMetaDataExportedKeysTest.java | 76 +++++ .../FBDatabaseMetaDataImportedKeysTest.java | 69 +++++ .../FBDatabaseMetaDataPrimaryKeysTest.java | 24 +- 11 files changed, 562 insertions(+), 19 deletions(-) create mode 100644 src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataAbstractKeysTest.java create mode 100644 src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataCrossReferenceTest.java create mode 100644 src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataExportedKeysTest.java create mode 100644 src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataImportedKeysTest.java diff --git a/src/docs/asciidoc/release_notes.adoc b/src/docs/asciidoc/release_notes.adoc index 8d6b14c64..342076c5c 100644 --- a/src/docs/asciidoc/release_notes.adoc +++ b/src/docs/asciidoc/release_notes.adoc @@ -987,6 +987,9 @@ This feature was backported to Jaybird 5.0.5. Disabling extended metadata may improve performance of these `ResultSetMetaData` methods in exchange for estimated precision information of `NUMERIC` and `DECIMAL` columns, and not being able to determine the auto-increment status of `INTEGER`, `BIGINT` or `SMALLINT` columns. + This feature was backported to Jaybird 5.0.5. +* Improvement: Added column `JB_PK_INDEX_NAME` and `JB_FK_INDEX_NAME` to the result set of to `getImportedKeys`, `getExportedKeys` and `getCrossReference` of `DatabaseMetaData` with the names of the index backing the primary key and foreign key (https://github.com/FirebirdSQL/jaybird/issues/798[#798]) ++ +Given this is a non-standard extension, it is advisable to retrieve these columns by name, not by position. [#potentially-breaking-changes] === Potentially breaking changes diff --git a/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java b/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java index ff2998826..3f9018184 100644 --- a/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java +++ b/src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java @@ -1409,16 +1409,46 @@ public ResultSet getPrimaryKeys(String catalog, String schema, String table) thr return GetPrimaryKeys.create(getDbMetadataMediator()).getPrimaryKeys(table); } + /** + * {@inheritDoc} + *

+ * Jaybird defines these additional columns: + *

    + *
  1. JB_FK_INDEX_NAME String => Index backing the foreign key
  2. + *
  3. JB_PK_INDEX_NAME String => Index backing the primary key
  4. + *
+ *

+ */ @Override public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { return GetImportedKeys.create(getDbMetadataMediator()).getImportedKeys(table); } + /** + * {@inheritDoc} + *

+ * Jaybird defines these additional columns: + *

    + *
  1. JB_FK_INDEX_NAME String => Index backing the foreign key
  2. + *
  3. JB_PK_INDEX_NAME String => Index backing the primary key
  4. + *
+ *

+ */ @Override public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { return GetExportedKeys.create(getDbMetadataMediator()).getExportedKeys(table); } + /** + * {@inheritDoc} + *

+ * Jaybird defines these additional columns: + *

    + *
  1. JB_FK_INDEX_NAME String => Index backing the foreign key
  2. + *
  3. JB_PK_INDEX_NAME String => Index backing the primary key
  4. + *
+ *

+ */ @Override public ResultSet getCrossReference( String primaryCatalog, String primarySchema, String primaryTable, diff --git a/src/main/org/firebirdsql/jdbc/metadata/AbstractKeysMethod.java b/src/main/org/firebirdsql/jdbc/metadata/AbstractKeysMethod.java index c5c79eaeb..a29b96cec 100644 --- a/src/main/org/firebirdsql/jdbc/metadata/AbstractKeysMethod.java +++ b/src/main/org/firebirdsql/jdbc/metadata/AbstractKeysMethod.java @@ -40,7 +40,7 @@ abstract class AbstractKeysMethod extends AbstractMetadataMethod { private static final String COLUMNINFO = "COLUMNINFO"; - private static final RowDescriptor ROW_DESCRIPTOR = DbMetadataMediator.newRowDescriptorBuilder(14) + private static final RowDescriptor ROW_DESCRIPTOR = DbMetadataMediator.newRowDescriptorBuilder(16) .at(0).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "PKTABLE_CAT", COLUMNINFO).addField() .at(1).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "PKTABLE_SCHEM", COLUMNINFO).addField() .at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PKTABLE_NAME", COLUMNINFO).addField() @@ -55,6 +55,8 @@ abstract class AbstractKeysMethod extends AbstractMetadataMethod { .at(11).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "FK_NAME", COLUMNINFO).addField() .at(12).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "PK_NAME", COLUMNINFO).addField() .at(13).simple(SQL_SHORT, 0, "DEFERRABILITY", COLUMNINFO).addField() + .at(14).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "JB_FK_INDEX_NAME", COLUMNINFO).addField() + .at(15).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "JB_PK_INDEX_NAME", COLUMNINFO).addField() .toRowDescriptor(); AbstractKeysMethod(DbMetadataMediator mediator) { @@ -74,6 +76,8 @@ final RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) thr .at(11).setString(rs.getString("FK_NAME")) .at(12).setString(rs.getString("PK_NAME")) .at(13).setShort(DatabaseMetaData.importedKeyNotDeferrable) + .at(14).setString(rs.getString("JB_FK_INDEX_NAME")) + .at(15).setString(rs.getString("JB_PK_INDEX_NAME")) .toRowValue(true); } @@ -86,8 +90,8 @@ final RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) thr */ private static Integer mapAction(String firebirdActionName) { return switch (firebirdActionName) { - // NOTE: Firebird has no "RESTRICT", however this mapping (to importedKeyNoAction) was also present in - // the previous implementation, so preserving it just in case. + // NOTE: Firebird has no ON UPDATE/DELETE option RESTRICT, but absence of a ON UPDATE/DELETE clause stores + // "RESTRICT", which behaves the same as NO ACTION. case "RESTRICT", "NO ACTION" -> DatabaseMetaData.importedKeyNoAction; case "CASCADE" -> DatabaseMetaData.importedKeyCascade; case "SET NULL" -> DatabaseMetaData.importedKeySetNull; diff --git a/src/main/org/firebirdsql/jdbc/metadata/GetCrossReference.java b/src/main/org/firebirdsql/jdbc/metadata/GetCrossReference.java index fe08571f1..203fa007c 100644 --- a/src/main/org/firebirdsql/jdbc/metadata/GetCrossReference.java +++ b/src/main/org/firebirdsql/jdbc/metadata/GetCrossReference.java @@ -24,6 +24,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import static org.firebirdsql.jaybird.util.StringUtils.isNullOrEmpty; + /** * Provides the implementation for {@link java.sql.DatabaseMetaData#getCrossReference(String, String, String, String, String, String)}. * @@ -42,7 +44,9 @@ public final class GetCrossReference extends AbstractKeysMethod { RC.RDB$UPDATE_RULE as UPDATE_RULE, RC.RDB$DELETE_RULE as DELETE_RULE, PK.RDB$CONSTRAINT_NAME as PK_NAME, - FK.RDB$CONSTRAINT_NAME as FK_NAME + FK.RDB$CONSTRAINT_NAME as FK_NAME, + PK.RDB$INDEX_NAME as JB_PK_INDEX_NAME, + FK.RDB$INDEX_NAME as JB_FK_INDEX_NAME from RDB$RELATION_CONSTRAINTS PK inner join RDB$REF_CONSTRAINTS RC on PK.RDB$CONSTRAINT_NAME = RC.RDB$CONST_NAME_UQ @@ -61,7 +65,7 @@ private GetCrossReference(DbMetadataMediator mediator) { } public ResultSet getCrossReference(String primaryTable, String foreignTable) throws SQLException { - if (primaryTable == null || primaryTable.isEmpty() || foreignTable == null || foreignTable.isEmpty()) { + if (isNullOrEmpty(primaryTable) || isNullOrEmpty(foreignTable)) { return createEmpty(); } diff --git a/src/main/org/firebirdsql/jdbc/metadata/GetExportedKeys.java b/src/main/org/firebirdsql/jdbc/metadata/GetExportedKeys.java index 307e9b459..8d3c34eb1 100644 --- a/src/main/org/firebirdsql/jdbc/metadata/GetExportedKeys.java +++ b/src/main/org/firebirdsql/jdbc/metadata/GetExportedKeys.java @@ -24,6 +24,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import static org.firebirdsql.jaybird.util.StringUtils.isNullOrEmpty; + /** * Provides the implementation for {@link java.sql.DatabaseMetaData#getExportedKeys(String, String, String)}. * @@ -42,7 +44,9 @@ public final class GetExportedKeys extends AbstractKeysMethod { RC.RDB$UPDATE_RULE as UPDATE_RULE, RC.RDB$DELETE_RULE as DELETE_RULE, PK.RDB$CONSTRAINT_NAME as PK_NAME, - FK.RDB$CONSTRAINT_NAME as FK_NAME + FK.RDB$CONSTRAINT_NAME as FK_NAME, + PK.RDB$INDEX_NAME as JB_PK_INDEX_NAME, + FK.RDB$INDEX_NAME as JB_FK_INDEX_NAME from RDB$RELATION_CONSTRAINTS PK inner join RDB$REF_CONSTRAINTS RC on PK.RDB$CONSTRAINT_NAME = RC.RDB$CONST_NAME_UQ @@ -61,7 +65,7 @@ private GetExportedKeys(DbMetadataMediator mediator) { } public ResultSet getExportedKeys(String table) throws SQLException { - if (table == null || "".equals(table)) { + if (isNullOrEmpty(table)) { return createEmpty(); } diff --git a/src/main/org/firebirdsql/jdbc/metadata/GetImportedKeys.java b/src/main/org/firebirdsql/jdbc/metadata/GetImportedKeys.java index 99b45f290..395153573 100644 --- a/src/main/org/firebirdsql/jdbc/metadata/GetImportedKeys.java +++ b/src/main/org/firebirdsql/jdbc/metadata/GetImportedKeys.java @@ -24,6 +24,8 @@ import java.sql.ResultSet; import java.sql.SQLException; +import static org.firebirdsql.jaybird.util.StringUtils.isNullOrEmpty; + /** * Provides the implementation for {@link java.sql.DatabaseMetaData#getImportedKeys(String, String, String)}. * @@ -42,7 +44,9 @@ public final class GetImportedKeys extends AbstractKeysMethod { RC.RDB$UPDATE_RULE as UPDATE_RULE, RC.RDB$DELETE_RULE as DELETE_RULE, PK.RDB$CONSTRAINT_NAME as PK_NAME, - FK.RDB$CONSTRAINT_NAME as FK_NAME + FK.RDB$CONSTRAINT_NAME as FK_NAME, + PK.RDB$INDEX_NAME as JB_PK_INDEX_NAME, + FK.RDB$INDEX_NAME as JB_FK_INDEX_NAME from RDB$RELATION_CONSTRAINTS PK inner join RDB$REF_CONSTRAINTS RC on PK.RDB$CONSTRAINT_NAME = RC.RDB$CONST_NAME_UQ @@ -61,7 +65,7 @@ private GetImportedKeys(DbMetadataMediator mediator) { } public ResultSet getImportedKeys(String table) throws SQLException { - if (table == null || "".equals(table)) { + if (isNullOrEmpty(table)) { return createEmpty(); } diff --git a/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataAbstractKeysTest.java b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataAbstractKeysTest.java new file mode 100644 index 000000000..1b8ee5948 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataAbstractKeysTest.java @@ -0,0 +1,272 @@ +/* + * Firebird Open Source JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc; + +import org.firebirdsql.common.extension.UsesDatabaseExtension; +import org.hamcrest.Matchers; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.extension.RegisterExtension; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.unmodifiableMap; +import static org.firebirdsql.common.FBTestProperties.getConnectionViaDriverManager; +import static org.firebirdsql.common.assertions.ResultSetAssertions.assertNextRow; +import static org.firebirdsql.common.assertions.ResultSetAssertions.assertNoNextRow; + +/** + * Base test class for subclasses of {@code org.firebirdsql.jdbc.metadata.AbstractKeysMethod}. + * + * @author Mark Rotteveel + */ +abstract class FBDatabaseMetaDataAbstractKeysTest { + + private static final String UNNAMED_CONSTRAINT_PREFIX = "INTEG_"; + private static final String UNNAMED_PK_INDEX_PREFIX = "RDB$PRIMARY"; + private static final String UNNAMED_FK_INDEX_PREFIX = "RDB$FOREIGN"; + + //@formatter:off + @RegisterExtension + static final UsesDatabaseExtension.UsesDatabaseForAll usesDatabase = UsesDatabaseExtension.usesDatabaseForAll( + """ + create table TABLE_1 ( + ID integer constraint PK_TABLE_1 primary key + )""", + """ + create table TABLE_2 ( + ID1 integer not null, + ID2 integer not null, + TABLE_1_ID integer constraint FK_TABLE_2_TO_1 references TABLE_1 (ID), + constraint PK_TABLE_2 unique (ID1, ID2) using index ALT_INDEX_NAME_2 + )""", + """ + create table TABLE_3 ( + ID integer constraint PK_TABLE_3 primary key using index ALT_INDEX_NAME_3, + TABLE_2_ID1 integer, + TABLE_2_ID2 integer, + constraint FK_TABLE_3_TO_2 foreign key (TABLE_2_ID1, TABLE_2_ID2) references TABLE_2 (ID1, ID2) + on delete cascade on update set default + )""", + """ + create table TABLE_4 ( + ID integer primary key using index ALT_INDEX_NAME_4, + TABLE_2_ID1 integer, + TABLE_2_ID2 integer, + constraint FK_TABLE_4_TO_2 foreign key (TABLE_2_ID1, TABLE_2_ID2) references TABLE_2 (ID1, ID2) + on delete set default on update set null + )""", + """ + create table TABLE_5 ( + ID integer primary key, + TABLE_2_ID1 integer, + TABLE_2_ID2 integer, + foreign key (TABLE_2_ID1, TABLE_2_ID2) references TABLE_2 (ID1, ID2) + on delete set null on update no action using index ALT_INDEX_NAME_5 + )""", + """ + create table TABLE_6 ( + ID integer primary key, + TABLE_2_ID1 integer, + TABLE_2_ID2 integer, + foreign key (TABLE_2_ID1, TABLE_2_ID2) references TABLE_2 (ID1, ID2) + on delete no action on update cascade + )""", + """ + create table TABLE_7 ( + ID integer primary key, + TABLE_6_ID integer constraint FK_TABLE_7_TO_6 references TABLE_6 (ID) on update cascade + )""" + ); + //@formatter:on + + protected static final MetadataResultSetDefinition keysDefinition = + new MetadataResultSetDefinition(KeysMetaData.class); + + protected static Connection con; + protected static DatabaseMetaData dbmd; + + @BeforeAll + static void setupAll() throws SQLException { + con = getConnectionViaDriverManager(); + dbmd = con.getMetaData(); + } + + @AfterAll + static void tearDownAll() throws SQLException { + try { + con.close(); + } finally { + con = null; + dbmd = null; + } + } + + protected static List> table1Fks() { + // No FKs in this table + return List.of(); + } + + protected static List> table2Fks() { + return List.of( + createKeysTestData("TABLE_1", "ID", "TABLE_2", "TABLE_1_ID", 1, DatabaseMetaData.importedKeyNoAction, + DatabaseMetaData.importedKeyNoAction, "PK_TABLE_1", "FK_TABLE_2_TO_1", "PK_TABLE_1", + "FK_TABLE_2_TO_1")); + } + + protected static List> table3Fks() { + return List.of( + createKeysTestData("TABLE_2", "ID1", "TABLE_3", "TABLE_2_ID1", 1, + DatabaseMetaData.importedKeySetDefault, DatabaseMetaData.importedKeyCascade, "PK_TABLE_2", + "FK_TABLE_3_TO_2", "ALT_INDEX_NAME_2", "FK_TABLE_3_TO_2"), + createKeysTestData("TABLE_2", "ID2", "TABLE_3", "TABLE_2_ID2", 2, + DatabaseMetaData.importedKeySetDefault, DatabaseMetaData.importedKeyCascade, "PK_TABLE_2", + "FK_TABLE_3_TO_2", "ALT_INDEX_NAME_2", "FK_TABLE_3_TO_2")); + } + + protected static List> table4Fks() { + return List.of( + createKeysTestData("TABLE_2", "ID1", "TABLE_4", "TABLE_2_ID1", 1, + DatabaseMetaData.importedKeySetNull, DatabaseMetaData.importedKeySetDefault, "PK_TABLE_2", + "FK_TABLE_4_TO_2", "ALT_INDEX_NAME_2", "FK_TABLE_4_TO_2"), + createKeysTestData("TABLE_2", "ID2", "TABLE_4", "TABLE_2_ID2", 2, + DatabaseMetaData.importedKeySetNull, DatabaseMetaData.importedKeySetDefault, "PK_TABLE_2", + "FK_TABLE_4_TO_2", "ALT_INDEX_NAME_2", "FK_TABLE_4_TO_2")); + } + + protected static List> table5Fks() { + return List.of( + createKeysTestData("TABLE_2", "ID1", "TABLE_5", "TABLE_2_ID1", 1, + DatabaseMetaData.importedKeyNoAction, DatabaseMetaData.importedKeySetNull, "PK_TABLE_2", + UNNAMED_CONSTRAINT_PREFIX, "ALT_INDEX_NAME_2", "ALT_INDEX_NAME_5"), + createKeysTestData("TABLE_2", "ID2", "TABLE_5", "TABLE_2_ID2", 2, + DatabaseMetaData.importedKeyNoAction, DatabaseMetaData.importedKeySetNull, "PK_TABLE_2", + UNNAMED_CONSTRAINT_PREFIX, "ALT_INDEX_NAME_2", "ALT_INDEX_NAME_5")); + } + + protected static List> table6Fks() { + return List.of( + createKeysTestData("TABLE_2", "ID1", "TABLE_6", "TABLE_2_ID1", 1, + DatabaseMetaData.importedKeyCascade, DatabaseMetaData.importedKeyNoAction, "PK_TABLE_2", + UNNAMED_CONSTRAINT_PREFIX, "ALT_INDEX_NAME_2", UNNAMED_FK_INDEX_PREFIX), + createKeysTestData("TABLE_2", "ID2", "TABLE_6", "TABLE_2_ID2", 2, + DatabaseMetaData.importedKeyCascade, DatabaseMetaData.importedKeyNoAction, "PK_TABLE_2", + UNNAMED_CONSTRAINT_PREFIX, "ALT_INDEX_NAME_2", UNNAMED_FK_INDEX_PREFIX)); + } + + protected static List> table7Fks() { + return List.of( + createKeysTestData("TABLE_6", "ID", "TABLE_7", "TABLE_6_ID", 1, DatabaseMetaData.importedKeyCascade, + DatabaseMetaData.importedKeyNoAction, UNNAMED_CONSTRAINT_PREFIX, "FK_TABLE_7_TO_6", + UNNAMED_PK_INDEX_PREFIX, "FK_TABLE_7_TO_6")); + } + + protected void validateExpectedKeys(ResultSet keys, List> expectedKeys) + throws Exception { + for (Map expectedColumn : expectedKeys) { + assertNextRow(keys); + keysDefinition.validateRowValues(keys, expectedColumn); + } + assertNoNextRow(keys); + } + + protected static Map createKeysTestData(String pkTable, String pkColumn, String fkTable, + String fkColumn, int keySeq, int updateRule, int deleteRule, String pkName, String fkName, + String pkIndexName, String fkIndexName) { + Map rules = getDefaultValidationRules(); + rules.put(KeysMetaData.PKTABLE_NAME, pkTable); + rules.put(KeysMetaData.PKCOLUMN_NAME, pkColumn); + rules.put(KeysMetaData.FKTABLE_NAME, fkTable); + rules.put(KeysMetaData.FKCOLUMN_NAME, fkColumn); + rules.put(KeysMetaData.KEY_SEQ, (short) keySeq); + rules.put(KeysMetaData.UPDATE_RULE, (short) updateRule); + rules.put(KeysMetaData.DELETE_RULE, (short) deleteRule); + rules.put(KeysMetaData.FK_NAME, constraintNameValidation(fkName)); + rules.put(KeysMetaData.PK_NAME, constraintNameValidation(pkName)); + rules.put(KeysMetaData.JB_FK_INDEX_NAME, UNNAMED_FK_INDEX_PREFIX.equals(fkIndexName) + ? Matchers.startsWith(UNNAMED_FK_INDEX_PREFIX) : fkIndexName); + rules.put(KeysMetaData.JB_PK_INDEX_NAME, UNNAMED_PK_INDEX_PREFIX.equals(pkIndexName) + ? Matchers.startsWith(UNNAMED_PK_INDEX_PREFIX) : pkIndexName); + return rules; + } + + private static Object constraintNameValidation(String constraintName) { + return UNNAMED_CONSTRAINT_PREFIX.equals(constraintName) + ? Matchers.startsWith(UNNAMED_CONSTRAINT_PREFIX) : constraintName; + } + + private static final Map DEFAULT_COLUMN_VALUES; + + static { + var defaults = new EnumMap<>(KeysMetaData.class); + defaults.put(KeysMetaData.UPDATE_RULE, DatabaseMetaData.importedKeyNoAction); + defaults.put(KeysMetaData.DELETE_RULE, DatabaseMetaData.importedKeyNoAction); + defaults.put(KeysMetaData.DEFERRABILITY, DatabaseMetaData.importedKeyNotDeferrable); + DEFAULT_COLUMN_VALUES = unmodifiableMap(defaults); + } + + protected static Map getDefaultValidationRules() { + return new EnumMap<>(DEFAULT_COLUMN_VALUES); + } + + enum KeysMetaData implements MetaDataInfo { + PKTABLE_CAT(1, String.class), + PKTABLE_SCHEM(2, String.class), + PKTABLE_NAME(3, String.class), + PKCOLUMN_NAME(4, String.class), + FKTABLE_CAT(5, String.class), + FKTABLE_SCHEM(6, String.class), + FKTABLE_NAME(7, String.class), + FKCOLUMN_NAME(8, String.class), + KEY_SEQ(9, Short.class), + UPDATE_RULE(10, Short.class), + DELETE_RULE(11, Short.class), + FK_NAME(12, String.class), + PK_NAME(13, String.class), + DEFERRABILITY(14, Short.class), + JB_FK_INDEX_NAME(15, String.class), + JB_PK_INDEX_NAME(16, String.class), + ; + + private final int position; + private final Class columnClass; + + KeysMetaData(int position, Class columnClass) { + this.position = position; + this.columnClass = columnClass; + } + + @Override + public int getPosition() { + return position; + } + + @Override + public Class getColumnClass() { + return columnClass; + } + } + +} diff --git a/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataCrossReferenceTest.java b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataCrossReferenceTest.java new file mode 100644 index 000000000..0e31698ec --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataCrossReferenceTest.java @@ -0,0 +1,73 @@ +/* + * Firebird Open Source JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.ResultSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Tests for {@link FBDatabaseMetaData#getCrossReference(String, String, String, String, String, String)}. + * + * @author Mark Rotteveel + */ +class FBDatabaseMetaDataCrossReferenceTest extends FBDatabaseMetaDataAbstractKeysTest { + + @Test + void testCrossReferenceMetaDataColumns() throws Exception { + try (ResultSet crossReference = dbmd.getCrossReference(null, null, "doesnotexit", null, null, "doesnotexist")) { + keysDefinition.validateResultSetColumns(crossReference); + } + } + + @ParameterizedTest(name = "{0} - {1}") + @MethodSource + void testCrossReference(String parentTable, String foreignTable, List> expectedKeys) + throws Exception { + try (ResultSet crossReference = dbmd.getCrossReference(null, null, parentTable, null, null, foreignTable)) { + validateExpectedKeys(crossReference, expectedKeys); + } + } + + static Stream testCrossReference() { + return Stream.of( + crossRefTestCase("TABLE_1", "TABLE_2", table2Fks()), + crossRefTestCase("TABLE_2", "TABLE_1", List.of()), + crossRefTestCase("TABLE_1", "TABLE_3", List.of()), + crossRefTestCase("TABLE_2", "TABLE_3", table3Fks()), + crossRefTestCase("TABLE_2", "TABLE_4", table4Fks()), + crossRefTestCase("TABLE_2", "TABLE_5", table5Fks()), + crossRefTestCase("TABLE_2", "TABLE_6", table6Fks()), + crossRefTestCase("TABLE_6", "TABLE_7", table7Fks()), + crossRefTestCase("TABLE_1", "doesnotexist", List.of()), + crossRefTestCase("doesnotexist", "TABLE_2", List.of())); + } + + private static Arguments crossRefTestCase(String parentTable, String foreignTable, + List> expectedKeys) { + return Arguments.of(parentTable, foreignTable, expectedKeys); + } + +} diff --git a/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataExportedKeysTest.java b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataExportedKeysTest.java new file mode 100644 index 000000000..1403bf366 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataExportedKeysTest.java @@ -0,0 +1,76 @@ +/* + * Firebird Open Source JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.ResultSet; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Tests for {@link java.sql.DatabaseMetaData#getExportedKeys(String, String, String)}. + * + * @author Mark Rotteveel + */ +class FBDatabaseMetaDataExportedKeysTest extends FBDatabaseMetaDataAbstractKeysTest { + + @Test + void testExportedKeysMetaDataColumns() throws Exception { + try (ResultSet exportedKeys = dbmd.getExportedKeys(null, null, "doesnotexit")) { + keysDefinition.validateResultSetColumns(exportedKeys); + } + } + + @ParameterizedTest + @MethodSource + void testExportedKeys(String table, List> expectedKeys) throws Exception { + try (ResultSet exportedKeys = dbmd.getExportedKeys(null, null, table)) { + validateExpectedKeys(exportedKeys, expectedKeys); + } + } + + static Stream testExportedKeys() { + return Stream.of( + exportedKeysTestCase("TABLE_1", table2Fks()), + exportedKeysTestCase("doesnotexist", List.of()), + exportedKeysTestCase("TABLE_2", table3Fks(), table4Fks(), table5Fks(), table6Fks()), + exportedKeysTestCase("TABLE_3", List.of()), + exportedKeysTestCase("TABLE_6", table7Fks())); + } + + private static Arguments exportedKeysTestCase(String table, List> expectedKeys) { + return Arguments.of(table, expectedKeys); + } + + @SuppressWarnings("SameParameterValue") + @SafeVarargs + private static Arguments exportedKeysTestCase(String table, List>... expectedKeys) { + var combinedExpectedKeys = new ArrayList>(); + Arrays.stream(expectedKeys).forEach(combinedExpectedKeys::addAll); + return exportedKeysTestCase(table, combinedExpectedKeys); + } + +} diff --git a/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataImportedKeysTest.java b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataImportedKeysTest.java new file mode 100644 index 000000000..033570075 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataImportedKeysTest.java @@ -0,0 +1,69 @@ +/* + * Firebird Open Source JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.sql.ResultSet; +import java.util.List; +import java.util.Map; +import java.util.stream.Stream; + +/** + * Tests for {@link java.sql.DatabaseMetaData#getImportedKeys(String, String, String)}. + * + * @author Mark Rotteveel + */ +class FBDatabaseMetaDataImportedKeysTest extends FBDatabaseMetaDataAbstractKeysTest { + + @Test + void testExportedKeysMetaDataColumns() throws Exception { + try (ResultSet importedKeys = dbmd.getImportedKeys(null, null, "doesnotexit")) { + keysDefinition.validateResultSetColumns(importedKeys); + } + } + + @ParameterizedTest + @MethodSource + void testImportedKeys(String table, List> expectedKeys) throws Exception { + try (ResultSet importedKeys = dbmd.getImportedKeys(null, null, table)) { + validateExpectedKeys(importedKeys, expectedKeys); + } + } + + static Stream testImportedKeys() { + return Stream.of( + importedKeysTestCase("TABLE_1", List.of()), + importedKeysTestCase("doesnotexist", List.of()), + importedKeysTestCase("TABLE_2", table2Fks()), + importedKeysTestCase("TABLE_3", table3Fks()), + importedKeysTestCase("TABLE_4", table4Fks()), + importedKeysTestCase("TABLE_5", table5Fks()), + importedKeysTestCase("TABLE_6", table6Fks()), + importedKeysTestCase("TABLE_7", table7Fks())); + } + + private static Arguments importedKeysTestCase(String table, List> expectedKeys) { + return Arguments.of(table, expectedKeys); + } + +} diff --git a/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataPrimaryKeysTest.java b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataPrimaryKeysTest.java index 876f0763d..0bf536136 100644 --- a/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataPrimaryKeysTest.java +++ b/src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataPrimaryKeysTest.java @@ -44,8 +44,8 @@ */ class FBDatabaseMetaDataPrimaryKeysTest { - private static final String UNNAMED_PK_PREFIX = "INTEG_"; - private static final String UNNAMED_INDEX_PREFIX = "RDB$PRIMARY"; + private static final String UNNAMED_CONSTRAINT_PREFIX = "INTEG_"; + private static final String UNNAMED_PK_INDEX_PREFIX = "RDB$PRIMARY"; //@formatter:off @RegisterExtension @@ -116,20 +116,24 @@ void testPrimaryKeysMetaDataColumns() throws Exception { @Test void unnamedSingleColumnPk() throws Exception { validateExpectedPrimaryKeys("UNNAMED_SINGLE_COLUMN_PK", List.of( - createPrimaryKeysRow("UNNAMED_SINGLE_COLUMN_PK", "ID", 1, UNNAMED_PK_PREFIX, UNNAMED_INDEX_PREFIX))); + createPrimaryKeysRow("UNNAMED_SINGLE_COLUMN_PK", "ID", 1, UNNAMED_CONSTRAINT_PREFIX, + UNNAMED_PK_INDEX_PREFIX))); } @Test void unnamedMultiColumnPk() throws Exception { validateExpectedPrimaryKeys("UNNAMED_MULTI_COLUMN_PK", List.of( - createPrimaryKeysRow("UNNAMED_MULTI_COLUMN_PK", "ID1", 1, UNNAMED_PK_PREFIX, UNNAMED_INDEX_PREFIX), - createPrimaryKeysRow("UNNAMED_MULTI_COLUMN_PK", "ID2", 2, UNNAMED_PK_PREFIX, UNNAMED_INDEX_PREFIX))); + createPrimaryKeysRow("UNNAMED_MULTI_COLUMN_PK", "ID1", 1, UNNAMED_CONSTRAINT_PREFIX, + UNNAMED_PK_INDEX_PREFIX), + createPrimaryKeysRow("UNNAMED_MULTI_COLUMN_PK", "ID2", 2, UNNAMED_CONSTRAINT_PREFIX, + UNNAMED_PK_INDEX_PREFIX))); } @Test void unnamedPkNamedIndex() throws Exception { validateExpectedPrimaryKeys("UNNAMED_PK_NAMED_INDEX", List.of( - createPrimaryKeysRow("UNNAMED_PK_NAMED_INDEX", "ID", 1, UNNAMED_PK_PREFIX, "ALT_NAMED_INDEX_3"))); + createPrimaryKeysRow("UNNAMED_PK_NAMED_INDEX", "ID", 1, UNNAMED_CONSTRAINT_PREFIX, + "ALT_NAMED_INDEX_3"))); } @Test @@ -157,10 +161,10 @@ private static Map createPrimaryKeysRow(String tabl rules.put(PrimaryKeysMetaData.TABLE_NAME, tableName); rules.put(PrimaryKeysMetaData.COLUMN_NAME, columnName); rules.put(PrimaryKeysMetaData.KEY_SEQ, (short) keySeq); - rules.put(PrimaryKeysMetaData.PK_NAME, - UNNAMED_PK_PREFIX.equals(pkName) ? Matchers.startsWith(UNNAMED_PK_PREFIX) : pkName); - rules.put(PrimaryKeysMetaData.JB_PK_INDEX_NAME, - UNNAMED_INDEX_PREFIX.equals(jbIndexName) ? Matchers.startsWith(UNNAMED_INDEX_PREFIX) : jbIndexName); + rules.put(PrimaryKeysMetaData.PK_NAME, UNNAMED_CONSTRAINT_PREFIX.equals(pkName) + ? Matchers.startsWith(UNNAMED_CONSTRAINT_PREFIX) : pkName); + rules.put(PrimaryKeysMetaData.JB_PK_INDEX_NAME, UNNAMED_PK_INDEX_PREFIX.equals(jbIndexName) + ? Matchers.startsWith(UNNAMED_PK_INDEX_PREFIX) : jbIndexName); return rules; }