5252import com .google .cloud .bigquery .exception .BigQueryJdbcException ;
5353import com .google .cloud .bigquery .jdbc .BigQueryJdbcTypeMappings .ColumnTypeInfo ;
5454import com .google .cloud .bigquery .jdbc .utils .BigQueryJdbcVersionUtility ;
55- import java .io .BufferedReader ;
56- import java .io .InputStream ;
57- import java .io .InputStreamReader ;
5855import java .sql .Connection ;
5956import java .sql .DatabaseMetaData ;
6057import java .sql .ResultSet ;
6158import java .sql .RowIdLifetime ;
6259import java .sql .SQLException ;
63- import java .sql .Statement ;
6460import java .sql .Types ;
6561import java .util .ArrayList ;
6662import java .util .Arrays ;
6763import java .util .Collections ;
6864import java .util .Comparator ;
6965import java .util .HashSet ;
7066import java .util .List ;
71- import java .util .Scanner ;
7267import java .util .Set ;
7368import java .util .concurrent .BlockingQueue ;
7469import java .util .concurrent .Callable ;
8984 *
9085 * @see BigQueryStatement
9186 */
92- // TODO(neenu): test and verify after post MVP implementation.
9387class BigQueryDatabaseMetaData implements DatabaseMetaData {
9488 final BigQueryJdbcCustomLogger LOG = new BigQueryJdbcCustomLogger (this .toString ());
9589 private static final String DATABASE_PRODUCT_NAME = "Google BigQuery" ;
9690 private static final String DATABASE_PRODUCT_VERSION = "2.0" ;
9791 private static final String DRIVER_NAME = "GoogleJDBCDriverForGoogleBigQuery" ;
98-
9992 private static final String SCHEMA_TERM = "Dataset" ;
10093 private static final String CATALOG_TERM = "Project" ;
10194 private static final String PROCEDURE_TERM = "Procedure" ;
102- private static final String GET_EXPORTED_KEYS_SQL = "DatabaseMetaData_GetExportedKeys.sql" ;
10395 private static final int DEFAULT_PAGE_SIZE = 500 ;
10496 private static final int DEFAULT_QUEUE_CAPACITY = 5000 ;
10597 // Declared package-private for testing.
@@ -1820,11 +1812,9 @@ public ResultSet getCatalogs() throws SQLException {
18201812 final BlockingQueue <BigQueryFieldValueListWrapper > queue =
18211813 new LinkedBlockingQueue <>(catalogRows .isEmpty () ? 1 : catalogRows .size () + 1 );
18221814
1823- populateQueue (catalogRows , queue , schemaFields );
1824- signalEndOfData (queue , schemaFields );
1815+ Future <?> fetcherFuture = populateQueueAsync (catalogRows , queue , schemaFields );
18251816
1826- return BigQueryJsonResultSet .of (
1827- catalogsSchema , catalogRows .size (), queue , null , new Future <?>[0 ]);
1817+ return BigQueryJsonResultSet .of (catalogsSchema , catalogRows .size (), queue , null , fetcherFuture );
18281818 }
18291819
18301820 Schema defineGetCatalogsSchema () {
@@ -1852,11 +1842,11 @@ public ResultSet getTableTypes() {
18521842 BlockingQueue <BigQueryFieldValueListWrapper > queue =
18531843 new LinkedBlockingQueue <>(tableTypeRows .size () + 1 );
18541844
1855- populateQueue ( tableTypeRows , queue , tableTypesSchema . getFields ());
1856- signalEndOfData ( queue , tableTypesSchema .getFields ());
1845+ Future <?> fetcherFuture =
1846+ populateQueueAsync ( tableTypeRows , queue , tableTypesSchema .getFields ());
18571847
18581848 return BigQueryJsonResultSet .of (
1859- tableTypesSchema , tableTypeRows .size (), queue , null , new Future <?>[ 0 ] );
1849+ tableTypesSchema , tableTypeRows .size (), queue , null , fetcherFuture );
18601850 }
18611851
18621852 static Schema defineGetTableTypesSchema () {
@@ -2413,17 +2403,6 @@ Schema defineGetVersionColumnsSchema() {
24132403 return Schema .of (fields );
24142404 }
24152405
2416- private void closeStatementIgnoreException (Statement statement ) {
2417- if (statement == null ) {
2418- return ;
2419- }
2420- try {
2421- statement .close ();
2422- } catch (SQLException e ) {
2423- // pass
2424- }
2425- }
2426-
24272406 @ Override
24282407 public ResultSet getPrimaryKeys (String catalog , String schema , String table ) throws SQLException {
24292408 if ((catalog != null && catalog .isEmpty ())
@@ -2458,9 +2437,8 @@ public ResultSet getPrimaryKeys(String catalog, String schema, String table) thr
24582437
24592438 final BlockingQueue <BigQueryFieldValueListWrapper > queue =
24602439 new LinkedBlockingQueue <>(DEFAULT_QUEUE_CAPACITY );
2461- populateQueue (collectedResults , queue , resultSchemaFields );
2462- signalEndOfData (queue , resultSchemaFields );
2463- return BigQueryJsonResultSet .of (resultSchema , -1 , queue , null );
2440+ Future <?> fetcherFuture = populateQueueAsync (collectedResults , queue , resultSchemaFields );
2441+ return BigQueryJsonResultSet .of (resultSchema , -1 , queue , null , fetcherFuture );
24642442 }
24652443
24662444 private Schema defineGetPrimaryKeysSchema () {
@@ -2562,24 +2540,59 @@ public ResultSet getImportedKeys(String catalog, String schema, String table)
25622540
25632541 final BlockingQueue <BigQueryFieldValueListWrapper > queue =
25642542 new LinkedBlockingQueue <>(DEFAULT_QUEUE_CAPACITY );
2565- populateQueue (collectedResults , queue , resultSchemaFields );
2566- signalEndOfData (queue , resultSchemaFields );
2567- return BigQueryJsonResultSet .of (resultSchema , -1 , queue , null );
2543+ Future <?> fetcherFuture = populateQueueAsync (collectedResults , queue , resultSchemaFields );
2544+ return BigQueryJsonResultSet .of (resultSchema , -1 , queue , null , fetcherFuture );
25682545 }
25692546
25702547 @ Override
25712548 public ResultSet getExportedKeys (String catalog , String schema , String table )
25722549 throws SQLException {
2573- String sql = readSqlFromFile (GET_EXPORTED_KEYS_SQL );
2574- Statement stmt = this .connection .createStatement ();
2575- try {
2576- stmt .closeOnCompletion ();
2577- String formattedSql = replaceSqlParameters (sql , catalog , schema , table );
2578- return stmt .executeQuery (formattedSql );
2579- } catch (SQLException e ) {
2580- closeStatementIgnoreException (stmt );
2581- throw new BigQueryJdbcException ("Error executing getExportedKeys" , e );
2550+ if ((catalog != null && catalog .isEmpty ())
2551+ || (schema != null && schema .isEmpty ())
2552+ || table == null
2553+ || table .isEmpty ()) {
2554+ LOG .warning (
2555+ "Returning empty ResultSet as required parameters are null/empty, or catalog/schema parameters are empty." );
2556+ return new BigQueryJsonResultSet ();
25822557 }
2558+
2559+ final Schema resultSchema = defineForeignKeyResultSetSchema ();
2560+ final FieldList resultSchemaFields = resultSchema .getFields ();
2561+
2562+ final List <FieldValueList > collectedResults = Collections .synchronizedList (new ArrayList <>());
2563+ List <DatasetId > targetDatasets = getTargetDatasets (catalog , null );
2564+
2565+ boolean ignoreAccessErrors = (catalog == null );
2566+ processTargetTablesConcurrently (
2567+ targetDatasets ,
2568+ null ,
2569+ collectedResults ,
2570+ resultSchemaFields ,
2571+ ignoreAccessErrors ,
2572+ (bqTable , results , fields ) -> {
2573+ TableConstraints constraints = bqTable .getTableConstraints ();
2574+ if (constraints == null || constraints .getForeignKeys () == null ) {
2575+ return ;
2576+ }
2577+ for (ForeignKey fk : constraints .getForeignKeys ()) {
2578+ TableId pkTableId = fk .getReferencedTable ();
2579+ if (pkTableId == null
2580+ || !equalsOrNullMatchesAll (catalog , pkTableId .getProject ())
2581+ || !equalsOrNullMatchesAll (schema , pkTableId .getDataset ())
2582+ || !table .equals (pkTableId .getTable ())) {
2583+ continue ;
2584+ }
2585+ processForeignKey (fk , pkTableId , bqTable .getTableId (), results , fields );
2586+ }
2587+ });
2588+
2589+ Comparator <FieldValueList > comparator = defineFkTableSortComparator (resultSchemaFields );
2590+ sortResults (collectedResults , comparator , "getExportedKeys" , LOG );
2591+
2592+ final BlockingQueue <BigQueryFieldValueListWrapper > queue =
2593+ new LinkedBlockingQueue <>(DEFAULT_QUEUE_CAPACITY );
2594+ Future <?> fetcherFuture = populateQueueAsync (collectedResults , queue , resultSchemaFields );
2595+ return BigQueryJsonResultSet .of (resultSchema , -1 , queue , null , fetcherFuture );
25832596 }
25842597
25852598 @ Override
@@ -2638,9 +2651,8 @@ public ResultSet getCrossReference(
26382651
26392652 final BlockingQueue <BigQueryFieldValueListWrapper > queue =
26402653 new LinkedBlockingQueue <>(DEFAULT_QUEUE_CAPACITY );
2641- populateQueue (collectedResults , queue , resultSchemaFields );
2642- signalEndOfData (queue , resultSchemaFields );
2643- return BigQueryJsonResultSet .of (resultSchema , -1 , queue , null );
2654+ Future <?> fetcherFuture = populateQueueAsync (collectedResults , queue , resultSchemaFields );
2655+ return BigQueryJsonResultSet .of (resultSchema , -1 , queue , null , fetcherFuture );
26442656 }
26452657
26462658 @ Override
@@ -2656,10 +2668,9 @@ public ResultSet getTypeInfo() {
26562668 final BlockingQueue <BigQueryFieldValueListWrapper > queue =
26572669 new LinkedBlockingQueue <>(typeInfoRows .size () + 1 );
26582670
2659- populateQueue (typeInfoRows , queue , schemaFields );
2660- signalEndOfData (queue , schemaFields );
2671+ Future <?> fetcherFuture = populateQueueAsync (typeInfoRows , queue , schemaFields );
26612672 return BigQueryJsonResultSet .of (
2662- typeInfoSchema , typeInfoRows .size (), queue , null , new Future <?>[ 0 ] );
2673+ typeInfoSchema , typeInfoRows .size (), queue , null , fetcherFuture );
26632674 }
26642675
26652676 Schema defineGetTypeInfoSchema () {
@@ -3580,9 +3591,8 @@ public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLExce
35803591 }
35813592 Comparator <FieldValueList > comparator = defineGetSchemasComparator (resultSchemaFields );
35823593 sortResults (collectedResults , comparator , "getSchemas" , LOG );
3583- populateQueue (collectedResults , queue , resultSchemaFields );
3584- signalEndOfData (queue , resultSchemaFields );
3585- return BigQueryJsonResultSet .of (resultSchema , -1 , queue , null );
3594+ Future <?> fetcherFuture = populateQueueAsync (collectedResults , queue , resultSchemaFields );
3595+ return BigQueryJsonResultSet .of (resultSchema , -1 , queue , null , fetcherFuture );
35863596 }
35873597
35883598 // Multi-Catalog Path: fan out using connection-scoped metadataExecutor
@@ -4894,6 +4904,19 @@ private void waitForTasksCompletion(List<Future<?>> taskFutures) throws Executio
48944904 LOG .info ("Finished waiting for tasks." );
48954905 }
48964906
4907+ private Future <?> populateQueueAsync (
4908+ List <FieldValueList > collectedResults ,
4909+ BlockingQueue <BigQueryFieldValueListWrapper > queue ,
4910+ FieldList resultSchemaFields ) {
4911+ return connection
4912+ .getMetadataExecutor ()
4913+ .submit (
4914+ () -> {
4915+ populateQueue (collectedResults , queue , resultSchemaFields );
4916+ signalEndOfData (queue , resultSchemaFields );
4917+ });
4918+ }
4919+
48974920 private void populateQueue (
48984921 List <FieldValueList > collectedResults ,
48994922 BlockingQueue <BigQueryFieldValueListWrapper > queue ,
@@ -5066,24 +5089,6 @@ private boolean equalsOrNullMatchesAll(String expected, String actual) {
50665089 return expected == null || expected .equals (actual );
50675090 }
50685091
5069- static String readSqlFromFile (String filename ) {
5070- InputStream in ;
5071- in = BigQueryDatabaseMetaData .class .getResourceAsStream (filename );
5072- BufferedReader reader = new BufferedReader (new InputStreamReader (in ));
5073- StringBuilder builder = new StringBuilder ();
5074- try (Scanner scanner = new Scanner (reader )) {
5075- while (scanner .hasNextLine ()) {
5076- String line = scanner .nextLine ();
5077- builder .append (line ).append ("\n " );
5078- }
5079- }
5080- return builder .toString ();
5081- }
5082-
5083- String replaceSqlParameters (String sql , String ... params ) throws SQLException {
5084- return String .format (sql , (Object []) params );
5085- }
5086-
50875092 private void writeErrorToQueue (BlockingQueue <BigQueryFieldValueListWrapper > queue , Throwable t ) {
50885093 Exception ex = (t instanceof Exception ) ? (Exception ) t : new Exception (t );
50895094 BigQueryFieldValueListWrapper element = BigQueryFieldValueListWrapper .ofError (ex );
@@ -5171,7 +5176,7 @@ private void processTargetTablesConcurrently(
51715176 boolean ignoreAccessErrors ,
51725177 TableProcessor processor )
51735178 throws SQLException {
5174- if (targetDatasets .size () == 1 ) {
5179+ if (targetDatasets .size () == 1 && tableName != null ) {
51755180 processSingleTable (
51765181 targetDatasets .get (0 ),
51775182 tableName ,
@@ -5186,19 +5191,59 @@ private void processTargetTablesConcurrently(
51865191 List <Future <?>> taskFutures = new ArrayList <>();
51875192
51885193 try {
5194+ List <Callable <Void >> tasks = new ArrayList <>();
51895195 for (DatasetId datasetId : targetDatasets ) {
5190- taskFutures .add (
5191- executor .submit (
5196+ if (tableName != null ) {
5197+ tasks .add (
5198+ () -> {
5199+ processSingleTable (
5200+ datasetId ,
5201+ tableName ,
5202+ collectedResults ,
5203+ resultSchemaFields ,
5204+ ignoreAccessErrors ,
5205+ processor );
5206+ return null ;
5207+ });
5208+ continue ;
5209+ }
5210+
5211+ try {
5212+ Page <Table > tablesPage =
5213+ bigquery .listTables (datasetId , TableListOption .pageSize (DEFAULT_PAGE_SIZE ));
5214+ if (tablesPage == null ) {
5215+ continue ;
5216+ }
5217+ for (Table table : tablesPage .iterateAll ()) {
5218+ if (table .getDefinition () == null
5219+ || table .getDefinition ().getType () != TableDefinition .Type .TABLE ) {
5220+ continue ;
5221+ }
5222+ tasks .add (
51925223 () -> {
51935224 processSingleTable (
51945225 datasetId ,
5195- tableName ,
5226+ table . getTableId (). getTable () ,
51965227 collectedResults ,
51975228 resultSchemaFields ,
51985229 ignoreAccessErrors ,
51995230 processor );
52005231 return null ;
5201- }));
5232+ });
5233+ }
5234+ } catch (BigQueryException e ) {
5235+ if (ignoreAccessErrors && (e .getCode () == 404 || e .getCode () == 403 )) {
5236+ LOG .info (
5237+ "Dataset '%s' not found/accessible in project '%s' (API error %d). Skipping." ,
5238+ datasetId .getDataset (), datasetId .getProject (), e .getCode ());
5239+ continue ;
5240+ }
5241+ throw new SQLException ("Error while listing tables: " + e .getMessage (), e );
5242+ }
5243+ }
5244+
5245+ for (Callable <Void > task : tasks ) {
5246+ taskFutures .add (executor .submit (task ));
52025247 }
52035248 waitForTasksCompletion (taskFutures );
52045249 if (Thread .currentThread ().isInterrupted ()) {
0 commit comments