Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEXT_CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
- `DatabaseMetaData.getColumns(...)` with a `null` catalog now issues a single `SHOW COLUMNS IN ALL CATALOGS` statement (consistent with `getSchemas`/`getTables`) instead of enumerating every catalog and issuing a per-catalog `SHOW COLUMNS`. Older DBR versions that do not support the syntax transparently fall back to the previous enumerate-and-fan-out behavior.

### Fixed
- Fixed `DatabaseMetaData.getTables(...)` treating an empty `types` array (`new String[]{}`) as "match no table types" (returning zero rows). Per the JDBC contract, an empty or `null` type list carries no type constraint and now matches all table types, identical to passing `null`. Affects both the SEA and Thrift metadata paths.

- Fixed `IdleConnectionEvictor` thread leak in long-running applications. Driver-side resources (HTTP client, background threads) are now always released when `Connection.close()` is called, even if statement cleanup or server-side session termination fails.

- Throw `DatabricksSQLException` instead of an unchecked `ClassCastException` when a complex-type getter (`getArray`, `getStruct`, `getMap`) is called on a column of a different complex type.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,9 +141,10 @@ public DatabricksResultSet listTables(
String tableNamePattern,
String[] tableTypes)
throws SQLException {
// Per JDBC spec: null types = return all types; empty array = return nothing
// Per JDBC spec: a null or empty types list carries no type constraint and matches all table
// types. Normalize an empty array to null so it behaves identically to null (match-all).
if (tableTypes != null && tableTypes.length == 0) {
return metadataResultSetBuilder.getTablesResult(catalog, tableTypes, new ArrayList<>());
tableTypes = null;
}
String[] validatedTableTypes = tableTypes != null ? tableTypes : DEFAULT_TABLE_TYPES;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -512,9 +512,10 @@ public DatabricksResultSet listTables(
session.toString(), catalog, schemaNamePattern, tableNamePattern);
LOGGER.debug(context);

// Per JDBC spec: null types = return all types; empty array = return nothing
// Per JDBC spec: a null or empty types list carries no type constraint and matches all table
// types. Normalize an empty array to null so it behaves identically to null (match-all).
if (tableTypes != null && tableTypes.length == 0) {
return metadataResultSetBuilder.getTablesResult(catalog, tableTypes, new ArrayList<>());
tableTypes = null;
}

if (!metadataResultSetBuilder.shouldAllowCatalogAccess(catalog, null, session)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.lenient;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand Down Expand Up @@ -592,18 +591,34 @@ void testListTables() throws SQLException {
}

@Test
void testListTablesWithEmptyTypesReturnsEmptyWithoutServerCall() throws SQLException {
// Per JDBC spec: empty types array means "no types selected" → return no rows.
// The driver must short-circuit and NOT send the Thrift request to the server.
void testListTablesWithEmptyTypesMatchesAll() throws SQLException {
// Per the JDBC DatabaseMetaData.getTables contract, an empty types array carries no type
// constraint and must match ALL table types, identical to passing null. The driver must
// therefore query the server (with no table-type filter set on the request), not short-circuit.
DatabricksThriftServiceClient client =
new DatabricksThriftServiceClient(thriftAccessor, connectionContext);
when(session.getSessionInfo()).thenReturn(SESSION_INFO);
client.setServerProtocolVersion(TProtocolVersion.SPARK_CLI_SERVICE_PROTOCOL_V1);

TFetchResultsResp response =
new TFetchResultsResp()
.setStatus(new TStatus().setStatusCode(TStatusCode.SUCCESS_STATUS))
.setResults(resultData)
.setResultSetMetadata(resultMetadataData);
TColumn tColumn = new TColumn();
tColumn.setStringVal(new TStringColumn().setValues(Collections.singletonList("")));
when(resultData.getColumns()).thenReturn(List.of(tColumn, tColumn, tColumn, tColumn));
when(thriftAccessor.getThriftResponse(any())).thenReturn(response);

DatabricksResultSet resultSet =
client.listTables(session, TEST_CATALOG, TEST_SCHEMA, TEST_TABLE, new String[0]);

assertEquals(StatementState.SUCCEEDED, resultSet.getStatementStatus().getState());
assertFalse(resultSet.next(), "Empty types array must yield zero rows");
verify(thriftAccessor, never()).getThriftResponse(any());
ArgumentCaptor<TGetTablesReq> captor = ArgumentCaptor.forClass(TGetTablesReq.class);
verify(thriftAccessor).getThriftResponse(captor.capture());
assertFalse(
captor.getValue().isSetTableTypes(),
"Empty types array must not set a table-type filter on the request (match-all)");
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,34 @@ void testTableInformation() throws SQLException {
deleteTable(connection, tableName);
}

@Test
void testGetTablesEmptyTypesMatchesAll() throws SQLException {
// Per the JDBC DatabaseMetaData.getTables contract, an empty types[] array carries no type
// constraint and must match ALL table types, identical to passing null.
DatabaseMetaData metaData = connection.getMetaData();
String catalog = getDatabricksCatalog();
String schema = getDatabricksSchema();
String tableName = "empty_types_match_all_test_table";
setupDatabaseTable(connection, tableName);
try {
// Sanity check: null types (match-all) lists the table.
try (ResultSet tablesNull = metaData.getTables(catalog, schema, tableName, null)) {
assertTrue(
tablesNull.next(),
"null types should match all table types and list the created table");
}
// Empty types[] must behave identically to null (match-all), not match-none.
try (ResultSet tablesEmpty =
metaData.getTables(catalog, schema, tableName, new String[] {})) {
assertTrue(
tablesEmpty.next(),
"empty types[] should match all table types and list the created table");
}
} finally {
deleteTable(connection, tableName);
}
}

@Test
void testTableInformationExactMatch() throws SQLException {
DatabaseMetaData metaData = connection.getMetaData();
Expand Down
Loading