diff --git a/src/main/java/org/duckdb/DuckDBResultSetMetaData.java b/src/main/java/org/duckdb/DuckDBResultSetMetaData.java index 9d468b08c..7366ebbc1 100644 --- a/src/main/java/org/duckdb/DuckDBResultSetMetaData.java +++ b/src/main/java/org/duckdb/DuckDBResultSetMetaData.java @@ -243,6 +243,10 @@ protected static String type_to_javaString(DuckDBColumnType type) { return LinkedHashMap.class.getName(); case STRUCT: return DuckDBStruct.class.getName(); + case UNION: + case VARIANT: + // UNION and VARIANT columns hold values of dynamic type, so only Object is a safe common supertype + return Object.class.getName(); default: return String.class.getName(); } diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index a2aeb750b..f78de4b47 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -1450,7 +1450,14 @@ public static void test_getColumnClassName() throws Exception { for (int i = 1; i <= rsmd.getColumnCount(); i++) { Object value = rs.getObject(i); - assertEquals(rsmd.getColumnClassName(i), value.getClass().getName()); + String typeName = rsmd.getColumnTypeName(i); + if (typeName.startsWith("UNION") || typeName.equals("VARIANT")) { + // UNION and VARIANT values have a dynamic runtime type, so the metadata can only report + // their common supertype; for those columns it must report exactly java.lang.Object. + assertEquals(Object.class.getName(), rsmd.getColumnClassName(i)); + } else { + assertEquals(rsmd.getColumnClassName(i), value.getClass().getName()); + } } } } diff --git a/src/test/java/org/duckdb/TestMetadata.java b/src/test/java/org/duckdb/TestMetadata.java index 19d764aaf..8e864241b 100644 --- a/src/test/java/org/duckdb/TestMetadata.java +++ b/src/test/java/org/duckdb/TestMetadata.java @@ -1145,4 +1145,62 @@ public static void test_metadata_get_udts() throws Exception { } } } + + public static void test_metadata_union_class() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE metadata_union_rows (u UNION(a INTEGER, b VARCHAR), s VARCHAR, j JSON)"); + stmt.execute("INSERT INTO metadata_union_rows VALUES (union_value(a := 1), 'plain', JSON '{\"a\": 1}'), " + + "(union_value(b := 'abc'), 'other', JSON '{\"b\": 2}'), (NULL, NULL, NULL)"); + try (ResultSet rs = stmt.executeQuery("SELECT u, s, j FROM metadata_union_rows")) { + ResultSetMetaData meta = rs.getMetaData(); + assertTrue(meta.getColumnTypeName(1).startsWith("UNION")); + assertEquals(meta.getColumnType(1), Types.OTHER); + assertEquals(meta.getColumnClassName(1), Object.class.getName()); + // neighboring VARCHAR/JSON columns must keep their dedicated Java classes + assertEquals(meta.getColumnType(2), Types.VARCHAR); + assertEquals(meta.getColumnClassName(2), String.class.getName()); + assertEquals(meta.getColumnClassName(3), JsonNode.class.getName()); + + int rows = 0; + while (rs.next()) { + rows++; + Object unionValue = rs.getObject(1); + if (unionValue != null) { + assertTrue(Class.forName(meta.getColumnClassName(1)).isInstance(unionValue)); + } + } + assertEquals(rows, 3); + } + } + } + + public static void test_metadata_variant_class() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT 'foo'::VARCHAR::VARIANT AS col1, 'plain' AS col2, " + + "JSON '{\"a\": 1}' AS col3" + + " UNION ALL " + + "SELECT NULL::VARIANT AS col1, NULL AS col2, NULL AS col3" + + " UNION ALL " + + "SELECT 42::INTEGER::VARIANT AS col1, 'other' AS col2, " + + "JSON '{\"b\": 2}' AS col3")) { + ResultSetMetaData meta = rs.getMetaData(); + assertEquals(meta.getColumnTypeName(1), "VARIANT"); + assertEquals(meta.getColumnType(1), Types.OTHER); + assertEquals(meta.getColumnClassName(1), Object.class.getName()); + // neighboring VARCHAR/JSON columns must keep their dedicated Java classes + assertEquals(meta.getColumnType(2), Types.VARCHAR); + assertEquals(meta.getColumnClassName(2), String.class.getName()); + assertEquals(meta.getColumnClassName(3), JsonNode.class.getName()); + + int rows = 0; + while (rs.next()) { + rows++; + Object variantValue = rs.getObject(1); + if (variantValue != null) { + assertTrue(Class.forName(meta.getColumnClassName(1)).isInstance(variantValue)); + } + } + assertEquals(rows, 3); + } + } } diff --git a/src/test/java/org/duckdb/TestParameterMetadata.java b/src/test/java/org/duckdb/TestParameterMetadata.java index 34413fd91..88c39b0e2 100644 --- a/src/test/java/org/duckdb/TestParameterMetadata.java +++ b/src/test/java/org/duckdb/TestParameterMetadata.java @@ -130,7 +130,20 @@ public static void test_parameter_metadata_union() throws Exception { try (PreparedStatement ps = conn.prepareStatement("INSERT INTO metadata_test_union_1 VALUES(?)")) { ParameterMetaData meta = ps.getParameterMetaData(); assertEquals(meta.getParameterTypeName(1), "UNION(num INTEGER, str VARCHAR)"); - assertEquals(meta.getParameterClassName(1), String.class.getName()); + assertEquals(meta.getParameterClassName(1), Object.class.getName()); + assertEquals(meta.getPrecision(1), 0); + assertEquals(meta.getScale(1), 0); + } + } + } + + public static void test_parameter_metadata_variant() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE metadata_test_variant_1 (col1 VARIANT)"); + try (PreparedStatement ps = conn.prepareStatement("INSERT INTO metadata_test_variant_1 VALUES(?)")) { + ParameterMetaData meta = ps.getParameterMetaData(); + assertEquals(meta.getParameterTypeName(1), "VARIANT"); + assertEquals(meta.getParameterClassName(1), Object.class.getName()); assertEquals(meta.getPrecision(1), 0); assertEquals(meta.getScale(1), 0); }