From 9df55a40668ca2796fb4c3281fc97f4d6792e4c5 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Mon, 13 Jul 2026 16:37:22 +0000 Subject: [PATCH 1/5] perf(bigquery-jdbc): optimize `getExportedKeys` performance using hybrid metadata lookup --- .../jdbc/BigQueryDatabaseMetaData.java | 85 ++++++++++++------- .../jdbc/DatabaseMetaData_GetExportedKeys.sql | 71 ++++++++++++++++ .../jdbc/it/ITDatabaseMetadataTest.java | 52 ++++++++++++ 3 files changed, 175 insertions(+), 33 deletions(-) create mode 100644 java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java index e6495b758f51..c5632b4c3d7b 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java @@ -52,11 +52,15 @@ import com.google.cloud.bigquery.exception.BigQueryJdbcException; import com.google.cloud.bigquery.jdbc.BigQueryJdbcTypeMappings.ColumnTypeInfo; import com.google.cloud.bigquery.jdbc.utils.BigQueryJdbcVersionUtility; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.ResultSet; import java.sql.RowIdLifetime; import java.sql.SQLException; +import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; @@ -64,6 +68,7 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Scanner; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; @@ -94,6 +99,7 @@ class BigQueryDatabaseMetaData implements DatabaseMetaData { private static final String PROCEDURE_TERM = "Procedure"; private static final int DEFAULT_PAGE_SIZE = 500; private static final int DEFAULT_QUEUE_CAPACITY = 5000; + private static final String GET_EXPORTED_KEYS_SQL = "DatabaseMetaData_GetExportedKeys.sql"; // Declared package-private for testing. static final String GOOGLE_SQL_QUOTED_IDENTIFIER = "`"; // Does not include SQL:2003 Keywords as per JDBC spec. @@ -2559,40 +2565,23 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) final Schema resultSchema = defineForeignKeyResultSetSchema(); final FieldList resultSchemaFields = resultSchema.getFields(); - final List collectedResults = Collections.synchronizedList(new ArrayList<>()); - List targetDatasets = getTargetDatasets(catalog, null); - - boolean ignoreAccessErrors = (catalog == null); - processTargetTablesConcurrently( - targetDatasets, - null, - collectedResults, - resultSchemaFields, - ignoreAccessErrors, - (bqTable, results, fields) -> { - TableConstraints constraints = bqTable.getTableConstraints(); - if (constraints == null || constraints.getForeignKeys() == null) { - return; - } - for (ForeignKey fk : constraints.getForeignKeys()) { - TableId pkTableId = fk.getReferencedTable(); - if (pkTableId == null - || !equalsOrNullMatchesAll(catalog, pkTableId.getProject()) - || !equalsOrNullMatchesAll(schema, pkTableId.getDataset()) - || !table.equals(pkTableId.getTable())) { - continue; - } - processForeignKey(fk, pkTableId, bqTable.getTableId(), results, fields); - } - }); - - Comparator comparator = defineFkTableSortComparator(resultSchemaFields); - sortResults(collectedResults, comparator, "getExportedKeys", LOG); + // Early return for PCNT catalog schemas (containing '.') as they do not support table constraints. + if (schema != null && schema.contains(".")) { + final BlockingQueue queue = new LinkedBlockingQueue<>(1); + signalEndOfData(queue, resultSchemaFields); + return BigQueryJsonResultSet.of(resultSchema, 0, queue, null); + } - final BlockingQueue queue = - new LinkedBlockingQueue<>(DEFAULT_QUEUE_CAPACITY); - Future fetcherFuture = populateQueueAsync(collectedResults, queue, resultSchemaFields); - return BigQueryJsonResultSet.of(resultSchema, -1, queue, null, fetcherFuture); + String sql = readSqlFromFile(GET_EXPORTED_KEYS_SQL); + Statement stmt = this.connection.createStatement(); + try { + stmt.closeOnCompletion(); + String formattedSql = replaceSqlParameters(sql, catalog, schema, table); + return stmt.executeQuery(formattedSql); + } catch (SQLException e) { + closeStatementIgnoreException(stmt); + throw new BigQueryJdbcException("Error executing getExportedKeys", e); + } } @Override @@ -5353,4 +5342,34 @@ private Comparator defineFkTableSortComparator(FieldList resultS (FieldValueList fvl) -> getLongValueOrNull(fvl, KEY_SEQ_IDX), Comparator.nullsFirst(Long::compareTo)); } + + static String readSqlFromFile(String filename) { + InputStream in = BigQueryDatabaseMetaData.class.getResourceAsStream(filename); + if (in == null) { + throw new IllegalArgumentException("SQL file not found: " + filename); + } + BufferedReader reader = new BufferedReader(new InputStreamReader(in)); + StringBuilder builder = new StringBuilder(); + try (Scanner scanner = new Scanner(reader)) { + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + builder.append(line).append("\n"); + } + } + return builder.toString(); + } + + String replaceSqlParameters(String sql, String... params) throws SQLException { + return String.format(sql, (Object[]) params); + } + + private void closeStatementIgnoreException(Statement stmt) { + if (stmt != null) { + try { + stmt.close(); + } catch (SQLException e) { + // ignore + } + } + } } diff --git a/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql b/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql new file mode 100644 index 000000000000..b00156a1393c --- /dev/null +++ b/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql @@ -0,0 +1,71 @@ +/* + * Copyright 2024 Google LLC + * + * 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 + * + * https://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. + */ + +SELECT PKTABLE_CAT, + PKTABLE_SCHEM, + PKTABLE_NAME, + PRIMARY.column_name AS PKCOLUMN_NAME, + FOREIGN.constraint_catalog AS FKTABLE_CAT, + FOREIGN.constraint_schema AS FKTABLE_SCHEM, + FOREIGN.table_name AS FKTABLE_NAME, + FOREIGN.column_name AS FKCOLUMN_NAME, + FOREIGN.ordinal_position AS KEY_SEQ, + NULL AS UPDATE_RULE, + NULL AS DELETE_RULE, + FOREIGN.constraint_name AS FK_NAME, + PRIMARY.constraint_name AS PK_NAME, + NULL AS DEFERRABILITY +FROM (SELECT DISTINCT CCU.table_catalog AS PKTABLE_CAT, + CCU.table_schema AS PKTABLE_SCHEM, + CCU.table_name AS PKTABLE_NAME, + TC.constraint_catalog, + TC.constraint_schema, + TC.constraint_name, + TC.table_catalog, + TC.table_schema, + TC.table_name, + TC.constraint_type, + KCU.column_name, + KCU.ordinal_position, + KCU.position_in_unique_constraint + FROM `%1$s.%2$s.INFORMATION_SCHEMA.TABLE_CONSTRAINTS` TC + INNER JOIN + `%1$s.%2$s.INFORMATION_SCHEMA.KEY_COLUMN_USAGE` KCU + USING + (constraint_catalog, + constraint_schema, + constraint_name, + table_catalog, + table_schema, + table_name) + INNER JOIN + `%1$s.%2$s.INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE` CCU + USING + (constraint_catalog, + constraint_schema, + constraint_name) + WHERE constraint_type = 'FOREIGN KEY') FOREIGN + INNER JOIN (SELECT * + FROM `%1$s.%2$s.INFORMATION_SCHEMA.KEY_COLUMN_USAGE` + WHERE position_in_unique_constraint IS NULL + AND RTRIM(table_name) = '%3$s') PRIMARY +ON + FOREIGN.PKTABLE_CAT = PRIMARY.table_catalog + AND FOREIGN.PKTABLE_SCHEM = PRIMARY.table_schema + AND FOREIGN.PKTABLE_NAME = PRIMARY.table_name + AND FOREIGN.position_in_unique_constraint = + PRIMARY.ordinal_position +ORDER BY FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, KEY_SEQ diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDatabaseMetadataTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDatabaseMetadataTest.java index 046f49991dbe..35b5b58b42d2 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDatabaseMetadataTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDatabaseMetadataTest.java @@ -57,6 +57,8 @@ public class ITDatabaseMetadataTest extends ITBase { private static final String CONSTRAINTS_TABLE_NAME = "JDBC_CONSTRAINTS_TEST_TABLE"; private static final String CONSTRAINTS_TABLE_NAME2 = "JDBC_CONSTRAINTS_TEST_TABLE2"; private static final String CONSTRAINTS_TABLE_NAME3 = "JDBC_CONSTRAINTS_TEST_TABLE3"; + private static final String PCNT_SCHEMA = "bq-drivers-test-warehouse.jdbc_pcnt_test_namespace"; + private static final String PCNT_TABLE_NAME = "PCNT_TEST_TABLE"; private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)\\.(\\d+)(?:\\.\\d+)+\\s*.*"); private static final String DEFAULT_CATALOG = ServiceOptions.getDefaultProjectId(); @@ -387,6 +389,56 @@ public void testGetExportedKeys_noKeys() throws SQLException { } } + @Test + public void testGetPrimaryKeys_pcntTable() throws SQLException { + try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl)) { + DatabaseMetaData metaData = connection.getMetaData(); + ResultSet primaryKeys = metaData.getPrimaryKeys(PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME); + Assertions.assertNotNull(primaryKeys); + ResultSetMetaData pkMetaData = primaryKeys.getMetaData(); + Assertions.assertEquals(6, pkMetaData.getColumnCount()); + Assertions.assertFalse(primaryKeys.next()); + } + } + + @Test + public void testGetImportedKeys_pcntTable() throws SQLException { + try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl)) { + DatabaseMetaData metaData = connection.getMetaData(); + ResultSet importedKeys = metaData.getImportedKeys(PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME); + Assertions.assertNotNull(importedKeys); + ResultSetMetaData ikMetaData = importedKeys.getMetaData(); + Assertions.assertEquals(14, ikMetaData.getColumnCount()); + Assertions.assertFalse(importedKeys.next()); + } + } + + @Test + public void testGetExportedKeys_pcntTable() throws SQLException { + try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl)) { + DatabaseMetaData metaData = connection.getMetaData(); + ResultSet exportedKeys = metaData.getExportedKeys(PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME); + Assertions.assertNotNull(exportedKeys); + ResultSetMetaData ekMetaData = exportedKeys.getMetaData(); + Assertions.assertEquals(14, ekMetaData.getColumnCount()); + Assertions.assertFalse(exportedKeys.next()); + } + } + + @Test + public void testGetCrossReference_pcntTable() throws SQLException { + try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl)) { + DatabaseMetaData metaData = connection.getMetaData(); + ResultSet crossReference = + metaData.getCrossReference( + PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME, PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME); + Assertions.assertNotNull(crossReference); + ResultSetMetaData crMetaData = crossReference.getMetaData(); + Assertions.assertEquals(14, crMetaData.getColumnCount()); + Assertions.assertFalse(crossReference.next()); + } + } + @Test public void testMetadataResultSetsDoNotInterfere() throws SQLException { try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl)) { From 3a9ab91a8acb4e103eaaf8577caa43ac3b9680a9 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Mon, 13 Jul 2026 16:37:42 +0000 Subject: [PATCH 2/5] lint --- .../google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java index c5632b4c3d7b..b1349a0c986a 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java @@ -2565,7 +2565,8 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) final Schema resultSchema = defineForeignKeyResultSetSchema(); final FieldList resultSchemaFields = resultSchema.getFields(); - // Early return for PCNT catalog schemas (containing '.') as they do not support table constraints. + // Early return for PCNT catalog schemas (containing '.') as they do not support table + // constraints. if (schema != null && schema.contains(".")) { final BlockingQueue queue = new LinkedBlockingQueue<>(1); signalEndOfData(queue, resultSchemaFields); From 1a0ec37e3d60f8ab7d0ac113bf632dc419d402c1 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Mon, 13 Jul 2026 17:02:52 +0000 Subject: [PATCH 3/5] include `null` support for catalogs and schemas --- .../jdbc/BigQueryDatabaseMetaData.java | 73 ++++++++++++++----- .../jdbc/DatabaseMetaData_GetExportedKeys.sql | 2 +- .../jdbc/it/ITDatabaseMetadataTest.java | 34 +++++---- 3 files changed, 77 insertions(+), 32 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java index b1349a0c986a..7cf9629bf456 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java @@ -52,11 +52,13 @@ import com.google.cloud.bigquery.exception.BigQueryJdbcException; import com.google.cloud.bigquery.jdbc.BigQueryJdbcTypeMappings.ColumnTypeInfo; import com.google.cloud.bigquery.jdbc.utils.BigQueryJdbcVersionUtility; -import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.sql.Connection; import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.RowIdLifetime; import java.sql.SQLException; @@ -68,7 +70,6 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; -import java.util.Scanner; import java.util.Set; import java.util.concurrent.BlockingQueue; import java.util.concurrent.Callable; @@ -2573,12 +2574,49 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) return BigQueryJsonResultSet.of(resultSchema, 0, queue, null); } + // Fallback Path: If catalog or schema is null, fall back to REST API metadata scan. + if (catalog == null || schema == null) { + final List collectedResults = Collections.synchronizedList(new ArrayList<>()); + List targetDatasets = getTargetDatasets(catalog, schema); + + boolean ignoreAccessErrors = (catalog == null); + processTargetTablesConcurrently( + targetDatasets, + null, + collectedResults, + resultSchemaFields, + ignoreAccessErrors, + (bqTable, results, fields) -> { + TableConstraints constraints = bqTable.getTableConstraints(); + if (constraints != null && constraints.getForeignKeys() != null) { + for (ForeignKey fk : constraints.getForeignKeys()) { + TableId pkTableId = fk.getReferencedTable(); + if (pkTableId != null + && equalsOrNullMatchesAll(catalog, pkTableId.getProject()) + && equalsOrNullMatchesAll(schema, pkTableId.getDataset()) + && table.equals(pkTableId.getTable())) { + processForeignKey(fk, pkTableId, bqTable.getTableId(), results, fields); + } + } + } + }); + + Comparator comparator = defineFkTableSortComparator(resultSchemaFields); + sortResults(collectedResults, comparator, "getExportedKeys", LOG); + + final BlockingQueue queue = + new LinkedBlockingQueue<>(DEFAULT_QUEUE_CAPACITY); + Future fetcherFuture = populateQueueAsync(collectedResults, queue, resultSchemaFields); + return BigQueryJsonResultSet.of(resultSchema, -1, queue, null, fetcherFuture); + } + String sql = readSqlFromFile(GET_EXPORTED_KEYS_SQL); - Statement stmt = this.connection.createStatement(); + String formattedSql = replaceSqlParameters(sql, catalog, schema); + PreparedStatement stmt = this.connection.prepareStatement(formattedSql); try { stmt.closeOnCompletion(); - String formattedSql = replaceSqlParameters(sql, catalog, schema, table); - return stmt.executeQuery(formattedSql); + stmt.setString(1, table); + return stmt.executeQuery(); } catch (SQLException e) { closeStatementIgnoreException(stmt); throw new BigQueryJdbcException("Error executing getExportedKeys", e); @@ -5345,19 +5383,20 @@ private Comparator defineFkTableSortComparator(FieldList resultS } static String readSqlFromFile(String filename) { - InputStream in = BigQueryDatabaseMetaData.class.getResourceAsStream(filename); - if (in == null) { - throw new IllegalArgumentException("SQL file not found: " + filename); - } - BufferedReader reader = new BufferedReader(new InputStreamReader(in)); - StringBuilder builder = new StringBuilder(); - try (Scanner scanner = new Scanner(reader)) { - while (scanner.hasNextLine()) { - String line = scanner.nextLine(); - builder.append(line).append("\n"); + try (InputStream in = BigQueryDatabaseMetaData.class.getResourceAsStream(filename)) { + if (in == null) { + throw new IllegalArgumentException("SQL file not found: " + filename); + } + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = in.read(buffer)) != -1) { + result.write(buffer, 0, length); } + return result.toString(StandardCharsets.UTF_8.name()); + } catch (IOException e) { + throw new RuntimeException("Failed to read SQL file: " + filename, e); } - return builder.toString(); } String replaceSqlParameters(String sql, String... params) throws SQLException { diff --git a/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql b/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql index b00156a1393c..74490bc8ab54 100644 --- a/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql +++ b/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql @@ -61,7 +61,7 @@ FROM (SELECT DISTINCT CCU.table_catalog AS PKTABLE_CAT, INNER JOIN (SELECT * FROM `%1$s.%2$s.INFORMATION_SCHEMA.KEY_COLUMN_USAGE` WHERE position_in_unique_constraint IS NULL - AND RTRIM(table_name) = '%3$s') PRIMARY + AND RTRIM(table_name) = ?) PRIMARY ON FOREIGN.PKTABLE_CAT = PRIMARY.table_catalog AND FOREIGN.PKTABLE_SCHEM = PRIMARY.table_schema diff --git a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDatabaseMetadataTest.java b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDatabaseMetadataTest.java index 35b5b58b42d2..0727a4a55f5e 100644 --- a/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDatabaseMetadataTest.java +++ b/java-bigquery-jdbc/src/test/java/com/google/cloud/bigquery/jdbc/it/ITDatabaseMetadataTest.java @@ -391,9 +391,9 @@ public void testGetExportedKeys_noKeys() throws SQLException { @Test public void testGetPrimaryKeys_pcntTable() throws SQLException { - try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl)) { - DatabaseMetaData metaData = connection.getMetaData(); - ResultSet primaryKeys = metaData.getPrimaryKeys(PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME); + try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl); + ResultSet primaryKeys = + connection.getMetaData().getPrimaryKeys(PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME)) { Assertions.assertNotNull(primaryKeys); ResultSetMetaData pkMetaData = primaryKeys.getMetaData(); Assertions.assertEquals(6, pkMetaData.getColumnCount()); @@ -403,9 +403,9 @@ public void testGetPrimaryKeys_pcntTable() throws SQLException { @Test public void testGetImportedKeys_pcntTable() throws SQLException { - try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl)) { - DatabaseMetaData metaData = connection.getMetaData(); - ResultSet importedKeys = metaData.getImportedKeys(PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME); + try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl); + ResultSet importedKeys = + connection.getMetaData().getImportedKeys(PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME)) { Assertions.assertNotNull(importedKeys); ResultSetMetaData ikMetaData = importedKeys.getMetaData(); Assertions.assertEquals(14, ikMetaData.getColumnCount()); @@ -415,9 +415,9 @@ public void testGetImportedKeys_pcntTable() throws SQLException { @Test public void testGetExportedKeys_pcntTable() throws SQLException { - try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl)) { - DatabaseMetaData metaData = connection.getMetaData(); - ResultSet exportedKeys = metaData.getExportedKeys(PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME); + try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl); + ResultSet exportedKeys = + connection.getMetaData().getExportedKeys(PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME)) { Assertions.assertNotNull(exportedKeys); ResultSetMetaData ekMetaData = exportedKeys.getMetaData(); Assertions.assertEquals(14, ekMetaData.getColumnCount()); @@ -427,11 +427,17 @@ public void testGetExportedKeys_pcntTable() throws SQLException { @Test public void testGetCrossReference_pcntTable() throws SQLException { - try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl)) { - DatabaseMetaData metaData = connection.getMetaData(); - ResultSet crossReference = - metaData.getCrossReference( - PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME, PROJECT_ID, PCNT_SCHEMA, PCNT_TABLE_NAME); + try (Connection connection = DriverManager.getConnection(ITBase.connectionUrl); + ResultSet crossReference = + connection + .getMetaData() + .getCrossReference( + PROJECT_ID, + PCNT_SCHEMA, + PCNT_TABLE_NAME, + PROJECT_ID, + PCNT_SCHEMA, + PCNT_TABLE_NAME)) { Assertions.assertNotNull(crossReference); ResultSetMetaData crMetaData = crossReference.getMetaData(); Assertions.assertEquals(14, crMetaData.getColumnCount()); From f01b7a1584488fb8fade5192d923fe6866e43fc7 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Mon, 13 Jul 2026 17:16:15 +0000 Subject: [PATCH 4/5] address pr feedback --- .../jdbc/BigQueryDatabaseMetaData.java | 43 ++++++++++++------- .../jdbc/DatabaseMetaData_GetExportedKeys.sql | 4 +- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java index 7cf9629bf456..30a65bf280b1 100644 --- a/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java +++ b/java-bigquery-jdbc/src/main/java/com/google/cloud/bigquery/jdbc/BigQueryDatabaseMetaData.java @@ -101,6 +101,7 @@ class BigQueryDatabaseMetaData implements DatabaseMetaData { private static final int DEFAULT_PAGE_SIZE = 500; private static final int DEFAULT_QUEUE_CAPACITY = 5000; private static final String GET_EXPORTED_KEYS_SQL = "DatabaseMetaData_GetExportedKeys.sql"; + private static String exportedKeysSqlContent; // Declared package-private for testing. static final String GOOGLE_SQL_QUOTED_IDENTIFIER = "`"; // Does not include SQL:2003 Keywords as per JDBC spec. @@ -2588,16 +2589,18 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) ignoreAccessErrors, (bqTable, results, fields) -> { TableConstraints constraints = bqTable.getTableConstraints(); - if (constraints != null && constraints.getForeignKeys() != null) { - for (ForeignKey fk : constraints.getForeignKeys()) { - TableId pkTableId = fk.getReferencedTable(); - if (pkTableId != null - && equalsOrNullMatchesAll(catalog, pkTableId.getProject()) - && equalsOrNullMatchesAll(schema, pkTableId.getDataset()) - && table.equals(pkTableId.getTable())) { - processForeignKey(fk, pkTableId, bqTable.getTableId(), results, fields); - } + if (constraints == null || constraints.getForeignKeys() == null) { + return; + } + for (ForeignKey fk : constraints.getForeignKeys()) { + TableId pkTableId = fk.getReferencedTable(); + if (pkTableId == null + || !equalsOrNullMatchesAll(catalog, pkTableId.getProject()) + || !equalsOrNullMatchesAll(schema, pkTableId.getDataset()) + || !table.equals(pkTableId.getTable())) { + continue; } + processForeignKey(fk, pkTableId, bqTable.getTableId(), results, fields); } }); @@ -2610,7 +2613,7 @@ && equalsOrNullMatchesAll(schema, pkTableId.getDataset()) return BigQueryJsonResultSet.of(resultSchema, -1, queue, null, fetcherFuture); } - String sql = readSqlFromFile(GET_EXPORTED_KEYS_SQL); + String sql = getExportedKeysSqlContent(); String formattedSql = replaceSqlParameters(sql, catalog, schema); PreparedStatement stmt = this.connection.prepareStatement(formattedSql); try { @@ -5382,6 +5385,13 @@ private Comparator defineFkTableSortComparator(FieldList resultS Comparator.nullsFirst(Long::compareTo)); } + private static synchronized String getExportedKeysSqlContent() { + if (exportedKeysSqlContent == null) { + exportedKeysSqlContent = readSqlFromFile(GET_EXPORTED_KEYS_SQL); + } + return exportedKeysSqlContent; + } + static String readSqlFromFile(String filename) { try (InputStream in = BigQueryDatabaseMetaData.class.getResourceAsStream(filename)) { if (in == null) { @@ -5404,12 +5414,13 @@ String replaceSqlParameters(String sql, String... params) throws SQLException { } private void closeStatementIgnoreException(Statement stmt) { - if (stmt != null) { - try { - stmt.close(); - } catch (SQLException e) { - // ignore - } + if (stmt == null) { + return; + } + try { + stmt.close(); + } catch (SQLException e) { + // ignore } } } diff --git a/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql b/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql index 74490bc8ab54..2d593bc83885 100644 --- a/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql +++ b/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql @@ -1,5 +1,5 @@ /* - * Copyright 2024 Google LLC + * Copyright 2026 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,7 +61,7 @@ FROM (SELECT DISTINCT CCU.table_catalog AS PKTABLE_CAT, INNER JOIN (SELECT * FROM `%1$s.%2$s.INFORMATION_SCHEMA.KEY_COLUMN_USAGE` WHERE position_in_unique_constraint IS NULL - AND RTRIM(table_name) = ?) PRIMARY + AND table_name = ?) PRIMARY ON FOREIGN.PKTABLE_CAT = PRIMARY.table_catalog AND FOREIGN.PKTABLE_SCHEM = PRIMARY.table_schema From d1ef2972d97b6aaa2773e74a55d9b60632ac9761 Mon Sep 17 00:00:00 2001 From: Keshav Dandeva Date: Mon, 13 Jul 2026 17:27:34 +0000 Subject: [PATCH 5/5] update sql query --- .../jdbc/DatabaseMetaData_GetExportedKeys.sql | 70 +++++-------------- 1 file changed, 16 insertions(+), 54 deletions(-) diff --git a/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql b/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql index 2d593bc83885..93b4e6b72a72 100644 --- a/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql +++ b/java-bigquery-jdbc/src/main/resources/com/google/cloud/bigquery/jdbc/DatabaseMetaData_GetExportedKeys.sql @@ -14,58 +14,20 @@ * limitations under the License. */ -SELECT PKTABLE_CAT, - PKTABLE_SCHEM, - PKTABLE_NAME, - PRIMARY.column_name AS PKCOLUMN_NAME, - FOREIGN.constraint_catalog AS FKTABLE_CAT, - FOREIGN.constraint_schema AS FKTABLE_SCHEM, - FOREIGN.table_name AS FKTABLE_NAME, - FOREIGN.column_name AS FKCOLUMN_NAME, - FOREIGN.ordinal_position AS KEY_SEQ, - NULL AS UPDATE_RULE, - NULL AS DELETE_RULE, - FOREIGN.constraint_name AS FK_NAME, - PRIMARY.constraint_name AS PK_NAME, - NULL AS DEFERRABILITY -FROM (SELECT DISTINCT CCU.table_catalog AS PKTABLE_CAT, - CCU.table_schema AS PKTABLE_SCHEM, - CCU.table_name AS PKTABLE_NAME, - TC.constraint_catalog, - TC.constraint_schema, - TC.constraint_name, - TC.table_catalog, - TC.table_schema, - TC.table_name, - TC.constraint_type, - KCU.column_name, - KCU.ordinal_position, - KCU.position_in_unique_constraint - FROM `%1$s.%2$s.INFORMATION_SCHEMA.TABLE_CONSTRAINTS` TC - INNER JOIN - `%1$s.%2$s.INFORMATION_SCHEMA.KEY_COLUMN_USAGE` KCU - USING - (constraint_catalog, - constraint_schema, - constraint_name, - table_catalog, - table_schema, - table_name) - INNER JOIN - `%1$s.%2$s.INFORMATION_SCHEMA.CONSTRAINT_COLUMN_USAGE` CCU - USING - (constraint_catalog, - constraint_schema, - constraint_name) - WHERE constraint_type = 'FOREIGN KEY') FOREIGN - INNER JOIN (SELECT * - FROM `%1$s.%2$s.INFORMATION_SCHEMA.KEY_COLUMN_USAGE` - WHERE position_in_unique_constraint IS NULL - AND table_name = ?) PRIMARY -ON - FOREIGN.PKTABLE_CAT = PRIMARY.table_catalog - AND FOREIGN.PKTABLE_SCHEM = PRIMARY.table_schema - AND FOREIGN.PKTABLE_NAME = PRIMARY.table_name - AND FOREIGN.position_in_unique_constraint = - PRIMARY.ordinal_position +SELECT referenced_table_catalog AS PKTABLE_CAT, + referenced_table_schema AS PKTABLE_SCHEM, + referenced_table_name AS PKTABLE_NAME, + referenced_column_name AS PKCOLUMN_NAME, + table_catalog AS FKTABLE_CAT, + table_schema AS FKTABLE_SCHEM, + table_name AS FKTABLE_NAME, + column_name AS FKCOLUMN_NAME, + ordinal_position AS KEY_SEQ, + NULL AS UPDATE_RULE, + NULL AS DELETE_RULE, + constraint_name AS FK_NAME, + NULL AS PK_NAME, + NULL AS DEFERRABILITY +FROM `%1$s.%2$s.INFORMATION_SCHEMA.KEY_COLUMN_USAGE` +WHERE referenced_table_name = ? ORDER BY FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, KEY_SEQ