diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index d9f75dd83f..978a8fd637 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -23,6 +23,7 @@ - Fixed volume operations not completing unless the ResultSet is fully iterated. - Fixed `connection.getMetadata().getColumns()` to return the correct SQL data type code for complex type columns. - Fixed a bug in the JDBC driver's metadata parsing for nested decimal fields within struct types. -- Fix case sensitive table search in `connection.getMetadata().getTables()` +- Fixed case sensitive table search in `connection.getMetadata().getTables()` +- Fixed `connection.getMetadata().getColumns()` to return the correct scale. --- *Note: When making changes, please add your change under the appropriate section with a brief description.* diff --git a/src/main/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilder.java b/src/main/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilder.java index 5eee378a6e..e386b18c8f 100644 --- a/src/main/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilder.java +++ b/src/main/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilder.java @@ -2,6 +2,7 @@ import static com.databricks.jdbc.common.MetadataResultConstants.*; import static com.databricks.jdbc.common.util.DatabricksTypeUtil.INTERVAL; +import static com.databricks.jdbc.common.util.WildcardUtil.isNullOrEmpty; import static com.databricks.jdbc.dbclient.impl.common.CommandConstants.*; import static com.databricks.jdbc.dbclient.impl.common.TypeValConstants.*; @@ -212,8 +213,13 @@ List> getRows( } else { object = "NO"; } - } else if (mappedColumn.getColumnName().equals(DECIMAL_DIGITS_COLUMN.getColumnName()) - || mappedColumn.getColumnName().equals(NUM_PREC_RADIX_COLUMN.getColumnName())) { + } else if (mappedColumn + .getColumnName() + .equals(DECIMAL_DIGITS_COLUMN.getColumnName())) { + object = getUpdatedDecimalDigits(stripBaseTypeName(typeVal), object); + } else if (mappedColumn + .getColumnName() + .equals(NUM_PREC_RADIX_COLUMN.getColumnName())) { if (object == null) { object = 0; } @@ -424,6 +430,37 @@ int getBufferLength(String typeVal) { return getSizeInBytes(sqlType); } + /** + * Overrides DECIMAL_DIGITS value for specific data types. Returns non-zero only for DECIMAL, + * NUMERIC, TIMESTAMP, and TIMESTAMP_NTZ. + * + * @param baseTypeVal the column type name + * @param scaleObject the original scale value (can be null) + * @return scale value for DECIMAL/NUMERIC, 9 for TIMESTAMP types, 0 otherwise + * @example + *
+   * getUpdatedDecimalDigits("DECIMAL", 2) → 2
+   * getUpdatedDecimalDigits("TIMESTAMP", 6) → 9
+   * getUpdatedDecimalDigits("FLOAT", 7) → 0
+   * 
+ */ + int getUpdatedDecimalDigits(String baseTypeVal, Object scaleObject) { + if (scaleObject == null) { + return 0; + } + int scale = (int) scaleObject; + if (isNullOrEmpty(baseTypeVal)) { + return 0; + } + if (baseTypeVal.contains(DECIMAL_TYPE) || baseTypeVal.contains(NUMERIC_TYPE)) { + return scale; + } + if (baseTypeVal.contains(TIMESTAMP_TYPE) || baseTypeVal.contains(TIMESTAMP_NTZ_TYPE)) { + return 9; + } + return 0; + } + /** * Extracts the character octet length from a given SQL type definition. For example, for input * "VARCHAR(100)", it returns 100. For inputs without a specified length or invalid inputs, it @@ -891,8 +928,10 @@ List> getThriftRows(List> rows, List col object = "YES"; } } - if (column.getColumnName().equals(DECIMAL_DIGITS_COLUMN.getColumnName()) - || column.getColumnName().equals(NUM_PREC_RADIX_COLUMN.getColumnName())) { + if (column.getColumnName().equals(DECIMAL_DIGITS_COLUMN.getColumnName())) { + object = getUpdatedDecimalDigits(stripBaseTypeName(typeVal), object); + } + if (column.getColumnName().equals(NUM_PREC_RADIX_COLUMN.getColumnName())) { if (object == null) { object = 0; } diff --git a/src/main/java/com/databricks/jdbc/dbclient/impl/common/TypeValConstants.java b/src/main/java/com/databricks/jdbc/dbclient/impl/common/TypeValConstants.java index 52be4245c1..2991d068bf 100644 --- a/src/main/java/com/databricks/jdbc/dbclient/impl/common/TypeValConstants.java +++ b/src/main/java/com/databricks/jdbc/dbclient/impl/common/TypeValConstants.java @@ -8,6 +8,7 @@ public class TypeValConstants { static final String BOOLEAN_TYPE = "BOOLEAN"; static final String DATE_TYPE = "DATE"; static final String TIMESTAMP_TYPE = "TIMESTAMP"; + static final String TIMESTAMP_NTZ_TYPE = "TIMESTAMP_NTZ"; static final String DECIMAL_TYPE = "DECIMAL"; static final String BINARY_TYPE = "BINARY"; static final String ARRAY_TYPE = "ARRAY"; diff --git a/src/test/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilderTest.java b/src/test/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilderTest.java index 01951eef56..4a6f3af07a 100644 --- a/src/test/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilderTest.java +++ b/src/test/java/com/databricks/jdbc/dbclient/impl/common/MetadataResultSetBuilderTest.java @@ -531,4 +531,66 @@ void testComplexTypesReturnActualCodesWhenSupportEnabled() throws SQLException { assertEquals(1111, variantRow.get(0)); assertEquals(1111, variantRow.get(2)); } + + @Test + void testDecimalDigitsColumnInGetThriftRows() { + List columns = Arrays.asList(COLUMN_TYPE_COLUMN, DECIMAL_DIGITS_COLUMN); + + List> rows = + Arrays.asList( + Arrays.asList("DECIMAL(10,2)", 2), + Arrays.asList("TIMESTAMP", 6), + Arrays.asList("INT", 0), + Arrays.asList("VARCHAR(100)", 0), + Arrays.asList("DECIMAL(15,5)", 5), + Arrays.asList("TIMESTAMP_NTZ", 6), + Arrays.asList("DECIMAL", 0)); + + List> updatedRows = metadataResultSetBuilder.getThriftRows(rows, columns); + + assertEquals(2, updatedRows.get(0).get(1), "DECIMAL(10,2) should have scale 2"); + assertEquals(9, updatedRows.get(1).get(1), "TIMESTAMP should have scale 9"); + assertEquals(0, updatedRows.get(2).get(1), "INT should have scale 0"); + assertEquals(0, updatedRows.get(3).get(1), "VARCHAR should have scale 0"); + assertEquals(5, updatedRows.get(4).get(1), "DECIMAL(15,5) should have scale 5"); + assertEquals(9, updatedRows.get(5).get(1), "TIMESTAMP_NTZ should have scale 9"); + assertEquals(0, updatedRows.get(6).get(1), "DECIMAL should have scale 0"); + } + + private static Stream provideDecimalDigitsArguments() { + return Stream.of( + Arguments.of("DECIMAL(10,2)", 2, 2, "DECIMAL(10,2) should have scale 2"), + Arguments.of("TIMESTAMP", 6, 9, "TIMESTAMP should have scale 9"), + Arguments.of("INT", 0, 0, "INT should have scale 0"), + Arguments.of("VARCHAR(100)", 0, 0, "VARCHAR should have scale 0"), + Arguments.of("DECIMAL(15,5)", 5, 5, "DECIMAL(15,5) should have scale 5"), + Arguments.of("TIMESTAMP_NTZ", 6, 9, "TIMESTAMP_NTZ should have scale 9"), + Arguments.of("DECIMAL", 0, 0, "DECIMAL should have scale 0")); + } + + @ParameterizedTest + @MethodSource("provideDecimalDigitsArguments") + void testDecimalDigitsColumnInGetRows( + String typeName, int inputScale, int expectedScale, String message) throws SQLException { + DatabricksResultSet resultSet = mock(DatabricksResultSet.class); + when(resultSet.next()).thenReturn(true).thenReturn(false); + + for (ResultColumn resultColumn : COLUMN_COLUMNS) { + if (resultColumn.getResultSetColumnName().equals("SQLDataType")) { + // Special handling begins from SQLDataType columns onward; getObject is no longer invoked. + break; + } + when(resultSet.getObject(resultColumn.getResultSetColumnName())).thenReturn(null); + } + when(resultSet.getObject(IS_NULLABLE_COLUMN.getResultSetColumnName())).thenReturn("true"); + when(resultSet.getString(COLUMN_TYPE_COLUMN.getResultSetColumnName())).thenReturn(typeName); + when(resultSet.getObject(DECIMAL_DIGITS_COLUMN.getResultSetColumnName())) + .thenReturn(inputScale); + + List> rows = + metadataResultSetBuilder.getRows( + resultSet, COLUMN_COLUMNS, new DefaultDatabricksResultSetAdapter()); + + assertEquals(expectedScale, rows.get(0).get(8), message); + } }