diff --git a/NEXT_CHANGELOG.md b/NEXT_CHANGELOG.md index 897b76bc04..c9b4090810 100644 --- a/NEXT_CHANGELOG.md +++ b/NEXT_CHANGELOG.md @@ -19,5 +19,6 @@ - Integrated Azure U2M flow into driver for improved stability. - Fixed `ResultSet.getString` for Boolean columns in Metadata result set. - 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. --- *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/api/impl/DatabricksResultSetMetaData.java b/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaData.java index 3afc6e383f..d7b4383ff0 100644 --- a/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaData.java +++ b/src/main/java/com/databricks/jdbc/api/impl/DatabricksResultSetMetaData.java @@ -108,7 +108,7 @@ public DatabricksResultSetMetaData( .columnTypeClassName(DatabricksTypeUtil.getColumnTypeClassName(columnTypeName)) .columnType(columnType) .columnTypeText( - metadataResultSetBuilder.stripTypeName( + metadataResultSetBuilder.stripBaseTypeName( columnInfo .getTypeText())) // store base type eg. DECIMAL instead of DECIMAL(7,2) .typePrecision(precision) 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 6db7d66644..5eee378a6e 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 @@ -184,14 +184,20 @@ List> getRows( if (typeVal == null) { // safety check object = null; } else { - object = getCode(stripTypeName(typeVal)); + // Check if complex datatype support is disabled and this is a complex type + if (!ctx.isComplexDatatypeSupportEnabled() && isComplexType(typeVal)) { + object = Types.VARCHAR; + } else { + object = getCode(stripBaseTypeName(typeVal)); + } } break; case "SQL_DATETIME_SUB": // check if typeVal is a date/time related field if (typeVal != null - && (typeVal.contains(DATE_TYPE) || typeVal.contains(TIMESTAMP_TYPE))) { - object = getCode(stripTypeName(typeVal)); + && (stripBaseTypeName(typeVal).contains(DATE_TYPE) + || stripBaseTypeName(typeVal).contains(TIMESTAMP_TYPE))) { + object = getCode(stripBaseTypeName(typeVal)); } else { object = null; } @@ -218,7 +224,11 @@ List> getRows( } } catch (SQLException e) { if (mappedColumn.getColumnName().equals(DATA_TYPE_COLUMN.getColumnName())) { - object = getCode(stripTypeName(typeVal)); + if (!ctx.isComplexDatatypeSupportEnabled() && isComplexType(typeVal)) { + object = Types.VARCHAR; + } else { + object = getCode(stripBaseTypeName(typeVal)); + } } else if (mappedColumn .getColumnName() .equals(CHAR_OCTET_LENGTH_COLUMN.getColumnName())) { @@ -252,11 +262,12 @@ List> getRows( if (mappedColumn.getColumnName().equals(COLUMN_TYPE_COLUMN.getColumnName())) { if (typeVal != null && (typeVal.contains(ARRAY_TYPE) + || typeVal.contains(MAP_TYPE) || typeVal.contains( - MAP_TYPE))) { // for complex data types, do not strip type name + STRUCT_TYPE))) { // for complex data types, do not strip type name object = typeVal; } else { - object = stripTypeName(typeVal); + object = stripBaseTypeName(typeVal); } } // Set COLUMN_SIZE to 255 if it's not present @@ -339,7 +350,7 @@ int getColumnSize(String typeVal) { if (isTextType(typeVal)) { return ctx.getDefaultStringColumnLength(); } - String typeName = stripTypeName(typeVal); + String typeName = stripBaseTypeName(typeVal); switch (typeName) { case "DECIMAL": case "NUMERIC": @@ -403,13 +414,13 @@ int getBufferLength(String typeVal) { if (typeVal == null || typeVal.isEmpty()) { return 0; } - if (typeVal.contains("ARRAY") || typeVal.contains("MAP")) { + if (typeVal.contains("ARRAY") || typeVal.contains("MAP") || typeVal.contains("STRUCT")) { return 255; } if (isTextType(typeVal)) { return getColumnSize(typeVal); } - int sqlType = getCode(stripTypeName(typeVal)); + int sqlType = getCode(stripBaseTypeName(typeVal)); return getSizeInBytes(sqlType); } @@ -480,6 +491,22 @@ public String stripBaseTypeName(String typeName) { return typeName; } + /** + * Checks if the given type string represents a complex type (ARRAY, MAP, or STRUCT). + * + * @param typeVal The type string to check + * @return true if the type is a complex type, false otherwise + */ + private boolean isComplexType(String typeVal) { + if (typeVal == null) { + return false; + } + String baseType = stripBaseTypeName(typeVal); + return baseType.contains(ARRAY_TYPE) + || baseType.contains(MAP_TYPE) + || baseType.contains(STRUCT_TYPE); + } + int getCode(String s) { switch (s) { case "STRING": @@ -809,30 +836,36 @@ List> getThriftRows(List> rows, List col List> updatedRows = new ArrayList<>(); for (List row : rows) { List updatedRow = new ArrayList<>(); + String typeVal = null; + int col_type_index = columns.indexOf(COLUMN_TYPE_COLUMN); // only relevant for getColumns + if (col_type_index != -1) { + typeVal = (String) row.get(col_type_index); + } for (ResultColumn column : columns) { if (NULL_COLUMN_COLUMNS.contains(column) || NULL_TABLE_COLUMNS.contains(column)) { updatedRow.add(null); continue; } Object object; - String typeVal = null; - int col_type_index = columns.indexOf(COLUMN_TYPE_COLUMN); // only relevant for getColumns - if (col_type_index != -1) { - typeVal = (String) row.get(col_type_index); - } switch (column.getColumnName()) { case "SQL_DATA_TYPE": if (typeVal == null) { // safety check object = null; } else { - object = getCode(stripTypeName(typeVal)); + // Check if complex datatype support is disabled and this is a complex type + if (!ctx.isComplexDatatypeSupportEnabled() && isComplexType(typeVal)) { + object = Types.VARCHAR; + } else { + object = getCode(stripBaseTypeName(typeVal)); + } } break; case "SQL_DATETIME_SUB": // check if typeVal is a date/time related field if (typeVal != null - && (typeVal.contains(DATE_TYPE) || typeVal.contains(TIMESTAMP_TYPE))) { - object = getCode(stripTypeName(typeVal)); + && (stripBaseTypeName(typeVal).contains(DATE_TYPE) + || stripBaseTypeName(typeVal).contains(TIMESTAMP_TYPE))) { + object = getCode(stripBaseTypeName(typeVal)); } else { object = null; } @@ -870,7 +903,12 @@ List> getThriftRows(List> rows, List col } } if (column.getColumnName().equals(DATA_TYPE_COLUMN.getColumnName())) { - object = getCode(stripTypeName(typeVal)); + // Check if complex datatype support is disabled and this is a complex type + if (!ctx.isComplexDatatypeSupportEnabled() && isComplexType(typeVal)) { + object = Types.VARCHAR; + } else { + object = getCode(stripBaseTypeName(typeVal)); + } } if (column.getColumnName().equals(CHAR_OCTET_LENGTH_COLUMN.getColumnName())) { object = getCharOctetLength(typeVal); @@ -889,10 +927,12 @@ List> getThriftRows(List> rows, List col // Handle TYPE_NAME separately for potential modifications if (column.getColumnName().equals(COLUMN_TYPE_COLUMN.getColumnName())) { if (typeVal != null - && (typeVal.contains(ARRAY_TYPE) || typeVal.contains(MAP_TYPE))) { + && (typeVal.contains(ARRAY_TYPE) + || typeVal.contains(MAP_TYPE) + || typeVal.contains(STRUCT_TYPE))) { object = typeVal; } else { - object = stripTypeName(typeVal); + object = stripBaseTypeName(typeVal); } } // Set COLUMN_SIZE to 255 if it's not present 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 465a94edfd..01951eef56 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 @@ -467,4 +467,68 @@ void testGetTablesResultWithNullTableType() throws SQLException { } assertEquals(2, rowCount); } + + @Test + void testComplexTypesReturnVarcharWhenSupportDisabled() throws SQLException { + when(connectionContext.isComplexDatatypeSupportEnabled()).thenReturn(false); + + List columns = + Arrays.asList(DATA_TYPE_COLUMN, COLUMN_TYPE_COLUMN, SQL_DATA_TYPE_COLUMN); + List> rows = + Arrays.asList( + Arrays.asList(2003, "ARRAY", 2003), + Arrays.asList(2002, "MAP", 2002), + Arrays.asList(2002, "STRUCT", 2002), + Arrays.asList(1111, "VARIANT", 1111)); + + List> updatedRows = metadataResultSetBuilder.getThriftRows(rows, columns); + + List arrayRow = updatedRows.get(0); + assertEquals(12, arrayRow.get(0)); + assertEquals(12, arrayRow.get(2)); + + List mapRow = updatedRows.get(1); + assertEquals(12, mapRow.get(0)); + assertEquals(12, mapRow.get(2)); + + List structRow = updatedRows.get(2); + assertEquals(12, structRow.get(0)); + assertEquals(12, structRow.get(2)); + + List variantRow = updatedRows.get(3); + assertEquals(1111, variantRow.get(0)); + assertEquals(1111, variantRow.get(2)); + } + + @Test + void testComplexTypesReturnActualCodesWhenSupportEnabled() throws SQLException { + when(connectionContext.isComplexDatatypeSupportEnabled()).thenReturn(true); + + List columns = + Arrays.asList(DATA_TYPE_COLUMN, COLUMN_TYPE_COLUMN, SQL_DATA_TYPE_COLUMN); + List> rows = + Arrays.asList( + Arrays.asList(2003, "ARRAY", 2003), + Arrays.asList(2002, "MAP", 2002), + Arrays.asList(2002, "STRUCT", 2002), + Arrays.asList(1111, "VARIANT", 1111)); + + List> updatedRows = metadataResultSetBuilder.getThriftRows(rows, columns); + + List arrayRow = updatedRows.get(0); + assertEquals(2003, arrayRow.get(0)); + assertEquals(2003, arrayRow.get(2)); + + List mapRow = updatedRows.get(1); + assertEquals(2002, mapRow.get(0)); + assertEquals(2002, mapRow.get(2)); + + List structRow = updatedRows.get(2); + assertEquals(2002, structRow.get(0)); + assertEquals(2002, structRow.get(2)); + + List variantRow = updatedRows.get(3); + assertEquals(1111, variantRow.get(0)); + assertEquals(1111, variantRow.get(2)); + } }