From b416a2ef19df61b5dae9500ecde94c3430fc3ee7 Mon Sep 17 00:00:00 2001 From: jbs Date: Thu, 6 Jun 2024 09:26:26 -0300 Subject: [PATCH 1/6] Metadata functions in DuckDBDatabaseMetaData.java for schemaspy. --- .../org/duckdb/DuckDBDatabaseMetaData.java | 98 +++++++++++++++++-- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java index 37c6ed37..6cbaa9d1 100644 --- a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java +++ b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java @@ -171,27 +171,76 @@ public String getIdentifierQuoteString() throws SQLException { @Override public String getSQLKeywords() throws SQLException { - throw new SQLFeatureNotSupportedException("getSQLKeywords"); + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT keyword_name FROM duckdb_keywords()"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); } @Override public String getNumericFunctions() throws SQLException { - throw new SQLFeatureNotSupportedException("getNumericFunctions"); + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() " + + "WHERE parameter_types[1] ='DECIMAL'" + + "OR parameter_types[1] ='DOUBLE'" + + "OR parameter_types[1] ='SMALLINT'" + + "OR parameter_types[1] = 'BIGINT'"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); } @Override public String getStringFunctions() throws SQLException { - throw new SQLFeatureNotSupportedException("getStringFunctions"); + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] = 'VARCHAR'"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); } @Override public String getSystemFunctions() throws SQLException { - throw new SQLFeatureNotSupportedException("getSystemFunctions"); + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] IS NULL"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); } @Override public String getTimeDateFunctions() throws SQLException { - throw new SQLFeatureNotSupportedException("getTimeDateFunctions"); + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] LIKE 'TIME%'"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); } @Override @@ -979,7 +1028,44 @@ public ResultSet getTypeInfo() throws SQLException { @Override public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) throws SQLException { - throw new SQLFeatureNotSupportedException("getIndexInfo("); + StringBuilder sb = new StringBuilder(); + sb.append("SELECT database_name AS TABLE_CAT " + + ", schema_name AS TABLE_SCHEM " + + ", table_name AS TABLE_NAME " + + ", index_name AS INDEX_NAME " + + ", CASE WHEN is_unique THEN 0 ELSE 1 END AS NON_UNIQUE " + + ", NULL AS TYPE " + + ", NULL AS ORDINAL_POSITION " + + ", NULL AS COLUMN_NAME " + + ", NULL AS ASC_OR_DESC " + + ", NULL AS CARDINALITY " + + ", NULL AS PAGES " + + ", NULL AS FILTER_CONDITION " + + "FROM duckdb_indexes() WHERE TRUE AND "); + if (catalog != null) { + sb.append(" AND database_name = ?"); + } + if (schema != null) { + sb.append(" AND schema_name = ?"); + } + if (table != null) { + sb.append(" AND table_name = ?"); + } + sb.append(" ORDER BY TABLE_CAT, TABLE_SCHEM, TABLE_NAME, NON_UNIQUE, INDEX_NAME, ORDINAL_POSITION"); + PreparedStatement ps = conn.prepareStatement(sb.toString()); + int paramIndex = 1; + if (catalog != null) { + ps.setString(paramIndex++, catalog); + } + if (schema != null) { + ps.setString(paramIndex++, schema); + } + if (table != null) { + ps.setString(paramIndex++, table); + } + ps.closeOnCompletion(); + return ps.executeQuery(); + } @Override From f1049117713bcbd1b03b4388fb8222a9fe289a1b Mon Sep 17 00:00:00 2001 From: jbs Date: Thu, 6 Jun 2024 12:34:06 -0300 Subject: [PATCH 2/6] Extra AND. --- src/main/java/org/duckdb/DuckDBDatabaseMetaData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java index 6cbaa9d1..2a5d11ce 100644 --- a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java +++ b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java @@ -1041,7 +1041,7 @@ public ResultSet getIndexInfo(String catalog, String schema, String table, boole ", NULL AS CARDINALITY " + ", NULL AS PAGES " + ", NULL AS FILTER_CONDITION " + - "FROM duckdb_indexes() WHERE TRUE AND "); + "FROM duckdb_indexes() WHERE TRUE "); if (catalog != null) { sb.append(" AND database_name = ?"); } From 2f277ee3f170383804774600fa6155ff836ec279 Mon Sep 17 00:00:00 2001 From: jbs Date: Sat, 8 Jun 2024 20:37:40 -0300 Subject: [PATCH 3/6] tests: test_metadata_* --- src/test/java/org/duckdb/TestDuckDBJDBC.java | 74 ++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/src/test/java/org/duckdb/TestDuckDBJDBC.java b/src/test/java/org/duckdb/TestDuckDBJDBC.java index 4e418e13..ab1dd325 100644 --- a/src/test/java/org/duckdb/TestDuckDBJDBC.java +++ b/src/test/java/org/duckdb/TestDuckDBJDBC.java @@ -4306,6 +4306,80 @@ public static void test_column_metadata() throws Exception { } } + public static void test_metadata_get_sql_keywords() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL)) { + String rs = conn.getMetaData().getSQLKeywords(); + String[] keywords = rs.split(","); + List list = asList(keywords); + assertTrue(list.contains("select")); + assertTrue(list.contains("update")); + assertTrue(list.contains("delete")); + assertTrue(list.contains("drop")); + } + } + + public static void test_metadata_get_numeric_functions() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL)) { + String rs = conn.getMetaData().getNumericFunctions(); + // print out rs + String[] functions = rs.split(","); + List list = asList(functions); + assertTrue(list.contains("abs")); + assertTrue(list.contains("ceil")); + assertTrue(list.contains("floor")); + assertTrue(list.contains("round")); + } + } + + public static void test_metadata_get_string_functions() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL)) { + String rs = conn.getMetaData().getStringFunctions(); + String[] functions = rs.split(","); + List list = asList(functions); + assertTrue(list.contains("md5")); + assertTrue(list.contains("json_keys")); + assertTrue(list.contains("repeat")); + assertTrue(list.contains("from_base64")); + } + } + + public static void test_metadata_get_system_functions() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL)) { + String rs = conn.getMetaData().getSystemFunctions(); + String[] functions = rs.split(","); + List list = asList(functions); + assertTrue(list.contains("current_date")); + assertTrue(list.contains("now")); + } + } + + public static void test_metadata_get_time_date_functions() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL)) { + String rs = conn.getMetaData().getTimeDateFunctions(); + String[] functions = rs.split(","); + List list = asList(functions); + assertTrue(list.contains("day")); + assertTrue(list.contains("dayname")); + assertTrue(list.contains("timezone_hour")); + } + } + + public static void test_metadata_get_index_info() throws Exception { + try (Connection conn = DriverManager.getConnection(JDBC_URL)) { + try (Statement stmt = conn.createStatement()) { + stmt.execute("CREATE TABLE test (id INT PRIMARY KEY, ok INT)"); + stmt.execute("CREATE INDEX idx_test_ok ON test(ok)"); + } + + try (ResultSet rs = conn.getMetaData().getIndexInfo(null, null, "test", false, false)) { + assertTrue(rs.next()); + assertEquals(rs.getString("TABLE_NAME"), "test"); + assertEquals(rs.getString("INDEX_NAME"), "idx_test_ok"); + assertEquals(rs.getBoolean("NON_UNIQUE"), true); + } + } + } + public static void main(String[] args) throws Exception { System.exit(runTests(args, TestDuckDBJDBC.class, TestExtensionTypes.class)); } From 6c815a93f5e8ed09dafa6313d51ea6234e97dd4e Mon Sep 17 00:00:00 2001 From: jbs Date: Sun, 30 Jun 2024 17:30:25 -0300 Subject: [PATCH 4/6] vargarg handling of system functions. cf: https://github.com/duckdb/duckdb-java/pull/27 --- src/main/java/org/duckdb/DuckDBDatabaseMetaData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java index 2a5d11ce..764b8d3e 100644 --- a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java +++ b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java @@ -220,7 +220,7 @@ public String getSystemFunctions() throws SQLException { Statement statement = conn.createStatement(); statement.closeOnCompletion(); ResultSet rs = statement.executeQuery( - "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] IS NULL"); + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE length(parameter_types) = 0"); StringBuilder sb = new StringBuilder(); while (rs.next()) { sb.append(rs.getString(1)); From a7f28b5439c787c874769040ac75c173b5f692c3 Mon Sep 17 00:00:00 2001 From: jbs Date: Mon, 1 Jul 2024 10:27:38 -0300 Subject: [PATCH 5/6] Run src/main/java/org/duckdb/DuckDBDatabaseMetaData.java through clang-format --- .../org/duckdb/DuckDBDatabaseMetaData.java | 2691 +++++++++-------- 1 file changed, 1371 insertions(+), 1320 deletions(-) diff --git a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java index 764b8d3e..5c888542 100644 --- a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java +++ b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java @@ -1,7 +1,6 @@ package org.duckdb; -import javax.sql.rowset.CachedRowSet; -import javax.sql.rowset.RowSetProvider; +import static java.lang.System.lineSeparator; import java.sql.Connection; import java.sql.DatabaseMetaData; @@ -15,1325 +14,1377 @@ import java.util.Arrays; import java.util.Map; import java.util.stream.Collectors; - -import static java.lang.System.lineSeparator; +import javax.sql.rowset.CachedRowSet; +import javax.sql.rowset.RowSetProvider; public class DuckDBDatabaseMetaData implements DatabaseMetaData { - DuckDBConnection conn; - - public DuckDBDatabaseMetaData(DuckDBConnection conn) { - this.conn = conn; - } - - @Override - public T unwrap(Class iface) throws SQLException { - return JdbcUtils.unwrap(this, iface); - } - - @Override - public boolean isWrapperFor(Class iface) { - return iface.isInstance(this); - } - - @Override - public boolean allProceduresAreCallable() throws SQLException { - return false; - } - - @Override - public boolean allTablesAreSelectable() throws SQLException { - return true; - } - - @Override - public String getURL() throws SQLException { - return conn.url; - } - - @Override - public String getUserName() throws SQLException { - return ""; - } - - @Override - public boolean isReadOnly() throws SQLException { - return conn.isReadOnly(); - } - - @Override - public boolean nullsAreSortedHigh() throws SQLException { - return true; - } - - @Override - public boolean nullsAreSortedLow() throws SQLException { - return false; - } - - @Override - public boolean nullsAreSortedAtStart() throws SQLException { - return true; - } - - @Override - public boolean nullsAreSortedAtEnd() throws SQLException { - return false; - } - - @Override - public String getDatabaseProductName() throws SQLException { - return "DuckDB"; - } - - @Override - public String getDatabaseProductVersion() throws SQLException { - try (Statement s = conn.createStatement(); ResultSet rs = s.executeQuery("PRAGMA version")) { - rs.next(); - String result = rs.getString(1); - return result; - } - } - - @Override - public String getDriverName() throws SQLException { - return "DuckDBJ"; - } - - @Override - public String getDriverVersion() throws SQLException { - return "1.0"; - } - - @Override - public int getDriverMajorVersion() { - return 1; - } - - @Override - public int getDriverMinorVersion() { - return 0; - } - - @Override - public boolean usesLocalFiles() throws SQLException { - return true; - } - - @Override - public boolean usesLocalFilePerTable() throws SQLException { - return false; - } - - @Override - public boolean supportsMixedCaseIdentifiers() throws SQLException { - return true; - } - - @Override - public boolean storesUpperCaseIdentifiers() throws SQLException { - return false; - } - - @Override - public boolean storesLowerCaseIdentifiers() throws SQLException { - return false; - } - - @Override - public boolean storesMixedCaseIdentifiers() throws SQLException { - return true; - } - - @Override - public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { - return true; - } - - @Override - public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { - return false; - } - - @Override - public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { - return false; - } - - @Override - public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { - return true; - } - - @Override - public String getIdentifierQuoteString() throws SQLException { - return "\""; - } - - @Override - public String getSQLKeywords() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = statement.executeQuery( - "SELECT keyword_name FROM duckdb_keywords()"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getNumericFunctions() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = statement.executeQuery( - "SELECT DISTINCT function_name FROM duckdb_functions() " + - "WHERE parameter_types[1] ='DECIMAL'" + - "OR parameter_types[1] ='DOUBLE'" + - "OR parameter_types[1] ='SMALLINT'" + - "OR parameter_types[1] = 'BIGINT'"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getStringFunctions() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = statement.executeQuery( - "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] = 'VARCHAR'"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getSystemFunctions() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = statement.executeQuery( - "SELECT DISTINCT function_name FROM duckdb_functions() WHERE length(parameter_types) = 0"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getTimeDateFunctions() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = statement.executeQuery( - "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] LIKE 'TIME%'"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getSearchStringEscape() throws SQLException { - return null; - } - - @Override - public String getExtraNameCharacters() throws SQLException { - return ""; - } - - @Override - public boolean supportsAlterTableWithAddColumn() throws SQLException { - return true; - } - - @Override - public boolean supportsAlterTableWithDropColumn() throws SQLException { - return true; - } - - @Override - public boolean supportsColumnAliasing() throws SQLException { - return true; - } - - @Override - public boolean nullPlusNonNullIsNull() throws SQLException { - return true; - } - - @Override - public boolean supportsConvert() throws SQLException { - return false; - } - - @Override - public boolean supportsConvert(int fromType, int toType) throws SQLException { - return false; - } - - @Override - public boolean supportsTableCorrelationNames() throws SQLException { - return true; - } - - @Override - public boolean supportsDifferentTableCorrelationNames() throws SQLException { - return false; - } - - @Override - public boolean supportsExpressionsInOrderBy() throws SQLException { - return true; - } - - @Override - public boolean supportsOrderByUnrelated() throws SQLException { - return true; - } - - @Override - public boolean supportsGroupBy() throws SQLException { - return true; - } - - @Override - public boolean supportsGroupByUnrelated() throws SQLException { - return true; - } - - @Override - public boolean supportsGroupByBeyondSelect() throws SQLException { - return true; - } - - @Override - public boolean supportsLikeEscapeClause() throws SQLException { - return true; - } - - @Override - public boolean supportsMultipleResultSets() throws SQLException { - return false; - } - - @Override - public boolean supportsMultipleTransactions() throws SQLException { - return true; - } - - @Override - public boolean supportsNonNullableColumns() throws SQLException { - return true; - } - - @Override - public boolean supportsMinimumSQLGrammar() throws SQLException { - return true; - } - - @Override - public boolean supportsCoreSQLGrammar() throws SQLException { - return true; - } - - @Override - public boolean supportsExtendedSQLGrammar() throws SQLException { - return true; - } - - @Override - public boolean supportsANSI92EntryLevelSQL() throws SQLException { - return true; - } - - @Override - public boolean supportsANSI92IntermediateSQL() throws SQLException { - return true; - } - - @Override - public boolean supportsANSI92FullSQL() throws SQLException { - return true; - } - - @Override - public boolean supportsIntegrityEnhancementFacility() throws SQLException { - return false; - } - - @Override - public boolean supportsOuterJoins() throws SQLException { - return true; - } - - @Override - public boolean supportsFullOuterJoins() throws SQLException { - return true; - } - - @Override - public boolean supportsLimitedOuterJoins() throws SQLException { - return true; - } - - @Override - public String getSchemaTerm() throws SQLException { - return "schema"; - } - - @Override - public String getProcedureTerm() throws SQLException { - return "procedure"; - } - - @Override - public String getCatalogTerm() throws SQLException { - return "catalog"; - } - - @Override - public boolean isCatalogAtStart() throws SQLException { - return true; - } - - @Override - public String getCatalogSeparator() throws SQLException { - return "."; - } - - @Override - public boolean supportsSchemasInDataManipulation() throws SQLException { - return true; - } - - @Override - public boolean supportsSchemasInProcedureCalls() throws SQLException { - return true; - } - - @Override - public boolean supportsSchemasInTableDefinitions() throws SQLException { - return true; - } - - @Override - public boolean supportsSchemasInIndexDefinitions() throws SQLException { - return true; - } - - @Override - public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { - return false; - } - - @Override - public boolean supportsCatalogsInDataManipulation() throws SQLException { - return true; - } - - @Override - public boolean supportsCatalogsInProcedureCalls() throws SQLException { - return false; - } - - @Override - public boolean supportsCatalogsInTableDefinitions() throws SQLException { - return true; - } - - @Override - public boolean supportsCatalogsInIndexDefinitions() throws SQLException { - return true; - } - - @Override - public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { - return false; - } - - @Override - public boolean supportsPositionedDelete() throws SQLException { - return false; - } - - @Override - public boolean supportsPositionedUpdate() throws SQLException { - return false; - } - - @Override - public boolean supportsSelectForUpdate() throws SQLException { - return false; - } - - @Override - public boolean supportsStoredProcedures() throws SQLException { - return false; - } - - @Override - public boolean supportsSubqueriesInComparisons() throws SQLException { - return true; - } - - @Override - public boolean supportsSubqueriesInExists() throws SQLException { - return true; - } - - @Override - public boolean supportsSubqueriesInIns() throws SQLException { - return true; - } - - @Override - public boolean supportsSubqueriesInQuantifieds() throws SQLException { - return true; - } - - @Override - public boolean supportsCorrelatedSubqueries() throws SQLException { - return true; - } - - @Override - public boolean supportsUnion() throws SQLException { - return true; - } - - @Override - public boolean supportsUnionAll() throws SQLException { - return true; - } - - @Override - public boolean supportsOpenCursorsAcrossCommit() throws SQLException { - return false; - } - - @Override - public boolean supportsOpenCursorsAcrossRollback() throws SQLException { - return false; - } - - @Override - public boolean supportsOpenStatementsAcrossCommit() throws SQLException { - return false; - } - - @Override - public boolean supportsOpenStatementsAcrossRollback() throws SQLException { - return false; - } - - @Override - public int getMaxBinaryLiteralLength() throws SQLException { - - return 0; - } - - @Override - public int getMaxCharLiteralLength() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInGroupBy() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInIndex() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInOrderBy() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInSelect() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInTable() throws SQLException { - return 0; - } - - @Override - public int getMaxConnections() throws SQLException { - return 0; - } - - @Override - public int getMaxCursorNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxIndexLength() throws SQLException { - return 0; - } - - @Override - public int getMaxSchemaNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxProcedureNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxCatalogNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxRowSize() throws SQLException { - return 0; - } - - @Override - public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { - return false; - } - - @Override - public int getMaxStatementLength() throws SQLException { - return 0; - } - - @Override - public int getMaxStatements() throws SQLException { - return 0; - } - - @Override - public int getMaxTableNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxTablesInSelect() throws SQLException { - return 0; - } - - @Override - public int getMaxUserNameLength() throws SQLException { - return 0; - } - - @Override - public int getDefaultTransactionIsolation() throws SQLException { - return Connection.TRANSACTION_REPEATABLE_READ; - } - - @Override - public boolean supportsTransactions() throws SQLException { - return true; - } - - @Override - public boolean supportsTransactionIsolationLevel(int level) throws SQLException { - return level < Connection.TRANSACTION_SERIALIZABLE; - } - - @Override - public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException { - return true; - } - - @Override - public boolean supportsDataManipulationTransactionsOnly() throws SQLException { - return false; - } - - @Override - public boolean dataDefinitionCausesTransactionCommit() throws SQLException { - return false; - } - - @Override - public boolean dataDefinitionIgnoredInTransactions() throws SQLException { - return false; - } - - @Override - public ResultSet getCatalogs() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery( - "SELECT DISTINCT catalog_name AS 'TABLE_CAT' FROM information_schema.schemata ORDER BY \"TABLE_CAT\""); - } - - @Override - public ResultSet getSchemas() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery( - "SELECT schema_name AS 'TABLE_SCHEM', catalog_name AS 'TABLE_CATALOG' FROM information_schema.schemata ORDER BY \"TABLE_CATALOG\", \"TABLE_SCHEM\""); - } - - @Override - public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { - StringBuilder sb = new StringBuilder(512); - sb.append("SELECT schema_name AS 'TABLE_SCHEM', catalog_name AS 'TABLE_CATALOG'"); - sb.append(lineSeparator()); - sb.append("FROM information_schema.schemata"); - sb.append(lineSeparator()); - if (catalog != null || schemaPattern != null) { - sb.append("WHERE "); - } - - if (catalog != null) { - if (catalog.isEmpty()) { - sb.append("catalog_name IS NULL"); - } else { - sb.append("catalog_name = ?"); - } - sb.append(lineSeparator()); - } - if (schemaPattern != null) { - if (catalog != null) { - sb.append("AND "); - } - if (schemaPattern.isEmpty()) { - sb.append("schema_name IS NULL"); - } else { - sb.append("schema_name LIKE ?"); - } - sb.append(lineSeparator()); - } - sb.append("ORDER BY \"TABLE_CATALOG\", \"TABLE_SCHEM\""); - sb.append(lineSeparator()); - - PreparedStatement ps = conn.prepareStatement(sb.toString()); - int paramIndex = 0; - if (catalog != null && !catalog.isEmpty()) { - ps.setString(++paramIndex, catalog); - } - if (schemaPattern != null && !schemaPattern.isEmpty()) { - ps.setString(++paramIndex, schemaPattern); - } - ps.closeOnCompletion(); - return ps.executeQuery(); - } - - @Override - public ResultSet getTableTypes() throws SQLException { - String[] tableTypesArray = new String[] {"BASE TABLE", "LOCAL TEMPORARY", "VIEW"}; - StringBuilder stringBuilder = new StringBuilder(128); - boolean first = true; - for (String tableType : tableTypesArray) { - if (!first) { - stringBuilder.append("\nUNION ALL\n"); - } - stringBuilder.append("SELECT '"); - stringBuilder.append(tableType); - stringBuilder.append("'"); - if (first) { - stringBuilder.append(" AS 'TABLE_TYPE'"); - first = false; - } - } - stringBuilder.append("\nORDER BY TABLE_TYPE"); - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery(stringBuilder.toString()); - } - - @Override - public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) - throws SQLException { - StringBuilder str = new StringBuilder(512); - - str.append("SELECT table_catalog AS 'TABLE_CAT'").append(lineSeparator()); - str.append(", table_schema AS 'TABLE_SCHEM'").append(lineSeparator()); - str.append(", table_name AS 'TABLE_NAME'").append(lineSeparator()); - str.append(", table_type AS 'TABLE_TYPE'").append(lineSeparator()); - str.append(", TABLE_COMMENT AS 'REMARKS'").append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'TYPE_CAT'").append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'TYPE_SCHEM'").append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'TYPE_NAME'").append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'SELF_REFERENCING_COL_NAME'").append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'REF_GENERATION'").append(lineSeparator()); - str.append("FROM information_schema.tables").append(lineSeparator()); - - // tableNamePattern - a table name pattern; must match the table name as it is stored in the database - if (tableNamePattern == null) { - // non-standard behavior. - tableNamePattern = "%"; - } - str.append("WHERE table_name LIKE ?").append(lineSeparator()); - - // catalog - a catalog name; must match the catalog name as it is stored in the database; - // "" retrieves those without a catalog; - // null means that the catalog name should not be used to narrow the search - boolean hasCatalogParam = false; - if (catalog != null) { - str.append("AND table_catalog "); - if (catalog.isEmpty()) { - str.append("IS NULL").append(lineSeparator()); - } else { - str.append("= ?").append(lineSeparator()); - hasCatalogParam = true; - } - } - - // schemaPattern - a schema name pattern; must match the schema name as it is stored in the database; - // "" retrieves those without a schema; - // null means that the schema name should not be used to narrow the search - boolean hasSchemaParam = false; - if (schemaPattern != null) { - str.append("AND table_schema "); - if (schemaPattern.isEmpty()) { - str.append("IS NULL").append(lineSeparator()); - } else { - str.append("LIKE ?").append(lineSeparator()); - hasSchemaParam = true; - } - } - - if (types != null && types.length > 0) { - str.append("AND table_type IN (").append(lineSeparator()); - for (int i = 0; i < types.length; i++) { - if (i > 0) { - str.append(','); - } - str.append('?'); - } - str.append(')'); - } - - // ordered by TABLE_TYPE, TABLE_CAT, TABLE_SCHEM and TABLE_NAME. - str.append("ORDER BY table_type").append(lineSeparator()); - str.append(", table_catalog").append(lineSeparator()); - str.append(", table_schema").append(lineSeparator()); - str.append(", table_name").append(lineSeparator()); - - PreparedStatement ps = conn.prepareStatement(str.toString()); - - int paramOffset = 1; - ps.setString(paramOffset++, tableNamePattern); - - if (hasCatalogParam) { - ps.setString(paramOffset++, catalog); - } - if (hasSchemaParam) { - ps.setString(paramOffset++, schemaPattern); - } - - if (types != null && types.length > 0) { - for (int i = 0; i < types.length; i++) { - ps.setString(paramOffset + i, types[i]); - } - } - ps.closeOnCompletion(); - return ps.executeQuery(); - } - - @Override - public ResultSet getColumns(String catalogPattern, String schemaPattern, String tableNamePattern, - String columnNamePattern) throws SQLException { - if (catalogPattern == null) { - catalogPattern = "%"; - } - if (schemaPattern == null) { - schemaPattern = "%"; - } - if (tableNamePattern == null) { - tableNamePattern = "%"; - } - if (columnNamePattern == null) { - columnNamePattern = "%"; - } - - PreparedStatement ps = - conn.prepareStatement("SELECT " - + "table_catalog AS 'TABLE_CAT', " - + "table_schema AS 'TABLE_SCHEM', " - + "table_name AS 'TABLE_NAME', " - + "column_name as 'COLUMN_NAME', " + makeDataMap("regexp_replace(c.data_type, '\\(.*\\)', '')", "DATA_TYPE") + ", " - + "c.data_type AS 'TYPE_NAME', " - + "NULL AS 'COLUMN_SIZE', NULL AS 'BUFFER_LENGTH', " - + "numeric_precision AS 'DECIMAL_DIGITS', " - + "10 AS 'NUM_PREC_RADIX', " - + "CASE WHEN is_nullable = 'YES' THEN 1 else 0 END AS 'NULLABLE', " - + "COLUMN_COMMENT as 'REMARKS', " - + "column_default AS 'COLUMN_DEF', " - + "NULL AS 'SQL_DATA_TYPE', " - + "NULL AS 'SQL_DATETIME_SUB', " - + "NULL AS 'CHAR_OCTET_LENGTH', " - + "ordinal_position AS 'ORDINAL_POSITION', " - + "is_nullable AS 'IS_NULLABLE', " - + "NULL AS 'SCOPE_CATALOG', " - + "NULL AS 'SCOPE_SCHEMA', " - + "NULL AS 'SCOPE_TABLE', " - + "NULL AS 'SOURCE_DATA_TYPE', " - + "'' AS 'IS_AUTOINCREMENT', " - + "'' AS 'IS_GENERATEDCOLUMN' " - + "FROM information_schema.columns c " - + "WHERE table_catalog LIKE ? AND " - + "table_schema LIKE ? AND " - + "table_name LIKE ? AND " - + "column_name LIKE ? " - + "ORDER BY \"TABLE_CAT\",\"TABLE_SCHEM\", \"TABLE_NAME\", \"ORDINAL_POSITION\""); - ps.setString(1, catalogPattern); - ps.setString(2, schemaPattern); - ps.setString(3, tableNamePattern); - ps.setString(4, columnNamePattern); - ps.closeOnCompletion(); - return ps.executeQuery(); - } - - @Override - public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) - throws SQLException { - throw new SQLFeatureNotSupportedException("getColumnPrivileges"); - } - - @Override - public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) - throws SQLException { - throw new SQLFeatureNotSupportedException("getTablePrivileges"); - } - - @Override - public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) - throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery("SELECT NULL WHERE FALSE"); - } - - @Override - public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, - String columnNamePattern) throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery("SELECT NULL WHERE FALSE"); - } - - @Override - public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) - throws SQLException { - throw new SQLFeatureNotSupportedException("getBestRowIdentifier"); - } - - @Override - public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { - throw new SQLFeatureNotSupportedException("getVersionColumns"); - } - - @Override - public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { - StringBuilder pw = new StringBuilder(512); - pw.append("WITH constraint_columns AS (").append(lineSeparator()); - pw.append("SELECT").append(lineSeparator()); - pw.append(" database_name AS \"TABLE_CAT\"").append(lineSeparator()); - pw.append(", schema_name AS \"TABLE_SCHEM\"").append(lineSeparator()); - pw.append(", table_name AS \"TABLE_NAME\"").append(lineSeparator()); - pw.append(", unnest(constraint_column_names) AS \"COLUMN_NAME\"").append(lineSeparator()); - pw.append(", CAST(NULL AS VARCHAR) AS \"PK_NAME\"").append(lineSeparator()); - pw.append("FROM duckdb_constraints").append(lineSeparator()); - pw.append("WHERE constraint_type = 'PRIMARY KEY'").append(lineSeparator()); - // catalog param - if (catalog != null) { - if (catalog.isEmpty()) { - pw.append("AND database_name IS NULL").append(lineSeparator()); - } else { - pw.append("AND database_name = ?").append(lineSeparator()); - } - } - // schema param - if (schema != null) { - if (schema.isEmpty()) { - pw.append("AND schema_name IS NULL").append(lineSeparator()); - } else { - pw.append("AND schema_name = ?").append(lineSeparator()); - } + DuckDBConnection conn; + + public DuckDBDatabaseMetaData(DuckDBConnection conn) { this.conn = conn; } + + @Override + public T unwrap(Class iface) throws SQLException { + return JdbcUtils.unwrap(this, iface); + } + + @Override + public boolean isWrapperFor(Class iface) { + return iface.isInstance(this); + } + + @Override + public boolean allProceduresAreCallable() throws SQLException { + return false; + } + + @Override + public boolean allTablesAreSelectable() throws SQLException { + return true; + } + + @Override + public String getURL() throws SQLException { + return conn.url; + } + + @Override + public String getUserName() throws SQLException { + return ""; + } + + @Override + public boolean isReadOnly() throws SQLException { + return conn.isReadOnly(); + } + + @Override + public boolean nullsAreSortedHigh() throws SQLException { + return true; + } + + @Override + public boolean nullsAreSortedLow() throws SQLException { + return false; + } + + @Override + public boolean nullsAreSortedAtStart() throws SQLException { + return true; + } + + @Override + public boolean nullsAreSortedAtEnd() throws SQLException { + return false; + } + + @Override + public String getDatabaseProductName() throws SQLException { + return "DuckDB"; + } + + @Override + public String getDatabaseProductVersion() throws SQLException { + try (Statement s = conn.createStatement(); + ResultSet rs = s.executeQuery("PRAGMA version")) { + rs.next(); + String result = rs.getString(1); + return result; + } + } + + @Override + public String getDriverName() throws SQLException { + return "DuckDBJ"; + } + + @Override + public String getDriverVersion() throws SQLException { + return "1.0"; + } + + @Override + public int getDriverMajorVersion() { + return 1; + } + + @Override + public int getDriverMinorVersion() { + return 0; + } + + @Override + public boolean usesLocalFiles() throws SQLException { + return true; + } + + @Override + public boolean usesLocalFilePerTable() throws SQLException { + return false; + } + + @Override + public boolean supportsMixedCaseIdentifiers() throws SQLException { + return true; + } + + @Override + public boolean storesUpperCaseIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesLowerCaseIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesMixedCaseIdentifiers() throws SQLException { + return true; + } + + @Override + public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { + return true; + } + + @Override + public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { + return true; + } + + @Override + public String getIdentifierQuoteString() throws SQLException { + return "\""; + } + + @Override + public String getSQLKeywords() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = + statement.executeQuery("SELECT keyword_name FROM duckdb_keywords()"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getNumericFunctions() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() " + + "WHERE parameter_types[1] ='DECIMAL'" + + "OR parameter_types[1] ='DOUBLE'" + + "OR parameter_types[1] ='SMALLINT'" + + "OR parameter_types[1] = 'BIGINT'"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getStringFunctions() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] = 'VARCHAR'"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getSystemFunctions() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE length(parameter_types) = 0"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getTimeDateFunctions() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] LIKE 'TIME%'"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getSearchStringEscape() throws SQLException { + return null; + } + + @Override + public String getExtraNameCharacters() throws SQLException { + return ""; + } + + @Override + public boolean supportsAlterTableWithAddColumn() throws SQLException { + return true; + } + + @Override + public boolean supportsAlterTableWithDropColumn() throws SQLException { + return true; + } + + @Override + public boolean supportsColumnAliasing() throws SQLException { + return true; + } + + @Override + public boolean nullPlusNonNullIsNull() throws SQLException { + return true; + } + + @Override + public boolean supportsConvert() throws SQLException { + return false; + } + + @Override + public boolean supportsConvert(int fromType, int toType) throws SQLException { + return false; + } + + @Override + public boolean supportsTableCorrelationNames() throws SQLException { + return true; + } + + @Override + public boolean supportsDifferentTableCorrelationNames() throws SQLException { + return false; + } + + @Override + public boolean supportsExpressionsInOrderBy() throws SQLException { + return true; + } + + @Override + public boolean supportsOrderByUnrelated() throws SQLException { + return true; + } + + @Override + public boolean supportsGroupBy() throws SQLException { + return true; + } + + @Override + public boolean supportsGroupByUnrelated() throws SQLException { + return true; + } + + @Override + public boolean supportsGroupByBeyondSelect() throws SQLException { + return true; + } + + @Override + public boolean supportsLikeEscapeClause() throws SQLException { + return true; + } + + @Override + public boolean supportsMultipleResultSets() throws SQLException { + return false; + } + + @Override + public boolean supportsMultipleTransactions() throws SQLException { + return true; + } + + @Override + public boolean supportsNonNullableColumns() throws SQLException { + return true; + } + + @Override + public boolean supportsMinimumSQLGrammar() throws SQLException { + return true; + } + + @Override + public boolean supportsCoreSQLGrammar() throws SQLException { + return true; + } + + @Override + public boolean supportsExtendedSQLGrammar() throws SQLException { + return true; + } + + @Override + public boolean supportsANSI92EntryLevelSQL() throws SQLException { + return true; + } + + @Override + public boolean supportsANSI92IntermediateSQL() throws SQLException { + return true; + } + + @Override + public boolean supportsANSI92FullSQL() throws SQLException { + return true; + } + + @Override + public boolean supportsIntegrityEnhancementFacility() throws SQLException { + return false; + } + + @Override + public boolean supportsOuterJoins() throws SQLException { + return true; + } + + @Override + public boolean supportsFullOuterJoins() throws SQLException { + return true; + } + + @Override + public boolean supportsLimitedOuterJoins() throws SQLException { + return true; + } + + @Override + public String getSchemaTerm() throws SQLException { + return "schema"; + } + + @Override + public String getProcedureTerm() throws SQLException { + return "procedure"; + } + + @Override + public String getCatalogTerm() throws SQLException { + return "catalog"; + } + + @Override + public boolean isCatalogAtStart() throws SQLException { + return true; + } + + @Override + public String getCatalogSeparator() throws SQLException { + return "."; + } + + @Override + public boolean supportsSchemasInDataManipulation() throws SQLException { + return true; + } + + @Override + public boolean supportsSchemasInProcedureCalls() throws SQLException { + return true; + } + + @Override + public boolean supportsSchemasInTableDefinitions() throws SQLException { + return true; + } + + @Override + public boolean supportsSchemasInIndexDefinitions() throws SQLException { + return true; + } + + @Override + public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsCatalogsInDataManipulation() throws SQLException { + return true; + } + + @Override + public boolean supportsCatalogsInProcedureCalls() throws SQLException { + return false; + } + + @Override + public boolean supportsCatalogsInTableDefinitions() throws SQLException { + return true; + } + + @Override + public boolean supportsCatalogsInIndexDefinitions() throws SQLException { + return true; + } + + @Override + public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsPositionedDelete() throws SQLException { + return false; + } + + @Override + public boolean supportsPositionedUpdate() throws SQLException { + return false; + } + + @Override + public boolean supportsSelectForUpdate() throws SQLException { + return false; + } + + @Override + public boolean supportsStoredProcedures() throws SQLException { + return false; + } + + @Override + public boolean supportsSubqueriesInComparisons() throws SQLException { + return true; + } + + @Override + public boolean supportsSubqueriesInExists() throws SQLException { + return true; + } + + @Override + public boolean supportsSubqueriesInIns() throws SQLException { + return true; + } + + @Override + public boolean supportsSubqueriesInQuantifieds() throws SQLException { + return true; + } + + @Override + public boolean supportsCorrelatedSubqueries() throws SQLException { + return true; + } + + @Override + public boolean supportsUnion() throws SQLException { + return true; + } + + @Override + public boolean supportsUnionAll() throws SQLException { + return true; + } + + @Override + public boolean supportsOpenCursorsAcrossCommit() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenCursorsAcrossRollback() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossCommit() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossRollback() throws SQLException { + return false; + } + + @Override + public int getMaxBinaryLiteralLength() throws SQLException { + + return 0; + } + + @Override + public int getMaxCharLiteralLength() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInGroupBy() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInIndex() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInOrderBy() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInSelect() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInTable() throws SQLException { + return 0; + } + + @Override + public int getMaxConnections() throws SQLException { + return 0; + } + + @Override + public int getMaxCursorNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxIndexLength() throws SQLException { + return 0; + } + + @Override + public int getMaxSchemaNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxProcedureNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxCatalogNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxRowSize() throws SQLException { + return 0; + } + + @Override + public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { + return false; + } + + @Override + public int getMaxStatementLength() throws SQLException { + return 0; + } + + @Override + public int getMaxStatements() throws SQLException { + return 0; + } + + @Override + public int getMaxTableNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxTablesInSelect() throws SQLException { + return 0; + } + + @Override + public int getMaxUserNameLength() throws SQLException { + return 0; + } + + @Override + public int getDefaultTransactionIsolation() throws SQLException { + return Connection.TRANSACTION_REPEATABLE_READ; + } + + @Override + public boolean supportsTransactions() throws SQLException { + return true; + } + + @Override + public boolean supportsTransactionIsolationLevel(int level) + throws SQLException { + return level < Connection.TRANSACTION_SERIALIZABLE; + } + + @Override + public boolean supportsDataDefinitionAndDataManipulationTransactions() + throws SQLException { + return true; + } + + @Override + public boolean supportsDataManipulationTransactionsOnly() + throws SQLException { + return false; + } + + @Override + public boolean dataDefinitionCausesTransactionCommit() throws SQLException { + return false; + } + + @Override + public boolean dataDefinitionIgnoredInTransactions() throws SQLException { + return false; + } + + @Override + public ResultSet getCatalogs() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery( + "SELECT DISTINCT catalog_name AS 'TABLE_CAT' FROM information_schema.schemata ORDER BY \"TABLE_CAT\""); + } + + @Override + public ResultSet getSchemas() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery( + "SELECT schema_name AS 'TABLE_SCHEM', catalog_name AS 'TABLE_CATALOG' FROM information_schema.schemata ORDER BY \"TABLE_CATALOG\", \"TABLE_SCHEM\""); + } + + @Override + public ResultSet getSchemas(String catalog, String schemaPattern) + throws SQLException { + StringBuilder sb = new StringBuilder(512); + sb.append( + "SELECT schema_name AS 'TABLE_SCHEM', catalog_name AS 'TABLE_CATALOG'"); + sb.append(lineSeparator()); + sb.append("FROM information_schema.schemata"); + sb.append(lineSeparator()); + if (catalog != null || schemaPattern != null) { + sb.append("WHERE "); + } + + if (catalog != null) { + if (catalog.isEmpty()) { + sb.append("catalog_name IS NULL"); + } else { + sb.append("catalog_name = ?"); + } + sb.append(lineSeparator()); + } + if (schemaPattern != null) { + if (catalog != null) { + sb.append("AND "); + } + if (schemaPattern.isEmpty()) { + sb.append("schema_name IS NULL"); + } else { + sb.append("schema_name LIKE ?"); + } + sb.append(lineSeparator()); + } + sb.append("ORDER BY \"TABLE_CATALOG\", \"TABLE_SCHEM\""); + sb.append(lineSeparator()); + + PreparedStatement ps = conn.prepareStatement(sb.toString()); + int paramIndex = 0; + if (catalog != null && !catalog.isEmpty()) { + ps.setString(++paramIndex, catalog); + } + if (schemaPattern != null && !schemaPattern.isEmpty()) { + ps.setString(++paramIndex, schemaPattern); + } + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public ResultSet getTableTypes() throws SQLException { + String[] tableTypesArray = + new String[] {"BASE TABLE", "LOCAL TEMPORARY", "VIEW"}; + StringBuilder stringBuilder = new StringBuilder(128); + boolean first = true; + for (String tableType : tableTypesArray) { + if (!first) { + stringBuilder.append("\nUNION ALL\n"); + } + stringBuilder.append("SELECT '"); + stringBuilder.append(tableType); + stringBuilder.append("'"); + if (first) { + stringBuilder.append(" AS 'TABLE_TYPE'"); + first = false; + } + } + stringBuilder.append("\nORDER BY TABLE_TYPE"); + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery(stringBuilder.toString()); + } + + @Override + public ResultSet getTables(String catalog, String schemaPattern, + String tableNamePattern, String[] types) + throws SQLException { + StringBuilder str = new StringBuilder(512); + + str.append("SELECT table_catalog AS 'TABLE_CAT'").append(lineSeparator()); + str.append(", table_schema AS 'TABLE_SCHEM'").append(lineSeparator()); + str.append(", table_name AS 'TABLE_NAME'").append(lineSeparator()); + str.append(", table_type AS 'TABLE_TYPE'").append(lineSeparator()); + str.append(", TABLE_COMMENT AS 'REMARKS'").append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'TYPE_CAT'").append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'TYPE_SCHEM'").append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'TYPE_NAME'").append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'SELF_REFERENCING_COL_NAME'") + .append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'REF_GENERATION'").append(lineSeparator()); + str.append("FROM information_schema.tables").append(lineSeparator()); + + // tableNamePattern - a table name pattern; must match the table name as it + // is stored in the database + if (tableNamePattern == null) { + // non-standard behavior. + tableNamePattern = "%"; + } + str.append("WHERE table_name LIKE ?").append(lineSeparator()); + + // catalog - a catalog name; must match the catalog name as it is stored in + // the database; + // "" retrieves those without a catalog; + // null means that the catalog name should not be used to narrow the search + boolean hasCatalogParam = false; + if (catalog != null) { + str.append("AND table_catalog "); + if (catalog.isEmpty()) { + str.append("IS NULL").append(lineSeparator()); + } else { + str.append("= ?").append(lineSeparator()); + hasCatalogParam = true; + } + } + + // schemaPattern - a schema name pattern; must match the schema name as it + // is stored in the database; + // "" retrieves those without a schema; + // null means that the schema name should not be used to narrow the search + boolean hasSchemaParam = false; + if (schemaPattern != null) { + str.append("AND table_schema "); + if (schemaPattern.isEmpty()) { + str.append("IS NULL").append(lineSeparator()); + } else { + str.append("LIKE ?").append(lineSeparator()); + hasSchemaParam = true; + } + } + + if (types != null && types.length > 0) { + str.append("AND table_type IN (").append(lineSeparator()); + for (int i = 0; i < types.length; i++) { + if (i > 0) { + str.append(','); } - // table name param - pw.append("AND table_name = ?").append(lineSeparator()); - - pw.append(")").append(lineSeparator()); - pw.append("SELECT \"TABLE_CAT\"").append(lineSeparator()); - pw.append(", \"TABLE_SCHEM\"").append(lineSeparator()); - pw.append(", \"TABLE_NAME\"").append(lineSeparator()); - pw.append(", \"COLUMN_NAME\"").append(lineSeparator()); - pw.append(", CAST(ROW_NUMBER() OVER ").append(lineSeparator()); - pw.append("(PARTITION BY \"TABLE_CAT\", \"TABLE_SCHEM\", \"TABLE_NAME\") AS INT) AS \"KEY_SEQ\"") - .append(lineSeparator()); - pw.append(", \"PK_NAME\"").append(lineSeparator()); - pw.append("FROM constraint_columns").append(lineSeparator()); - pw.append("ORDER BY \"TABLE_CAT\", \"TABLE_SCHEM\", \"TABLE_NAME\", \"KEY_SEQ\"").append(lineSeparator()); - - int paramIndex = 1; - PreparedStatement ps = conn.prepareStatement(pw.toString()); - - if (catalog != null && !catalog.isEmpty()) { - ps.setString(paramIndex++, catalog); - } - if (schema != null && !schema.isEmpty()) { - ps.setString(paramIndex++, schema); - } - ps.setString(paramIndex++, table); - ps.closeOnCompletion(); - return ps.executeQuery(); - } - - @Override - public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { - throw new SQLFeatureNotSupportedException("getImportedKeys"); - } - - @Override - public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { - throw new SQLFeatureNotSupportedException("getExportedKeys"); - } - - @Override - public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, - String foreignCatalog, String foreignSchema, String foreignTable) - throws SQLException { - throw new SQLFeatureNotSupportedException("getCrossReference"); - } - - @Override - public ResultSet getTypeInfo() throws SQLException { - throw new SQLFeatureNotSupportedException("getTypeInfo"); - } - - @Override - public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) - throws SQLException { - StringBuilder sb = new StringBuilder(); - sb.append("SELECT database_name AS TABLE_CAT " + - ", schema_name AS TABLE_SCHEM " + - ", table_name AS TABLE_NAME " + - ", index_name AS INDEX_NAME " + - ", CASE WHEN is_unique THEN 0 ELSE 1 END AS NON_UNIQUE " + - ", NULL AS TYPE " + - ", NULL AS ORDINAL_POSITION " + - ", NULL AS COLUMN_NAME " + - ", NULL AS ASC_OR_DESC " + - ", NULL AS CARDINALITY " + - ", NULL AS PAGES " + - ", NULL AS FILTER_CONDITION " + - "FROM duckdb_indexes() WHERE TRUE "); - if (catalog != null) { - sb.append(" AND database_name = ?"); - } - if (schema != null) { - sb.append(" AND schema_name = ?"); - } - if (table != null) { - sb.append(" AND table_name = ?"); - } - sb.append(" ORDER BY TABLE_CAT, TABLE_SCHEM, TABLE_NAME, NON_UNIQUE, INDEX_NAME, ORDINAL_POSITION"); - PreparedStatement ps = conn.prepareStatement(sb.toString()); - int paramIndex = 1; - if (catalog != null) { - ps.setString(paramIndex++, catalog); - } - if (schema != null) { - ps.setString(paramIndex++, schema); - } - if (table != null) { - ps.setString(paramIndex++, table); - } - ps.closeOnCompletion(); - return ps.executeQuery(); - - } - - @Override - public boolean supportsResultSetType(int type) throws SQLException { - return type == ResultSet.TYPE_FORWARD_ONLY; - } - - @Override - public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException { - return type == ResultSet.TYPE_FORWARD_ONLY && concurrency == ResultSet.CONCUR_READ_ONLY; - } - - @Override - public boolean ownUpdatesAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("ownUpdatesAreVisible"); - } - - @Override - public boolean ownDeletesAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("ownDeletesAreVisible"); - } - - @Override - public boolean ownInsertsAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("ownInsertsAreVisible"); - } - - @Override - public boolean othersUpdatesAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("othersUpdatesAreVisible"); - } - - @Override - public boolean othersDeletesAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("othersDeletesAreVisible"); - } - - @Override - public boolean othersInsertsAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("othersInsertsAreVisible"); - } - - @Override - public boolean updatesAreDetected(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("updatesAreDetected"); - } - - @Override - public boolean deletesAreDetected(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("deletesAreDetected"); - } - - @Override - public boolean insertsAreDetected(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("insertsAreDetected"); - } - - @Override - public boolean supportsBatchUpdates() throws SQLException { - return true; - } - - @Override - public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) - throws SQLException { - throw new SQLFeatureNotSupportedException("getUDTs"); - } - - @Override - public Connection getConnection() throws SQLException { - return conn; - } - - @Override - public boolean supportsSavepoints() throws SQLException { - return false; - } - - @Override - public boolean supportsNamedParameters() throws SQLException { - return false; - } - - @Override - public boolean supportsMultipleOpenResults() throws SQLException { - return true; - } - - @Override - public boolean supportsGetGeneratedKeys() throws SQLException { - return false; - } - - @Override - public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { - throw new SQLFeatureNotSupportedException("getSuperTypes"); - } - - @Override - public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { - throw new SQLFeatureNotSupportedException("getSuperTables"); - } - - @Override - public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, - String attributeNamePattern) throws SQLException { - throw new SQLFeatureNotSupportedException("getAttributes"); - } - - @Override - public boolean supportsResultSetHoldability(int holdability) throws SQLException { - return false; - } - - @Override - public int getResultSetHoldability() throws SQLException { - throw new SQLFeatureNotSupportedException("getResultSetHoldability"); - } - - @Override - public int getDatabaseMajorVersion() throws SQLException { - return 1; - } - - @Override - public int getDatabaseMinorVersion() throws SQLException { - return 0; - } - - @Override - public int getJDBCMajorVersion() throws SQLException { - return 1; - } - - @Override - public int getJDBCMinorVersion() throws SQLException { - return 0; - } - - @Override - public int getSQLStateType() throws SQLException { - throw new SQLFeatureNotSupportedException("getSQLStateType"); - } - - @Override - public boolean locatorsUpdateCopy() throws SQLException { - throw new SQLFeatureNotSupportedException("locatorsUpdateCopy"); - } - - @Override - public boolean supportsStatementPooling() throws SQLException { - return false; - } - - @Override - public RowIdLifetime getRowIdLifetime() throws SQLException { - throw new SQLFeatureNotSupportedException("getRowIdLifetime"); - } - - @Override - public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { - return false; - } - - @Override - public boolean autoCommitFailureClosesAllResultSets() throws SQLException { - throw new SQLFeatureNotSupportedException("autoCommitFailureClosesAllResultSets"); - } - - @Override - public ResultSet getClientInfoProperties() throws SQLException { - throw new SQLFeatureNotSupportedException("getClientInfoProperties"); - } - - /** - * - * @param catalog a catalog name; must match the catalog name as it - * is stored in the database; "" retrieves those without a catalog; - * null means that the catalog name should not be used to narrow - * the search - * @param schemaPattern a schema name pattern; must match the schema name - * as it is stored in the database; "" retrieves those without a schema; - * null means that the schema name should not be used to narrow - * the search - * @param functionNamePattern a function name pattern; must match the - * function name as it is stored in the database - * FUNCTION_CAT String => function catalog (may be null) - * FUNCTION_SCHEM String => function schema (may be null) - * FUNCTION_NAME String => function name. This is the name used to invoke the function - * REMARKS String => explanatory comment on the function - * FUNCTION_TYPE short => kind of function: - * - functionResultUnknown - Cannot determine if a return value or table will be returned - * - functionNoTable- Does not return a table - * - functionReturnsTable - Returns a table - * SPECIFIC_NAME String => the name which uniquely identifies this function within its schema. This is a user - * specified, or DBMS generated, name that may be different then the FUNCTION_NAME for example with overload - * functions - */ - @Override - public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) - throws SQLException { - try (PreparedStatement statement = - conn.prepareStatement("SELECT " - + "null as FUNCTION_CAT, " - + "function_name as FUNCTION_NAME, " - + "schema_name as FUNCTION_SCHEM, " - + "description as REMARKS," - + "CASE function_type " - + "WHEN 'table' THEN " + functionReturnsTable + " " - + "WHEN 'table_macro' THEN " + functionReturnsTable + " " - + "ELSE " + functionNoTable + " " - + "END as FUNCTION_TYPE " - + "FROM duckdb_functions() " - + "WHERE function_name like ? and " - + "schema_name like ?")) { - statement.setString(1, functionNamePattern); - statement.setString(2, schemaPattern); - - CachedRowSet cachedRowSet = RowSetProvider.newFactory().createCachedRowSet(); - cachedRowSet.populate(statement.executeQuery()); - return cachedRowSet; - } - } - - @Override - public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, - String columnNamePattern) throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery("SELECT NULL WHERE FALSE"); - } - - @Override - public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, - String columnNamePattern) throws SQLException { - throw new SQLFeatureNotSupportedException("getPseudoColumns"); - } - - @Override - public boolean generatedKeyAlwaysReturned() throws SQLException { - throw new SQLFeatureNotSupportedException("generatedKeyAlwaysReturned"); - } - - static String dataMap; - static { - dataMap = makeCase( - Arrays.stream(DuckDBColumnType.values()) - .collect(Collectors.toMap(ty -> ty.name().replace("_", " "), DuckDBResultSetMetaData::type_to_int))); - } - - private static String makeCase(Map values) { - return values.entrySet() - .stream() - .map(ty -> { - T value = ty.getValue(); - return String.format("WHEN '%s' THEN %s ", ty.getKey(), - value instanceof String ? String.format("'%s'", value) : value); - }) - .collect(Collectors.joining()); - } - - /** - * @param srcColumnName - * @param destColumnName - * @return - * @see DuckDBResultSetMetaData#type_to_int(DuckDBColumnType) - */ - private static String makeDataMap(String srcColumnName, String destColumnName) { - return String.format("CASE %s %s ELSE %d END as %s", srcColumnName, dataMap, Types.JAVA_OBJECT, destColumnName); - } + str.append('?'); + } + str.append(')'); + } + + // ordered by TABLE_TYPE, TABLE_CAT, TABLE_SCHEM and TABLE_NAME. + str.append("ORDER BY table_type").append(lineSeparator()); + str.append(", table_catalog").append(lineSeparator()); + str.append(", table_schema").append(lineSeparator()); + str.append(", table_name").append(lineSeparator()); + + PreparedStatement ps = conn.prepareStatement(str.toString()); + + int paramOffset = 1; + ps.setString(paramOffset++, tableNamePattern); + + if (hasCatalogParam) { + ps.setString(paramOffset++, catalog); + } + if (hasSchemaParam) { + ps.setString(paramOffset++, schemaPattern); + } + + if (types != null && types.length > 0) { + for (int i = 0; i < types.length; i++) { + ps.setString(paramOffset + i, types[i]); + } + } + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public ResultSet getColumns(String catalogPattern, String schemaPattern, + String tableNamePattern, String columnNamePattern) + throws SQLException { + if (catalogPattern == null) { + catalogPattern = "%"; + } + if (schemaPattern == null) { + schemaPattern = "%"; + } + if (tableNamePattern == null) { + tableNamePattern = "%"; + } + if (columnNamePattern == null) { + columnNamePattern = "%"; + } + + PreparedStatement ps = conn.prepareStatement( + "SELECT " + + "table_catalog AS 'TABLE_CAT', " + + "table_schema AS 'TABLE_SCHEM', " + + "table_name AS 'TABLE_NAME', " + + "column_name as 'COLUMN_NAME', " + + makeDataMap("regexp_replace(c.data_type, '\\(.*\\)', '')", + "DATA_TYPE") + + ", " + + "c.data_type AS 'TYPE_NAME', " + + "NULL AS 'COLUMN_SIZE', NULL AS 'BUFFER_LENGTH', " + + "numeric_precision AS 'DECIMAL_DIGITS', " + + "10 AS 'NUM_PREC_RADIX', " + + "CASE WHEN is_nullable = 'YES' THEN 1 else 0 END AS 'NULLABLE', " + + "COLUMN_COMMENT as 'REMARKS', " + + "column_default AS 'COLUMN_DEF', " + + "NULL AS 'SQL_DATA_TYPE', " + + "NULL AS 'SQL_DATETIME_SUB', " + + "NULL AS 'CHAR_OCTET_LENGTH', " + + "ordinal_position AS 'ORDINAL_POSITION', " + + "is_nullable AS 'IS_NULLABLE', " + + "NULL AS 'SCOPE_CATALOG', " + + "NULL AS 'SCOPE_SCHEMA', " + + "NULL AS 'SCOPE_TABLE', " + + "NULL AS 'SOURCE_DATA_TYPE', " + + "'' AS 'IS_AUTOINCREMENT', " + + "'' AS 'IS_GENERATEDCOLUMN' " + + "FROM information_schema.columns c " + + "WHERE table_catalog LIKE ? AND " + + "table_schema LIKE ? AND " + + "table_name LIKE ? AND " + + "column_name LIKE ? " + + + "ORDER BY \"TABLE_CAT\",\"TABLE_SCHEM\", \"TABLE_NAME\", \"ORDINAL_POSITION\""); + ps.setString(1, catalogPattern); + ps.setString(2, schemaPattern); + ps.setString(3, tableNamePattern); + ps.setString(4, columnNamePattern); + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public ResultSet getColumnPrivileges(String catalog, String schema, + String table, String columnNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getColumnPrivileges"); + } + + @Override + public ResultSet getTablePrivileges(String catalog, String schemaPattern, + String tableNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getTablePrivileges"); + } + + @Override + public ResultSet getProcedures(String catalog, String schemaPattern, + String procedureNamePattern) + throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery("SELECT NULL WHERE FALSE"); + } + + @Override + public ResultSet getProcedureColumns(String catalog, String schemaPattern, + String procedureNamePattern, + String columnNamePattern) + throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery("SELECT NULL WHERE FALSE"); + } + + @Override + public ResultSet getBestRowIdentifier(String catalog, String schema, + String table, int scope, + boolean nullable) throws SQLException { + throw new SQLFeatureNotSupportedException("getBestRowIdentifier"); + } + + @Override + public ResultSet getVersionColumns(String catalog, String schema, + String table) throws SQLException { + throw new SQLFeatureNotSupportedException("getVersionColumns"); + } + + @Override + public ResultSet getPrimaryKeys(String catalog, String schema, String table) + throws SQLException { + StringBuilder pw = new StringBuilder(512); + pw.append("WITH constraint_columns AS (").append(lineSeparator()); + pw.append("SELECT").append(lineSeparator()); + pw.append(" database_name AS \"TABLE_CAT\"").append(lineSeparator()); + pw.append(", schema_name AS \"TABLE_SCHEM\"").append(lineSeparator()); + pw.append(", table_name AS \"TABLE_NAME\"").append(lineSeparator()); + pw.append(", unnest(constraint_column_names) AS \"COLUMN_NAME\"") + .append(lineSeparator()); + pw.append(", CAST(NULL AS VARCHAR) AS \"PK_NAME\"").append(lineSeparator()); + pw.append("FROM duckdb_constraints").append(lineSeparator()); + pw.append("WHERE constraint_type = 'PRIMARY KEY'").append(lineSeparator()); + // catalog param + if (catalog != null) { + if (catalog.isEmpty()) { + pw.append("AND database_name IS NULL").append(lineSeparator()); + } else { + pw.append("AND database_name = ?").append(lineSeparator()); + } + } + // schema param + if (schema != null) { + if (schema.isEmpty()) { + pw.append("AND schema_name IS NULL").append(lineSeparator()); + } else { + pw.append("AND schema_name = ?").append(lineSeparator()); + } + } + // table name param + pw.append("AND table_name = ?").append(lineSeparator()); + + pw.append(")").append(lineSeparator()); + pw.append("SELECT \"TABLE_CAT\"").append(lineSeparator()); + pw.append(", \"TABLE_SCHEM\"").append(lineSeparator()); + pw.append(", \"TABLE_NAME\"").append(lineSeparator()); + pw.append(", \"COLUMN_NAME\"").append(lineSeparator()); + pw.append(", CAST(ROW_NUMBER() OVER ").append(lineSeparator()); + pw.append( + "(PARTITION BY \"TABLE_CAT\", \"TABLE_SCHEM\", \"TABLE_NAME\") AS INT) AS \"KEY_SEQ\"") + .append(lineSeparator()); + pw.append(", \"PK_NAME\"").append(lineSeparator()); + pw.append("FROM constraint_columns").append(lineSeparator()); + pw.append( + "ORDER BY \"TABLE_CAT\", \"TABLE_SCHEM\", \"TABLE_NAME\", \"KEY_SEQ\"") + .append(lineSeparator()); + + int paramIndex = 1; + PreparedStatement ps = conn.prepareStatement(pw.toString()); + + if (catalog != null && !catalog.isEmpty()) { + ps.setString(paramIndex++, catalog); + } + if (schema != null && !schema.isEmpty()) { + ps.setString(paramIndex++, schema); + } + ps.setString(paramIndex++, table); + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public ResultSet getImportedKeys(String catalog, String schema, String table) + throws SQLException { + throw new SQLFeatureNotSupportedException("getImportedKeys"); + } + + @Override + public ResultSet getExportedKeys(String catalog, String schema, String table) + throws SQLException { + throw new SQLFeatureNotSupportedException("getExportedKeys"); + } + + @Override + public ResultSet getCrossReference(String parentCatalog, String parentSchema, + String parentTable, String foreignCatalog, + String foreignSchema, String foreignTable) + throws SQLException { + throw new SQLFeatureNotSupportedException("getCrossReference"); + } + + @Override + public ResultSet getTypeInfo() throws SQLException { + throw new SQLFeatureNotSupportedException("getTypeInfo"); + } + + @Override + public ResultSet getIndexInfo(String catalog, String schema, String table, + boolean unique, boolean approximate) + throws SQLException { + StringBuilder sb = new StringBuilder(); + sb.append("SELECT database_name AS TABLE_CAT " + + ", schema_name AS TABLE_SCHEM " + + ", table_name AS TABLE_NAME " + + ", index_name AS INDEX_NAME " + + ", CASE WHEN is_unique THEN 0 ELSE 1 END AS NON_UNIQUE " + + ", NULL AS TYPE " + + ", NULL AS ORDINAL_POSITION " + + ", NULL AS COLUMN_NAME " + + ", NULL AS ASC_OR_DESC " + + ", NULL AS CARDINALITY " + + ", NULL AS PAGES " + + ", NULL AS FILTER_CONDITION " + + "FROM duckdb_indexes() WHERE TRUE "); + if (catalog != null) { + sb.append(" AND database_name = ?"); + } + if (schema != null) { + sb.append(" AND schema_name = ?"); + } + if (table != null) { + sb.append(" AND table_name = ?"); + } + sb.append( + " ORDER BY TABLE_CAT, TABLE_SCHEM, TABLE_NAME, NON_UNIQUE, INDEX_NAME, ORDINAL_POSITION"); + PreparedStatement ps = conn.prepareStatement(sb.toString()); + int paramIndex = 1; + if (catalog != null) { + ps.setString(paramIndex++, catalog); + } + if (schema != null) { + ps.setString(paramIndex++, schema); + } + if (table != null) { + ps.setString(paramIndex++, table); + } + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public boolean supportsResultSetType(int type) throws SQLException { + return type == ResultSet.TYPE_FORWARD_ONLY; + } + + @Override + public boolean supportsResultSetConcurrency(int type, int concurrency) + throws SQLException { + return type == ResultSet.TYPE_FORWARD_ONLY && + concurrency == ResultSet.CONCUR_READ_ONLY; + } + + @Override + public boolean ownUpdatesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("ownUpdatesAreVisible"); + } + + @Override + public boolean ownDeletesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("ownDeletesAreVisible"); + } + + @Override + public boolean ownInsertsAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("ownInsertsAreVisible"); + } + + @Override + public boolean othersUpdatesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("othersUpdatesAreVisible"); + } + + @Override + public boolean othersDeletesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("othersDeletesAreVisible"); + } + + @Override + public boolean othersInsertsAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("othersInsertsAreVisible"); + } + + @Override + public boolean updatesAreDetected(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("updatesAreDetected"); + } + + @Override + public boolean deletesAreDetected(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("deletesAreDetected"); + } + + @Override + public boolean insertsAreDetected(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("insertsAreDetected"); + } + + @Override + public boolean supportsBatchUpdates() throws SQLException { + return true; + } + + @Override + public ResultSet getUDTs(String catalog, String schemaPattern, + String typeNamePattern, int[] types) + throws SQLException { + throw new SQLFeatureNotSupportedException("getUDTs"); + } + + @Override + public Connection getConnection() throws SQLException { + return conn; + } + + @Override + public boolean supportsSavepoints() throws SQLException { + return false; + } + + @Override + public boolean supportsNamedParameters() throws SQLException { + return false; + } + + @Override + public boolean supportsMultipleOpenResults() throws SQLException { + return true; + } + + @Override + public boolean supportsGetGeneratedKeys() throws SQLException { + return false; + } + + @Override + public ResultSet getSuperTypes(String catalog, String schemaPattern, + String typeNamePattern) throws SQLException { + throw new SQLFeatureNotSupportedException("getSuperTypes"); + } + + @Override + public ResultSet getSuperTables(String catalog, String schemaPattern, + String tableNamePattern) throws SQLException { + throw new SQLFeatureNotSupportedException("getSuperTables"); + } + + @Override + public ResultSet getAttributes(String catalog, String schemaPattern, + String typeNamePattern, + String attributeNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getAttributes"); + } + + @Override + public boolean supportsResultSetHoldability(int holdability) + throws SQLException { + return false; + } + + @Override + public int getResultSetHoldability() throws SQLException { + throw new SQLFeatureNotSupportedException("getResultSetHoldability"); + } + + @Override + public int getDatabaseMajorVersion() throws SQLException { + return 1; + } + + @Override + public int getDatabaseMinorVersion() throws SQLException { + return 0; + } + + @Override + public int getJDBCMajorVersion() throws SQLException { + return 1; + } + + @Override + public int getJDBCMinorVersion() throws SQLException { + return 0; + } + + @Override + public int getSQLStateType() throws SQLException { + throw new SQLFeatureNotSupportedException("getSQLStateType"); + } + + @Override + public boolean locatorsUpdateCopy() throws SQLException { + throw new SQLFeatureNotSupportedException("locatorsUpdateCopy"); + } + + @Override + public boolean supportsStatementPooling() throws SQLException { + return false; + } + + @Override + public RowIdLifetime getRowIdLifetime() throws SQLException { + throw new SQLFeatureNotSupportedException("getRowIdLifetime"); + } + + @Override + public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { + return false; + } + + @Override + public boolean autoCommitFailureClosesAllResultSets() throws SQLException { + throw new SQLFeatureNotSupportedException( + "autoCommitFailureClosesAllResultSets"); + } + + @Override + public ResultSet getClientInfoProperties() throws SQLException { + throw new SQLFeatureNotSupportedException("getClientInfoProperties"); + } + + /** + * + * @param catalog a catalog name; must match the catalog name as it + * is stored in the database; "" retrieves those without a catalog; + * null means that the catalog name should not be used to + * narrow the search + * @param schemaPattern a schema name pattern; must match the schema name + * as it is stored in the database; "" retrieves those without a + * schema; null means that the schema name should not be used to + * narrow the search + * @param functionNamePattern a function name pattern; must match the + * function name as it is stored in the database + * FUNCTION_CAT String => function catalog (may be null) + * FUNCTION_SCHEM String => function schema (may be null) + * FUNCTION_NAME String => function name. This is the name used to invoke the + * function REMARKS String => explanatory comment on the function + * FUNCTION_TYPE short => kind of function: + * - functionResultUnknown - Cannot determine if a return value or table will + * be returned + * - functionNoTable- Does not return a table + * - functionReturnsTable - Returns a table + * SPECIFIC_NAME String => the name which uniquely identifies this function + * within its schema. This is a user specified, or DBMS generated, name that + * may be different then the FUNCTION_NAME for example with overload functions + */ + @Override + public ResultSet getFunctions(String catalog, String schemaPattern, + String functionNamePattern) + throws SQLException { + try (PreparedStatement statement = conn.prepareStatement( + "SELECT " + + "null as FUNCTION_CAT, " + + "function_name as FUNCTION_NAME, " + + "schema_name as FUNCTION_SCHEM, " + + "description as REMARKS," + + "CASE function_type " + + "WHEN 'table' THEN " + functionReturnsTable + " " + + "WHEN 'table_macro' THEN " + functionReturnsTable + " " + + "ELSE " + functionNoTable + " " + + "END as FUNCTION_TYPE " + + "FROM duckdb_functions() " + + "WHERE function_name like ? and " + + "schema_name like ?")) { + statement.setString(1, functionNamePattern); + statement.setString(2, schemaPattern); + + CachedRowSet cachedRowSet = + RowSetProvider.newFactory().createCachedRowSet(); + cachedRowSet.populate(statement.executeQuery()); + return cachedRowSet; + } + } + + @Override + public ResultSet getFunctionColumns(String catalog, String schemaPattern, + String functionNamePattern, + String columnNamePattern) + throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery("SELECT NULL WHERE FALSE"); + } + + @Override + public ResultSet getPseudoColumns(String catalog, String schemaPattern, + String tableNamePattern, + String columnNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getPseudoColumns"); + } + + @Override + public boolean generatedKeyAlwaysReturned() throws SQLException { + throw new SQLFeatureNotSupportedException("generatedKeyAlwaysReturned"); + } + + static String dataMap; + static { + dataMap = makeCase( + Arrays.stream(DuckDBColumnType.values()) + .collect(Collectors.toMap(ty + -> ty.name().replace("_", " "), + DuckDBResultSetMetaData::type_to_int))); + } + + private static String makeCase(Map values) { + return values.entrySet() + .stream() + .map(ty -> { + T value = ty.getValue(); + return String.format( + "WHEN '%s' THEN %s ", ty.getKey(), + value instanceof String ? String.format("'%s'", value) : value); + }) + .collect(Collectors.joining()); + } + + /** + * @param srcColumnName + * @param destColumnName + * @return + * @see DuckDBResultSetMetaData#type_to_int(DuckDBColumnType) + */ + private static String makeDataMap(String srcColumnName, + String destColumnName) { + return String.format("CASE %s %s ELSE %d END as %s", srcColumnName, dataMap, + Types.JAVA_OBJECT, destColumnName); + } } From 283c4910a0d1e2fa32abfb3718416515731bba0e Mon Sep 17 00:00:00 2001 From: jbs Date: Mon, 1 Jul 2024 10:47:38 -0300 Subject: [PATCH 6/6] Run src/main/java/org/duckdb/DuckDBDatabaseMetaData.java through clang-format --- .../org/duckdb/DuckDBDatabaseMetaData.java | 2686 ++++++++--------- src/test/test.iml | 11 + 2 files changed, 1329 insertions(+), 1368 deletions(-) create mode 100644 src/test/test.iml diff --git a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java index 15cec421..7074b0ca 100644 --- a/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java +++ b/src/main/java/org/duckdb/DuckDBDatabaseMetaData.java @@ -19,1373 +19,1323 @@ public class DuckDBDatabaseMetaData implements DatabaseMetaData { - DuckDBConnection conn; - - public DuckDBDatabaseMetaData(DuckDBConnection conn) { this.conn = conn; } - - @Override - public T unwrap(Class iface) throws SQLException { - return JdbcUtils.unwrap(this, iface); - } - - @Override - public boolean isWrapperFor(Class iface) { - return iface.isInstance(this); - } - - @Override - public boolean allProceduresAreCallable() throws SQLException { - return false; - } - - @Override - public boolean allTablesAreSelectable() throws SQLException { - return true; - } - - @Override - public String getURL() throws SQLException { - return conn.url; - } - - @Override - public String getUserName() throws SQLException { - return ""; - } - - @Override - public boolean isReadOnly() throws SQLException { - return conn.isReadOnly(); - } - - @Override - public boolean nullsAreSortedHigh() throws SQLException { - return true; - } - - @Override - public boolean nullsAreSortedLow() throws SQLException { - return false; - } - - @Override - public boolean nullsAreSortedAtStart() throws SQLException { - return true; - } - - @Override - public boolean nullsAreSortedAtEnd() throws SQLException { - return false; - } - - @Override - public String getDatabaseProductName() throws SQLException { - return "DuckDB"; - } - - @Override - public String getDatabaseProductVersion() throws SQLException { - try (Statement s = conn.createStatement(); - ResultSet rs = s.executeQuery("PRAGMA version")) { - rs.next(); - String result = rs.getString(1); - return result; - } - } - - @Override - public String getDriverName() throws SQLException { - return "DuckDBJ"; - } - - @Override - public String getDriverVersion() throws SQLException { - return "1.0"; - } - - @Override - public int getDriverMajorVersion() { - return 1; - } - - @Override - public int getDriverMinorVersion() { - return 0; - } - - @Override - public boolean usesLocalFiles() throws SQLException { - return true; - } - - @Override - public boolean usesLocalFilePerTable() throws SQLException { - return false; - } - - @Override - public boolean supportsMixedCaseIdentifiers() throws SQLException { - return true; - } - - @Override - public boolean storesUpperCaseIdentifiers() throws SQLException { - return false; - } - - @Override - public boolean storesLowerCaseIdentifiers() throws SQLException { - return false; - } - - @Override - public boolean storesMixedCaseIdentifiers() throws SQLException { - return true; - } - - @Override - public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { - return true; - } - - @Override - public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { - return false; - } - - @Override - public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { - return false; - } - - @Override - public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { - return true; - } - - @Override - public String getIdentifierQuoteString() throws SQLException { - return "\""; - } - - @Override - public String getSQLKeywords() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = - statement.executeQuery("SELECT keyword_name FROM duckdb_keywords()"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getNumericFunctions() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = statement.executeQuery( - "SELECT DISTINCT function_name FROM duckdb_functions() " - + "WHERE parameter_types[1] ='DECIMAL'" - + "OR parameter_types[1] ='DOUBLE'" - + "OR parameter_types[1] ='SMALLINT'" - + "OR parameter_types[1] = 'BIGINT'"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getStringFunctions() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = statement.executeQuery( - "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] = 'VARCHAR'"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getSystemFunctions() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = statement.executeQuery( - "SELECT DISTINCT function_name FROM duckdb_functions() WHERE length(parameter_types) = 0"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getTimeDateFunctions() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - ResultSet rs = statement.executeQuery( - "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] LIKE 'TIME%'"); - StringBuilder sb = new StringBuilder(); - while (rs.next()) { - sb.append(rs.getString(1)); - sb.append(','); - } - return sb.toString(); - } - - @Override - public String getSearchStringEscape() throws SQLException { - return null; - } - - @Override - public String getExtraNameCharacters() throws SQLException { - return ""; - } - - @Override - public boolean supportsAlterTableWithAddColumn() throws SQLException { - return true; - } - - @Override - public boolean supportsAlterTableWithDropColumn() throws SQLException { - return true; - } - - @Override - public boolean supportsColumnAliasing() throws SQLException { - return true; - } - - @Override - public boolean nullPlusNonNullIsNull() throws SQLException { - return true; - } - - @Override - public boolean supportsConvert() throws SQLException { - return false; - } - - @Override - public boolean supportsConvert(int fromType, int toType) throws SQLException { - return false; - } - - @Override - public boolean supportsTableCorrelationNames() throws SQLException { - return true; - } - - @Override - public boolean supportsDifferentTableCorrelationNames() throws SQLException { - return false; - } - - @Override - public boolean supportsExpressionsInOrderBy() throws SQLException { - return true; - } - - @Override - public boolean supportsOrderByUnrelated() throws SQLException { - return true; - } - - @Override - public boolean supportsGroupBy() throws SQLException { - return true; - } - - @Override - public boolean supportsGroupByUnrelated() throws SQLException { - return true; - } - - @Override - public boolean supportsGroupByBeyondSelect() throws SQLException { - return true; - } - - @Override - public boolean supportsLikeEscapeClause() throws SQLException { - return true; - } - - @Override - public boolean supportsMultipleResultSets() throws SQLException { - return false; - } - - @Override - public boolean supportsMultipleTransactions() throws SQLException { - return true; - } - - @Override - public boolean supportsNonNullableColumns() throws SQLException { - return true; - } - - @Override - public boolean supportsMinimumSQLGrammar() throws SQLException { - return true; - } - - @Override - public boolean supportsCoreSQLGrammar() throws SQLException { - return true; - } - - @Override - public boolean supportsExtendedSQLGrammar() throws SQLException { - return true; - } - - @Override - public boolean supportsANSI92EntryLevelSQL() throws SQLException { - return true; - } - - @Override - public boolean supportsANSI92IntermediateSQL() throws SQLException { - return true; - } - - @Override - public boolean supportsANSI92FullSQL() throws SQLException { - return true; - } - - @Override - public boolean supportsIntegrityEnhancementFacility() throws SQLException { - return false; - } - - @Override - public boolean supportsOuterJoins() throws SQLException { - return true; - } - - @Override - public boolean supportsFullOuterJoins() throws SQLException { - return true; - } - - @Override - public boolean supportsLimitedOuterJoins() throws SQLException { - return true; - } - - @Override - public String getSchemaTerm() throws SQLException { - return "schema"; - } - - @Override - public String getProcedureTerm() throws SQLException { - return "procedure"; - } - - @Override - public String getCatalogTerm() throws SQLException { - return "catalog"; - } - - @Override - public boolean isCatalogAtStart() throws SQLException { - return true; - } - - @Override - public String getCatalogSeparator() throws SQLException { - return "."; - } - - @Override - public boolean supportsSchemasInDataManipulation() throws SQLException { - return true; - } - - @Override - public boolean supportsSchemasInProcedureCalls() throws SQLException { - return true; - } - - @Override - public boolean supportsSchemasInTableDefinitions() throws SQLException { - return true; - } - - @Override - public boolean supportsSchemasInIndexDefinitions() throws SQLException { - return true; - } - - @Override - public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { - return false; - } - - @Override - public boolean supportsCatalogsInDataManipulation() throws SQLException { - return true; - } - - @Override - public boolean supportsCatalogsInProcedureCalls() throws SQLException { - return false; - } - - @Override - public boolean supportsCatalogsInTableDefinitions() throws SQLException { - return true; - } - - @Override - public boolean supportsCatalogsInIndexDefinitions() throws SQLException { - return true; - } - - @Override - public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { - return false; - } - - @Override - public boolean supportsPositionedDelete() throws SQLException { - return false; - } - - @Override - public boolean supportsPositionedUpdate() throws SQLException { - return false; - } - - @Override - public boolean supportsSelectForUpdate() throws SQLException { - return false; - } - - @Override - public boolean supportsStoredProcedures() throws SQLException { - return false; - } - - @Override - public boolean supportsSubqueriesInComparisons() throws SQLException { - return true; - } - - @Override - public boolean supportsSubqueriesInExists() throws SQLException { - return true; - } - - @Override - public boolean supportsSubqueriesInIns() throws SQLException { - return true; - } - - @Override - public boolean supportsSubqueriesInQuantifieds() throws SQLException { - return true; - } - - @Override - public boolean supportsCorrelatedSubqueries() throws SQLException { - return true; - } - - @Override - public boolean supportsUnion() throws SQLException { - return true; - } - - @Override - public boolean supportsUnionAll() throws SQLException { - return true; - } - - @Override - public boolean supportsOpenCursorsAcrossCommit() throws SQLException { - return false; - } - - @Override - public boolean supportsOpenCursorsAcrossRollback() throws SQLException { - return false; - } - - @Override - public boolean supportsOpenStatementsAcrossCommit() throws SQLException { - return false; - } - - @Override - public boolean supportsOpenStatementsAcrossRollback() throws SQLException { - return false; - } - - @Override - public int getMaxBinaryLiteralLength() throws SQLException { - - return 0; - } - - @Override - public int getMaxCharLiteralLength() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInGroupBy() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInIndex() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInOrderBy() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInSelect() throws SQLException { - return 0; - } - - @Override - public int getMaxColumnsInTable() throws SQLException { - return 0; - } - - @Override - public int getMaxConnections() throws SQLException { - return 0; - } - - @Override - public int getMaxCursorNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxIndexLength() throws SQLException { - return 0; - } - - @Override - public int getMaxSchemaNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxProcedureNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxCatalogNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxRowSize() throws SQLException { - return 0; - } - - @Override - public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { - return false; - } - - @Override - public int getMaxStatementLength() throws SQLException { - return 0; - } - - @Override - public int getMaxStatements() throws SQLException { - return 0; - } - - @Override - public int getMaxTableNameLength() throws SQLException { - return 0; - } - - @Override - public int getMaxTablesInSelect() throws SQLException { - return 0; - } - - @Override - public int getMaxUserNameLength() throws SQLException { - return 0; - } - - @Override - public int getDefaultTransactionIsolation() throws SQLException { - return Connection.TRANSACTION_REPEATABLE_READ; - } - - @Override - public boolean supportsTransactions() throws SQLException { - return true; - } - - @Override - public boolean supportsTransactionIsolationLevel(int level) - throws SQLException { - return level < Connection.TRANSACTION_SERIALIZABLE; - } - - @Override - public boolean supportsDataDefinitionAndDataManipulationTransactions() - throws SQLException { - return true; - } - - @Override - public boolean supportsDataManipulationTransactionsOnly() - throws SQLException { - return false; - } - - @Override - public boolean dataDefinitionCausesTransactionCommit() throws SQLException { - return false; - } - - @Override - public boolean dataDefinitionIgnoredInTransactions() throws SQLException { - return false; - } - - @Override - public ResultSet getCatalogs() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery( - "SELECT DISTINCT catalog_name AS 'TABLE_CAT' FROM information_schema.schemata ORDER BY \"TABLE_CAT\""); - } - - @Override - public ResultSet getSchemas() throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery( - "SELECT schema_name AS 'TABLE_SCHEM', catalog_name AS 'TABLE_CATALOG' FROM information_schema.schemata ORDER BY \"TABLE_CATALOG\", \"TABLE_SCHEM\""); - } - - @Override - public ResultSet getSchemas(String catalog, String schemaPattern) - throws SQLException { - StringBuilder sb = new StringBuilder(512); - sb.append( - "SELECT schema_name AS 'TABLE_SCHEM', catalog_name AS 'TABLE_CATALOG'"); - sb.append(lineSeparator()); - sb.append("FROM information_schema.schemata"); - sb.append(lineSeparator()); - if (catalog != null || schemaPattern != null) { - sb.append("WHERE "); - } - - if (catalog != null) { - if (catalog.isEmpty()) { - sb.append("catalog_name IS NULL"); - } else { - sb.append("catalog_name = ?"); - } - sb.append(lineSeparator()); - } - if (schemaPattern != null) { - if (catalog != null) { - sb.append("AND "); - } - if (schemaPattern.isEmpty()) { - sb.append("schema_name IS NULL"); - } else { - sb.append("schema_name LIKE ?"); - } - sb.append(lineSeparator()); - } - sb.append("ORDER BY \"TABLE_CATALOG\", \"TABLE_SCHEM\""); - sb.append(lineSeparator()); - - PreparedStatement ps = conn.prepareStatement(sb.toString()); - int paramIndex = 0; - if (catalog != null && !catalog.isEmpty()) { - ps.setString(++paramIndex, catalog); - } - if (schemaPattern != null && !schemaPattern.isEmpty()) { - ps.setString(++paramIndex, schemaPattern); - } - ps.closeOnCompletion(); - return ps.executeQuery(); - } - - @Override - public ResultSet getTableTypes() throws SQLException { - String[] tableTypesArray = - new String[] {"BASE TABLE", "LOCAL TEMPORARY", "VIEW"}; - StringBuilder stringBuilder = new StringBuilder(128); - boolean first = true; - for (String tableType : tableTypesArray) { - if (!first) { - stringBuilder.append("\nUNION ALL\n"); - } - stringBuilder.append("SELECT '"); - stringBuilder.append(tableType); - stringBuilder.append("'"); - if (first) { - stringBuilder.append(" AS 'TABLE_TYPE'"); - first = false; - } - } - stringBuilder.append("\nORDER BY TABLE_TYPE"); - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery(stringBuilder.toString()); - } - - @Override - public ResultSet getTables(String catalog, String schemaPattern, - String tableNamePattern, String[] types) - throws SQLException { - StringBuilder str = new StringBuilder(512); - - str.append("SELECT table_catalog AS 'TABLE_CAT'").append(lineSeparator()); - str.append(", table_schema AS 'TABLE_SCHEM'").append(lineSeparator()); - str.append(", table_name AS 'TABLE_NAME'").append(lineSeparator()); - str.append(", table_type AS 'TABLE_TYPE'").append(lineSeparator()); - str.append(", TABLE_COMMENT AS 'REMARKS'").append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'TYPE_CAT'").append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'TYPE_SCHEM'").append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'TYPE_NAME'").append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'SELF_REFERENCING_COL_NAME'") - .append(lineSeparator()); - str.append(", NULL::VARCHAR AS 'REF_GENERATION'").append(lineSeparator()); - str.append("FROM information_schema.tables").append(lineSeparator()); - - // tableNamePattern - a table name pattern; must match the table name as it - // is stored in the database - if (tableNamePattern == null) { - // non-standard behavior. - tableNamePattern = "%"; - } - str.append("WHERE table_name LIKE ?").append(lineSeparator()); - - // catalog - a catalog name; must match the catalog name as it is stored in - // the database; - // "" retrieves those without a catalog; - // null means that the catalog name should not be used to narrow the search - boolean hasCatalogParam = false; - if (catalog != null) { - str.append("AND table_catalog "); - if (catalog.isEmpty()) { - str.append("IS NULL").append(lineSeparator()); - } else { - str.append("= ?").append(lineSeparator()); - hasCatalogParam = true; - } - } - - // schemaPattern - a schema name pattern; must match the schema name as it - // is stored in the database; - // "" retrieves those without a schema; - // null means that the schema name should not be used to narrow the search - boolean hasSchemaParam = false; - if (schemaPattern != null) { - str.append("AND table_schema "); - if (schemaPattern.isEmpty()) { - str.append("IS NULL").append(lineSeparator()); - } else { - str.append("LIKE ?").append(lineSeparator()); - hasSchemaParam = true; - } - } - - if (types != null && types.length > 0) { - str.append("AND table_type IN (").append(lineSeparator()); - for (int i = 0; i < types.length; i++) { - if (i > 0) { - str.append(','); + DuckDBConnection conn; + + public DuckDBDatabaseMetaData(DuckDBConnection conn) { + this.conn = conn; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return JdbcUtils.unwrap(this, iface); + } + + @Override + public boolean isWrapperFor(Class iface) { + return iface.isInstance(this); + } + + @Override + public boolean allProceduresAreCallable() throws SQLException { + return false; + } + + @Override + public boolean allTablesAreSelectable() throws SQLException { + return true; + } + + @Override + public String getURL() throws SQLException { + return conn.url; + } + + @Override + public String getUserName() throws SQLException { + return ""; + } + + @Override + public boolean isReadOnly() throws SQLException { + return conn.isReadOnly(); + } + + @Override + public boolean nullsAreSortedHigh() throws SQLException { + return true; + } + + @Override + public boolean nullsAreSortedLow() throws SQLException { + return false; + } + + @Override + public boolean nullsAreSortedAtStart() throws SQLException { + return true; + } + + @Override + public boolean nullsAreSortedAtEnd() throws SQLException { + return false; + } + + @Override + public String getDatabaseProductName() throws SQLException { + return "DuckDB"; + } + + @Override + public String getDatabaseProductVersion() throws SQLException { + try (Statement s = conn.createStatement(); ResultSet rs = s.executeQuery("PRAGMA version")) { + rs.next(); + String result = rs.getString(1); + return result; + } + } + + @Override + public String getDriverName() throws SQLException { + return "DuckDBJ"; + } + + @Override + public String getDriverVersion() throws SQLException { + return "1.0"; + } + + @Override + public int getDriverMajorVersion() { + return 1; + } + + @Override + public int getDriverMinorVersion() { + return 0; + } + + @Override + public boolean usesLocalFiles() throws SQLException { + return true; + } + + @Override + public boolean usesLocalFilePerTable() throws SQLException { + return false; + } + + @Override + public boolean supportsMixedCaseIdentifiers() throws SQLException { + return true; + } + + @Override + public boolean storesUpperCaseIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesLowerCaseIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesMixedCaseIdentifiers() throws SQLException { + return true; + } + + @Override + public boolean supportsMixedCaseQuotedIdentifiers() throws SQLException { + return true; + } + + @Override + public boolean storesUpperCaseQuotedIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesLowerCaseQuotedIdentifiers() throws SQLException { + return false; + } + + @Override + public boolean storesMixedCaseQuotedIdentifiers() throws SQLException { + return true; + } + + @Override + public String getIdentifierQuoteString() throws SQLException { + return "\""; + } + + @Override + public String getSQLKeywords() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery("SELECT keyword_name FROM duckdb_keywords()"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getNumericFunctions() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery("SELECT DISTINCT function_name FROM duckdb_functions() " + + "WHERE parameter_types[1] ='DECIMAL'" + + "OR parameter_types[1] ='DOUBLE'" + + "OR parameter_types[1] ='SMALLINT'" + + "OR parameter_types[1] = 'BIGINT'"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getStringFunctions() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] = 'VARCHAR'"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getSystemFunctions() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE length(parameter_types) = 0"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getTimeDateFunctions() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + ResultSet rs = statement.executeQuery( + "SELECT DISTINCT function_name FROM duckdb_functions() WHERE parameter_types[1] LIKE 'TIME%'"); + StringBuilder sb = new StringBuilder(); + while (rs.next()) { + sb.append(rs.getString(1)); + sb.append(','); + } + return sb.toString(); + } + + @Override + public String getSearchStringEscape() throws SQLException { + return null; + } + + @Override + public String getExtraNameCharacters() throws SQLException { + return ""; + } + + @Override + public boolean supportsAlterTableWithAddColumn() throws SQLException { + return true; + } + + @Override + public boolean supportsAlterTableWithDropColumn() throws SQLException { + return true; + } + + @Override + public boolean supportsColumnAliasing() throws SQLException { + return true; + } + + @Override + public boolean nullPlusNonNullIsNull() throws SQLException { + return true; + } + + @Override + public boolean supportsConvert() throws SQLException { + return false; + } + + @Override + public boolean supportsConvert(int fromType, int toType) throws SQLException { + return false; + } + + @Override + public boolean supportsTableCorrelationNames() throws SQLException { + return true; + } + + @Override + public boolean supportsDifferentTableCorrelationNames() throws SQLException { + return false; + } + + @Override + public boolean supportsExpressionsInOrderBy() throws SQLException { + return true; + } + + @Override + public boolean supportsOrderByUnrelated() throws SQLException { + return true; + } + + @Override + public boolean supportsGroupBy() throws SQLException { + return true; + } + + @Override + public boolean supportsGroupByUnrelated() throws SQLException { + return true; + } + + @Override + public boolean supportsGroupByBeyondSelect() throws SQLException { + return true; + } + + @Override + public boolean supportsLikeEscapeClause() throws SQLException { + return true; + } + + @Override + public boolean supportsMultipleResultSets() throws SQLException { + return false; + } + + @Override + public boolean supportsMultipleTransactions() throws SQLException { + return true; + } + + @Override + public boolean supportsNonNullableColumns() throws SQLException { + return true; + } + + @Override + public boolean supportsMinimumSQLGrammar() throws SQLException { + return true; + } + + @Override + public boolean supportsCoreSQLGrammar() throws SQLException { + return true; + } + + @Override + public boolean supportsExtendedSQLGrammar() throws SQLException { + return true; + } + + @Override + public boolean supportsANSI92EntryLevelSQL() throws SQLException { + return true; + } + + @Override + public boolean supportsANSI92IntermediateSQL() throws SQLException { + return true; + } + + @Override + public boolean supportsANSI92FullSQL() throws SQLException { + return true; + } + + @Override + public boolean supportsIntegrityEnhancementFacility() throws SQLException { + return false; + } + + @Override + public boolean supportsOuterJoins() throws SQLException { + return true; + } + + @Override + public boolean supportsFullOuterJoins() throws SQLException { + return true; + } + + @Override + public boolean supportsLimitedOuterJoins() throws SQLException { + return true; + } + + @Override + public String getSchemaTerm() throws SQLException { + return "schema"; + } + + @Override + public String getProcedureTerm() throws SQLException { + return "procedure"; + } + + @Override + public String getCatalogTerm() throws SQLException { + return "catalog"; + } + + @Override + public boolean isCatalogAtStart() throws SQLException { + return true; + } + + @Override + public String getCatalogSeparator() throws SQLException { + return "."; + } + + @Override + public boolean supportsSchemasInDataManipulation() throws SQLException { + return true; + } + + @Override + public boolean supportsSchemasInProcedureCalls() throws SQLException { + return true; + } + + @Override + public boolean supportsSchemasInTableDefinitions() throws SQLException { + return true; + } + + @Override + public boolean supportsSchemasInIndexDefinitions() throws SQLException { + return true; + } + + @Override + public boolean supportsSchemasInPrivilegeDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsCatalogsInDataManipulation() throws SQLException { + return true; + } + + @Override + public boolean supportsCatalogsInProcedureCalls() throws SQLException { + return false; + } + + @Override + public boolean supportsCatalogsInTableDefinitions() throws SQLException { + return true; + } + + @Override + public boolean supportsCatalogsInIndexDefinitions() throws SQLException { + return true; + } + + @Override + public boolean supportsCatalogsInPrivilegeDefinitions() throws SQLException { + return false; + } + + @Override + public boolean supportsPositionedDelete() throws SQLException { + return false; + } + + @Override + public boolean supportsPositionedUpdate() throws SQLException { + return false; + } + + @Override + public boolean supportsSelectForUpdate() throws SQLException { + return false; + } + + @Override + public boolean supportsStoredProcedures() throws SQLException { + return false; + } + + @Override + public boolean supportsSubqueriesInComparisons() throws SQLException { + return true; + } + + @Override + public boolean supportsSubqueriesInExists() throws SQLException { + return true; + } + + @Override + public boolean supportsSubqueriesInIns() throws SQLException { + return true; + } + + @Override + public boolean supportsSubqueriesInQuantifieds() throws SQLException { + return true; + } + + @Override + public boolean supportsCorrelatedSubqueries() throws SQLException { + return true; + } + + @Override + public boolean supportsUnion() throws SQLException { + return true; + } + + @Override + public boolean supportsUnionAll() throws SQLException { + return true; + } + + @Override + public boolean supportsOpenCursorsAcrossCommit() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenCursorsAcrossRollback() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossCommit() throws SQLException { + return false; + } + + @Override + public boolean supportsOpenStatementsAcrossRollback() throws SQLException { + return false; + } + + @Override + public int getMaxBinaryLiteralLength() throws SQLException { + + return 0; + } + + @Override + public int getMaxCharLiteralLength() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInGroupBy() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInIndex() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInOrderBy() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInSelect() throws SQLException { + return 0; + } + + @Override + public int getMaxColumnsInTable() throws SQLException { + return 0; + } + + @Override + public int getMaxConnections() throws SQLException { + return 0; + } + + @Override + public int getMaxCursorNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxIndexLength() throws SQLException { + return 0; + } + + @Override + public int getMaxSchemaNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxProcedureNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxCatalogNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxRowSize() throws SQLException { + return 0; + } + + @Override + public boolean doesMaxRowSizeIncludeBlobs() throws SQLException { + return false; + } + + @Override + public int getMaxStatementLength() throws SQLException { + return 0; + } + + @Override + public int getMaxStatements() throws SQLException { + return 0; + } + + @Override + public int getMaxTableNameLength() throws SQLException { + return 0; + } + + @Override + public int getMaxTablesInSelect() throws SQLException { + return 0; + } + + @Override + public int getMaxUserNameLength() throws SQLException { + return 0; + } + + @Override + public int getDefaultTransactionIsolation() throws SQLException { + return Connection.TRANSACTION_REPEATABLE_READ; + } + + @Override + public boolean supportsTransactions() throws SQLException { + return true; + } + + @Override + public boolean supportsTransactionIsolationLevel(int level) throws SQLException { + return level < Connection.TRANSACTION_SERIALIZABLE; + } + + @Override + public boolean supportsDataDefinitionAndDataManipulationTransactions() throws SQLException { + return true; + } + + @Override + public boolean supportsDataManipulationTransactionsOnly() throws SQLException { + return false; + } + + @Override + public boolean dataDefinitionCausesTransactionCommit() throws SQLException { + return false; + } + + @Override + public boolean dataDefinitionIgnoredInTransactions() throws SQLException { + return false; + } + + @Override + public ResultSet getCatalogs() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery( + "SELECT DISTINCT catalog_name AS 'TABLE_CAT' FROM information_schema.schemata ORDER BY \"TABLE_CAT\""); + } + + @Override + public ResultSet getSchemas() throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery( + "SELECT schema_name AS 'TABLE_SCHEM', catalog_name AS 'TABLE_CATALOG' FROM information_schema.schemata ORDER BY \"TABLE_CATALOG\", \"TABLE_SCHEM\""); + } + + @Override + public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { + StringBuilder sb = new StringBuilder(512); + sb.append("SELECT schema_name AS 'TABLE_SCHEM', catalog_name AS 'TABLE_CATALOG'"); + sb.append(lineSeparator()); + sb.append("FROM information_schema.schemata"); + sb.append(lineSeparator()); + if (catalog != null || schemaPattern != null) { + sb.append("WHERE "); + } + + if (catalog != null) { + if (catalog.isEmpty()) { + sb.append("catalog_name IS NULL"); + } else { + sb.append("catalog_name = ?"); + } + sb.append(lineSeparator()); + } + if (schemaPattern != null) { + if (catalog != null) { + sb.append("AND "); + } + if (schemaPattern.isEmpty()) { + sb.append("schema_name IS NULL"); + } else { + sb.append("schema_name LIKE ?"); + } + sb.append(lineSeparator()); + } + sb.append("ORDER BY \"TABLE_CATALOG\", \"TABLE_SCHEM\""); + sb.append(lineSeparator()); + + PreparedStatement ps = conn.prepareStatement(sb.toString()); + int paramIndex = 0; + if (catalog != null && !catalog.isEmpty()) { + ps.setString(++paramIndex, catalog); + } + if (schemaPattern != null && !schemaPattern.isEmpty()) { + ps.setString(++paramIndex, schemaPattern); + } + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public ResultSet getTableTypes() throws SQLException { + String[] tableTypesArray = new String[] {"BASE TABLE", "LOCAL TEMPORARY", "VIEW"}; + StringBuilder stringBuilder = new StringBuilder(128); + boolean first = true; + for (String tableType : tableTypesArray) { + if (!first) { + stringBuilder.append("\nUNION ALL\n"); + } + stringBuilder.append("SELECT '"); + stringBuilder.append(tableType); + stringBuilder.append("'"); + if (first) { + stringBuilder.append(" AS 'TABLE_TYPE'"); + first = false; + } + } + stringBuilder.append("\nORDER BY TABLE_TYPE"); + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery(stringBuilder.toString()); + } + + @Override + public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) + throws SQLException { + StringBuilder str = new StringBuilder(512); + + str.append("SELECT table_catalog AS 'TABLE_CAT'").append(lineSeparator()); + str.append(", table_schema AS 'TABLE_SCHEM'").append(lineSeparator()); + str.append(", table_name AS 'TABLE_NAME'").append(lineSeparator()); + str.append(", table_type AS 'TABLE_TYPE'").append(lineSeparator()); + str.append(", TABLE_COMMENT AS 'REMARKS'").append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'TYPE_CAT'").append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'TYPE_SCHEM'").append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'TYPE_NAME'").append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'SELF_REFERENCING_COL_NAME'").append(lineSeparator()); + str.append(", NULL::VARCHAR AS 'REF_GENERATION'").append(lineSeparator()); + str.append("FROM information_schema.tables").append(lineSeparator()); + + // tableNamePattern - a table name pattern; must match the table name as it + // is stored in the database + if (tableNamePattern == null) { + // non-standard behavior. + tableNamePattern = "%"; + } + str.append("WHERE table_name LIKE ?").append(lineSeparator()); + + // catalog - a catalog name; must match the catalog name as it is stored in + // the database; + // "" retrieves those without a catalog; + // null means that the catalog name should not be used to narrow the search + boolean hasCatalogParam = false; + if (catalog != null) { + str.append("AND table_catalog "); + if (catalog.isEmpty()) { + str.append("IS NULL").append(lineSeparator()); + } else { + str.append("= ?").append(lineSeparator()); + hasCatalogParam = true; + } + } + + // schemaPattern - a schema name pattern; must match the schema name as it + // is stored in the database; + // "" retrieves those without a schema; + // null means that the schema name should not be used to narrow the search + boolean hasSchemaParam = false; + if (schemaPattern != null) { + str.append("AND table_schema "); + if (schemaPattern.isEmpty()) { + str.append("IS NULL").append(lineSeparator()); + } else { + str.append("LIKE ?").append(lineSeparator()); + hasSchemaParam = true; + } + } + + if (types != null && types.length > 0) { + str.append("AND table_type IN (").append(lineSeparator()); + for (int i = 0; i < types.length; i++) { + if (i > 0) { + str.append(','); + } + str.append('?'); + } + str.append(')'); + } + + // ordered by TABLE_TYPE, TABLE_CAT, TABLE_SCHEM and TABLE_NAME. + str.append("ORDER BY table_type").append(lineSeparator()); + str.append(", table_catalog").append(lineSeparator()); + str.append(", table_schema").append(lineSeparator()); + str.append(", table_name").append(lineSeparator()); + + PreparedStatement ps = conn.prepareStatement(str.toString()); + + int paramOffset = 1; + ps.setString(paramOffset++, tableNamePattern); + + if (hasCatalogParam) { + ps.setString(paramOffset++, catalog); + } + if (hasSchemaParam) { + ps.setString(paramOffset++, schemaPattern); + } + + if (types != null && types.length > 0) { + for (int i = 0; i < types.length; i++) { + ps.setString(paramOffset + i, types[i]); + } + } + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public ResultSet getColumns(String catalogPattern, String schemaPattern, String tableNamePattern, + String columnNamePattern) throws SQLException { + if (catalogPattern == null) { + catalogPattern = "%"; + } + if (schemaPattern == null) { + schemaPattern = "%"; + } + if (tableNamePattern == null) { + tableNamePattern = "%"; + } + if (columnNamePattern == null) { + columnNamePattern = "%"; + } + + PreparedStatement ps = + conn.prepareStatement("SELECT " + + "table_catalog AS 'TABLE_CAT', " + + "table_schema AS 'TABLE_SCHEM', " + + "table_name AS 'TABLE_NAME', " + + "column_name as 'COLUMN_NAME', " + + makeDataMap("regexp_replace(c.data_type, '\\(.*\\)', '')", "DATA_TYPE") + ", " + + "c.data_type AS 'TYPE_NAME', " + + "NULL AS 'COLUMN_SIZE', NULL AS 'BUFFER_LENGTH', " + + "numeric_precision AS 'DECIMAL_DIGITS', " + + "10 AS 'NUM_PREC_RADIX', " + + "CASE WHEN is_nullable = 'YES' THEN 1 else 0 END AS 'NULLABLE', " + + "COLUMN_COMMENT as 'REMARKS', " + + "column_default AS 'COLUMN_DEF', " + + "NULL AS 'SQL_DATA_TYPE', " + + "NULL AS 'SQL_DATETIME_SUB', " + + "NULL AS 'CHAR_OCTET_LENGTH', " + + "ordinal_position AS 'ORDINAL_POSITION', " + + "is_nullable AS 'IS_NULLABLE', " + + "NULL AS 'SCOPE_CATALOG', " + + "NULL AS 'SCOPE_SCHEMA', " + + "NULL AS 'SCOPE_TABLE', " + + "NULL AS 'SOURCE_DATA_TYPE', " + + "'' AS 'IS_AUTOINCREMENT', " + + "'' AS 'IS_GENERATEDCOLUMN' " + + "FROM information_schema.columns c " + + "WHERE table_catalog LIKE ? AND " + + "table_schema LIKE ? AND " + + "table_name LIKE ? AND " + + "column_name LIKE ? " + + "ORDER BY \"TABLE_CAT\",\"TABLE_SCHEM\", \"TABLE_NAME\", \"ORDINAL_POSITION\""); + ps.setString(1, catalogPattern); + ps.setString(2, schemaPattern); + ps.setString(3, tableNamePattern); + ps.setString(4, columnNamePattern); + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public ResultSet getColumnPrivileges(String catalog, String schema, String table, String columnNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getColumnPrivileges"); + } + + @Override + public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern) + throws SQLException { + throw new SQLFeatureNotSupportedException("getTablePrivileges"); + } + + @Override + public ResultSet getProcedures(String catalog, String schemaPattern, String procedureNamePattern) + throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery("SELECT NULL WHERE FALSE"); + } + + @Override + public ResultSet getProcedureColumns(String catalog, String schemaPattern, String procedureNamePattern, + String columnNamePattern) throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery("SELECT NULL WHERE FALSE"); + } + + @Override + public ResultSet getBestRowIdentifier(String catalog, String schema, String table, int scope, boolean nullable) + throws SQLException { + throw new SQLFeatureNotSupportedException("getBestRowIdentifier"); + } + + @Override + public ResultSet getVersionColumns(String catalog, String schema, String table) throws SQLException { + throw new SQLFeatureNotSupportedException("getVersionColumns"); + } + + @Override + public ResultSet getPrimaryKeys(String catalog, String schema, String table) throws SQLException { + StringBuilder pw = new StringBuilder(512); + pw.append("WITH constraint_columns AS (").append(lineSeparator()); + pw.append("SELECT").append(lineSeparator()); + pw.append(" database_name AS \"TABLE_CAT\"").append(lineSeparator()); + pw.append(", schema_name AS \"TABLE_SCHEM\"").append(lineSeparator()); + pw.append(", table_name AS \"TABLE_NAME\"").append(lineSeparator()); + pw.append(", unnest(constraint_column_names) AS \"COLUMN_NAME\"").append(lineSeparator()); + pw.append(", CAST(NULL AS VARCHAR) AS \"PK_NAME\"").append(lineSeparator()); + pw.append("FROM duckdb_constraints").append(lineSeparator()); + pw.append("WHERE constraint_type = 'PRIMARY KEY'").append(lineSeparator()); + // catalog param + if (catalog != null) { + if (catalog.isEmpty()) { + pw.append("AND database_name IS NULL").append(lineSeparator()); + } else { + pw.append("AND database_name = ?").append(lineSeparator()); + } + } + // schema param + if (schema != null) { + if (schema.isEmpty()) { + pw.append("AND schema_name IS NULL").append(lineSeparator()); + } else { + pw.append("AND schema_name = ?").append(lineSeparator()); + } + } + // table name param + pw.append("AND table_name = ?").append(lineSeparator()); + + pw.append(")").append(lineSeparator()); + pw.append("SELECT \"TABLE_CAT\"").append(lineSeparator()); + pw.append(", \"TABLE_SCHEM\"").append(lineSeparator()); + pw.append(", \"TABLE_NAME\"").append(lineSeparator()); + pw.append(", \"COLUMN_NAME\"").append(lineSeparator()); + pw.append(", CAST(ROW_NUMBER() OVER ").append(lineSeparator()); + pw.append("(PARTITION BY \"TABLE_CAT\", \"TABLE_SCHEM\", \"TABLE_NAME\") AS INT) AS \"KEY_SEQ\"") + .append(lineSeparator()); + pw.append(", \"PK_NAME\"").append(lineSeparator()); + pw.append("FROM constraint_columns").append(lineSeparator()); + pw.append("ORDER BY \"TABLE_CAT\", \"TABLE_SCHEM\", \"TABLE_NAME\", \"KEY_SEQ\"").append(lineSeparator()); + + int paramIndex = 1; + PreparedStatement ps = conn.prepareStatement(pw.toString()); + + if (catalog != null && !catalog.isEmpty()) { + ps.setString(paramIndex++, catalog); } - str.append('?'); - } - str.append(')'); - } - - // ordered by TABLE_TYPE, TABLE_CAT, TABLE_SCHEM and TABLE_NAME. - str.append("ORDER BY table_type").append(lineSeparator()); - str.append(", table_catalog").append(lineSeparator()); - str.append(", table_schema").append(lineSeparator()); - str.append(", table_name").append(lineSeparator()); - - PreparedStatement ps = conn.prepareStatement(str.toString()); - - int paramOffset = 1; - ps.setString(paramOffset++, tableNamePattern); - - if (hasCatalogParam) { - ps.setString(paramOffset++, catalog); - } - if (hasSchemaParam) { - ps.setString(paramOffset++, schemaPattern); - } - - if (types != null && types.length > 0) { - for (int i = 0; i < types.length; i++) { - ps.setString(paramOffset + i, types[i]); - } - } - ps.closeOnCompletion(); - return ps.executeQuery(); - } - - @Override - public ResultSet getColumns(String catalogPattern, String schemaPattern, - String tableNamePattern, String columnNamePattern) - throws SQLException { - if (catalogPattern == null) { - catalogPattern = "%"; - } - if (schemaPattern == null) { - schemaPattern = "%"; - } - if (tableNamePattern == null) { - tableNamePattern = "%"; - } - if (columnNamePattern == null) { - columnNamePattern = "%"; - } - - PreparedStatement ps = conn.prepareStatement( - "SELECT " - + "table_catalog AS 'TABLE_CAT', " - + "table_schema AS 'TABLE_SCHEM', " - + "table_name AS 'TABLE_NAME', " - + "column_name as 'COLUMN_NAME', " + - makeDataMap("regexp_replace(c.data_type, '\\(.*\\)', '')", - "DATA_TYPE") + - ", " - + "c.data_type AS 'TYPE_NAME', " - + "NULL AS 'COLUMN_SIZE', NULL AS 'BUFFER_LENGTH', " - + "numeric_precision AS 'DECIMAL_DIGITS', " - + "10 AS 'NUM_PREC_RADIX', " - + "CASE WHEN is_nullable = 'YES' THEN 1 else 0 END AS 'NULLABLE', " - + "COLUMN_COMMENT as 'REMARKS', " - + "column_default AS 'COLUMN_DEF', " - + "NULL AS 'SQL_DATA_TYPE', " - + "NULL AS 'SQL_DATETIME_SUB', " - + "NULL AS 'CHAR_OCTET_LENGTH', " - + "ordinal_position AS 'ORDINAL_POSITION', " - + "is_nullable AS 'IS_NULLABLE', " - + "NULL AS 'SCOPE_CATALOG', " - + "NULL AS 'SCOPE_SCHEMA', " - + "NULL AS 'SCOPE_TABLE', " - + "NULL AS 'SOURCE_DATA_TYPE', " - + "'' AS 'IS_AUTOINCREMENT', " - + "'' AS 'IS_GENERATEDCOLUMN' " - + "FROM information_schema.columns c " - + "WHERE table_catalog LIKE ? AND " - + "table_schema LIKE ? AND " - + "table_name LIKE ? AND " - + "column_name LIKE ? " - + - "ORDER BY \"TABLE_CAT\",\"TABLE_SCHEM\", \"TABLE_NAME\", \"ORDINAL_POSITION\""); - ps.setString(1, catalogPattern); - ps.setString(2, schemaPattern); - ps.setString(3, tableNamePattern); - ps.setString(4, columnNamePattern); - ps.closeOnCompletion(); - return ps.executeQuery(); - } - - @Override - public ResultSet getColumnPrivileges(String catalog, String schema, - String table, String columnNamePattern) - throws SQLException { - throw new SQLFeatureNotSupportedException("getColumnPrivileges"); - } - - @Override - public ResultSet getTablePrivileges(String catalog, String schemaPattern, - String tableNamePattern) - throws SQLException { - throw new SQLFeatureNotSupportedException("getTablePrivileges"); - } - - @Override - public ResultSet getProcedures(String catalog, String schemaPattern, - String procedureNamePattern) - throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery("SELECT NULL WHERE FALSE"); - } - - @Override - public ResultSet getProcedureColumns(String catalog, String schemaPattern, - String procedureNamePattern, - String columnNamePattern) - throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery("SELECT NULL WHERE FALSE"); - } - - @Override - public ResultSet getBestRowIdentifier(String catalog, String schema, - String table, int scope, - boolean nullable) throws SQLException { - throw new SQLFeatureNotSupportedException("getBestRowIdentifier"); - } - - @Override - public ResultSet getVersionColumns(String catalog, String schema, - String table) throws SQLException { - throw new SQLFeatureNotSupportedException("getVersionColumns"); - } - - @Override - public ResultSet getPrimaryKeys(String catalog, String schema, String table) - throws SQLException { - StringBuilder pw = new StringBuilder(512); - pw.append("WITH constraint_columns AS (").append(lineSeparator()); - pw.append("SELECT").append(lineSeparator()); - pw.append(" database_name AS \"TABLE_CAT\"").append(lineSeparator()); - pw.append(", schema_name AS \"TABLE_SCHEM\"").append(lineSeparator()); - pw.append(", table_name AS \"TABLE_NAME\"").append(lineSeparator()); - pw.append(", unnest(constraint_column_names) AS \"COLUMN_NAME\"") - .append(lineSeparator()); - pw.append(", CAST(NULL AS VARCHAR) AS \"PK_NAME\"").append(lineSeparator()); - pw.append("FROM duckdb_constraints").append(lineSeparator()); - pw.append("WHERE constraint_type = 'PRIMARY KEY'").append(lineSeparator()); - // catalog param - if (catalog != null) { - if (catalog.isEmpty()) { - pw.append("AND database_name IS NULL").append(lineSeparator()); - } else { - pw.append("AND database_name = ?").append(lineSeparator()); - } - } - // schema param - if (schema != null) { - if (schema.isEmpty()) { - pw.append("AND schema_name IS NULL").append(lineSeparator()); - } else { - pw.append("AND schema_name = ?").append(lineSeparator()); - } - } - // table name param - pw.append("AND table_name = ?").append(lineSeparator()); - - pw.append(")").append(lineSeparator()); - pw.append("SELECT \"TABLE_CAT\"").append(lineSeparator()); - pw.append(", \"TABLE_SCHEM\"").append(lineSeparator()); - pw.append(", \"TABLE_NAME\"").append(lineSeparator()); - pw.append(", \"COLUMN_NAME\"").append(lineSeparator()); - pw.append(", CAST(ROW_NUMBER() OVER ").append(lineSeparator()); - pw.append( - "(PARTITION BY \"TABLE_CAT\", \"TABLE_SCHEM\", \"TABLE_NAME\") AS INT) AS \"KEY_SEQ\"") - .append(lineSeparator()); - pw.append(", \"PK_NAME\"").append(lineSeparator()); - pw.append("FROM constraint_columns").append(lineSeparator()); - pw.append( - "ORDER BY \"TABLE_CAT\", \"TABLE_SCHEM\", \"TABLE_NAME\", \"KEY_SEQ\"") - .append(lineSeparator()); - - int paramIndex = 1; - PreparedStatement ps = conn.prepareStatement(pw.toString()); - - if (catalog != null && !catalog.isEmpty()) { - ps.setString(paramIndex++, catalog); - } - if (schema != null && !schema.isEmpty()) { - ps.setString(paramIndex++, schema); - } - ps.setString(paramIndex++, table); - ps.closeOnCompletion(); - return ps.executeQuery(); - } - - @Override - public ResultSet getImportedKeys(String catalog, String schema, String table) - throws SQLException { - throw new SQLFeatureNotSupportedException("getImportedKeys"); - } - - @Override - public ResultSet getExportedKeys(String catalog, String schema, String table) - throws SQLException { - throw new SQLFeatureNotSupportedException("getExportedKeys"); - } - - @Override - public ResultSet getCrossReference(String parentCatalog, String parentSchema, - String parentTable, String foreignCatalog, - String foreignSchema, String foreignTable) - throws SQLException { - throw new SQLFeatureNotSupportedException("getCrossReference"); - } - - @Override - public ResultSet getTypeInfo() throws SQLException { - throw new SQLFeatureNotSupportedException("getTypeInfo"); - } - - @Override - public ResultSet getIndexInfo(String catalog, String schema, String table, - boolean unique, boolean approximate) - throws SQLException { - StringBuilder sb = new StringBuilder(); - sb.append("SELECT database_name AS TABLE_CAT " - + ", schema_name AS TABLE_SCHEM " - + ", table_name AS TABLE_NAME " - + ", index_name AS INDEX_NAME " - + ", CASE WHEN is_unique THEN 0 ELSE 1 END AS NON_UNIQUE " - + ", NULL AS TYPE " - + ", NULL AS ORDINAL_POSITION " - + ", NULL AS COLUMN_NAME " - + ", NULL AS ASC_OR_DESC " - + ", NULL AS CARDINALITY " - + ", NULL AS PAGES " - + ", NULL AS FILTER_CONDITION " - + "FROM duckdb_indexes() WHERE TRUE "); - if (catalog != null) { - sb.append(" AND database_name = ?"); - } - if (schema != null) { - sb.append(" AND schema_name = ?"); - } - if (table != null) { - sb.append(" AND table_name = ?"); - } - sb.append( - " ORDER BY TABLE_CAT, TABLE_SCHEM, TABLE_NAME, NON_UNIQUE, INDEX_NAME, ORDINAL_POSITION"); - PreparedStatement ps = conn.prepareStatement(sb.toString()); - int paramIndex = 1; - if (catalog != null) { - ps.setString(paramIndex++, catalog); - } - if (schema != null) { - ps.setString(paramIndex++, schema); - } - if (table != null) { - ps.setString(paramIndex++, table); - } - ps.closeOnCompletion(); - return ps.executeQuery(); - } - - @Override - public boolean supportsResultSetType(int type) throws SQLException { - return type == ResultSet.TYPE_FORWARD_ONLY; - } - - @Override - public boolean supportsResultSetConcurrency(int type, int concurrency) - throws SQLException { - return type == ResultSet.TYPE_FORWARD_ONLY && - concurrency == ResultSet.CONCUR_READ_ONLY; - } - - @Override - public boolean ownUpdatesAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("ownUpdatesAreVisible"); - } - - @Override - public boolean ownDeletesAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("ownDeletesAreVisible"); - } - - @Override - public boolean ownInsertsAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("ownInsertsAreVisible"); - } - - @Override - public boolean othersUpdatesAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("othersUpdatesAreVisible"); - } - - @Override - public boolean othersDeletesAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("othersDeletesAreVisible"); - } - - @Override - public boolean othersInsertsAreVisible(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("othersInsertsAreVisible"); - } - - @Override - public boolean updatesAreDetected(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("updatesAreDetected"); - } - - @Override - public boolean deletesAreDetected(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("deletesAreDetected"); - } - - @Override - public boolean insertsAreDetected(int type) throws SQLException { - throw new SQLFeatureNotSupportedException("insertsAreDetected"); - } - - @Override - public boolean supportsBatchUpdates() throws SQLException { - return true; - } - - @Override - public ResultSet getUDTs(String catalog, String schemaPattern, - String typeNamePattern, int[] types) - throws SQLException { - throw new SQLFeatureNotSupportedException("getUDTs"); - } - - @Override - public Connection getConnection() throws SQLException { - return conn; - } - - @Override - public boolean supportsSavepoints() throws SQLException { - return false; - } - - @Override - public boolean supportsNamedParameters() throws SQLException { - return false; - } - - @Override - public boolean supportsMultipleOpenResults() throws SQLException { - return true; - } - - @Override - public boolean supportsGetGeneratedKeys() throws SQLException { - return false; - } - - @Override - public ResultSet getSuperTypes(String catalog, String schemaPattern, - String typeNamePattern) throws SQLException { - throw new SQLFeatureNotSupportedException("getSuperTypes"); - } - - @Override - public ResultSet getSuperTables(String catalog, String schemaPattern, - String tableNamePattern) throws SQLException { - throw new SQLFeatureNotSupportedException("getSuperTables"); - } - - @Override - public ResultSet getAttributes(String catalog, String schemaPattern, - String typeNamePattern, - String attributeNamePattern) - throws SQLException { - throw new SQLFeatureNotSupportedException("getAttributes"); - } - - @Override - public boolean supportsResultSetHoldability(int holdability) - throws SQLException { - return false; - } - - @Override - public int getResultSetHoldability() throws SQLException { - throw new SQLFeatureNotSupportedException("getResultSetHoldability"); - } - - @Override - public int getDatabaseMajorVersion() throws SQLException { - return 1; - } - - @Override - public int getDatabaseMinorVersion() throws SQLException { - return 0; - } - - @Override - public int getJDBCMajorVersion() throws SQLException { - return 1; - } - - @Override - public int getJDBCMinorVersion() throws SQLException { - return 0; - } - - @Override - public int getSQLStateType() throws SQLException { - throw new SQLFeatureNotSupportedException("getSQLStateType"); - } - - @Override - public boolean locatorsUpdateCopy() throws SQLException { - throw new SQLFeatureNotSupportedException("locatorsUpdateCopy"); - } - - @Override - public boolean supportsStatementPooling() throws SQLException { - return false; - } - - @Override - public RowIdLifetime getRowIdLifetime() throws SQLException { - throw new SQLFeatureNotSupportedException("getRowIdLifetime"); - } - - @Override - public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { - return false; - } - - @Override - public boolean autoCommitFailureClosesAllResultSets() throws SQLException { - throw new SQLFeatureNotSupportedException( - "autoCommitFailureClosesAllResultSets"); - } - - @Override - public ResultSet getClientInfoProperties() throws SQLException { - throw new SQLFeatureNotSupportedException("getClientInfoProperties"); - } - - /** - * - * @param catalog a catalog name; must match the catalog name as it - * is stored in the database; "" retrieves those without a catalog; - * null means that the catalog name should not be used to - * narrow the search - * @param schemaPattern a schema name pattern; must match the schema name - * as it is stored in the database; "" retrieves those without a - * schema; null means that the schema name should not be used to - * narrow the search - * @param functionNamePattern a function name pattern; must match the - * function name as it is stored in the database - * FUNCTION_CAT String => function catalog (may be null) - * FUNCTION_SCHEM String => function schema (may be null) - * FUNCTION_NAME String => function name. This is the name used to invoke the - * function REMARKS String => explanatory comment on the function - * FUNCTION_TYPE short => kind of function: - * - functionResultUnknown - Cannot determine if a return value or table will - * be returned - * - functionNoTable- Does not return a table - * - functionReturnsTable - Returns a table - * SPECIFIC_NAME String => the name which uniquely identifies this function - * within its schema. This is a user specified, or DBMS generated, name that - * may be different then the FUNCTION_NAME for example with overload functions - */ - @Override - public ResultSet getFunctions(String catalog, String schemaPattern, - String functionNamePattern) - throws SQLException { - try (PreparedStatement statement = conn.prepareStatement( - "SELECT " - + "null as FUNCTION_CAT, " - + "function_name as FUNCTION_NAME, " - + "schema_name as FUNCTION_SCHEM, " - + "description as REMARKS," - + "CASE function_type " - + "WHEN 'table' THEN " + functionReturnsTable + " " - + "WHEN 'table_macro' THEN " + functionReturnsTable + " " - + "ELSE " + functionNoTable + " " - + "END as FUNCTION_TYPE " - + "FROM duckdb_functions() " - + "WHERE function_name like ? and " - + "schema_name like ?")) { - statement.setString(1, functionNamePattern); - statement.setString(2, schemaPattern); - - CachedRowSet cachedRowSet = - RowSetProvider.newFactory().createCachedRowSet(); - cachedRowSet.populate(statement.executeQuery()); - return cachedRowSet; - } - } - - @Override - public ResultSet getFunctionColumns(String catalog, String schemaPattern, - String functionNamePattern, - String columnNamePattern) - throws SQLException { - Statement statement = conn.createStatement(); - statement.closeOnCompletion(); - return statement.executeQuery("SELECT NULL WHERE FALSE"); - } - - @Override - public ResultSet getPseudoColumns(String catalog, String schemaPattern, - String tableNamePattern, - String columnNamePattern) - throws SQLException { - throw new SQLFeatureNotSupportedException("getPseudoColumns"); - } - - @Override - public boolean generatedKeyAlwaysReturned() throws SQLException { - throw new SQLFeatureNotSupportedException("generatedKeyAlwaysReturned"); - } - - static String dataMap; - static { - dataMap = makeCase( - Arrays.stream(DuckDBColumnType.values()) - .collect(Collectors.toMap(ty - -> ty.name().replace("_", " "), - DuckDBResultSetMetaData::type_to_int))); - } - - private static String makeCase(Map values) { - return values.entrySet() - .stream() - .map(ty -> { - T value = ty.getValue(); - return String.format( - "WHEN '%s' THEN %s ", ty.getKey(), - value instanceof String ? String.format("'%s'", value) : value); - }) - .collect(Collectors.joining()); - } - - /** - * @param srcColumnName - * @param destColumnName - * @return - * @see DuckDBResultSetMetaData#type_to_int(DuckDBColumnType) - */ - private static String makeDataMap(String srcColumnName, - String destColumnName) { - return String.format("CASE %s %s ELSE %d END as %s", srcColumnName, dataMap, - Types.JAVA_OBJECT, destColumnName); - } + if (schema != null && !schema.isEmpty()) { + ps.setString(paramIndex++, schema); + } + ps.setString(paramIndex++, table); + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { + throw new SQLFeatureNotSupportedException("getImportedKeys"); + } + + @Override + public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { + throw new SQLFeatureNotSupportedException("getExportedKeys"); + } + + @Override + public ResultSet getCrossReference(String parentCatalog, String parentSchema, String parentTable, + String foreignCatalog, String foreignSchema, String foreignTable) + throws SQLException { + throw new SQLFeatureNotSupportedException("getCrossReference"); + } + + @Override + public ResultSet getTypeInfo() throws SQLException { + throw new SQLFeatureNotSupportedException("getTypeInfo"); + } + + @Override + public ResultSet getIndexInfo(String catalog, String schema, String table, boolean unique, boolean approximate) + throws SQLException { + StringBuilder sb = new StringBuilder(); + sb.append("SELECT database_name AS TABLE_CAT " + + ", schema_name AS TABLE_SCHEM " + + ", table_name AS TABLE_NAME " + + ", index_name AS INDEX_NAME " + + ", CASE WHEN is_unique THEN 0 ELSE 1 END AS NON_UNIQUE " + + ", NULL AS TYPE " + + ", NULL AS ORDINAL_POSITION " + + ", NULL AS COLUMN_NAME " + + ", NULL AS ASC_OR_DESC " + + ", NULL AS CARDINALITY " + + ", NULL AS PAGES " + + ", NULL AS FILTER_CONDITION " + + "FROM duckdb_indexes() WHERE TRUE "); + if (catalog != null) { + sb.append(" AND database_name = ?"); + } + if (schema != null) { + sb.append(" AND schema_name = ?"); + } + if (table != null) { + sb.append(" AND table_name = ?"); + } + sb.append(" ORDER BY TABLE_CAT, TABLE_SCHEM, TABLE_NAME, NON_UNIQUE, INDEX_NAME, ORDINAL_POSITION"); + PreparedStatement ps = conn.prepareStatement(sb.toString()); + int paramIndex = 1; + if (catalog != null) { + ps.setString(paramIndex++, catalog); + } + if (schema != null) { + ps.setString(paramIndex++, schema); + } + if (table != null) { + ps.setString(paramIndex++, table); + } + ps.closeOnCompletion(); + return ps.executeQuery(); + } + + @Override + public boolean supportsResultSetType(int type) throws SQLException { + return type == ResultSet.TYPE_FORWARD_ONLY; + } + + @Override + public boolean supportsResultSetConcurrency(int type, int concurrency) throws SQLException { + return type == ResultSet.TYPE_FORWARD_ONLY && concurrency == ResultSet.CONCUR_READ_ONLY; + } + + @Override + public boolean ownUpdatesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("ownUpdatesAreVisible"); + } + + @Override + public boolean ownDeletesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("ownDeletesAreVisible"); + } + + @Override + public boolean ownInsertsAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("ownInsertsAreVisible"); + } + + @Override + public boolean othersUpdatesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("othersUpdatesAreVisible"); + } + + @Override + public boolean othersDeletesAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("othersDeletesAreVisible"); + } + + @Override + public boolean othersInsertsAreVisible(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("othersInsertsAreVisible"); + } + + @Override + public boolean updatesAreDetected(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("updatesAreDetected"); + } + + @Override + public boolean deletesAreDetected(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("deletesAreDetected"); + } + + @Override + public boolean insertsAreDetected(int type) throws SQLException { + throw new SQLFeatureNotSupportedException("insertsAreDetected"); + } + + @Override + public boolean supportsBatchUpdates() throws SQLException { + return true; + } + + @Override + public ResultSet getUDTs(String catalog, String schemaPattern, String typeNamePattern, int[] types) + throws SQLException { + throw new SQLFeatureNotSupportedException("getUDTs"); + } + + @Override + public Connection getConnection() throws SQLException { + return conn; + } + + @Override + public boolean supportsSavepoints() throws SQLException { + return false; + } + + @Override + public boolean supportsNamedParameters() throws SQLException { + return false; + } + + @Override + public boolean supportsMultipleOpenResults() throws SQLException { + return true; + } + + @Override + public boolean supportsGetGeneratedKeys() throws SQLException { + return false; + } + + @Override + public ResultSet getSuperTypes(String catalog, String schemaPattern, String typeNamePattern) throws SQLException { + throw new SQLFeatureNotSupportedException("getSuperTypes"); + } + + @Override + public ResultSet getSuperTables(String catalog, String schemaPattern, String tableNamePattern) throws SQLException { + throw new SQLFeatureNotSupportedException("getSuperTables"); + } + + @Override + public ResultSet getAttributes(String catalog, String schemaPattern, String typeNamePattern, + String attributeNamePattern) throws SQLException { + throw new SQLFeatureNotSupportedException("getAttributes"); + } + + @Override + public boolean supportsResultSetHoldability(int holdability) throws SQLException { + return false; + } + + @Override + public int getResultSetHoldability() throws SQLException { + throw new SQLFeatureNotSupportedException("getResultSetHoldability"); + } + + @Override + public int getDatabaseMajorVersion() throws SQLException { + return 1; + } + + @Override + public int getDatabaseMinorVersion() throws SQLException { + return 0; + } + + @Override + public int getJDBCMajorVersion() throws SQLException { + return 1; + } + + @Override + public int getJDBCMinorVersion() throws SQLException { + return 0; + } + + @Override + public int getSQLStateType() throws SQLException { + throw new SQLFeatureNotSupportedException("getSQLStateType"); + } + + @Override + public boolean locatorsUpdateCopy() throws SQLException { + throw new SQLFeatureNotSupportedException("locatorsUpdateCopy"); + } + + @Override + public boolean supportsStatementPooling() throws SQLException { + return false; + } + + @Override + public RowIdLifetime getRowIdLifetime() throws SQLException { + throw new SQLFeatureNotSupportedException("getRowIdLifetime"); + } + + @Override + public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { + return false; + } + + @Override + public boolean autoCommitFailureClosesAllResultSets() throws SQLException { + throw new SQLFeatureNotSupportedException("autoCommitFailureClosesAllResultSets"); + } + + @Override + public ResultSet getClientInfoProperties() throws SQLException { + throw new SQLFeatureNotSupportedException("getClientInfoProperties"); + } + + /** + * + * @param catalog a catalog name; must match the catalog name as it + * is stored in the database; "" retrieves those without a catalog; + * null means that the catalog name should not be used to + * narrow the search + * @param schemaPattern a schema name pattern; must match the schema name + * as it is stored in the database; "" retrieves those without a + * schema; null means that the schema name should not be used to + * narrow the search + * @param functionNamePattern a function name pattern; must match the + * function name as it is stored in the database + * FUNCTION_CAT String => function catalog (may be null) + * FUNCTION_SCHEM String => function schema (may be null) + * FUNCTION_NAME String => function name. This is the name used to invoke the + * function REMARKS String => explanatory comment on the function + * FUNCTION_TYPE short => kind of function: + * - functionResultUnknown - Cannot determine if a return value or table will + * be returned + * - functionNoTable- Does not return a table + * - functionReturnsTable - Returns a table + * SPECIFIC_NAME String => the name which uniquely identifies this function + * within its schema. This is a user specified, or DBMS generated, name that + * may be different then the FUNCTION_NAME for example with overload functions + */ + @Override + public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern) + throws SQLException { + try (PreparedStatement statement = + conn.prepareStatement("SELECT " + + "null as FUNCTION_CAT, " + + "function_name as FUNCTION_NAME, " + + "schema_name as FUNCTION_SCHEM, " + + "description as REMARKS," + + "CASE function_type " + + "WHEN 'table' THEN " + functionReturnsTable + " " + + "WHEN 'table_macro' THEN " + functionReturnsTable + " " + + "ELSE " + functionNoTable + " " + + "END as FUNCTION_TYPE " + + "FROM duckdb_functions() " + + "WHERE function_name like ? and " + + "schema_name like ?")) { + statement.setString(1, functionNamePattern); + statement.setString(2, schemaPattern); + + CachedRowSet cachedRowSet = RowSetProvider.newFactory().createCachedRowSet(); + cachedRowSet.populate(statement.executeQuery()); + return cachedRowSet; + } + } + + @Override + public ResultSet getFunctionColumns(String catalog, String schemaPattern, String functionNamePattern, + String columnNamePattern) throws SQLException { + Statement statement = conn.createStatement(); + statement.closeOnCompletion(); + return statement.executeQuery("SELECT NULL WHERE FALSE"); + } + + @Override + public ResultSet getPseudoColumns(String catalog, String schemaPattern, String tableNamePattern, + String columnNamePattern) throws SQLException { + throw new SQLFeatureNotSupportedException("getPseudoColumns"); + } + + @Override + public boolean generatedKeyAlwaysReturned() throws SQLException { + throw new SQLFeatureNotSupportedException("generatedKeyAlwaysReturned"); + } + + static String dataMap; + static { + dataMap = makeCase( + Arrays.stream(DuckDBColumnType.values()) + .collect(Collectors.toMap(ty -> ty.name().replace("_", " "), DuckDBResultSetMetaData::type_to_int))); + } + + private static String makeCase(Map values) { + return values.entrySet() + .stream() + .map(ty -> { + T value = ty.getValue(); + return String.format("WHEN '%s' THEN %s ", ty.getKey(), + value instanceof String ? String.format("'%s'", value) : value); + }) + .collect(Collectors.joining()); + } + + /** + * @param srcColumnName + * @param destColumnName + * @return + * @see DuckDBResultSetMetaData#type_to_int(DuckDBColumnType) + */ + private static String makeDataMap(String srcColumnName, String destColumnName) { + return String.format("CASE %s %s ELSE %d END as %s", srcColumnName, dataMap, Types.JAVA_OBJECT, destColumnName); + } } diff --git a/src/test/test.iml b/src/test/test.iml new file mode 100644 index 00000000..a0e49a3b --- /dev/null +++ b/src/test/test.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file