Skip to content

Commit 3ef8603

Browse files
authored
Fix DatabaseMetaData.getColumns() returning type name in COLUMN_DEF for columns with no default (#1270)
## Summary - `COLUMN_DEF` (column 13 of `getColumns()`) was returning the column's **type name** instead of `null` for columns with no default value, violating the JDBC spec. - Root cause: `COLUMN_DEF_COLUMN` was mapped to the `"columnType"` result-set field, so both the SQL path (default switch case) and the Thrift path (explicit `case "COLUMN_DEF"`) returned the type name. - Neither `SHOW COLUMNS` nor the Thrift `TGetColumns` RPC exposes column default values, so `COLUMN_DEF` must always be `null` for now. - Fix: added `COLUMN_DEF_COLUMN` to `NULL_COLUMN_COLUMNS` (handles the Thrift path) and added an explicit `case "COLUMN_DEF": object = null` in `getRows()` (handles the SQL path). ## Test plan - added uts - Tested with legacy driver too : we return null only if it is null, we don't have a way to determine if default has been set ``` Driver: DatabricksJDBC v02.07.06.1023 id: TYPE_NAME=INT, COLUMN_DEF=null name: TYPE_NAME=STRING, COLUMN_DEF=null description: TYPE_NAME=VARCHAR, COLUMN_DEF=null status: TYPE_NAME=STRING, COLUMN_DEF=null ← has DEFAULT 'active' score: TYPE_NAME=INT, COLUMN_DEF=null ← has DEFAULT 0 ``` Test : ``` String catalog = "samikshya-catalog"; String schema = "newschema"; String table = "simba_test_column_def"; con.createStatement().execute( "CREATE TABLE IF NOT EXISTS `" + catalog + "`.`" + schema + "`.`" + table + "`" + " (id INT NOT NULL, name STRING NOT NULL, description VARCHAR(255)," + " status STRING NOT NULL DEFAULT 'active', score INT DEFAULT 0)" + " TBLPROPERTIES('delta.feature.allowColumnDefaults' = 'supported')"); try (ResultSet rs = con.getMetaData().getColumns(catalog, schema, table, null)) { while (rs.next()) { System.out.println(rs.getString("COLUMN_NAME") + ": TYPE_NAME=" + rs.getString("TYPE_NAME") + ", COLUMN_DEF=" + rs.getString("COLUMN_DEF")); } } con.createStatement().execute("DROP TABLE IF EXISTS `" + catalog + "`.`" + schema + "`.`" + table + "`"); con.close(); } } ``` Closes #1267 --------- Signed-off-by: samikshya-chand_data <samikshya.chand@databricks.com>
1 parent 93cf304 commit 3ef8603

3 files changed

Lines changed: 65 additions & 3 deletions

File tree

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
- Fixed `fetchAutoCommitStateFromServer()` to accept both `"1"`/`"0"` and `"true"`/`"false"` responses from `SET AUTOCOMMIT` query, since different server implementations return different formats.
1919
- Fixed socket leak in SDK HTTP client that prevented CRaC checkpointing. The SDK's connection pool was not shut down on `connection.close()`, leaving TCP sockets open.
2020
- Fixed Date fields within complex types (ARRAY, STRUCT, MAP) being returned as epoch day integers instead of proper date values.
21+
- Fixed `DatabaseMetaData.getColumns()` returning the column type name in `COLUMN_DEF` for columns with no default value. `COLUMN_DEF` now correctly returns `null` per the JDBC specification.
2122

2223
---
2324
*Note: When making changes, please add your change under the appropriate section

src/main/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilder.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -684,6 +684,10 @@ List<List<Object>> getRows(
684684
object = null;
685685
}
686686
break;
687+
case "COLUMN_DEF":
688+
// SHOW COLUMNS does not expose column default values; return null per JDBC spec
689+
object = null;
690+
break;
687691
default:
688692
// If column does not match any of the special cases, try to get it from the ResultSet
689693
try {
@@ -1412,9 +1416,6 @@ List<List<Object>> getThriftRows(List<List<Object>> rows, List<ResultColumn> col
14121416
int ordinalPositionIndex = columns.indexOf(ORDINAL_POSITION_COLUMN);
14131417
object = (int) row.get(ordinalPositionIndex) + 1; // 1-based index
14141418
break;
1415-
case "COLUMN_DEF":
1416-
object = row.get(columns.indexOf(COLUMN_TYPE_COLUMN));
1417-
break;
14181419
default:
14191420
int index = columns.indexOf(column);
14201421
if (index >= row.size()) { // index out of bound (eg: IS_GENERATED_COL in getColumns)

src/test/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilderTest.java

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -766,4 +766,64 @@ void shouldDenyCatalogAccessWhenCatalogDiffers() throws SQLException {
766766
assertFalse(metadataResultSetBuilder.shouldAllowCatalogAccess("other", null, session));
767767
verify(session).getCurrentCatalog();
768768
}
769+
770+
@ParameterizedTest
771+
@MethodSource("provideColumnDefTypeNames")
772+
void testColumnDefIsNullInGetRows(String typeName) throws SQLException {
773+
// COLUMN_DEF (index 12 in COLUMN_COLUMNS) must always be null per JDBC spec when no default
774+
// is defined; SHOW COLUMNS does not expose column defaults.
775+
DatabricksResultSet resultSet = mock(DatabricksResultSet.class);
776+
when(resultSet.next()).thenReturn(true).thenReturn(false);
777+
for (ResultColumn resultColumn : COLUMN_COLUMNS) {
778+
if (resultColumn.getResultSetColumnName().equals("SQLDataType")) {
779+
break;
780+
}
781+
when(resultSet.getObject(resultColumn.getResultSetColumnName())).thenReturn(null);
782+
}
783+
when(resultSet.getString(COLUMN_TYPE_COLUMN.getResultSetColumnName())).thenReturn(typeName);
784+
when(resultSet.getObject(IS_NULLABLE_COLUMN.getResultSetColumnName())).thenReturn("true");
785+
786+
List<List<Object>> rows =
787+
metadataResultSetBuilder.getRows(
788+
resultSet, COLUMN_COLUMNS, new DefaultDatabricksResultSetAdapter());
789+
790+
assertNull(rows.get(0).get(12), "COLUMN_DEF should be null when no default is defined");
791+
}
792+
793+
@ParameterizedTest
794+
@MethodSource("provideColumnDefTypeNames")
795+
void testColumnDefIsNullInGetThriftRowsWhenNoDefault(String typeName) {
796+
// COLUMN_DEF (index 12) must be null when the server returns null for that column.
797+
List<Object> row =
798+
Arrays.asList(
799+
"cat", "schema", "tbl", "col", 4, typeName, 10, null, 0, 10, 1, "", null, null, null,
800+
null, 0, "YES", null, null, null, null, "NO", "NO");
801+
List<List<Object>> updatedRows =
802+
metadataResultSetBuilder.getThriftRows(List.of(row), COLUMN_COLUMNS);
803+
804+
assertNull(updatedRows.get(0).get(12), "COLUMN_DEF should be null when no default is defined");
805+
}
806+
807+
@Test
808+
void testColumnDefIsPreservedInGetThriftRowsWhenDefaultExists() {
809+
// COLUMN_DEF (index 12) must return the actual default value from the Thrift response.
810+
List<Object> row =
811+
Arrays.asList(
812+
"cat", "schema", "tbl", "col", 4, "INT", 10, null, 0, 10, 1, "", "'42'", null, null,
813+
null, 0, "YES", null, null, null, null, "NO", "NO");
814+
List<List<Object>> updatedRows =
815+
metadataResultSetBuilder.getThriftRows(List.of(row), COLUMN_COLUMNS);
816+
817+
assertEquals(
818+
"'42'", updatedRows.get(0).get(12), "COLUMN_DEF should return the actual default value");
819+
}
820+
821+
private static Stream<Arguments> provideColumnDefTypeNames() {
822+
return Stream.of(
823+
Arguments.of("INT"),
824+
Arguments.of("STRING"),
825+
Arguments.of("VARCHAR(255)"),
826+
Arguments.of("DECIMAL(10,2)"),
827+
Arguments.of("TIMESTAMP"));
828+
}
769829
}

0 commit comments

Comments
 (0)