From 0e57e02c7a65cf41a3981ae096bba15fdf3f11e4 Mon Sep 17 00:00:00 2001 From: Algebrazebra Date: Sun, 12 Apr 2026 10:28:09 +0200 Subject: [PATCH 1/3] Implement getUDTs method --- .../org/duckdb/DuckDBDatabaseMetaData.java | 56 +++++++++++++++- src/test/java/org/duckdb/TestMetadata.java | 65 +++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java index 1fc7fdd2b..1e37be066 100644 --- a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java +++ b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java @@ -1283,7 +1283,61 @@ public boolean supportsBatchUpdates() throws SQLException { @Override public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) throws SQLException { - throw new SQLFeatureNotSupportedException("getUDTs"); + String udtDataTypeExpr = + "CASE WHEN logical_type IN ('STRUCT', 'UNION') THEN " + Types.STRUCT + " ELSE " + Types.DISTINCT + " END"; + String baseTypeExpr = "CASE WHEN logical_type IN ('STRUCT', 'UNION') THEN NULL::SMALLINT " + + "WHEN logical_type = 'ENUM' THEN " + Types.VARCHAR + "::SMALLINT " + + "ELSE CAST(CASE logical_type " + dataMap + " ELSE " + Types.OTHER + + " END AS SMALLINT) END"; + + StringBuilder sb = new StringBuilder(QUERY_SB_DEFAULT_CAPACITY); + sb.append("SELECT").append(lineSeparator()); + sb.append("database_name AS 'TYPE_CAT'").append(TRAILING_COMMA).append(lineSeparator()); + sb.append("schema_name AS 'TYPE_SCHEM'").append(TRAILING_COMMA).append(lineSeparator()); + sb.append("type_name AS 'TYPE_NAME'").append(TRAILING_COMMA).append(lineSeparator()); + sb.append("NULL::VARCHAR AS 'CLASS_NAME'").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(udtDataTypeExpr).append(" AS 'DATA_TYPE'").append(TRAILING_COMMA).append(lineSeparator()); + sb.append("comment AS 'REMARKS'").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(baseTypeExpr).append(" AS 'BASE_TYPE'").append(lineSeparator()); + sb.append("FROM duckdb_types()").append(lineSeparator()); + sb.append("WHERE internal = FALSE").append(lineSeparator()); + boolean hasCatalogParam = appendEqualsQual(sb, "database_name", catalog); + boolean hasSchemaParam = appendLikeQual(sb, "schema_name", schemaPattern); + sb.append("AND type_name LIKE ? ESCAPE '\\'").append(lineSeparator()); + + if (types != null && types.length > 0) { + sb.append("AND (").append(udtDataTypeExpr).append(") IN ("); + for (int i = 0; i < types.length; i++) { + if (i > 0) { + sb.append(','); + } + sb.append('?'); + } + sb.append(')').append(lineSeparator()); + } + + sb.append("ORDER BY").append(lineSeparator()); + sb.append("\"DATA_TYPE\"").append(TRAILING_COMMA).append(lineSeparator()); + sb.append("\"TYPE_CAT\"").append(TRAILING_COMMA).append(lineSeparator()); + sb.append("\"TYPE_SCHEM\"").append(TRAILING_COMMA).append(lineSeparator()); + sb.append("\"TYPE_NAME\"").append(lineSeparator()); + + PreparedStatement ps = conn.prepareStatement(sb.toString()); + int paramIdx = 1; + if (hasCatalogParam) { + ps.setString(paramIdx++, catalog); + } + if (hasSchemaParam) { + ps.setString(paramIdx++, schemaPattern); + } + ps.setString(paramIdx++, nullPatternToWildcard(typeNamePattern)); + if (types != null) { + for (int type : types) { + ps.setInt(paramIdx++, type); + } + } + ps.closeOnCompletion(); + return ps.executeQuery(); } @Override diff --git a/src/test/java/org/duckdb/TestMetadata.java b/src/test/java/org/duckdb/TestMetadata.java index c5c9220ec..6afce04ce 100644 --- a/src/test/java/org/duckdb/TestMetadata.java +++ b/src/test/java/org/duckdb/TestMetadata.java @@ -1052,4 +1052,69 @@ public static void test_metadata_system_columns() throws Exception { assertTrue(count > 0); } } + + public static void test_metadata_get_udts() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TYPE mood AS ENUM ('sad','ok','happy')"); + stmt.execute("CREATE TYPE many_things AS STRUCT(name VARCHAR, age INTEGER)"); + stmt.execute("CREATE TYPE one_thing AS UNION(a INTEGER, b VARCHAR)"); + stmt.execute("CREATE TYPE x_index AS INTEGER"); + + DatabaseMetaData dbMetaData = conn.getMetaData(); + Map dataTypes = new HashMap<>(); + Map baseTypes = new HashMap<>(); + try (ResultSet rs = dbMetaData.getUDTs(null, null, "%", null)) { + ResultSetMetaData rsMetaData = rs.getMetaData(); + assertEquals(rsMetaData.getColumnName(1), "TYPE_CAT"); + assertEquals(rsMetaData.getColumnName(2), "TYPE_SCHEM"); + assertEquals(rsMetaData.getColumnName(3), "TYPE_NAME"); + assertEquals(rsMetaData.getColumnName(4), "CLASS_NAME"); + assertEquals(rsMetaData.getColumnName(5), "DATA_TYPE"); + assertEquals(rsMetaData.getColumnName(6), "REMARKS"); + assertEquals(rsMetaData.getColumnName(7), "BASE_TYPE"); + + while (rs.next()) { + dataTypes.put(rs.getString("TYPE_NAME"), rs.getInt("DATA_TYPE")); + int baseType = rs.getInt("BASE_TYPE"); + if (rs.wasNull()) { + baseTypes.put(rs.getString("TYPE_NAME"), null); + } else { + baseTypes.put(rs.getString("TYPE_NAME"), baseType); + } + } + } + + assertEquals(dataTypes.get("mood"), Types.DISTINCT); + assertEquals(dataTypes.get("many_things"), Types.STRUCT); + assertEquals(dataTypes.get("one_thing"), Types.STRUCT); + assertEquals(dataTypes.get("x_index"), Types.DISTINCT); + + assertEquals(baseTypes.get("mood"), Types.VARCHAR); + assertNull(baseTypes.get("many_things")); + assertNull(baseTypes.get("one_thing")); + assertEquals(baseTypes.get("x_index"), Types.INTEGER); + + try (ResultSet rs = dbMetaData.getUDTs(null, null, "m%", new int[] {Types.DISTINCT})) { + Set names = new HashSet<>(); + while (rs.next()) { + names.add(rs.getString("TYPE_NAME")); + assertEquals(rs.getInt("DATA_TYPE"), Types.DISTINCT); + } + assertTrue(names.contains("mood")); + assertTrue(names.contains("x_index")); + assertEquals(names.size(), 2); + } + + try (ResultSet rs = dbMetaData.getUDTs(null, null, "%", new int[] {Types.STRUCT})) { + Set names = new HashSet<>(); + while (rs.next()) { + names.add(rs.getString("TYPE_NAME")); + assertEquals(rs.getInt("DATA_TYPE"), Types.STRUCT); + } + assertTrue(names.contains("many_things")); + assertTrue(names.contains("one_thing")); + assertEquals(names.size(), 2); + } + } + } } From 15506a213e4be9b9ef84d4c66316433d49e424c0 Mon Sep 17 00:00:00 2001 From: Algebrazebra Date: Sun, 12 Apr 2026 13:39:25 +0200 Subject: [PATCH 2/3] Fix formatting to follow clang format --- src/main/java/org/duckdb/DuckDBDatabaseMetaData.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java index 1e37be066..36a6e9f1c 100644 --- a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java +++ b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java @@ -1287,8 +1287,8 @@ public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePa "CASE WHEN logical_type IN ('STRUCT', 'UNION') THEN " + Types.STRUCT + " ELSE " + Types.DISTINCT + " END"; String baseTypeExpr = "CASE WHEN logical_type IN ('STRUCT', 'UNION') THEN NULL::SMALLINT " + "WHEN logical_type = 'ENUM' THEN " + Types.VARCHAR + "::SMALLINT " - + "ELSE CAST(CASE logical_type " + dataMap + " ELSE " + Types.OTHER - + " END AS SMALLINT) END"; + + "ELSE CAST(CASE logical_type " + dataMap + " ELSE " + Types.OTHER + + " END AS SMALLINT) END"; StringBuilder sb = new StringBuilder(QUERY_SB_DEFAULT_CAPACITY); sb.append("SELECT").append(lineSeparator()); From 9a83454101f28f04601813514f72202e2078bc09 Mon Sep 17 00:00:00 2001 From: Algebrazebra Date: Sun, 12 Apr 2026 14:28:29 +0200 Subject: [PATCH 3/3] fixup! Implement getUDTs method --- src/test/java/org/duckdb/TestMetadata.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/test/java/org/duckdb/TestMetadata.java b/src/test/java/org/duckdb/TestMetadata.java index 6afce04ce..96f2650be 100644 --- a/src/test/java/org/duckdb/TestMetadata.java +++ b/src/test/java/org/duckdb/TestMetadata.java @@ -1101,8 +1101,7 @@ public static void test_metadata_get_udts() throws Exception { assertEquals(rs.getInt("DATA_TYPE"), Types.DISTINCT); } assertTrue(names.contains("mood")); - assertTrue(names.contains("x_index")); - assertEquals(names.size(), 2); + assertEquals(names.size(), 1); } try (ResultSet rs = dbMetaData.getUDTs(null, null, "%", new int[] {Types.STRUCT})) {