diff --git a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java index 5ff1e3bc5..47aa5eada 100644 --- a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java +++ b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java @@ -2,16 +2,9 @@ import static java.lang.System.lineSeparator; -import java.sql.Connection; -import java.sql.DatabaseMetaData; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.RowIdLifetime; -import java.sql.SQLException; -import java.sql.SQLFeatureNotSupportedException; -import java.sql.Statement; -import java.sql.Types; +import java.sql.*; import java.util.Arrays; +import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -1109,7 +1102,62 @@ public ResultSet getCrossReference(String parentCatalog, String parentSchema, St @Override public ResultSet getTypeInfo() throws SQLException { - throw new SQLFeatureNotSupportedException("getTypeInfo"); + List entries = Arrays.asList(new TypeInfoEntry("BOOLEAN", Types.BIT, typePredBasic), + new TypeInfoEntry("TINYINT", Types.TINYINT, typePredBasic), + new TypeInfoEntry("SMALLINT", Types.SMALLINT, typePredBasic), + new TypeInfoEntry("INTEGER", Types.INTEGER, typePredBasic), + new TypeInfoEntry("BIGINT", Types.BIGINT, typePredBasic), + new TypeInfoEntry("BIGINT", Types.BIGINT, typePredBasic), + new TypeInfoEntry("FLOAT", Types.FLOAT, typePredBasic), + new TypeInfoEntry("REAL", Types.REAL, typePredBasic), + new TypeInfoEntry("DOUBLE", Types.DOUBLE, typePredBasic), + new TypeInfoEntry("DECIMAL", Types.NUMERIC, typePredBasic, 0, 38), + new TypeInfoEntry("DECIMAL", Types.DECIMAL, typePredBasic, 0, 38), + new TypeInfoEntry("VARCHAR", Types.CHAR, typePredChar), + new TypeInfoEntry("VARCHAR", Types.VARCHAR, typePredChar), + new TypeInfoEntry("VARCHAR", Types.LONGVARCHAR, typePredChar), + new TypeInfoEntry("DATE", Types.DATE, typePredBasic), + new TypeInfoEntry("TIME", Types.TIME, typePredBasic), + new TypeInfoEntry("TIMESTAMP", Types.TIMESTAMP, typePredBasic), + new TypeInfoEntry("BLOB", Types.BINARY, typePredChar), + new TypeInfoEntry("BLOB", Types.VARBINARY, typePredChar), + new TypeInfoEntry("BLOB", Types.LONGVARBINARY, typePredChar), + new TypeInfoEntry("NULL", Types.LONGVARBINARY, typePredBasic)); + + StringBuilder sb = new StringBuilder(QUERY_SB_DEFAULT_CAPACITY); + boolean first = true; + for (TypeInfoEntry en : entries) { + if (first) { + sb.append("SELECT").append(lineSeparator()); + first = false; + } else { + sb.append("UNION ALL SELECT").append(lineSeparator()); + } + sb.append(" '" + en.name + "'::VARCHAR AS TYPE_NAME").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" " + en.sqlType + "::INTEGER AS DATA_TYPE").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" 0::INTEGER AS PRECISION").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" NULL::VARCHAR AS LITERAL_PREFIX").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" NULL::VARCHAR AS LITERAL_SUFFIX").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" NULL::VARCHAR AS CREATE_PARAMS").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" " + typeNullableUnknown + "::SMALLINT AS NULLABLE") + .append(TRAILING_COMMA) + .append(lineSeparator()); + sb.append(" TRUE::BOOL AS CASE_SENSITIVE").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" " + en.searchable + "::SMALLINT AS SEARCHABLE").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" FALSE::BOOL AS UNSIGNED_ATTRIBUTE").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" FALSE::BOOL AS FIXED_PREC_SCALE").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" FALSE::BOOL AS AUTO_INCREMENT").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" NULL::VARCHAR AS LOCAL_TYPE_NAME").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" 0::SMALLINT AS MINIMUM_SCALE").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" 0::SMALLINT AS MAXIMUM_SCALE").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" 0::INTEGER AS SQL_DATA_TYPE").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" 0::INTEGER AS SQL_DATETIME_SUB").append(TRAILING_COMMA).append(lineSeparator()); + sb.append(" 10::INTEGER AS NUM_PREC_RADIX").append(TRAILING_COMMA).append(lineSeparator()); + } + + PreparedStatement ps = conn.prepareStatement(sb.toString()); + ps.closeOnCompletion(); + return ps.executeQuery(); } @Override @@ -1492,4 +1540,28 @@ private static String nullPatternToWildcard(String pattern) { } return pattern; } + + private static class TypeInfoEntry { + final String name; + final int sqlType; + final int searchable; + final int precision; + final int scale; + + private TypeInfoEntry(String name, int sqlType, int searchable) { + this.name = name; + this.sqlType = sqlType; + this.searchable = searchable; + this.precision = 0; + this.scale = 0; + } + + public TypeInfoEntry(String name, int sqlType, int searchable, int precision, int scale) { + this.name = name; + this.sqlType = sqlType; + this.searchable = searchable; + this.precision = precision; + this.scale = scale; + } + } } diff --git a/src/main/java/org/duckdb/DuckDBDriver.java b/src/main/java/org/duckdb/DuckDBDriver.java index ae33189b1..e045d14f7 100644 --- a/src/main/java/org/duckdb/DuckDBDriver.java +++ b/src/main/java/org/duckdb/DuckDBDriver.java @@ -110,6 +110,9 @@ public Connection connect(String url, Properties info) throws SQLException { // to be established. props.remove("path"); + // LibreOffice Base adds this option with value 'simple' + props.remove("Type"); + // DuckLake connection if (pp.shortUrl.startsWith(DUCKLAKE_URL_PREFIX)) { setDefaultOptionValue(props, JDBC_PIN_DB, true); diff --git a/src/test/java/org/duckdb/TestMetadata.java b/src/test/java/org/duckdb/TestMetadata.java index cf6c7d4b8..c0e7b9cf9 100644 --- a/src/test/java/org/duckdb/TestMetadata.java +++ b/src/test/java/org/duckdb/TestMetadata.java @@ -1015,4 +1015,15 @@ public static void test_medatada_cross_reference() throws Exception { } } } + + public static void test_metadata_type_info() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL); ResultSet rs = conn.getMetaData().getTypeInfo()) { + // static table, not produced by engine, only checking the count + int count = 0; + while (rs.next()) { + count += 1; + } + assertEquals(count, 21); + } + } }