|
34 | 34 | import com.google.cloud.bigquery.FieldList; |
35 | 35 | import com.google.cloud.bigquery.FieldValue; |
36 | 36 | import com.google.cloud.bigquery.FieldValueList; |
| 37 | +import com.google.cloud.bigquery.PrimaryKey; |
37 | 38 | import com.google.cloud.bigquery.Routine; |
38 | 39 | import com.google.cloud.bigquery.RoutineArgument; |
39 | 40 | import com.google.cloud.bigquery.RoutineId; |
|
42 | 43 | import com.google.cloud.bigquery.StandardSQLField; |
43 | 44 | import com.google.cloud.bigquery.StandardSQLTableType; |
44 | 45 | import com.google.cloud.bigquery.StandardSQLTypeName; |
| 46 | +import com.google.cloud.bigquery.StandardTableDefinition; |
45 | 47 | import com.google.cloud.bigquery.Table; |
| 48 | +import com.google.cloud.bigquery.TableConstraints; |
46 | 49 | import com.google.cloud.bigquery.TableDefinition; |
47 | 50 | import com.google.cloud.bigquery.TableId; |
48 | 51 | import com.google.cloud.bigquery.exception.BigQueryJdbcException; |
@@ -2425,16 +2428,102 @@ private void closeStatementIgnoreException(Statement statement) { |
2425 | 2428 |
|
2426 | 2429 | @Override |
2427 | 2430 | public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { |
2428 | | - String sql = readSqlFromFile(GET_PRIMARY_KEYS_SQL); |
2429 | | - Statement stmt = this.connection.createStatement(); |
2430 | | - try { |
2431 | | - stmt.closeOnCompletion(); |
2432 | | - String formattedSql = replaceSqlParameters(sql, catalog, schema, table); |
2433 | | - return stmt.executeQuery(formattedSql); |
2434 | | - } catch (SQLException e) { |
2435 | | - closeStatementIgnoreException(stmt); |
2436 | | - throw new BigQueryJdbcException("Error executing getPrimaryKeys", e); |
| 2431 | + if ((catalog != null && catalog.isEmpty()) |
| 2432 | + || (schema != null && schema.isEmpty()) |
| 2433 | + || table == null |
| 2434 | + || table.isEmpty()) { |
| 2435 | + LOG.warning( |
| 2436 | + "Returning empty ResultSet as one or more patterns are empty or catalog is empty."); |
| 2437 | + return new BigQueryJsonResultSet(); |
| 2438 | + } |
| 2439 | + |
| 2440 | + final Schema resultSchema = defineGetPrimaryKeysSchema(); |
| 2441 | + final FieldList resultSchemaFields = resultSchema.getFields(); |
| 2442 | + |
| 2443 | + final List<FieldValueList> collectedResults = Collections.synchronizedList(new ArrayList<>()); |
| 2444 | + List<DatasetId> targetDatasets = getTargetDatasets(catalog, schema); |
| 2445 | + |
| 2446 | + boolean ignoreAccessErrors = (catalog == null); |
| 2447 | + processTargetTablesConcurrently( |
| 2448 | + targetDatasets, |
| 2449 | + table, |
| 2450 | + collectedResults, |
| 2451 | + resultSchemaFields, |
| 2452 | + ignoreAccessErrors, |
| 2453 | + (bqTable, results, fields) -> { |
| 2454 | + TableConstraints constraints = null; |
| 2455 | + if (bqTable.getDefinition() instanceof StandardTableDefinition) { |
| 2456 | + constraints = ((StandardTableDefinition) bqTable.getDefinition()).getTableConstraints(); |
| 2457 | + } |
| 2458 | + processPrimaryKey(constraints, bqTable.getTableId(), results, fields); |
| 2459 | + }); |
| 2460 | + |
| 2461 | + Comparator<FieldValueList> comparator = defineGetPrimaryKeysComparator(resultSchemaFields); |
| 2462 | + sortResults(collectedResults, comparator, "getPrimaryKeys", LOG); |
| 2463 | + |
| 2464 | + final BlockingQueue<BigQueryFieldValueListWrapper> queue = |
| 2465 | + new LinkedBlockingQueue<>(DEFAULT_QUEUE_CAPACITY); |
| 2466 | + populateQueue(collectedResults, queue, resultSchemaFields); |
| 2467 | + signalEndOfData(queue, resultSchemaFields); |
| 2468 | + return BigQueryJsonResultSet.of(resultSchema, -1, queue, null); |
| 2469 | + } |
| 2470 | + |
| 2471 | + private Schema defineGetPrimaryKeysSchema() { |
| 2472 | + List<Field> fields = new ArrayList<>(6); |
| 2473 | + fields.add( |
| 2474 | + Field.newBuilder("TABLE_CAT", StandardSQLTypeName.STRING) |
| 2475 | + .setMode(Field.Mode.NULLABLE) |
| 2476 | + .build()); |
| 2477 | + fields.add( |
| 2478 | + Field.newBuilder("TABLE_SCHEM", StandardSQLTypeName.STRING) |
| 2479 | + .setMode(Field.Mode.NULLABLE) |
| 2480 | + .build()); |
| 2481 | + fields.add( |
| 2482 | + Field.newBuilder("TABLE_NAME", StandardSQLTypeName.STRING) |
| 2483 | + .setMode(Field.Mode.REQUIRED) |
| 2484 | + .build()); |
| 2485 | + fields.add( |
| 2486 | + Field.newBuilder("COLUMN_NAME", StandardSQLTypeName.STRING) |
| 2487 | + .setMode(Field.Mode.REQUIRED) |
| 2488 | + .build()); |
| 2489 | + fields.add( |
| 2490 | + Field.newBuilder("KEY_SEQ", StandardSQLTypeName.INT64) |
| 2491 | + .setMode(Field.Mode.REQUIRED) |
| 2492 | + .build()); |
| 2493 | + fields.add( |
| 2494 | + Field.newBuilder("PK_NAME", StandardSQLTypeName.STRING) |
| 2495 | + .setMode(Field.Mode.NULLABLE) |
| 2496 | + .build()); |
| 2497 | + return Schema.of(fields); |
| 2498 | + } |
| 2499 | + |
| 2500 | + private void processPrimaryKey( |
| 2501 | + TableConstraints constraints, |
| 2502 | + TableId tableId, |
| 2503 | + List<FieldValueList> collectedResults, |
| 2504 | + FieldList resultSchemaFields) { |
| 2505 | + if (constraints == null || constraints.getPrimaryKey() == null) { |
| 2506 | + return; |
2437 | 2507 | } |
| 2508 | + PrimaryKey pk = constraints.getPrimaryKey(); |
| 2509 | + List<String> pkColumns = pk.getColumns(); |
| 2510 | + for (int i = 0; i < pkColumns.size(); i++) { |
| 2511 | + List<FieldValue> row = new ArrayList<>(6); |
| 2512 | + row.add(createStringFieldValue(tableId.getProject())); // 1. TABLE_CAT |
| 2513 | + row.add(createStringFieldValue(tableId.getDataset())); // 2. TABLE_SCHEM |
| 2514 | + row.add(createStringFieldValue(tableId.getTable())); // 3. TABLE_NAME |
| 2515 | + row.add(createStringFieldValue(pkColumns.get(i))); // 4. COLUMN_NAME |
| 2516 | + row.add(createLongFieldValue((long) (i + 1))); // 5. KEY_SEQ |
| 2517 | + row.add(createNullFieldValue()); // 6. PK_NAME |
| 2518 | + collectedResults.add(FieldValueList.of(row, resultSchemaFields)); |
| 2519 | + } |
| 2520 | + } |
| 2521 | + |
| 2522 | + private Comparator<FieldValueList> defineGetPrimaryKeysComparator(FieldList resultSchemaFields) { |
| 2523 | + final int COLUMN_NAME_IDX = resultSchemaFields.getIndex("COLUMN_NAME"); |
| 2524 | + return Comparator.comparing( |
| 2525 | + (FieldValueList fvl) -> getStringValueOrNull(fvl, COLUMN_NAME_IDX), |
| 2526 | + Comparator.nullsFirst(String::compareTo)); |
2438 | 2527 | } |
2439 | 2528 |
|
2440 | 2529 | @Override |
@@ -4950,4 +5039,117 @@ private void writeErrorToQueue(BlockingQueue<BigQueryFieldValueListWrapper> queu |
4950 | 5039 | } |
4951 | 5040 | } |
4952 | 5041 | } |
| 5042 | + |
| 5043 | + private List<DatasetId> getTargetDatasets(String catalog, String schema) throws SQLException { |
| 5044 | + if (schema == null) { |
| 5045 | + List<Dataset> datasets = fetchMatchingDatasets(catalog, null, null); |
| 5046 | + List<DatasetId> datasetIds = new ArrayList<>(datasets.size()); |
| 5047 | + for (Dataset dataset : datasets) { |
| 5048 | + datasetIds.add(dataset.getDatasetId()); |
| 5049 | + } |
| 5050 | + return datasetIds; |
| 5051 | + } |
| 5052 | + |
| 5053 | + if (schema.isEmpty()) { |
| 5054 | + return Collections.emptyList(); |
| 5055 | + } |
| 5056 | + |
| 5057 | + List<String> projects = resolveTargetProjects(catalog); |
| 5058 | + List<DatasetId> datasetIds = new ArrayList<>(projects.size()); |
| 5059 | + for (String project : projects) { |
| 5060 | + datasetIds.add(DatasetId.of(project, schema)); |
| 5061 | + } |
| 5062 | + return datasetIds; |
| 5063 | + } |
| 5064 | + |
| 5065 | + private List<String> resolveTargetProjects(String catalog) throws SQLException { |
| 5066 | + return (catalog != null) ? Collections.singletonList(catalog) : getAccessibleCatalogNames(); |
| 5067 | + } |
| 5068 | + |
| 5069 | + @FunctionalInterface |
| 5070 | + private interface TableProcessor { |
| 5071 | + void process(Table bqTable, List<FieldValueList> collectedResults, FieldList resultSchemaFields) |
| 5072 | + throws SQLException; |
| 5073 | + } |
| 5074 | + |
| 5075 | + private void processSingleTable( |
| 5076 | + DatasetId datasetId, |
| 5077 | + String tableName, |
| 5078 | + List<FieldValueList> collectedResults, |
| 5079 | + FieldList resultSchemaFields, |
| 5080 | + boolean ignoreAccessErrors, |
| 5081 | + TableProcessor processor) |
| 5082 | + throws SQLException { |
| 5083 | + Table bqTable; |
| 5084 | + try { |
| 5085 | + bqTable = |
| 5086 | + bigquery.getTable(TableId.of(datasetId.getProject(), datasetId.getDataset(), tableName)); |
| 5087 | + } catch (BigQueryException e) { |
| 5088 | + if (ignoreAccessErrors && (e.getCode() == 404 || e.getCode() == 403)) { |
| 5089 | + LOG.info( |
| 5090 | + "Table '%s' or dataset '%s' not found/accessible in project '%s' (API error %d). Skipping.", |
| 5091 | + tableName, datasetId.getDataset(), datasetId.getProject(), e.getCode()); |
| 5092 | + bqTable = null; |
| 5093 | + } else { |
| 5094 | + throw new SQLException("Error while fetching table metadata: " + e.getMessage(), e); |
| 5095 | + } |
| 5096 | + } catch (Exception e) { |
| 5097 | + throw new SQLException("Error while fetching table metadata: " + e.getMessage(), e); |
| 5098 | + } |
| 5099 | + if (bqTable != null && bqTable.getDefinition() != null) { |
| 5100 | + processor.process(bqTable, collectedResults, resultSchemaFields); |
| 5101 | + } |
| 5102 | + } |
| 5103 | + |
| 5104 | + private void processTargetTablesConcurrently( |
| 5105 | + List<DatasetId> targetDatasets, |
| 5106 | + String tableName, |
| 5107 | + List<FieldValueList> collectedResults, |
| 5108 | + FieldList resultSchemaFields, |
| 5109 | + boolean ignoreAccessErrors, |
| 5110 | + TableProcessor processor) |
| 5111 | + throws SQLException { |
| 5112 | + if (targetDatasets.size() == 1) { |
| 5113 | + processSingleTable( |
| 5114 | + targetDatasets.get(0), |
| 5115 | + tableName, |
| 5116 | + collectedResults, |
| 5117 | + resultSchemaFields, |
| 5118 | + ignoreAccessErrors, |
| 5119 | + processor); |
| 5120 | + return; |
| 5121 | + } |
| 5122 | + |
| 5123 | + ExecutorService executor = connection.getMetadataExecutor(); |
| 5124 | + List<Future<?>> taskFutures = new ArrayList<>(); |
| 5125 | + |
| 5126 | + try { |
| 5127 | + for (DatasetId datasetId : targetDatasets) { |
| 5128 | + taskFutures.add( |
| 5129 | + executor.submit( |
| 5130 | + () -> { |
| 5131 | + processSingleTable( |
| 5132 | + datasetId, |
| 5133 | + tableName, |
| 5134 | + collectedResults, |
| 5135 | + resultSchemaFields, |
| 5136 | + ignoreAccessErrors, |
| 5137 | + processor); |
| 5138 | + return null; |
| 5139 | + })); |
| 5140 | + } |
| 5141 | + waitForTasksCompletion(taskFutures); |
| 5142 | + if (Thread.currentThread().isInterrupted()) { |
| 5143 | + throw new SQLException("Interrupted while parallel-fetching metadata"); |
| 5144 | + } |
| 5145 | + } catch (ExecutionException e) { |
| 5146 | + Throwable cause = e.getCause(); |
| 5147 | + if (cause instanceof SQLException) { |
| 5148 | + throw (SQLException) cause; |
| 5149 | + } |
| 5150 | + throw new SQLException("Error while fetching metadata", e); |
| 5151 | + } finally { |
| 5152 | + taskFutures.forEach(future -> future.cancel(true)); |
| 5153 | + } |
| 5154 | + } |
4953 | 5155 | } |
0 commit comments