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:
+ *
+ * - JB_FK_INDEX_NAME String => Index backing the foreign key
+ * - JB_PK_INDEX_NAME String => Index backing the primary key
+ *
+ *
+ */
@Override
public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException {
return GetImportedKeys.create(getDbMetadataMediator()).getImportedKeys(table);
}
+ /**
+ * {@inheritDoc}
+ *
+ * Jaybird defines these additional columns:
+ *
+ * - JB_FK_INDEX_NAME String => Index backing the foreign key
+ * - JB_PK_INDEX_NAME String => Index backing the primary key
+ *
+ *
+ */
@Override
public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException {
return GetExportedKeys.create(getDbMetadataMediator()).getExportedKeys(table);
}
+ /**
+ * {@inheritDoc}
+ *
+ * Jaybird defines these additional columns:
+ *
+ * - JB_FK_INDEX_NAME String => Index backing the foreign key
+ * - JB_PK_INDEX_NAME String => Index backing the primary key
+ *
+ *
+ */
@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