From e7fe0a97452e1e6f11d751833a2223e1f922531e Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Mon, 16 Jan 2023 15:18:22 +0400 Subject: [PATCH 01/10] feat: Add an ability to read metadata for attached databases --- src/main/java/org/sqlite/SQLiteConfig.java | 33 +- .../org/sqlite/core/CoreDatabaseMetaData.java | 29 ++ .../sqlite/jdbc3/JDBC3DatabaseMetaData.java | 119 ++++--- src/test/java/org/sqlite/DBMetaDataTest.java | 329 +++++++++++++++++- 4 files changed, 449 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/sqlite/SQLiteConfig.java b/src/main/java/org/sqlite/SQLiteConfig.java index 56b8bc26f..04b9c66dd 100755 --- a/src/main/java/org/sqlite/SQLiteConfig.java +++ b/src/main/java/org/sqlite/SQLiteConfig.java @@ -56,6 +56,7 @@ public class SQLiteConfig { private final int busyTimeout; private boolean explicitReadOnly; + private boolean readAttachedDatabases; private final SQLiteConnectionConfig defaultConnectionConfig; @@ -93,6 +94,10 @@ public SQLiteConfig(Properties prop) { this.explicitReadOnly = Boolean.parseBoolean( pragmaTable.getProperty(Pragma.JDBC_EXPLICIT_READONLY.pragmaName, "false")); + this.readAttachedDatabases = + Boolean.parseBoolean( + pragmaTable.getProperty( + Pragma.METADATA_READ_ATTACHED_DATABASES.pragmaName, "false")); } public SQLiteConnectionConfig newConnectionConfig() { @@ -186,8 +191,9 @@ public void apply(Connection conn) throws SQLException { pragmaParams.remove(Pragma.LIMIT_WORKER_THREADS.pragmaName); pragmaParams.remove(Pragma.LIMIT_PAGE_COUNT.pragmaName); - // exclude this "fake" pragma from execution + // exclude these "fake" pragmas from execution pragmaParams.remove(Pragma.JDBC_EXPLICIT_READONLY.pragmaName); + pragmaParams.remove(Pragma.METADATA_READ_ATTACHED_DATABASES.pragmaName); Statement stat = conn.createStatement(); try { @@ -330,6 +336,10 @@ public Properties toProperties() { defaultConnectionConfig.getDateStringFormat()); pragmaTable.setProperty( Pragma.JDBC_EXPLICIT_READONLY.pragmaName, this.explicitReadOnly ? "true" : "false"); + pragmaTable.setProperty( + Pragma.METADATA_READ_ATTACHED_DATABASES.pragmaName, + this.readAttachedDatabases ? "true" : "false" + ); return pragmaTable; } @@ -373,6 +383,20 @@ public void setExplicitReadOnly(boolean readOnly) { this.explicitReadOnly = readOnly; } + /** @return true if reading attached databases is allowed */ + public boolean isReadAttachedDatabases() { + return readAttachedDatabases; + } + + /** + * Enable reading attached databases in metadata, they will be shown as schemas + * + * @param readAttachedDatabases whether to read attached databases + */ + public void setReadAttachedDatabases(boolean readAttachedDatabases) { + this.readAttachedDatabases = readAttachedDatabases; + } + public enum Pragma { // Parameters requiring SQLite3 API invocation @@ -534,8 +558,11 @@ public enum Pragma { // extensions: "fake" pragmas to allow conformance with JDBC JDBC_EXPLICIT_READONLY( - "jdbc.explicit_readonly", "Set explicit read only transactions", null); - + "jdbc.explicit_readonly", "Set explicit read only transactions", null), + // "fake" pragma to allow configurating metadata reading by driver + METADATA_READ_ATTACHED_DATABASES("metadata.read_attached_databases", + "Set read attached databases as schemas", + OnOff); public final String pragmaName; public final String[] choices; public final String description; diff --git a/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java b/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java index 1c6a4fdf6..fdb8eabef 100644 --- a/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java +++ b/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java @@ -184,6 +184,35 @@ protected String escape(final String val) { return buf.toString(); } + /** + * Returns line without changes or with escaped schema prefix + * @param schema schema name + * @param line of text to prepend to + * @return The SQL escaped schema name with dot or empty string + */ + protected String prependSchemaPrefix(String schema, String line) { + if (schema == null) { + return line; + } else { + return escape(schema) + "." + line; + } + } + + /** + * Adds line without changes or with escaped schema prefix + * @param sql String builder for sql request + * @param schema schema name + * @param line line to prepend schema prefix to + */ + protected void prependSchemaPrefix(StringBuilder sql, String schema, String line) { + if (schema == null) { + sql.append(line); + } else { + sql.append(schema).append('.').append(line); + } + } + + // inner classes /** Pattern used to extract column order for an unnamed primary key. */ diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java index 4416d239b..2a8c0c358 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -681,7 +681,7 @@ public boolean supportsSavepoints() { /** @see java.sql.DatabaseMetaData#supportsSchemasInDataManipulation() */ public boolean supportsSchemasInDataManipulation() { - return false; + return conn.getDatabase().getConfig().isReadAttachedDatabases(); } /** @see java.sql.DatabaseMetaData#supportsSchemasInIndexDefinitions() */ @@ -915,7 +915,7 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co checkOpen(); StringBuilder sql = new StringBuilder(700); - sql.append("select null as TABLE_CAT, null as TABLE_SCHEM, tblname as TABLE_NAME, ") + sql.append("select null as TABLE_CAT, ").append(quote(s)).append(" as TABLE_SCHEM, tblname as TABLE_NAME, ") .append( "cn as COLUMN_NAME, ct as DATA_TYPE, tn as TYPE_NAME, colSize as COLUMN_SIZE, ") .append( @@ -948,12 +948,10 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co ResultSet rsColAutoinc = null; try { statColAutoinc = conn.createStatement(); - rsColAutoinc = - statColAutoinc.executeQuery( - "SELECT LIKE('%autoincrement%', LOWER(sql)) FROM sqlite_master " - + "WHERE LOWER(name) = LOWER('" - + escape(tableName) - + "') AND TYPE IN ('table', 'view')"); + rsColAutoinc = statColAutoinc.executeQuery( + "SELECT LIKE('%autoincrement%', LOWER(sql)) FROM " + prependSchemaPrefix(s, + "sqlite_master WHERE LOWER(name) = LOWER('") + escape(tableName) + + "') AND TYPE IN ('table', 'view')"); rsColAutoinc.next(); isAutoIncrement = rsColAutoinc.getInt(1) == 1; } finally { @@ -974,7 +972,8 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co } // For each table, get the column info and build into overall SQL - String pragmaStatement = "PRAGMA table_xinfo('" + escape(tableName) + "')"; + String pragmaStatement = "PRAGMA " + prependSchemaPrefix(s, "table_xinfo('" + escape(tableName) + + "')"); try (Statement colstat = conn.createStatement(); ResultSet rscol = colstat.executeQuery(pragmaStatement)) { @@ -1173,9 +1172,12 @@ public ResultSet getCrossReference( /** @see java.sql.DatabaseMetaData#getSchemas() */ public ResultSet getSchemas() throws SQLException { if (getSchemas == null) { - getSchemas = - conn.prepareStatement( - "select null as TABLE_SCHEM, null as TABLE_CATALOG limit 0;"); + if (conn.getDatabase().getConfig().isReadAttachedDatabases()) { + getSchemas = conn.prepareStatement( + "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list;"); + } else { + getSchemas = conn.prepareStatement("select null as TABLE_SCHEM, null as TABLE_CATALOG limit 0;"); + } } return getSchemas.executeQuery(); @@ -1195,12 +1197,12 @@ public ResultSet getCatalogs() throws SQLException { * java.lang.String) */ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLException { - PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table); + PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table, s); String[] columns = pkFinder.getColumns(); Statement stat = conn.createStatement(); StringBuilder sql = new StringBuilder(512); - sql.append("select null as TABLE_CAT, null as TABLE_SCHEM, '") + sql.append("select null as TABLE_CAT, ").append(quote(s)).append(" as TABLE_SCHEM, '") .append(escape(table)) .append("' as TABLE_NAME, cn as COLUMN_NAME, ks as KEY_SEQ, pk as PK_NAME from ("); @@ -1245,12 +1247,13 @@ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLExce */ public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { - PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table); + PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table, schema); String[] pkColumns = pkFinder.getColumns(); Statement stat = conn.createStatement(); catalog = (catalog != null) ? quote(catalog) : null; - schema = (schema != null) ? quote(schema) : null; + + String quotedSchema = (schema != null) ? quote(schema) : null; StringBuilder exportedKeysQuery = new StringBuilder(512); @@ -1259,8 +1262,11 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) if (pkColumns != null) { // retrieve table list ArrayList tableList; - try (ResultSet rs = - stat.executeQuery("select name from sqlite_master where type = 'table'")) { + try ( + ResultSet rs = stat.executeQuery( + "select name from " + prependSchemaPrefix(schema, "sqlite_master where type = " + + "'table'")) + ) { tableList = new ArrayList<>(); while (rs.next()) { @@ -1276,7 +1282,7 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) // find imported keys for each table for (String tbl : tableList) { - final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(tbl); + final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(tbl, schema); List fkNames = impFkFinder.getFkList(); for (ForeignKey foreignKey : fkNames) { @@ -1340,7 +1346,7 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) sql.append("select ") .append(catalog) .append(" as PKTABLE_CAT, ") - .append(schema) + .append(quotedSchema) .append(" as PKTABLE_SCHEM, ") .append(quote(target)) .append(" as PKTABLE_NAME, ") @@ -1348,7 +1354,7 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) .append(" as PKCOLUMN_NAME, ") .append(catalog) .append(" as FKTABLE_CAT, ") - .append(schema) + .append(quotedSchema) .append(" as FKTABLE_SCHEM, ") .append(hasImportedKey ? "fkt" : "''") .append(" as FKTABLE_NAME, ") @@ -1426,7 +1432,7 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) return ((CoreStatement) stat).executeQuery(sql.toString(), true); } - final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(table); + final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(table, schema); List fkNames = impFkFinder.getFkList(); int i = 0; @@ -1439,7 +1445,7 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) String pkName = null; try { - PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(PKTabName); + PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(PKTabName, schema); pkName = pkFinder.getName(); if (PKColName == null) { PKColName = pkFinder.getColumns()[0]; @@ -1523,7 +1529,9 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole // define the column header // this is from the JDBC spec, it is part of the driver protocol - sql.append("select null as TABLE_CAT, null as TABLE_SCHEM, '") + sql.append("select null as TABLE_CAT,") + .append(quote(s)) + .append(" as TABLE_SCHEM, '") .append(escape(table)) .append( "' as TABLE_NAME, un as NON_UNIQUE, null as INDEX_QUALIFIER, n as INDEX_NAME, ") @@ -1533,7 +1541,7 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole "cn as COLUMN_NAME, null as ASC_OR_DESC, 0 as CARDINALITY, 0 as PAGES, null as FILTER_CONDITION from ("); // this always returns a result set now, previously threw exception - rs = stat.executeQuery("pragma index_list('" + escape(table) + "');"); + rs = stat.executeQuery("pragma " + prependSchemaPrefix(s, "index_list('" + escape(table) + "');")); ArrayList> indexList = new ArrayList<>(); while (rs.next()) { @@ -1557,7 +1565,7 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole while (indexIterator.hasNext()) { currentIndex = indexIterator.next(); String indexName = currentIndex.get(0).toString(); - rs = stat.executeQuery("pragma index_info('" + escape(indexName) + "');"); + rs = stat.executeQuery("pragma " + prependSchemaPrefix(s, "index_info('" + escape(indexName) + "');")); while (rs.next()) { @@ -1704,7 +1712,8 @@ public synchronized ResultSet getTables( sql.append(" NAME,").append("\n"); sql.append(" UPPER(TYPE) AS TYPE").append("\n"); sql.append(" FROM").append("\n"); - sql.append(" sqlite_master").append("\n"); + sql.append(" "); + prependSchemaPrefix(sql, s, "sqlite_master\n"); sql.append(" WHERE").append("\n"); sql.append(" NAME NOT LIKE 'sqlite\\_%' ESCAPE '\\'").append("\n"); sql.append(" AND UPPER(TYPE) IN ('TABLE', 'VIEW')").append("\n"); @@ -1719,7 +1728,8 @@ public synchronized ResultSet getTables( sql.append(" NAME,").append("\n"); sql.append(" 'SYSTEM TABLE' AS TYPE").append("\n"); sql.append(" FROM").append("\n"); - sql.append(" sqlite_master").append("\n"); + sql.append(" "); + prependSchemaPrefix(sql, s, "sqlite_master\n"); sql.append(" WHERE").append("\n"); sql.append(" NAME LIKE 'sqlite\\_%' ESCAPE '\\'").append("\n"); sql.append(" )").append("\n"); @@ -1962,6 +1972,7 @@ public ResultSet getFunctionColumns(String a, String b, String c, String d) /** Parses the sqlite_master table for a table's primary key */ class PrimaryKeyFinder { + String schema; /** The table name. */ String table; @@ -1971,15 +1982,20 @@ class PrimaryKeyFinder { /** The column(s) for the primary key. */ String[] pkColumns = null; + public PrimaryKeyFinder(String table) throws SQLException { + this(table, null); + } + /** * Constructor. * - * @param table The table for which to get find a primary key. + * @param table The table for which to get find a primary key. + * @param schema Schema in which table is located * @throws SQLException */ - public PrimaryKeyFinder(String table) throws SQLException { + public PrimaryKeyFinder(String table, String schema) throws SQLException { this.table = table; - + this.schema = schema; if (table == null || table.trim().length() == 0) { throw new SQLException("Invalid table name: '" + this.table + "'"); } @@ -1988,10 +2004,10 @@ public PrimaryKeyFinder(String table) throws SQLException { // read create SQL script for table ResultSet rs = stat.executeQuery( - "select sql from sqlite_master where" + "select sql from " + prependSchemaPrefix(schema, "sqlite_master where" + " lower(name) = lower('" + escape(table) - + "') and type in ('table', 'view')")) { + + "') and type in ('table', 'view')"))) { if (!rs.next()) throw new SQLException("Table not found: '" + table + "'"); @@ -2007,8 +2023,10 @@ public PrimaryKeyFinder(String table) throws SQLException { } if (pkColumns == null) { - try (ResultSet rs2 = - stat.executeQuery("pragma table_info('" + escape(table) + "');")) { + try ( + ResultSet rs2 = stat.executeQuery( + "pragma " + prependSchemaPrefix(schema, "table_info('" + escape(table) + "');")) + ) { while (rs2.next()) { if (rs2.getBoolean(6)) pkColumns = new String[] {rs2.getString(2)}; } @@ -2046,6 +2064,10 @@ class ImportedKeyFinder { private final List fkList = new ArrayList<>(); public ImportedKeyFinder(String table) throws SQLException { + this(table, null); + } + + public ImportedKeyFinder(String table, String schema) throws SQLException { if (table == null || table.trim().length() == 0) { throw new SQLException("Invalid table name: '" + table + "'"); @@ -2053,14 +2075,15 @@ public ImportedKeyFinder(String table) throws SQLException { this.fkTableName = table; - List fkNames = getForeignKeyNames(this.fkTableName); + List fkNames = getForeignKeyNames(this.fkTableName, schema); - try (Statement stat = conn.createStatement(); - ResultSet rs = - stat.executeQuery( - "pragma foreign_key_list('" - + escape(this.fkTableName.toLowerCase()) - + "')")) { + try ( + Statement stat = conn.createStatement(); + ResultSet rs = stat.executeQuery("pragma " + prependSchemaPrefix( + schema, + "foreign_key_list('" + escape(this.fkTableName.toLowerCase()) + "')" + )) + ) { int prevFkId = -1; int count = 0; @@ -2097,19 +2120,15 @@ public ImportedKeyFinder(String table) throws SQLException { } } - private List getForeignKeyNames(String tbl) throws SQLException { + private List getForeignKeyNames(String tbl, String schema) throws SQLException { List fkNames = new ArrayList<>(); if (tbl == null) { return fkNames; } try (Statement stat2 = conn.createStatement(); - ResultSet rs = - stat2.executeQuery( - "select sql from sqlite_master where" - + " lower(name) = lower('" - + escape(tbl) - + "')")) { - + ResultSet rs = stat2.executeQuery("select sql from " + prependSchemaPrefix(schema, + "sqlite_master where" + " lower(name) = lower('" + escape(tbl) + "')" + ))) { if (rs.next()) { Matcher matcher = FK_NAMED_PATTERN.matcher(rs.getString(1)); diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index dae70ba41..560511a96 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -3,7 +3,10 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assumptions.assumeThat; +import java.io.File; +import java.io.IOException; import java.io.InputStream; +import java.nio.file.Files; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; @@ -16,9 +19,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; + +import org.junit.jupiter.api.*; /** These tests are designed to stress Statements on memory databases. */ public class DBMetaDataTest { @@ -1398,10 +1400,10 @@ public void columnOrderOfgetImportedKeys() throws SQLException { stat.executeUpdate( "create table address (pid integer, name, foreign key(pid) references person(id))"); - ResultSet importedKeys = meta.getImportedKeys("default", "global", "address"); + ResultSet importedKeys = meta.getImportedKeys("default", null, "address"); assertThat(importedKeys.next()).isTrue(); assertThat(importedKeys.getString("PKTABLE_CAT")).isEqualTo("default"); - assertThat(importedKeys.getString("PKTABLE_SCHEM")).isEqualTo("global"); + assertThat(importedKeys.getString("PKTABLE_SCHEM")).isEqualTo(null); assertThat(importedKeys.getString("FKTABLE_CAT")).isEqualTo("default"); assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("person"); assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id"); @@ -1423,12 +1425,12 @@ public void columnOrderOfgetExportedKeys() throws SQLException { stat.executeUpdate( "create table address (pid integer, name, foreign key(pid) references person(id))"); - ResultSet exportedKeys = meta.getExportedKeys("default", "global", "person"); + ResultSet exportedKeys = meta.getExportedKeys("default", null, "person"); assertThat(exportedKeys.next()).isTrue(); assertThat(exportedKeys.getString("PKTABLE_CAT")).isEqualTo("default"); - assertThat(exportedKeys.getString("PKTABLE_SCHEM")).isEqualTo("global"); + assertThat(exportedKeys.getString("PKTABLE_SCHEM")).isEqualTo(null); assertThat(exportedKeys.getString("FKTABLE_CAT")).isEqualTo("default"); - assertThat(exportedKeys.getString("FKTABLE_SCHEM")).isEqualTo("global"); + assertThat(exportedKeys.getString("FKTABLE_SCHEM")).isEqualTo(null); assertThat(exportedKeys.getString("PK_NAME")).isNotNull(); assertThat(exportedKeys.getString("FK_NAME")).isNotNull(); @@ -1568,4 +1570,315 @@ public void version() throws Exception { assertThat(meta.getDatabaseMinorVersion()).as("db minor version").isEqualTo(minorVersion); assertThat(meta.getUserName()).as("user name").isNull(); } + + @Nested + class DBMetadataTestWithAttachedDatabases { + File testDB; + + @BeforeEach + public void init() throws IOException, SQLException { + ((SQLiteConnection) conn).getDatabase().getConfig().setReadAttachedDatabases(true); + testDB = Files.createTempFile("temp", ".sqlite").toFile(); + stat.executeUpdate("attach database \"" + testDB.toURI().toURL() + "\" as db2;"); + stat.executeUpdate( + "create table db2.test2 (id integer primary key, fn float default 0.0, sn not null, intvalue integer" + + "(5), realvalue real(8,3));"); + stat.executeUpdate("create view db2.testView2 as select * from db2.test2;"); + } + + @Test + public void readSchemaWithAttachedDatabases() throws SQLException { + ResultSet rs = meta.getSchemas(); + assertThat(rs.next()).isTrue(); + ResultSetMetaData rsMeta = rs.getMetaData(); + assertThat(rsMeta.getColumnCount()).isEqualTo(2); + assertThat(rsMeta.getColumnName(1)).isEqualTo("TABLE_SCHEM"); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("main"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("db2"); + assertThat(rsMeta.getColumnName(2)).isEqualTo("TABLE_CATALOG"); + } + + @Test + public void getTablesForAttachedDatabase() throws SQLException { + ResultSet rs = meta.getTables(null, "db2", null, null); + assertThat(rs).isNotNull(); + + stat.getGeneratedKeys().close(); + stat.close(); + + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("SYSTEM TABLE"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); // 3 + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("TABLE"); // 4 + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); + assertThat(rs.getString("TABLE_TYPE")).isEqualTo("VIEW"); + assertThat(rs.next()).isFalse(); + rs.close(); + + rs = meta.getTables(null, null, "bob", null); + assertThat(rs.next()).isFalse(); + rs.close(); + rs = meta.getTables(null, null, "test", null); + assertThat(rs.next()).isTrue(); + assertThat(rs.next()).isFalse(); + rs.close(); + rs = meta.getTables(null, null, "test%", null); + assertThat(rs.next()).isTrue(); + assertThat(rs.next()).isTrue(); + rs.close(); + + rs = meta.getTables(null, null, null, new String[]{"table"}); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.next()).isFalse(); + rs.close(); + + rs = meta.getTables(null, null, null, new String[]{"view"}); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.next()).isFalse(); + rs.close(); + + rs = meta.getTables(null, null, null, new String[]{"system table"}); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + assertThat(rs.next()).isFalse(); + rs.close(); + } + + @Test + public void getColumnsForAttachedDatabaseTables() throws SQLException { + ResultSet rs = meta.getColumns(null, "db2", "test2", "id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.INTEGER); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(2000000000); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(0); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, "db2", "test2", "fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.FLOAT); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getString("COLUMN_DEF")).isEqualTo("0.0"); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(2000000000); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(10); + assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, "db2", "test2", "sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("NO"); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(2000000000); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(10); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, "db2", "test2", "intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.INTEGER); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(5); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(0); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, "db2", "test2", "realvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.getInt("DATA_TYPE")).isEqualTo(Types.FLOAT); + assertThat(rs.getString("IS_NULLABLE")).isEqualTo("YES"); + assertThat(rs.getInt("COLUMN_SIZE")).isEqualTo(11); + assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(3); + assertThat(rs.getString("COLUMN_DEF")).isNull(); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, "db2", "test2", "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, "db2", "test2", "%n"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, "db2", "test%", "%"); + // TABLE "test2" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + // VIEW "testView2" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, "db2", "%", "%"); + // TABLE "test2" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + // VIEW "testView2" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isFalse(); + + rs = meta.getColumns(null, "db2", "doesnotexist", "%"); + assertThat(rs.next()).isFalse(); + assertThat(rs.getMetaData().getColumnCount()).isEqualTo(24); + } + + @Test + public void columnOrderOfgetExportedKeysForAttachedDatabase() throws SQLException { + + stat.executeUpdate("create table db2.person (id integer primary key)"); + stat.executeUpdate("create table db2.address (pid integer, name, foreign key(pid) references person(id))"); + + ResultSet exportedKeys = meta.getExportedKeys("default", "db2", "person"); + assertThat(exportedKeys.next()).isTrue(); + assertThat(exportedKeys.getString("PKTABLE_CAT")).isEqualTo("default"); + assertThat(exportedKeys.getString("PKTABLE_SCHEM")).isEqualTo("db2"); + assertThat(exportedKeys.getString("FKTABLE_CAT")).isEqualTo("default"); + assertThat(exportedKeys.getString("FKTABLE_SCHEM")).isEqualTo("db2"); + assertThat(exportedKeys.getString("PK_NAME")).isNotNull(); + assertThat(exportedKeys.getString("FK_NAME")).isNotNull(); + + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("person"); + assertThat(exportedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id"); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("address"); + assertThat(exportedKeys.getString("FKCOLUMN_NAME")).isEqualTo("pid"); + + exportedKeys.close(); + + exportedKeys = meta.getExportedKeys(null, "db2", "address"); + assertThat(exportedKeys.next()).isFalse(); + exportedKeys.close(); + + // With explicit primary column defined. + stat.executeUpdate("create table db2.REFERRED (ID integer primary key not null)"); + stat.executeUpdate( + "create table db2.REFERRING (ID integer, RID integer, constraint fk\r\n foreign\tkey\r\n(RID) " + + "references REFERRED(id))"); + + exportedKeys = meta.getExportedKeys(null, "db2", "referred"); + assertThat(exportedKeys.getString("PKTABLE_NAME")).isEqualTo("REFERRED"); + assertThat(exportedKeys.getString("FKTABLE_NAME")).isEqualTo("REFERRING"); + assertThat(exportedKeys.getString("FK_NAME")).isEqualTo("fk"); + exportedKeys.close(); + } + + @Test + public void getIndexInfoOnTestForAttachedDatabase() throws SQLException { + ResultSet rs = meta.getIndexInfo(null, "db2", "test2", false, false); + assertThat(rs).isNotNull(); + } + + @Test + public void getIndexInfoIndexedSingleForAttachedDatabase() throws SQLException { + stat.executeUpdate("create table db2.testindex (id integer primary key, fn float default 0.0, sn not null);"); + stat.executeUpdate("create index db2.testindex_idx on testindex (sn);"); + + ResultSet rs = meta.getIndexInfo(null, "db2", "testindex", false, false); + ResultSetMetaData rsmd = rs.getMetaData(); + + assertThat(rs).isNotNull(); + assertThat(rsmd).isNotNull(); + } + + @Test + public void getIndexInfoIndexedSingleExprForAttachedDatabase() throws SQLException { + stat.executeUpdate("create table db2.testindex (id integer primary key, fn float default 0.0, sn not null);"); + stat.executeUpdate("create index db2.testindex_idx on testindex (sn, fn/2);"); + + ResultSet rs = meta.getIndexInfo(null, "db2", "testindex", false, false); + ResultSetMetaData rsmd = rs.getMetaData(); + + assertThat(rs).isNotNull(); + assertThat(rsmd).isNotNull(); + } + + @Test + public void getIndexInfoIndexedMultiForAttachedDatabase() throws SQLException { + stat.executeUpdate("create table db2.testindex (id integer primary key, fn float default 0.0, sn not null);"); + stat.executeUpdate("create index db2.testindex_idx on testindex (sn);"); + stat.executeUpdate("create index db2.testindex_pk_idx on testindex (id);"); + + ResultSet rs = meta.getIndexInfo(null, "db2", "testindex", false, false); + ResultSetMetaData rsmd = rs.getMetaData(); + + assertThat(rs).isNotNull(); + assertThat(rsmd).isNotNull(); + } + + @AfterEach + public void exit() throws SQLException { + ((SQLiteConnection) conn).getDatabase().getConfig().setReadAttachedDatabases(false); + stat.executeUpdate("Detach database db2;"); + testDB.deleteOnExit(); + } + + } } From 8ccbf02bc14ab89e75d16f361dbbe66fb2d4d0c2 Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Tue, 17 Jan 2023 13:58:51 +0400 Subject: [PATCH 02/10] remove read attached databases pragma --- src/main/java/org/sqlite/SQLiteConfig.java | 33 ++----------------- .../sqlite/jdbc3/JDBC3DatabaseMetaData.java | 10 ++---- src/test/java/org/sqlite/DBMetaDataTest.java | 4 +-- 3 files changed, 7 insertions(+), 40 deletions(-) diff --git a/src/main/java/org/sqlite/SQLiteConfig.java b/src/main/java/org/sqlite/SQLiteConfig.java index 04b9c66dd..56b8bc26f 100755 --- a/src/main/java/org/sqlite/SQLiteConfig.java +++ b/src/main/java/org/sqlite/SQLiteConfig.java @@ -56,7 +56,6 @@ public class SQLiteConfig { private final int busyTimeout; private boolean explicitReadOnly; - private boolean readAttachedDatabases; private final SQLiteConnectionConfig defaultConnectionConfig; @@ -94,10 +93,6 @@ public SQLiteConfig(Properties prop) { this.explicitReadOnly = Boolean.parseBoolean( pragmaTable.getProperty(Pragma.JDBC_EXPLICIT_READONLY.pragmaName, "false")); - this.readAttachedDatabases = - Boolean.parseBoolean( - pragmaTable.getProperty( - Pragma.METADATA_READ_ATTACHED_DATABASES.pragmaName, "false")); } public SQLiteConnectionConfig newConnectionConfig() { @@ -191,9 +186,8 @@ public void apply(Connection conn) throws SQLException { pragmaParams.remove(Pragma.LIMIT_WORKER_THREADS.pragmaName); pragmaParams.remove(Pragma.LIMIT_PAGE_COUNT.pragmaName); - // exclude these "fake" pragmas from execution + // exclude this "fake" pragma from execution pragmaParams.remove(Pragma.JDBC_EXPLICIT_READONLY.pragmaName); - pragmaParams.remove(Pragma.METADATA_READ_ATTACHED_DATABASES.pragmaName); Statement stat = conn.createStatement(); try { @@ -336,10 +330,6 @@ public Properties toProperties() { defaultConnectionConfig.getDateStringFormat()); pragmaTable.setProperty( Pragma.JDBC_EXPLICIT_READONLY.pragmaName, this.explicitReadOnly ? "true" : "false"); - pragmaTable.setProperty( - Pragma.METADATA_READ_ATTACHED_DATABASES.pragmaName, - this.readAttachedDatabases ? "true" : "false" - ); return pragmaTable; } @@ -383,20 +373,6 @@ public void setExplicitReadOnly(boolean readOnly) { this.explicitReadOnly = readOnly; } - /** @return true if reading attached databases is allowed */ - public boolean isReadAttachedDatabases() { - return readAttachedDatabases; - } - - /** - * Enable reading attached databases in metadata, they will be shown as schemas - * - * @param readAttachedDatabases whether to read attached databases - */ - public void setReadAttachedDatabases(boolean readAttachedDatabases) { - this.readAttachedDatabases = readAttachedDatabases; - } - public enum Pragma { // Parameters requiring SQLite3 API invocation @@ -558,11 +534,8 @@ public enum Pragma { // extensions: "fake" pragmas to allow conformance with JDBC JDBC_EXPLICIT_READONLY( - "jdbc.explicit_readonly", "Set explicit read only transactions", null), - // "fake" pragma to allow configurating metadata reading by driver - METADATA_READ_ATTACHED_DATABASES("metadata.read_attached_databases", - "Set read attached databases as schemas", - OnOff); + "jdbc.explicit_readonly", "Set explicit read only transactions", null); + public final String pragmaName; public final String[] choices; public final String description; diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java index 2a8c0c358..419ecafd7 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -681,7 +681,7 @@ public boolean supportsSavepoints() { /** @see java.sql.DatabaseMetaData#supportsSchemasInDataManipulation() */ public boolean supportsSchemasInDataManipulation() { - return conn.getDatabase().getConfig().isReadAttachedDatabases(); + return true; } /** @see java.sql.DatabaseMetaData#supportsSchemasInIndexDefinitions() */ @@ -1172,12 +1172,8 @@ public ResultSet getCrossReference( /** @see java.sql.DatabaseMetaData#getSchemas() */ public ResultSet getSchemas() throws SQLException { if (getSchemas == null) { - if (conn.getDatabase().getConfig().isReadAttachedDatabases()) { - getSchemas = conn.prepareStatement( - "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list;"); - } else { - getSchemas = conn.prepareStatement("select null as TABLE_SCHEM, null as TABLE_CATALOG limit 0;"); - } + getSchemas = conn.prepareStatement( + "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list;"); } return getSchemas.executeQuery(); diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index 560511a96..d02352515 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -1087,7 +1087,7 @@ public void columnOrderOfgetProcedurColumns() throws SQLException { @Test public void columnOrderOfgetSchemas() throws SQLException { ResultSet rs = meta.getSchemas(); - assertThat(rs.next()).isFalse(); + assertThat(rs.next()).isTrue(); ResultSetMetaData rsmeta = rs.getMetaData(); assertThat(rsmeta.getColumnCount()).isEqualTo(2); assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_SCHEM"); @@ -1577,7 +1577,6 @@ class DBMetadataTestWithAttachedDatabases { @BeforeEach public void init() throws IOException, SQLException { - ((SQLiteConnection) conn).getDatabase().getConfig().setReadAttachedDatabases(true); testDB = Files.createTempFile("temp", ".sqlite").toFile(); stat.executeUpdate("attach database \"" + testDB.toURI().toURL() + "\" as db2;"); stat.executeUpdate( @@ -1875,7 +1874,6 @@ public void getIndexInfoIndexedMultiForAttachedDatabase() throws SQLException { @AfterEach public void exit() throws SQLException { - ((SQLiteConnection) conn).getDatabase().getConfig().setReadAttachedDatabases(false); stat.executeUpdate("Detach database db2;"); testDB.deleteOnExit(); } From 24fcc05fbdd4d55e4f180823e2506252c5ecd44b Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Tue, 17 Jan 2023 14:37:37 +0400 Subject: [PATCH 03/10] suggestions from code review --- src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java index 419ecafd7..a3301f873 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -681,7 +681,7 @@ public boolean supportsSavepoints() { /** @see java.sql.DatabaseMetaData#supportsSchemasInDataManipulation() */ public boolean supportsSchemasInDataManipulation() { - return true; + return false; } /** @see java.sql.DatabaseMetaData#supportsSchemasInIndexDefinitions() */ From 6e5922714edc189a9d64831178ecdd79b9ab3c25 Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Wed, 18 Jan 2023 15:01:04 +0400 Subject: [PATCH 04/10] additional test cases & improve metadata reading & formatting --- .../org/sqlite/core/CoreDatabaseMetaData.java | 3 +- .../sqlite/jdbc3/JDBC3DatabaseMetaData.java | 114 +++++++++++------- src/test/java/org/sqlite/DBMetaDataTest.java | 57 +++++++-- 3 files changed, 119 insertions(+), 55 deletions(-) diff --git a/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java b/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java index fdb8eabef..33646fd8a 100644 --- a/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java +++ b/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java @@ -186,6 +186,7 @@ protected String escape(final String val) { /** * Returns line without changes or with escaped schema prefix + * * @param schema schema name * @param line of text to prepend to * @return The SQL escaped schema name with dot or empty string @@ -200,6 +201,7 @@ protected String prependSchemaPrefix(String schema, String line) { /** * Adds line without changes or with escaped schema prefix + * * @param sql String builder for sql request * @param schema schema name * @param line line to prepend schema prefix to @@ -212,7 +214,6 @@ protected void prependSchemaPrefix(StringBuilder sql, String schema, String line } } - // inner classes /** Pattern used to extract column order for an unnamed primary key. */ diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java index a3301f873..651818af9 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -915,7 +915,9 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co checkOpen(); StringBuilder sql = new StringBuilder(700); - sql.append("select null as TABLE_CAT, ").append(quote(s)).append(" as TABLE_SCHEM, tblname as TABLE_NAME, ") + sql.append("select null as TABLE_CAT, ") + .append(quote(s == null ? "main" : s)) + .append(" as TABLE_SCHEM, tblname as TABLE_NAME, ") .append( "cn as COLUMN_NAME, ct as DATA_TYPE, tn as TYPE_NAME, colSize as COLUMN_SIZE, ") .append( @@ -948,10 +950,13 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co ResultSet rsColAutoinc = null; try { statColAutoinc = conn.createStatement(); - rsColAutoinc = statColAutoinc.executeQuery( - "SELECT LIKE('%autoincrement%', LOWER(sql)) FROM " + prependSchemaPrefix(s, - "sqlite_master WHERE LOWER(name) = LOWER('") + escape(tableName) - + "') AND TYPE IN ('table', 'view')"); + rsColAutoinc = + statColAutoinc.executeQuery( + "SELECT LIKE('%autoincrement%', LOWER(sql)) FROM " + + prependSchemaPrefix( + s, "sqlite_master WHERE LOWER(name) = LOWER('") + + escape(tableName) + + "') AND TYPE IN ('table', 'view')"); rsColAutoinc.next(); isAutoIncrement = rsColAutoinc.getInt(1) == 1; } finally { @@ -972,8 +977,10 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co } // For each table, get the column info and build into overall SQL - String pragmaStatement = "PRAGMA " + prependSchemaPrefix(s, "table_xinfo('" + escape(tableName) + - "')"); + String pragmaStatement = + "PRAGMA " + + prependSchemaPrefix( + s, "table_xinfo('" + escape(tableName) + "')"); try (Statement colstat = conn.createStatement(); ResultSet rscol = colstat.executeQuery(pragmaStatement)) { @@ -1172,8 +1179,9 @@ public ResultSet getCrossReference( /** @see java.sql.DatabaseMetaData#getSchemas() */ public ResultSet getSchemas() throws SQLException { if (getSchemas == null) { - getSchemas = conn.prepareStatement( - "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list;"); + getSchemas = + conn.prepareStatement( + "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list;"); } return getSchemas.executeQuery(); @@ -1198,7 +1206,9 @@ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLExce Statement stat = conn.createStatement(); StringBuilder sql = new StringBuilder(512); - sql.append("select null as TABLE_CAT, ").append(quote(s)).append(" as TABLE_SCHEM, '") + sql.append("select null as TABLE_CAT, ") + .append(quote(s == null ? "main" : s)) + .append(" as TABLE_SCHEM, '") .append(escape(table)) .append("' as TABLE_NAME, cn as COLUMN_NAME, ks as KEY_SEQ, pk as PK_NAME from ("); @@ -1249,7 +1259,7 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) catalog = (catalog != null) ? quote(catalog) : null; - String quotedSchema = (schema != null) ? quote(schema) : null; + String quotedSchema = (schema != null) ? quote(schema) : quote("main"); StringBuilder exportedKeysQuery = new StringBuilder(512); @@ -1258,11 +1268,11 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) if (pkColumns != null) { // retrieve table list ArrayList tableList; - try ( - ResultSet rs = stat.executeQuery( - "select name from " + prependSchemaPrefix(schema, "sqlite_master where type = " + - "'table'")) - ) { + try (ResultSet rs = + stat.executeQuery( + "select name from " + + prependSchemaPrefix( + schema, "sqlite_master where type = " + "'table'"))) { tableList = new ArrayList<>(); while (rs.next()) { @@ -1406,12 +1416,12 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) sql.append("select ") .append(quote(catalog)) .append(" as PKTABLE_CAT, ") - .append(quote(schema)) + .append(quote(schema == null ? "main" : schema)) .append(" as PKTABLE_SCHEM, ") .append("ptn as PKTABLE_NAME, pcn as PKCOLUMN_NAME, ") .append(quote(catalog)) .append(" as FKTABLE_CAT, ") - .append(quote(schema)) + .append(quote(schema == null ? "main" : schema)) .append(" as FKTABLE_SCHEM, ") .append(quote(table)) .append(" as FKTABLE_NAME, ") @@ -1526,7 +1536,7 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole // define the column header // this is from the JDBC spec, it is part of the driver protocol sql.append("select null as TABLE_CAT,") - .append(quote(s)) + .append(quote(s == null ? "main" : s)) .append(" as TABLE_SCHEM, '") .append(escape(table)) .append( @@ -1537,7 +1547,9 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole "cn as COLUMN_NAME, null as ASC_OR_DESC, 0 as CARDINALITY, 0 as PAGES, null as FILTER_CONDITION from ("); // this always returns a result set now, previously threw exception - rs = stat.executeQuery("pragma " + prependSchemaPrefix(s, "index_list('" + escape(table) + "');")); + rs = + stat.executeQuery( + "pragma " + prependSchemaPrefix(s, "index_list('" + escape(table) + "');")); ArrayList> indexList = new ArrayList<>(); while (rs.next()) { @@ -1561,7 +1573,11 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole while (indexIterator.hasNext()) { currentIndex = indexIterator.next(); String indexName = currentIndex.get(0).toString(); - rs = stat.executeQuery("pragma " + prependSchemaPrefix(s, "index_info('" + escape(indexName) + "');")); + rs = + stat.executeQuery( + "pragma " + + prependSchemaPrefix( + s, "index_info('" + escape(indexName) + "');")); while (rs.next()) { @@ -1689,7 +1705,10 @@ public synchronized ResultSet getTables( StringBuilder sql = new StringBuilder(); sql.append("SELECT").append("\n"); sql.append(" NULL AS TABLE_CAT,").append("\n"); - sql.append(" NULL AS TABLE_SCHEM,").append("\n"); + sql.append(" ") + .append(quote(s == null ? "main" : s)) + .append(" AS TABLE_SCHEM,") + .append("\n"); sql.append(" NAME AS TABLE_NAME,").append("\n"); sql.append(" TYPE AS TABLE_TYPE,").append("\n"); sql.append(" NULL AS REMARKS,").append("\n"); @@ -1985,7 +2004,7 @@ public PrimaryKeyFinder(String table) throws SQLException { /** * Constructor. * - * @param table The table for which to get find a primary key. + * @param table The table for which to get find a primary key. * @param schema Schema in which table is located * @throws SQLException */ @@ -2000,10 +2019,13 @@ public PrimaryKeyFinder(String table, String schema) throws SQLException { // read create SQL script for table ResultSet rs = stat.executeQuery( - "select sql from " + prependSchemaPrefix(schema, "sqlite_master where" - + " lower(name) = lower('" - + escape(table) - + "') and type in ('table', 'view')"))) { + "select sql from " + + prependSchemaPrefix( + schema, + "sqlite_master where" + + " lower(name) = lower('" + + escape(table) + + "') and type in ('table', 'view')"))) { if (!rs.next()) throw new SQLException("Table not found: '" + table + "'"); @@ -2019,10 +2041,12 @@ public PrimaryKeyFinder(String table, String schema) throws SQLException { } if (pkColumns == null) { - try ( - ResultSet rs2 = stat.executeQuery( - "pragma " + prependSchemaPrefix(schema, "table_info('" + escape(table) + "');")) - ) { + try (ResultSet rs2 = + stat.executeQuery( + "pragma " + + prependSchemaPrefix( + schema, + "table_info('" + escape(table) + "');"))) { while (rs2.next()) { if (rs2.getBoolean(6)) pkColumns = new String[] {rs2.getString(2)}; } @@ -2073,13 +2097,15 @@ public ImportedKeyFinder(String table, String schema) throws SQLException { List fkNames = getForeignKeyNames(this.fkTableName, schema); - try ( - Statement stat = conn.createStatement(); - ResultSet rs = stat.executeQuery("pragma " + prependSchemaPrefix( - schema, - "foreign_key_list('" + escape(this.fkTableName.toLowerCase()) + "')" - )) - ) { + try (Statement stat = conn.createStatement(); + ResultSet rs = + stat.executeQuery( + "pragma " + + prependSchemaPrefix( + schema, + "foreign_key_list('" + + escape(this.fkTableName.toLowerCase()) + + "')"))) { int prevFkId = -1; int count = 0; @@ -2122,9 +2148,15 @@ private List getForeignKeyNames(String tbl, String schema) throws SQLExc return fkNames; } try (Statement stat2 = conn.createStatement(); - ResultSet rs = stat2.executeQuery("select sql from " + prependSchemaPrefix(schema, - "sqlite_master where" + " lower(name) = lower('" + escape(tbl) + "')" - ))) { + ResultSet rs = + stat2.executeQuery( + "select sql from " + + prependSchemaPrefix( + schema, + "sqlite_master where" + + " lower(name) = lower('" + + escape(tbl) + + "')"))) { if (rs.next()) { Matcher matcher = FK_NAMED_PATTERN.matcher(rs.getString(1)); diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index d02352515..3d26aa0a2 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -19,7 +19,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; - import org.junit.jupiter.api.*; /** These tests are designed to stress Statements on memory databases. */ @@ -1372,7 +1371,7 @@ private void assertPrimaryKey( .isNull(); assertThat(rs.getString("TABLE_SCHEM")) .as("DatabaseMetaData.getPrimaryKeys: TABLE_SCHEM") - .isNull(); + .isEqualTo("main"); assertThat(rs.getString("TABLE_NAME")) .as("DatabaseMetaData.getPrimaryKeys: TABLE_NAME") .isEqualTo(tableName); @@ -1403,7 +1402,7 @@ public void columnOrderOfgetImportedKeys() throws SQLException { ResultSet importedKeys = meta.getImportedKeys("default", null, "address"); assertThat(importedKeys.next()).isTrue(); assertThat(importedKeys.getString("PKTABLE_CAT")).isEqualTo("default"); - assertThat(importedKeys.getString("PKTABLE_SCHEM")).isEqualTo(null); + assertThat(importedKeys.getString("PKTABLE_SCHEM")).isEqualTo("main"); assertThat(importedKeys.getString("FKTABLE_CAT")).isEqualTo("default"); assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("person"); assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id"); @@ -1428,9 +1427,9 @@ public void columnOrderOfgetExportedKeys() throws SQLException { ResultSet exportedKeys = meta.getExportedKeys("default", null, "person"); assertThat(exportedKeys.next()).isTrue(); assertThat(exportedKeys.getString("PKTABLE_CAT")).isEqualTo("default"); - assertThat(exportedKeys.getString("PKTABLE_SCHEM")).isEqualTo(null); + assertThat(exportedKeys.getString("PKTABLE_SCHEM")).isEqualTo("main"); assertThat(exportedKeys.getString("FKTABLE_CAT")).isEqualTo("default"); - assertThat(exportedKeys.getString("FKTABLE_SCHEM")).isEqualTo(null); + assertThat(exportedKeys.getString("FKTABLE_SCHEM")).isEqualTo("main"); assertThat(exportedKeys.getString("PK_NAME")).isNotNull(); assertThat(exportedKeys.getString("FK_NAME")).isNotNull(); @@ -1630,25 +1629,44 @@ public void getTablesForAttachedDatabase() throws SQLException { assertThat(rs.next()).isTrue(); rs.close(); - rs = meta.getTables(null, null, null, new String[]{"table"}); + rs = meta.getTables(null, null, null, new String[] {"table"}); assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); assertThat(rs.next()).isFalse(); rs.close(); - rs = meta.getTables(null, null, null, new String[]{"view"}); + rs = meta.getTables(null, null, null, new String[] {"view"}); assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); assertThat(rs.next()).isFalse(); rs.close(); - rs = meta.getTables(null, null, null, new String[]{"system table"}); + rs = meta.getTables(null, null, null, new String[] {"system table"}); assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); assertThat(rs.next()).isFalse(); rs.close(); } + @Test + public void testDynamicDatabaseAttachment() throws IOException, SQLException { + ResultSet schemas = meta.getSchemas(); + schemas.close(); + File thirdDB = Files.createTempFile("db3", ".sqlite").toFile(); + stat.executeUpdate("attach database \"" + thirdDB.toURI().toURL() + "\" as db3;"); + try { + schemas = meta.getSchemas(); + boolean schemaFound = false; + while (schemas.next()) { + schemaFound = "db3".equals(schemas.getString("TABLE_SCHEM")); + } + assertThat(schemaFound).isTrue(); + } finally { + stat.executeUpdate("detach database db3;"); + thirdDB.deleteOnExit(); + } + } + @Test public void getColumnsForAttachedDatabaseTables() throws SQLException { ResultSet rs = meta.getColumns(null, "db2", "test2", "id"); @@ -1790,11 +1808,22 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.getMetaData().getColumnCount()).isEqualTo(24); } + @Test + public void getTablesForDefaultSchema() throws SQLException { + ResultSet rs = meta.getTables(null, null, null, new String[] {"table"}); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("main"); + assertThat(rs.next()).isFalse(); + rs.close(); + } + @Test public void columnOrderOfgetExportedKeysForAttachedDatabase() throws SQLException { stat.executeUpdate("create table db2.person (id integer primary key)"); - stat.executeUpdate("create table db2.address (pid integer, name, foreign key(pid) references person(id))"); + stat.executeUpdate( + "create table db2.address (pid integer, name, foreign key(pid) references person(id))"); ResultSet exportedKeys = meta.getExportedKeys("default", "db2", "person"); assertThat(exportedKeys.next()).isTrue(); @@ -1837,7 +1866,8 @@ public void getIndexInfoOnTestForAttachedDatabase() throws SQLException { @Test public void getIndexInfoIndexedSingleForAttachedDatabase() throws SQLException { - stat.executeUpdate("create table db2.testindex (id integer primary key, fn float default 0.0, sn not null);"); + stat.executeUpdate( + "create table db2.testindex (id integer primary key, fn float default 0.0, sn not null);"); stat.executeUpdate("create index db2.testindex_idx on testindex (sn);"); ResultSet rs = meta.getIndexInfo(null, "db2", "testindex", false, false); @@ -1849,7 +1879,8 @@ public void getIndexInfoIndexedSingleForAttachedDatabase() throws SQLException { @Test public void getIndexInfoIndexedSingleExprForAttachedDatabase() throws SQLException { - stat.executeUpdate("create table db2.testindex (id integer primary key, fn float default 0.0, sn not null);"); + stat.executeUpdate( + "create table db2.testindex (id integer primary key, fn float default 0.0, sn not null);"); stat.executeUpdate("create index db2.testindex_idx on testindex (sn, fn/2);"); ResultSet rs = meta.getIndexInfo(null, "db2", "testindex", false, false); @@ -1861,7 +1892,8 @@ public void getIndexInfoIndexedSingleExprForAttachedDatabase() throws SQLExcepti @Test public void getIndexInfoIndexedMultiForAttachedDatabase() throws SQLException { - stat.executeUpdate("create table db2.testindex (id integer primary key, fn float default 0.0, sn not null);"); + stat.executeUpdate( + "create table db2.testindex (id integer primary key, fn float default 0.0, sn not null);"); stat.executeUpdate("create index db2.testindex_idx on testindex (sn);"); stat.executeUpdate("create index db2.testindex_pk_idx on testindex (id);"); @@ -1877,6 +1909,5 @@ public void exit() throws SQLException { stat.executeUpdate("Detach database db2;"); testDB.deleteOnExit(); } - } } From e5e4f1d6eed98404037dc3ecd6143feac7704a4d Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Mon, 13 Feb 2023 21:39:57 +0400 Subject: [PATCH 05/10] getSchemas(String, String) support & schema pattern matching --- .../org/sqlite/core/CoreDatabaseMetaData.java | 15 + .../sqlite/jdbc3/JDBC3DatabaseMetaData.java | 305 +++++++++++------- .../sqlite/jdbc4/JDBC4DatabaseMetaData.java | 4 - src/test/java/org/sqlite/DBMetaDataTest.java | 15 +- 4 files changed, 208 insertions(+), 131 deletions(-) diff --git a/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java b/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java index 33646fd8a..c1a80fa40 100644 --- a/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java +++ b/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java @@ -163,6 +163,21 @@ protected static String quote(String tableName) { } } + /** + * Escapes all wildcards, to prevent pattern matching for + * functions which should not support it + * @param val The string to escape + * @return The string with escaped wildcards + */ + protected static String escapeWildcards(final String val) { + if (val == null) { + return null; + } + String replacement = val.replace("%", "\\%"); + replacement = replacement.replace("_", "\\_"); + return replacement; + } + /** * Applies SQL escapes for special characters in a given string. * diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java index 651818af9..f04cd2bc5 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -1187,6 +1187,20 @@ public ResultSet getSchemas() throws SQLException { return getSchemas.executeQuery(); } + /** @see java.sql.DatabaseMetaData#getSchemas(String, String) */ + public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { + if (schemaPattern == null) { + return getSchemas(); + } + if ("".equals(schemaPattern)) { + schemaPattern = "main"; + } + Statement stat = conn.createStatement(); + String sql = "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list\n" + + "where TABLE_SCHEM like '" + schemaPattern + "' escape '" + getSearchStringEscape() + "';"; + return stat.executeQuery(sql); + } + /** @see java.sql.DatabaseMetaData#getCatalogs() */ public ResultSet getCatalogs() throws SQLException { if (getCatalogs == null) { @@ -1201,21 +1215,40 @@ public ResultSet getCatalogs() throws SQLException { * java.lang.String) */ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLException { - PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table, s); - String[] columns = pkFinder.getColumns(); Statement stat = conn.createStatement(); StringBuilder sql = new StringBuilder(512); + ResultSet schemas = getSchemas(c, escapeWildcards(s)); + ArrayList schemaNames = getSchemasNames(schemas); + for (int i = 0; i < schemaNames.size(); i++) { + createSchemaPrimaryKeysQuery(schemaNames.get(i), table, sql); + if (i != schemaNames.size() - 1) { + sql.append(" union all "); + } + } + return ((CoreStatement) stat).executeQuery(sql.toString(), true); + } + + private ArrayList getSchemasNames(ResultSet schemas) throws SQLException { + ArrayList schemaNames = new ArrayList<>(); + while (schemas.next()) { + schemaNames.add(schemas.getString("TABLE_SCHEM")); + } + return schemaNames; + } + + private void createSchemaPrimaryKeysQuery(String s, String table, StringBuilder sql) throws SQLException { + PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table, s); + String[] columns = pkFinder.getColumns(); sql.append("select null as TABLE_CAT, ") - .append(quote(s == null ? "main" : s)) + .append(quote(s)) .append(" as TABLE_SCHEM, '") .append(escape(table)) .append("' as TABLE_NAME, cn as COLUMN_NAME, ks as KEY_SEQ, pk as PK_NAME from ("); if (columns == null) { sql.append("select null as cn, null as pk, 0 as ks) limit 0;"); - - return ((CoreStatement) stat).executeQuery(sql.toString(), true); + return; } String pkName = pkFinder.getName(); @@ -1233,8 +1266,7 @@ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLExce .append(i + 1) .append(" as ks"); } - - return ((CoreStatement) stat).executeQuery(sql.append(") order by cn;").toString(), true); + sql.append(") order by cn;"); } private static final Map RULE_MAP = new HashMap<>(); @@ -1253,70 +1285,75 @@ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLExce */ public ResultSet getExportedKeys(String catalog, String schema, String table) throws SQLException { - PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table, schema); - String[] pkColumns = pkFinder.getColumns(); Statement stat = conn.createStatement(); + StringBuilder sql = new StringBuilder(2048); - catalog = (catalog != null) ? quote(catalog) : null; + ResultSet schemas = getSchemas(catalog, escapeWildcards(schema)); + ArrayList schemasNames = getSchemasNames(schemas); + for (int i = 0; i < schemasNames.size(); i++) { + PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table, schemasNames.get(i)); + String[] pkColumns = pkFinder.getColumns(); - String quotedSchema = (schema != null) ? quote(schema) : quote("main"); + catalog = (catalog != null) ? quote(catalog) : null; - StringBuilder exportedKeysQuery = new StringBuilder(512); + String quotedSchema = (schema != null) ? quote(schemasNames.get(i)) : quote("main"); - String target = null; - int count = 0; - if (pkColumns != null) { - // retrieve table list - ArrayList tableList; - try (ResultSet rs = - stat.executeQuery( - "select name from " - + prependSchemaPrefix( - schema, "sqlite_master where type = " + "'table'"))) { - tableList = new ArrayList<>(); + StringBuilder exportedKeysQuery = new StringBuilder(512); - while (rs.next()) { - String tblname = rs.getString(1); - tableList.add(tblname); - if (tblname.equalsIgnoreCase(table)) { - // get the correct case as in the database - // (not uppercase nor lowercase) - target = tblname; + String target = null; + int count = 0; + if (pkColumns != null) { + // retrieve table list + ArrayList tableList; + try (ResultSet rs = + stat.executeQuery( + "select name from " + + prependSchemaPrefix( + schema, "sqlite_master where type = " + "'table'"))) { + tableList = new ArrayList<>(); + + while (rs.next()) { + String tblname = rs.getString(1); + tableList.add(tblname); + if (tblname.equalsIgnoreCase(table)) { + // get the correct case as in the database + // (not uppercase nor lowercase) + target = tblname; + } } } - } - // find imported keys for each table - for (String tbl : tableList) { - final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(tbl, schema); - List fkNames = impFkFinder.getFkList(); + // find imported keys for each table + for (String tbl : tableList) { + final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(tbl, schema); + List fkNames = impFkFinder.getFkList(); - for (ForeignKey foreignKey : fkNames) { - String PKTabName = foreignKey.getPkTableName(); + for (ForeignKey foreignKey : fkNames) { + String PKTabName = foreignKey.getPkTableName(); - if (PKTabName == null || !PKTabName.equalsIgnoreCase(target)) { - continue; - } + if (PKTabName == null || !PKTabName.equalsIgnoreCase(target)) { + continue; + } - for (int j = 0; j < foreignKey.getColumnMappingCount(); j++) { - int keySeq = j + 1; - String[] columnMapping = foreignKey.getColumnMapping(j); - String PKColName = columnMapping[1]; - PKColName = (PKColName == null) ? "" : PKColName; - String FKColName = columnMapping[0]; - FKColName = (FKColName == null) ? "" : FKColName; - - boolean usePkName = false; - for (String pkColumn : pkColumns) { - if (pkColumn != null && pkColumn.equalsIgnoreCase(PKColName)) { - usePkName = true; - break; + for (int j = 0; j < foreignKey.getColumnMappingCount(); j++) { + int keySeq = j + 1; + String[] columnMapping = foreignKey.getColumnMapping(j); + String PKColName = columnMapping[1]; + PKColName = (PKColName == null) ? "" : PKColName; + String FKColName = columnMapping[0]; + FKColName = (FKColName == null) ? "" : FKColName; + + boolean usePkName = false; + for (String pkColumn : pkColumns) { + if (pkColumn != null && pkColumn.equalsIgnoreCase(PKColName)) { + usePkName = true; + break; + } } - } - String pkName = + String pkName = (usePkName && pkFinder.getName() != null) ? pkFinder.getName() : ""; - exportedKeysQuery + exportedKeysQuery .append(count > 0 ? " union all select " : "select ") .append(keySeq) .append(" as ks, '") @@ -1333,23 +1370,22 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) .append(RULE_MAP.get(foreignKey.getOnDelete())) .append(" as dr, "); - String fkName = foreignKey.getFkName(); + String fkName = foreignKey.getFkName(); - if (fkName != null) { - exportedKeysQuery.append("'").append(escape(fkName)).append("' as fkn"); - } else { - exportedKeysQuery.append("'' as fkn"); - } + if (fkName != null) { + exportedKeysQuery.append("'").append(escape(fkName)).append("' as fkn"); + } else { + exportedKeysQuery.append("'' as fkn"); + } - count++; + count++; + } } } } - } - boolean hasImportedKey = (count > 0); - StringBuilder sql = new StringBuilder(512); - sql.append("select ") + boolean hasImportedKey = (count > 0); + sql.append("select ") .append(catalog) .append(" as PKTABLE_CAT, ") .append(quotedSchema) @@ -1380,12 +1416,18 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) // foreign_keys = true ? .append(" as DEFERRABILITY "); - if (hasImportedKey) { - sql.append("from (") + if (hasImportedKey) { + sql.append("from (") .append(exportedKeysQuery) .append(") ORDER BY FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, KEY_SEQ"); - } else { - sql.append("limit 0"); + } else { + sql.append("limit 0"); + } + if (i != schemasNames.size() - 1) { + sql.append(" union all "); + } else { + sql.append(";"); + } } return ((CoreStatement) stat).executeQuery(sql.toString(), true); @@ -1412,64 +1454,70 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) ResultSet rs; Statement stat = conn.createStatement(); StringBuilder sql = new StringBuilder(700); - - sql.append("select ") + ResultSet schemas = getSchemas(null, escapeWildcards(schema)); + List schemasNames = getSchemasNames(schemas); + for (int i = 0; i < schemasNames.size(); i++) { + String schemaName = schemasNames.get(i); + sql.append("select ") .append(quote(catalog)) .append(" as PKTABLE_CAT, ") - .append(quote(schema == null ? "main" : schema)) + .append(quote(schemaName)) .append(" as PKTABLE_SCHEM, ") .append("ptn as PKTABLE_NAME, pcn as PKCOLUMN_NAME, ") .append(quote(catalog)) .append(" as FKTABLE_CAT, ") - .append(quote(schema == null ? "main" : schema)) + .append(quote(schemaName)) .append(" as FKTABLE_SCHEM, ") .append(quote(table)) .append(" as FKTABLE_NAME, ") .append( - "fcn as FKCOLUMN_NAME, ks as KEY_SEQ, ur as UPDATE_RULE, dr as DELETE_RULE, fkn as FK_NAME, pkn as PK_NAME, ") + "fcn as FKCOLUMN_NAME, ks as KEY_SEQ, ur as UPDATE_RULE, dr as DELETE_RULE, fkn as FK_NAME, pkn as PK_NAME, ") .append(DatabaseMetaData.importedKeyInitiallyDeferred) .append(" as DEFERRABILITY from ("); - // Use a try catch block to avoid "query does not return ResultSet" error - try { - rs = stat.executeQuery("pragma foreign_key_list('" + escape(table) + "');"); - } catch (SQLException e) { - sql = appendDummyForeignKeyList(sql); - return ((CoreStatement) stat).executeQuery(sql.toString(), true); - } + // Use a try catch block to avoid "query does not return ResultSet" error + try { + rs = stat.executeQuery("pragma foreign_key_list('" + escape(table) + "');"); + } catch (SQLException e) { + appendDummyForeignKeyList(sql); + if (i != schemasNames.size() - 1) { + sql.append(" union all "); + } + continue; + } - final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(table, schema); - List fkNames = impFkFinder.getFkList(); + final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(table, schemaName); + List fkNames = impFkFinder.getFkList(); - int i = 0; - for (; rs.next(); i++) { - int keySeq = rs.getInt(2) + 1; - int keyId = rs.getInt(1); - String PKTabName = rs.getString(3); - String FKColName = rs.getString(4); - String PKColName = rs.getString(5); + int j = 0; + for (; rs.next(); j++) { + int keySeq = rs.getInt(2) + 1; + int keyId = rs.getInt(1); + String PKTabName = rs.getString(3); + String FKColName = rs.getString(4); + String PKColName = rs.getString(5); - String pkName = null; - try { - PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(PKTabName, schema); - pkName = pkFinder.getName(); - if (PKColName == null) { - PKColName = pkFinder.getColumns()[0]; + String pkName = null; + try { + PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(PKTabName, schemaName); + pkName = pkFinder.getName(); + if (PKColName == null) { + PKColName = pkFinder.getColumns()[0]; + } + } catch (SQLException ignored) { } - } catch (SQLException ignored) { - } - String updateRule = rs.getString(6); - String deleteRule = rs.getString(7); + String updateRule = rs.getString(6); + String deleteRule = rs.getString(7); - if (i > 0) { - sql.append(" union all "); - } + if (j > 0) { + sql.append(" union all "); + } - String fkName = null; - if (fkNames.size() > keyId) fkName = fkNames.get(keyId).getFkName(); + String fkName = null; + if (fkNames.size() > keyId) fkName = fkNames.get(keyId).getFkName(); - sql.append("select ") + sql.append("select ") .append(keySeq) .append(" as ks,") .append("'") @@ -1511,13 +1559,18 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) .append(" as fkn, ") .append(pkName == null ? "''" : quote(pkName)) .append(" as pkn"); - } - rs.close(); + } + rs.close(); + + if (j == 0) { + appendDummyForeignKeyList(sql); + } + if (i != schemasNames.size() - 1) { + sql.append(") UNION ALL "); + } else { + sql.append(") ORDER BY PKTABLE_CAT, PKTABLE_SCHEM, PKTABLE_NAME, KEY_SEQ;"); + } - if (i == 0) { - sql = appendDummyForeignKeyList(sql); - } else { - sql.append(") ORDER BY PKTABLE_CAT, PKTABLE_SCHEM, PKTABLE_NAME, KEY_SEQ;"); } return ((CoreStatement) stat).executeQuery(sql.toString(), true); @@ -1652,7 +1705,7 @@ public ResultSet getSuperTables(String c, String s, String t) throws SQLExceptio if (getSuperTables == null) { getSuperTables = conn.prepareStatement( - "select null as TABLE_CAT, null as TABLE_SCHEM, " + "select null as TABLE_CAT, s as TABLE_SCHEM, " + "null as TABLE_NAME, null as SUPERTABLE_NAME limit 0;"); } return getSuperTables.executeQuery(); @@ -1696,13 +1749,26 @@ public synchronized ResultSet getTables( String c, String s, String tblNamePattern, String[] types) throws SQLException { checkOpen(); + ArrayList schemasNames = getSchemasNames(getSchemas(c, s)); + StringBuilder sql = new StringBuilder(); + for (int i = 0; i < schemasNames.size(); i++) { + appendSchemaTablesRequests(sql, schemasNames.get(i), tblNamePattern, types); + if (i != schemasNames.size() - 1) { + sql.append("\nUNION ALL\n"); + } else { + sql.append(" ORDER BY TABLE_TYPE, TABLE_NAME;"); + } + } + return ((CoreStatement) conn.createStatement()).executeQuery(sql.toString(), true); + } + private StringBuilder appendSchemaTablesRequests( + StringBuilder sql, String s, String tblNamePattern, String[] types + ) { tblNamePattern = (tblNamePattern == null || "".equals(tblNamePattern)) ? "%" : escape(tblNamePattern); - - StringBuilder sql = new StringBuilder(); sql.append("SELECT").append("\n"); sql.append(" NULL AS TABLE_CAT,").append("\n"); sql.append(" ") @@ -1762,10 +1828,7 @@ public synchronized ResultSet getTables( .collect(Collectors.joining(","))); sql.append(")"); } - - sql.append(" ORDER BY TABLE_TYPE, TABLE_NAME;"); - - return ((CoreStatement) conn.createStatement()).executeQuery(sql.toString(), true); + return sql; } /** @see java.sql.DatabaseMetaData#getTableTypes() */ diff --git a/src/main/java/org/sqlite/jdbc4/JDBC4DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc4/JDBC4DatabaseMetaData.java index 703eb5360..5abd4f349 100644 --- a/src/main/java/org/sqlite/jdbc4/JDBC4DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc4/JDBC4DatabaseMetaData.java @@ -25,10 +25,6 @@ public RowIdLifetime getRowIdLifetime() throws SQLException { throw new SQLFeatureNotSupportedException(); } - public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLException { - throw new SQLFeatureNotSupportedException(); - } - public boolean supportsStoredFunctionsUsingCallSyntax() throws SQLException { throw new SQLFeatureNotSupportedException(); } diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index 3d26aa0a2..759ecf96b 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -91,7 +91,10 @@ public void getTables() throws SQLException { rs = meta.getTables(null, null, null, new String[] {"system table"}); assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); - assertThat(rs.next()).isFalse(); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("main"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("temp"); rs.close(); } @@ -928,7 +931,7 @@ public void columnOrderOfgetTables() throws SQLException { ResultSet rsTables = meta.getTables( null, - null, + "", null, new String[] {"TABLE", "VIEW", "GLOBAL TEMPORARY", "SYSTEM TABLE"}); @@ -1629,19 +1632,19 @@ public void getTablesForAttachedDatabase() throws SQLException { assertThat(rs.next()).isTrue(); rs.close(); - rs = meta.getTables(null, null, null, new String[] {"table"}); + rs = meta.getTables(null, "main", null, new String[] {"table"}); assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); assertThat(rs.next()).isFalse(); rs.close(); - rs = meta.getTables(null, null, null, new String[] {"view"}); + rs = meta.getTables(null, "main", null, new String[] {"view"}); assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); assertThat(rs.next()).isFalse(); rs.close(); - rs = meta.getTables(null, null, null, new String[] {"system table"}); + rs = meta.getTables(null, "main", null, new String[] {"system table"}); assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); assertThat(rs.next()).isFalse(); @@ -1810,7 +1813,7 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { @Test public void getTablesForDefaultSchema() throws SQLException { - ResultSet rs = meta.getTables(null, null, null, new String[] {"table"}); + ResultSet rs = meta.getTables(null, "", null, new String[] {"table"}); assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("main"); From 6e6eea89705f1211054c3021374b5ff1e414d5c8 Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Mon, 13 Feb 2023 23:09:21 +0400 Subject: [PATCH 06/10] update getColumns to support patterns --- .../sqlite/jdbc3/JDBC3DatabaseMetaData.java | 46 +++++++++++++------ src/test/java/org/sqlite/DBMetaDataTest.java | 6 +-- 2 files changed, 36 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java index f04cd2bc5..233270fca 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -913,8 +913,28 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co // empty string --- if it cannot be determined whether the column is auto incremented // parameter is unknown checkOpen(); - + ResultSet schemas = getSchemas(c, s); + ArrayList schemasNames = getSchemasNames(schemas); StringBuilder sql = new StringBuilder(700); + sql.append("SELECT * FROM ("); + for (int i = 0; i < schemasNames.size(); i++) { + appendGetSchemaColumns(sql ,c, schemasNames.get(i), tblNamePattern, colNamePattern); + if (i != schemasNames.size() - 1) { + sql.append(" UNION ALL "); + } else { + sql.append("\n) order by TABLE_SCHEM, TABLE_NAME, ORDINAL_POSITION;"); + } + } + + Statement stat = conn.createStatement(); + return ((CoreStatement) stat).executeQuery(sql.toString(), true); + } + + private StringBuilder appendGetSchemaColumns( + StringBuilder sql, String c, String s, String tblNamePattern, String colNamePattern + ) + throws SQLException { + sql.append("select null as TABLE_CAT, ") .append(quote(s == null ? "main" : s)) .append(" as TABLE_SCHEM, tblname as TABLE_NAME, ") @@ -953,8 +973,7 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co rsColAutoinc = statColAutoinc.executeQuery( "SELECT LIKE('%autoincrement%', LOWER(sql)) FROM " - + prependSchemaPrefix( - s, "sqlite_master WHERE LOWER(name) = LOWER('") + + prependSchemaPrefix(s, "sqlite_master WHERE LOWER(name) = LOWER('") + escape(tableName) + "') AND TYPE IN ('table', 'view')"); rsColAutoinc.next(); @@ -979,8 +998,7 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co // For each table, get the column info and build into overall SQL String pragmaStatement = "PRAGMA " - + prependSchemaPrefix( - s, "table_xinfo('" + escape(tableName) + "')"); + + prependSchemaPrefix(s, "table_xinfo('" + escape(tableName) + "')"); try (Statement colstat = conn.createStatement(); ResultSet rscol = colstat.executeQuery(pragmaStatement)) { @@ -1130,14 +1148,14 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co } if (colFound) { - sql.append(") order by TABLE_SCHEM, TABLE_NAME, ORDINAL_POSITION;"); + sql.append(") "); } else { sql.append( - "select null as ordpos, null as colnullable, null as ct, null as colsize, null as colDecimalDigits, null as tblname, null as cn, null as tn, null as colDefault, null as colautoincrement, null as colgenerated) limit 0;"); + "select null as ordpos, null as colnullable, null as ct, null as colsize, null as " + + "colDecimalDigits, null as tblname, null as cn, null as tn, null as colDefault, null as " + + "colautoincrement, null as colgenerated limit 0)"); } - - Statement stat = conn.createStatement(); - return ((CoreStatement) stat).executeQuery(sql.toString(), true); + return sql; } /** @@ -1224,6 +1242,8 @@ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLExce createSchemaPrimaryKeysQuery(schemaNames.get(i), table, sql); if (i != schemaNames.size() - 1) { sql.append(" union all "); + } else { + sql.append(" order by cn;"); } } return ((CoreStatement) stat).executeQuery(sql.toString(), true); @@ -1266,7 +1286,7 @@ private void createSchemaPrimaryKeysQuery(String s, String table, StringBuilder .append(i + 1) .append(" as ks"); } - sql.append(") order by cn;"); + sql.append(") "); } private static final Map RULE_MAP = new HashMap<>(); @@ -1772,7 +1792,7 @@ private StringBuilder appendSchemaTablesRequests( sql.append("SELECT").append("\n"); sql.append(" NULL AS TABLE_CAT,").append("\n"); sql.append(" ") - .append(quote(s == null ? "main" : s)) + .append(quote(s)) .append(" AS TABLE_SCHEM,") .append("\n"); sql.append(" NAME AS TABLE_NAME,").append("\n"); @@ -1787,7 +1807,7 @@ private StringBuilder appendSchemaTablesRequests( sql.append(" (").append("\n"); sql.append(" SELECT\n"); sql.append(" 'sqlite_schema' AS NAME,\n"); - sql.append(" 'SYSTEM TABLE' AS TYPE"); + sql.append(" 'SYSTEM TABLE' AS TYPE\n"); sql.append(" UNION ALL").append("\n"); sql.append(" SELECT").append("\n"); sql.append(" NAME,").append("\n"); diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index 759ecf96b..b76b0168e 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -92,9 +92,9 @@ public void getTables() throws SQLException { assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("main"); - assertThat(rs.next()).isTrue(); - assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); - assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("temp"); +// assertThat(rs.next()).isTrue(); +// assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); +// assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("temp"); rs.close(); } From b0eab66f48d00422a99da49ece44aece8dd5e687 Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Tue, 14 Feb 2023 15:38:21 +0400 Subject: [PATCH 07/10] add handling when schema is not found --- .../sqlite/jdbc3/JDBC3DatabaseMetaData.java | 215 +++++++++++++----- src/test/java/org/sqlite/DBMetaDataTest.java | 116 ++++++++++ 2 files changed, 270 insertions(+), 61 deletions(-) diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java index 233270fca..21bf914b6 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -916,8 +916,10 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co ResultSet schemas = getSchemas(c, s); ArrayList schemasNames = getSchemasNames(schemas); StringBuilder sql = new StringBuilder(700); - sql.append("SELECT * FROM ("); for (int i = 0; i < schemasNames.size(); i++) { + if (i == 0) { + sql.append("SELECT * FROM ("); + } appendGetSchemaColumns(sql ,c, schemasNames.get(i), tblNamePattern, colNamePattern); if (i != schemasNames.size() - 1) { sql.append(" UNION ALL "); @@ -925,7 +927,34 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co sql.append("\n) order by TABLE_SCHEM, TABLE_NAME, ORDINAL_POSITION;"); } } - + if (schemasNames.size() == 0) { + sql.append("select "); + sql.append("\tnull as TABLE_CAT,\n") + .append("\tnull as TABLE_SCHEM,\n") + .append("\tnull as TABLE_NAME,\n") + .append("\tnull as COLUMN_NAME,\n") + .append("\tnull as DATA_TYPE,\n") + .append("\tnull as TYPE_NAME,\n") + .append("\tnull as COLUMN_SIZE,\n") + .append("\tnull as BUFFER_LENGTH,\n") + .append("\tnull as DECIMAL_DIGITS,\n") + .append("\tnull as NUM_PREC_RADIX,\n") + .append("\tnull as NULLABLE,\n") + .append("\tnull as REMARKS,\n") + .append("\tnull as COLUMN_DEF,\n") + .append("\tnull as SQL_DATA_TYPE,\n") + .append("\tnull as SQL_DATETIME_SUB,\n") + .append("\tnull as CHAR_OCTET_LENGTH,\n") + .append("\tnull as ORDINAL_POSITION,\n") + .append("\tnull as IS_NULLABLE,\n") + .append("\tnull as SCOPE_CATLOG,\n") + .append("\tnull as SCOPE_SCHEMA,\n") + .append("\tnull as SCOPE_TABLE,\n") + .append("\tnull as SOURCE_DATA_TYPE,\n") + .append("\tnull as IS_AUTOINCREMENT,\n") + .append("\tnull as IS_GENERATEDCOLUMN\n") + .append("\t limit 0"); + } Statement stat = conn.createStatement(); return ((CoreStatement) stat).executeQuery(sql.toString(), true); } @@ -1246,6 +1275,14 @@ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLExce sql.append(" order by cn;"); } } + if (schemaNames.size() == 0) { + sql.append("select null as TABLE_CAT, ") + .append("null") + .append(" as TABLE_SCHEM, '") + .append("null") + .append("' as TABLE_NAME, null as COLUMN_NAME, null as KEY_SEQ, null as PK_NAME limit 0;"); + } + return ((CoreStatement) stat).executeQuery(sql.toString(), true); } @@ -1449,7 +1486,24 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) sql.append(";"); } } - + if (schemasNames.size() == 0) { + sql.append("select\n") + .append("\tnull as PKTABLE_CAT,\n") + .append("\tnull as PKTABLE_SCHEM,\n") + .append("\tnull as PKTABLE_NAME,\n") + .append("\tnull as PKCOLUMN_NAME,\n") + .append("\tnull as FKTABLE_CAT,\n") + .append("\tnull as FKTABLE_SCHEM,\n") + .append("\t'null' as FKTABLE_NAME,\n") + .append("\tnull as FKCOLUMN_NAME,\n") + .append("\tnull as KEY_SEQ,\n") + .append("\tnull as UPDATE_RULE,\n") + .append("\tnull as DELETE_RULE,\n") + .append("\tnull as FK_NAME,\n") + .append("\tnull as PK_NAME,\n") + .append("\tnull as DEFERRABILITY\n") + .append("limit 0;"); + } return ((CoreStatement) stat).executeQuery(sql.toString(), true); } @@ -1461,7 +1515,7 @@ private StringBuilder appendDummyForeignKeyList(StringBuilder sql) { .append(" as dr, ") .append(" '' as fkn, ") .append(" '' as pkn ") - .append(") limit 0;"); + .append(" limit 0"); return sql; } @@ -1502,6 +1556,8 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) appendDummyForeignKeyList(sql); if (i != schemasNames.size() - 1) { sql.append(" union all "); + } else { + sql.append(";"); } continue; } @@ -1590,6 +1646,23 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) } else { sql.append(") ORDER BY PKTABLE_CAT, PKTABLE_SCHEM, PKTABLE_NAME, KEY_SEQ;"); } + } + if (schemasNames.size() == 0) { + sql.append("select\n") + .append("\tnull as PKTABLE_CAT,\n") + .append("\tnull as PKTABLE_SCHEM,\n") + .append("\tnull as PKTABLE_NAME,\n") + .append("\tnull as PKCOLUMN_NAME,\n") + .append("\tnull as FKTABLE_CAT,\n") + .append("\tnull as FKTABLE_SCHEM,\n") + .append("\tnull as FKTABLE_NAME,\n") + .append("\tnull as FKCOLUMN_NAME,\n") + .append("\tnull as KEY_SEQ,\n") + .append("\tnull as UPDATE_RULE,\n") + .append("\tnull as DELETE_RULE,\n") + .append("\tnull as FK_NAME,\n") + .append("\tnull as PK_NAME,\n") + .append("\tnull as DEFERRABILITY limit 0;"); } @@ -1602,86 +1675,91 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) */ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boolean approximate) throws SQLException { - ResultSet rs; Statement stat = conn.createStatement(); - StringBuilder sql = new StringBuilder(500); - - // define the column header - // this is from the JDBC spec, it is part of the driver protocol - sql.append("select null as TABLE_CAT,") - .append(quote(s == null ? "main" : s)) - .append(" as TABLE_SCHEM, '") + ResultSet schemas = getSchemas(c, escapeWildcards(s)); + ArrayList schemasNames = getSchemasNames(schemas); + StringBuilder sql = new StringBuilder(1000); + for (int i = 0; i < schemasNames.size(); i++) { + ResultSet rs; + // define the column header + // this is from the JDBC spec, it is part of the driver protocol + sql.append("select null as TABLE_CAT,'") + .append(escape(schemasNames.get(i))) + .append("' as TABLE_SCHEM, '") .append(escape(table)) - .append( - "' as TABLE_NAME, un as NON_UNIQUE, null as INDEX_QUALIFIER, n as INDEX_NAME, ") + .append("' as TABLE_NAME, un as NON_UNIQUE, null as INDEX_QUALIFIER, n as INDEX_NAME, ") .append(Integer.toString(DatabaseMetaData.tableIndexOther)) .append(" as TYPE, op as ORDINAL_POSITION, ") .append( - "cn as COLUMN_NAME, null as ASC_OR_DESC, 0 as CARDINALITY, 0 as PAGES, null as FILTER_CONDITION from ("); - - // this always returns a result set now, previously threw exception - rs = - stat.executeQuery( - "pragma " + prependSchemaPrefix(s, "index_list('" + escape(table) + "');")); - - ArrayList> indexList = new ArrayList<>(); - while (rs.next()) { - indexList.add(new ArrayList<>()); - indexList.get(indexList.size() - 1).add(rs.getString(2)); - indexList.get(indexList.size() - 1).add(rs.getInt(3)); - } - rs.close(); - if (indexList.size() == 0) { - // if pragma index_list() returns no information, use this null block - sql.append("select null as un, null as n, null as op, null as cn) limit 0;"); - return ((CoreStatement) stat).executeQuery(sql.toString(), true); - } else { - // loop over results from pragma call, getting specific info for each index + "cn as COLUMN_NAME, null as ASC_OR_DESC, 0 as CARDINALITY, 0 as PAGES, null as FILTER_CONDITION from ("); - Iterator> indexIterator = indexList.iterator(); - ArrayList currentIndex; + // this always returns a result set now, previously threw exception + rs = stat.executeQuery("pragma " + prependSchemaPrefix(s, "index_list('" + escape(schemasNames.get(i)) + + "');")); - ArrayList unionAll = new ArrayList<>(); + ArrayList> indexList = new ArrayList<>(); + while (rs.next()) { + indexList.add(new ArrayList<>()); + indexList.get(indexList.size() - 1).add(rs.getString(2)); + indexList.get(indexList.size() - 1).add(rs.getInt(3)); + } + rs.close(); + if (indexList.size() == 0) { + // if pragma index_list() returns no information, use this null block + sql.append("select null as un, null as n, null as op, null as cn limit 0"); + } else { + // loop over results from pragma call, getting specific info for each index - while (indexIterator.hasNext()) { - currentIndex = indexIterator.next(); - String indexName = currentIndex.get(0).toString(); - rs = - stat.executeQuery( - "pragma " - + prependSchemaPrefix( - s, "index_info('" + escape(indexName) + "');")); + Iterator> indexIterator = indexList.iterator(); + ArrayList currentIndex; - while (rs.next()) { + ArrayList unionAll = new ArrayList<>(); - StringBuilder sqlRow = new StringBuilder(); + while (indexIterator.hasNext()) { + currentIndex = indexIterator.next(); + String indexName = currentIndex.get(0).toString(); + rs = stat.executeQuery( + "pragma " + prependSchemaPrefix(s, "index_info('" + escape(indexName) + "');")); - String colName = rs.getString(3); - sqlRow.append("select ") + while (rs.next()) { + + StringBuilder sqlRow = new StringBuilder(); + + String colName = rs.getString(3); + sqlRow.append("select ") .append(1 - (Integer) currentIndex.get(1)) .append(" as un,'") .append(escape(indexName)) .append("' as n,") .append(rs.getInt(1) + 1) .append(" as op,"); - if (colName == null) { // expression index - sqlRow.append("null"); - } else { - sqlRow.append("'").append(escape(colName)).append("'"); + if (colName == null) { // expression index + sqlRow.append("null"); + } else { + sqlRow.append("'").append(escape(colName)).append("'"); + } + sqlRow.append(" as cn"); + + unionAll.add(sqlRow.toString()); } - sqlRow.append(" as cn"); - unionAll.add(sqlRow.toString()); + rs.close(); } - rs.close(); + String sqlBlock = StringUtils.join(unionAll, " union all "); + sql.append(sqlBlock); } - - String sqlBlock = StringUtils.join(unionAll, " union all "); - - return ((CoreStatement) stat) - .executeQuery(sql.append(sqlBlock).append(");").toString(), true); + if (i != schemasNames.size() - 1) { + sql.append(") union all "); + } else { + sql.append(");"); + } + } + if (schemasNames.size() == 0) { + sql.append("select null as un, null as n, null as op, null as cn limit 0"); } + return ((CoreStatement) stat) + .executeQuery(sql.toString(), true); } /** @@ -1779,6 +1857,21 @@ public synchronized ResultSet getTables( sql.append(" ORDER BY TABLE_TYPE, TABLE_NAME;"); } } + if (schemasNames.size() == 0) { + sql.append("\nSelect"); + sql.append("\n NULL AS TABLE_CAT,"); + sql.append("\n NULL AS TABLE_SCHEM,"); + sql.append("\n NULL AS TABLE_NAME,"); + sql.append("\n NULL AS TABLE_TYPE,"); + sql.append("\n NULL AS REMARKS,"); + sql.append("\n NULL AS TYPE_CAT,"); + sql.append("\n NULL AS TYPE_SCHEM,"); + sql.append("\n NULL AS TYPE_NAME,"); + sql.append("\n NULL AS SELF_REFERENCING_COL_NAME,"); + sql.append("\n NULL AS REF_GENERATION"); + sql.append("\n LIMIT 0;"); + } + return ((CoreStatement) conn.createStatement()).executeQuery(sql.toString(), true); } diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index b76b0168e..dd9573a7d 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -1573,6 +1573,120 @@ public void version() throws Exception { assertThat(meta.getUserName()).as("user name").isNull(); } + @Test + public void testRequestsWithNonExistentSchemas() throws SQLException { + ResultSet rs = meta.getSchemas(null, "nonexistent"); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isFalse(); + ResultSetMetaData rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(2); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_CATALOG"); + + rs = meta.getIndexInfo(null, "nonexistent", null, false, false); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isFalse(); + + rs = meta.getTables(null, "nonexistent", null, null); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isFalse(); + rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(10); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("TABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("TABLE_TYPE"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("REMARKS"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("TYPE_CAT"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("TYPE_SCHEM"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("TYPE_NAME"); + assertThat(rsmeta.getColumnName(9)).isEqualTo("SELF_REFERENCING_COL_NAME"); + assertThat(rsmeta.getColumnName(10)).isEqualTo("REF_GENERATION"); + + + rs = meta.getColumns(null, "nonexistent", null, null); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isFalse(); + rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnCount()).isEqualTo(24); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("TABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("COLUMN_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("DATA_TYPE"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("TYPE_NAME"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("COLUMN_SIZE"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("BUFFER_LENGTH"); + assertThat(rsmeta.getColumnName(9)).isEqualTo("DECIMAL_DIGITS"); + assertThat(rsmeta.getColumnName(10)).isEqualTo("NUM_PREC_RADIX"); + assertThat(rsmeta.getColumnName(11)).isEqualTo("NULLABLE"); + assertThat(rsmeta.getColumnName(12)).isEqualTo("REMARKS"); + assertThat(rsmeta.getColumnName(13)).isEqualTo("COLUMN_DEF"); + assertThat(rsmeta.getColumnName(14)).isEqualTo("SQL_DATA_TYPE"); + assertThat(rsmeta.getColumnName(15)).isEqualTo("SQL_DATETIME_SUB"); + assertThat(rsmeta.getColumnName(16)).isEqualTo("CHAR_OCTET_LENGTH"); + assertThat(rsmeta.getColumnName(17)).isEqualTo("ORDINAL_POSITION"); + assertThat(rsmeta.getColumnName(18)).isEqualTo("IS_NULLABLE"); + // should be SCOPE_CATALOG, but misspelt in the standard + assertThat(rsmeta.getColumnName(19)).isEqualTo("SCOPE_CATLOG"); + assertThat(rsmeta.getColumnName(20)).isEqualTo("SCOPE_SCHEMA"); + assertThat(rsmeta.getColumnName(21)).isEqualTo("SCOPE_TABLE"); + assertThat(rsmeta.getColumnName(22)).isEqualTo("SOURCE_DATA_TYPE"); + assertThat(rsmeta.getColumnName(23)).isEqualTo("IS_AUTOINCREMENT"); + assertThat(rsmeta.getColumnName(24)).isEqualTo("IS_GENERATEDCOLUMN"); + + rs = meta.getExportedKeys(null, "nonexistent", null); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isFalse(); + rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnName(1)).isEqualTo("PKTABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("PKTABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("PKTABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("PKCOLUMN_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("FKTABLE_CAT"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("FKTABLE_SCHEM"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("FKTABLE_NAME"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("FKCOLUMN_NAME"); + assertThat(rsmeta.getColumnName(9)).isEqualTo("KEY_SEQ"); + assertThat(rsmeta.getColumnName(10)).isEqualTo("UPDATE_RULE"); + assertThat(rsmeta.getColumnName(11)).isEqualTo("DELETE_RULE"); + assertThat(rsmeta.getColumnName(12)).isEqualTo("FK_NAME"); + assertThat(rsmeta.getColumnName(13)).isEqualTo("PK_NAME"); + assertThat(rsmeta.getColumnName(14)).isEqualTo("DEFERRABILITY"); + + rs = meta.getImportedKeys(null, "nonexistent", null); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isFalse(); + rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnName(1)).isEqualTo("PKTABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("PKTABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("PKTABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("PKCOLUMN_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("FKTABLE_CAT"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("FKTABLE_SCHEM"); + assertThat(rsmeta.getColumnName(7)).isEqualTo("FKTABLE_NAME"); + assertThat(rsmeta.getColumnName(8)).isEqualTo("FKCOLUMN_NAME"); + assertThat(rsmeta.getColumnName(9)).isEqualTo("KEY_SEQ"); + assertThat(rsmeta.getColumnName(10)).isEqualTo("UPDATE_RULE"); + assertThat(rsmeta.getColumnName(11)).isEqualTo("DELETE_RULE"); + assertThat(rsmeta.getColumnName(12)).isEqualTo("FK_NAME"); + assertThat(rsmeta.getColumnName(13)).isEqualTo("PK_NAME"); + assertThat(rsmeta.getColumnName(14)).isEqualTo("DEFERRABILITY"); + + + rs = meta.getPrimaryKeys(null, "nonexistent", null); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isFalse(); + rsmeta = rs.getMetaData(); + assertThat(rsmeta.getColumnName(1)).isEqualTo("TABLE_CAT"); + assertThat(rsmeta.getColumnName(2)).isEqualTo("TABLE_SCHEM"); + assertThat(rsmeta.getColumnName(3)).isEqualTo("TABLE_NAME"); + assertThat(rsmeta.getColumnName(4)).isEqualTo("COLUMN_NAME"); + assertThat(rsmeta.getColumnName(5)).isEqualTo("KEY_SEQ"); + assertThat(rsmeta.getColumnName(6)).isEqualTo("PK_NAME"); + + } + @Nested class DBMetadataTestWithAttachedDatabases { File testDB; @@ -1893,6 +2007,8 @@ public void getIndexInfoIndexedSingleExprForAttachedDatabase() throws SQLExcepti assertThat(rsmd).isNotNull(); } + + @Test public void getIndexInfoIndexedMultiForAttachedDatabase() throws SQLException { stat.executeUpdate( From 1313587c6e8b9047860ccd12add732fdbc82110a Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Tue, 14 Feb 2023 21:34:09 +0400 Subject: [PATCH 08/10] update tests --- .../sqlite/jdbc3/JDBC3DatabaseMetaData.java | 10 +- src/test/java/org/sqlite/DBMetaDataTest.java | 125 +++++++++++++++++- 2 files changed, 126 insertions(+), 9 deletions(-) diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java index 21bf914b6..d04c39026 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -924,7 +924,7 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co if (i != schemasNames.size() - 1) { sql.append(" UNION ALL "); } else { - sql.append("\n) order by TABLE_SCHEM, TABLE_NAME, ORDINAL_POSITION;"); + sql.append("\n) order by TABLE_NAME, TABLE_SCHEM, ORDINAL_POSITION;"); } } if (schemasNames.size() == 0) { @@ -1228,7 +1228,8 @@ public ResultSet getSchemas() throws SQLException { if (getSchemas == null) { getSchemas = conn.prepareStatement( - "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list;"); + "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list order by " + + "TABLE_SCHEM;"); } return getSchemas.executeQuery(); @@ -1244,7 +1245,8 @@ public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLExce } Statement stat = conn.createStatement(); String sql = "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list\n" - + "where TABLE_SCHEM like '" + schemaPattern + "' escape '" + getSearchStringEscape() + "';"; + + "where TABLE_SCHEM like '" + schemaPattern + "' escape '" + getSearchStringEscape() + "' order by " + + "TABLE_SCHEM;"; return stat.executeQuery(sql); } @@ -1527,7 +1529,7 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) throws SQLException { ResultSet rs; Statement stat = conn.createStatement(); - StringBuilder sql = new StringBuilder(700); + StringBuilder sql = new StringBuilder(1024); ResultSet schemas = getSchemas(null, escapeWildcards(schema)); List schemasNames = getSchemasNames(schemas); for (int i = 0; i < schemasNames.size(); i++) { diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index dd9573a7d..eca204f3b 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -1708,10 +1708,10 @@ public void readSchemaWithAttachedDatabases() throws SQLException { ResultSetMetaData rsMeta = rs.getMetaData(); assertThat(rsMeta.getColumnCount()).isEqualTo(2); assertThat(rsMeta.getColumnName(1)).isEqualTo("TABLE_SCHEM"); - assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("main"); - assertThat(rs.next()).isTrue(); - assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("db2"); assertThat(rsMeta.getColumnName(2)).isEqualTo("TABLE_CATALOG"); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("db2"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("main"); } @Test @@ -1767,8 +1767,7 @@ public void getTablesForAttachedDatabase() throws SQLException { @Test public void testDynamicDatabaseAttachment() throws IOException, SQLException { - ResultSet schemas = meta.getSchemas(); - schemas.close(); + ResultSet schemas; File thirdDB = Files.createTempFile("db3", ".sqlite").toFile(); stat.executeUpdate("attach database \"" + thirdDB.toURI().toURL() + "\" as db3;"); try { @@ -1776,14 +1775,58 @@ public void testDynamicDatabaseAttachment() throws IOException, SQLException { boolean schemaFound = false; while (schemas.next()) { schemaFound = "db3".equals(schemas.getString("TABLE_SCHEM")); + if (schemaFound) { + break; + } } assertThat(schemaFound).isTrue(); + } finally { stat.executeUpdate("detach database db3;"); thirdDB.deleteOnExit(); } } + @Test + public void testGetSchemasForAttachedDatabases() throws SQLException, IOException { + ResultSet rs; + rs = meta.getSchemas(null, "db2"); + assertThat(rs).isNotNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("db2"); + assertThat(rs.getString("TABLE_CATALOG")).isNull(); + rs.close(); + File thirdDB = Files.createTempFile("db3", ".sqlite").toFile(); + thirdDB.deleteOnExit(); + stat.executeUpdate("attach database \"" + thirdDB.toURI().toURL() + "\" as db3;"); + try { + rs = meta.getSchemas(null, "db_"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("db2"); + assertThat(rs.getString("TABLE_CATALOG")).isNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("db3"); + assertThat(rs.getString("TABLE_CATALOG")).isNull(); + rs.close(); + rs = meta.getSchemas(null, "%"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("db2"); + assertThat(rs.getString("TABLE_CATALOG")).isNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("db3"); + assertThat(rs.getString("TABLE_CATALOG")).isNull(); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("main"); + assertThat(rs.getString("TABLE_CATALOG")).isNull(); + rs.close(); + rs = meta.getSchemas(null, "\\%"); + assertThat(rs.next()).isFalse(); + + } finally { + stat.executeUpdate("detach database db3;"); + } + } + @Test public void getColumnsForAttachedDatabaseTables() throws SQLException { ResultSet rs = meta.getColumns(null, "db2", "test2", "id"); @@ -1797,6 +1840,7 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(0); assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); assertThat(rs.next()).isFalse(); + rs.close(); rs = meta.getColumns(null, "db2", "test2", "fn"); assertThat(rs.next()).isTrue(); @@ -1808,6 +1852,7 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(10); assertThat(rs.getString("IS_AUTOINCREMENT")).isEqualTo("NO"); assertThat(rs.next()).isFalse(); + rs.close(); rs = meta.getColumns(null, "db2", "test2", "sn"); assertThat(rs.next()).isTrue(); @@ -1817,6 +1862,7 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(10); assertThat(rs.getString("COLUMN_DEF")).isNull(); assertThat(rs.next()).isFalse(); + rs.close(); rs = meta.getColumns(null, "db2", "test2", "intvalue"); assertThat(rs.next()).isTrue(); @@ -1827,6 +1873,7 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(0); assertThat(rs.getString("COLUMN_DEF")).isNull(); assertThat(rs.next()).isFalse(); + rs.close(); rs = meta.getColumns(null, "db2", "test2", "realvalue"); assertThat(rs.next()).isTrue(); @@ -1837,6 +1884,7 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.getInt("DECIMAL_DIGITS")).isEqualTo(3); assertThat(rs.getString("COLUMN_DEF")).isNull(); assertThat(rs.next()).isFalse(); + rs.close(); rs = meta.getColumns(null, "db2", "test2", "%"); assertThat(rs.next()).isTrue(); @@ -1850,6 +1898,7 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.next()).isTrue(); assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); assertThat(rs.next()).isFalse(); + rs.close(); rs = meta.getColumns(null, "db2", "test2", "%n"); assertThat(rs.next()).isTrue(); @@ -1857,6 +1906,7 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.next()).isTrue(); assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); assertThat(rs.next()).isFalse(); + rs.close(); rs = meta.getColumns(null, "db2", "test%", "%"); // TABLE "test2" @@ -1892,6 +1942,8 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); assertThat(rs.next()).isFalse(); + rs.close(); + rs = meta.getColumns(null, "db2", "%", "%"); // TABLE "test2" @@ -1919,10 +1971,73 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.next()).isTrue(); assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); assertThat(rs.next()).isFalse(); + rs.close(); rs = meta.getColumns(null, "db2", "doesnotexist", "%"); assertThat(rs.next()).isFalse(); assertThat(rs.getMetaData().getColumnCount()).isEqualTo(24); + rs.close(); + + + rs = meta.getColumns(null, null, "%", "%"); + assertReadsAllColumns(rs); + rs = meta.getColumns(null, "%", "%", "%"); + assertReadsAllColumns(rs); + rs = meta.getColumns(null, "\\%", "%", "%"); + assertThat(rs.next()).isFalse(); + } + + private void assertReadsAllColumns(ResultSet rs) throws SQLException { + // TABLE "test" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + // TABLE "test2" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + // VIEW "testView" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + // VIEW "testView2" + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("id"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("fn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sn"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isFalse(); + rs.close(); } @Test From 61f53448317ec87ca24f34d47e2bf171501bca59 Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Tue, 18 Apr 2023 18:46:19 +0400 Subject: [PATCH 09/10] Fix error in the tests after the merge conflict resolution --- src/test/java/org/sqlite/DBMetaDataTest.java | 53 ++++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index 0be09c14f..549cb8a09 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -20,6 +20,9 @@ import java.util.Map; import java.util.Properties; import org.junit.jupiter.api.*; +import org.junit.jupiter.api.condition.DisabledInNativeImage; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; /** These tests are designed to stress Statements on memory databases. */ public class DBMetaDataTest { @@ -417,18 +420,13 @@ public void getColumns() throws SQLException { assertThat(rs.next()).isFalse(); rs = meta.getColumns(null, null, "%", "%"); - // SYSTEM TABLE "sqlite_schema" - assertThat(rs.next()).isTrue(); - assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); - assertThat(rs.getString("COLUMN_NAME")).isEqualTo("type"); - assertThat(rs.next()).isTrue(); - assertThat(rs.getString("COLUMN_NAME")).isEqualTo("name"); - assertThat(rs.next()).isTrue(); - assertThat(rs.getString("COLUMN_NAME")).isEqualTo("tbl_name"); - assertThat(rs.next()).isTrue(); - assertThat(rs.getString("COLUMN_NAME")).isEqualTo("rootpage"); - assertThat(rs.next()).isTrue(); - assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sql"); + + // SYSTEM TABLE "sqlite_schema" for main + assertSystemSchema(rs); + + // SYSTEM TABLE "sqlite_schema" for temp + assertSystemSchema(rs); + // TABLE "test" assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); @@ -2041,8 +2039,9 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.next()).isFalse(); rs.close(); - rs = meta.getColumns(null, "db2", "%", "%"); + assertSystemSchema(rs); + // TABLE "test2" assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); @@ -2077,14 +2076,18 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { rs = meta.getColumns(null, null, "%", "%"); - assertReadsAllColumns(rs); + assertReadsAllColumns(rs, 3); rs = meta.getColumns(null, "%", "%", "%"); - assertReadsAllColumns(rs); + assertReadsAllColumns(rs, 3); rs = meta.getColumns(null, "\\%", "%", "%"); assertThat(rs.next()).isFalse(); } - private void assertReadsAllColumns(ResultSet rs) throws SQLException { + private void assertReadsAllColumns(ResultSet rs, int schemasNumber) throws SQLException { + for (int i = 0; i < schemasNumber; i++) { + // When full pattern used we acquire system table for each individual schema + assertSystemSchema(rs); + } // TABLE "test" assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("test"); @@ -2097,6 +2100,8 @@ private void assertReadsAllColumns(ResultSet rs) throws SQLException { assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); assertThat(rs.next()).isTrue(); assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); // TABLE "test2" assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("test2"); @@ -2121,6 +2126,8 @@ private void assertReadsAllColumns(ResultSet rs) throws SQLException { assertThat(rs.getString("COLUMN_NAME")).isEqualTo("intvalue"); assertThat(rs.next()).isTrue(); assertThat(rs.getString("COLUMN_NAME")).isEqualTo("realvalue"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); // VIEW "testView2" assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("testView2"); @@ -2241,4 +2248,18 @@ public void exit() throws SQLException { testDB.deleteOnExit(); } } + + private void assertSystemSchema(ResultSet rs) throws SQLException { + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("type"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("name"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("tbl_name"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("rootpage"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("sql"); + } } From cd76f5785393f942a07792fd3b714d151a532c1a Mon Sep 17 00:00:00 2001 From: Georgii Gvinepadze Date: Thu, 27 Apr 2023 15:32:01 +0400 Subject: [PATCH 10/10] Apply code style --- .../org/sqlite/core/CoreDatabaseMetaData.java | 4 +- .../sqlite/jdbc3/JDBC3DatabaseMetaData.java | 432 +++++++++--------- src/test/java/org/sqlite/DBMetaDataTest.java | 12 +- 3 files changed, 230 insertions(+), 218 deletions(-) diff --git a/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java b/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java index c1a80fa40..456b286ae 100644 --- a/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java +++ b/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java @@ -164,8 +164,8 @@ protected static String quote(String tableName) { } /** - * Escapes all wildcards, to prevent pattern matching for - * functions which should not support it + * Escapes all wildcards, to prevent pattern matching for functions which should not support it + * * @param val The string to escape * @return The string with escaped wildcards */ diff --git a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java index 8c6212c24..41b061a77 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -920,7 +920,7 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co if (i == 0) { sql.append("SELECT * FROM ("); } - appendGetSchemaColumns(sql ,c, schemasNames.get(i), tblNamePattern, colNamePattern); + appendGetSchemaColumns(sql, c, schemasNames.get(i), tblNamePattern, colNamePattern); if (i != schemasNames.size() - 1) { sql.append(" UNION ALL "); } else { @@ -930,39 +930,38 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co if (schemasNames.size() == 0) { sql.append("select "); sql.append("\tnull as TABLE_CAT,\n") - .append("\tnull as TABLE_SCHEM,\n") - .append("\tnull as TABLE_NAME,\n") - .append("\tnull as COLUMN_NAME,\n") - .append("\tnull as DATA_TYPE,\n") - .append("\tnull as TYPE_NAME,\n") - .append("\tnull as COLUMN_SIZE,\n") - .append("\tnull as BUFFER_LENGTH,\n") - .append("\tnull as DECIMAL_DIGITS,\n") - .append("\tnull as NUM_PREC_RADIX,\n") - .append("\tnull as NULLABLE,\n") - .append("\tnull as REMARKS,\n") - .append("\tnull as COLUMN_DEF,\n") - .append("\tnull as SQL_DATA_TYPE,\n") - .append("\tnull as SQL_DATETIME_SUB,\n") - .append("\tnull as CHAR_OCTET_LENGTH,\n") - .append("\tnull as ORDINAL_POSITION,\n") - .append("\tnull as IS_NULLABLE,\n") - .append("\tnull as SCOPE_CATLOG,\n") - .append("\tnull as SCOPE_SCHEMA,\n") - .append("\tnull as SCOPE_TABLE,\n") - .append("\tnull as SOURCE_DATA_TYPE,\n") - .append("\tnull as IS_AUTOINCREMENT,\n") - .append("\tnull as IS_GENERATEDCOLUMN\n") - .append("\t limit 0"); + .append("\tnull as TABLE_SCHEM,\n") + .append("\tnull as TABLE_NAME,\n") + .append("\tnull as COLUMN_NAME,\n") + .append("\tnull as DATA_TYPE,\n") + .append("\tnull as TYPE_NAME,\n") + .append("\tnull as COLUMN_SIZE,\n") + .append("\tnull as BUFFER_LENGTH,\n") + .append("\tnull as DECIMAL_DIGITS,\n") + .append("\tnull as NUM_PREC_RADIX,\n") + .append("\tnull as NULLABLE,\n") + .append("\tnull as REMARKS,\n") + .append("\tnull as COLUMN_DEF,\n") + .append("\tnull as SQL_DATA_TYPE,\n") + .append("\tnull as SQL_DATETIME_SUB,\n") + .append("\tnull as CHAR_OCTET_LENGTH,\n") + .append("\tnull as ORDINAL_POSITION,\n") + .append("\tnull as IS_NULLABLE,\n") + .append("\tnull as SCOPE_CATLOG,\n") + .append("\tnull as SCOPE_SCHEMA,\n") + .append("\tnull as SCOPE_TABLE,\n") + .append("\tnull as SOURCE_DATA_TYPE,\n") + .append("\tnull as IS_AUTOINCREMENT,\n") + .append("\tnull as IS_GENERATEDCOLUMN\n") + .append("\t limit 0"); } Statement stat = conn.createStatement(); return ((CoreStatement) stat).executeQuery(sql.toString(), true); } private StringBuilder appendGetSchemaColumns( - StringBuilder sql, String c, String s, String tblNamePattern, String colNamePattern - ) - throws SQLException { + StringBuilder sql, String c, String s, String tblNamePattern, String colNamePattern) + throws SQLException { sql.append("select null as TABLE_CAT, ") .append(quote(s == null ? "main" : s)) @@ -1001,7 +1000,8 @@ private StringBuilder appendGetSchemaColumns( rsColAutoinc = statColAutoinc.executeQuery( "SELECT LIKE('%autoincrement%', LOWER(sql)) FROM " - + prependSchemaPrefix(s, "sqlite_schema WHERE LOWER(name) = LOWER('") + + prependSchemaPrefix( + s, "sqlite_schema WHERE LOWER(name) = LOWER('") + escape(tableName) + "') AND TYPE IN ('table', 'view')"); rsColAutoinc.next(); @@ -1026,7 +1026,8 @@ private StringBuilder appendGetSchemaColumns( // For each table, get the column info and build into overall SQL String pragmaStatement = "PRAGMA " - + prependSchemaPrefix(s, "table_xinfo('" + escape(tableName) + "')"); + + prependSchemaPrefix( + s, "table_xinfo('" + escape(tableName) + "')"); try (Statement colstat = conn.createStatement(); ResultSet rscol = colstat.executeQuery(pragmaStatement)) { @@ -1181,8 +1182,8 @@ private StringBuilder appendGetSchemaColumns( } else { sql.append( "select null as ordpos, null as colnullable, null as ct, null as colsize, null as " - + "colDecimalDigits, null as tblname, null as cn, null as tn, null as colDefault, null as " - + "colautoincrement, null as colgenerated limit 0)"); + + "colDecimalDigits, null as tblname, null as cn, null as tn, null as colDefault, null as " + + "colautoincrement, null as colgenerated limit 0)"); } return sql; } @@ -1229,7 +1230,7 @@ public ResultSet getSchemas() throws SQLException { getSchemas = conn.prepareStatement( "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list order by " - + "TABLE_SCHEM;"); + + "TABLE_SCHEM;"); } return getSchemas.executeQuery(); @@ -1244,9 +1245,14 @@ public ResultSet getSchemas(String catalog, String schemaPattern) throws SQLExce schemaPattern = "main"; } Statement stat = conn.createStatement(); - String sql = "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list\n" - + "where TABLE_SCHEM like '" + schemaPattern + "' escape '" + getSearchStringEscape() + "' order by " - + "TABLE_SCHEM;"; + String sql = + "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list\n" + + "where TABLE_SCHEM like '" + + schemaPattern + + "' escape '" + + getSearchStringEscape() + + "' order by " + + "TABLE_SCHEM;"; return stat.executeQuery(sql); } @@ -1279,10 +1285,11 @@ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLExce } if (schemaNames.size() == 0) { sql.append("select null as TABLE_CAT, ") - .append("null") - .append(" as TABLE_SCHEM, '") - .append("null") - .append("' as TABLE_NAME, null as COLUMN_NAME, null as KEY_SEQ, null as PK_NAME limit 0;"); + .append("null") + .append(" as TABLE_SCHEM, '") + .append("null") + .append( + "' as TABLE_NAME, null as COLUMN_NAME, null as KEY_SEQ, null as PK_NAME limit 0;"); } return ((CoreStatement) stat).executeQuery(sql.toString(), true); @@ -1296,7 +1303,8 @@ private ArrayList getSchemasNames(ResultSet schemas) throws SQLException return schemaNames; } - private void createSchemaPrimaryKeysQuery(String s, String table, StringBuilder sql) throws SQLException { + private void createSchemaPrimaryKeysQuery(String s, String table, StringBuilder sql) + throws SQLException { PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table, s); String[] columns = pkFinder.getColumns(); sql.append("select null as TABLE_CAT, ") @@ -1365,10 +1373,11 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) // retrieve table list ArrayList tableList; try (ResultSet rs = - stat.executeQuery( - "select name from " - + prependSchemaPrefix( - schema, "sqlite_schema where type = " + "'table'"))) { + stat.executeQuery( + "select name from " + + prependSchemaPrefix( + schema, + "sqlite_schema where type = " + "'table'"))) { tableList = new ArrayList<>(); while (rs.next()) { @@ -1410,29 +1419,34 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) } } String pkName = - (usePkName && pkFinder.getName() != null) ? pkFinder.getName() : ""; + (usePkName && pkFinder.getName() != null) + ? pkFinder.getName() + : ""; exportedKeysQuery - .append(count > 0 ? " union all select " : "select ") - .append(keySeq) - .append(" as ks, '") - .append(escape(tbl)) - .append("' as fkt, '") - .append(escape(FKColName)) - .append("' as fcn, '") - .append(escape(PKColName)) - .append("' as pcn, '") - .append(escape(pkName)) - .append("' as pkn, ") - .append(RULE_MAP.get(foreignKey.getOnUpdate())) - .append(" as ur, ") - .append(RULE_MAP.get(foreignKey.getOnDelete())) - .append(" as dr, "); + .append(count > 0 ? " union all select " : "select ") + .append(keySeq) + .append(" as ks, '") + .append(escape(tbl)) + .append("' as fkt, '") + .append(escape(FKColName)) + .append("' as fcn, '") + .append(escape(PKColName)) + .append("' as pcn, '") + .append(escape(pkName)) + .append("' as pkn, ") + .append(RULE_MAP.get(foreignKey.getOnUpdate())) + .append(" as ur, ") + .append(RULE_MAP.get(foreignKey.getOnDelete())) + .append(" as dr, "); String fkName = foreignKey.getFkName(); if (fkName != null) { - exportedKeysQuery.append("'").append(escape(fkName)).append("' as fkn"); + exportedKeysQuery + .append("'") + .append(escape(fkName)) + .append("' as fkn"); } else { exportedKeysQuery.append("'' as fkn"); } @@ -1445,40 +1459,42 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) boolean hasImportedKey = (count > 0); sql.append("select ") - .append(catalog) - .append(" as PKTABLE_CAT, ") - .append(quotedSchema) - .append(" as PKTABLE_SCHEM, ") - .append(quote(target)) - .append(" as PKTABLE_NAME, ") - .append(hasImportedKey ? "pcn" : "''") - .append(" as PKCOLUMN_NAME, ") - .append(catalog) - .append(" as FKTABLE_CAT, ") - .append(quotedSchema) - .append(" as FKTABLE_SCHEM, ") - .append(hasImportedKey ? "fkt" : "''") - .append(" as FKTABLE_NAME, ") - .append(hasImportedKey ? "fcn" : "''") - .append(" as FKCOLUMN_NAME, ") - .append(hasImportedKey ? "ks" : "-1") - .append(" as KEY_SEQ, ") - .append(hasImportedKey ? "ur" : "3") - .append(" as UPDATE_RULE, ") - .append(hasImportedKey ? "dr" : "3") - .append(" as DELETE_RULE, ") - .append(hasImportedKey ? "fkn" : "''") - .append(" as FK_NAME, ") - .append(hasImportedKey ? "pkn" : "''") - .append(" as PK_NAME, ") - .append(DatabaseMetaData.importedKeyInitiallyDeferred) // FIXME: Check for pragma - // foreign_keys = true ? - .append(" as DEFERRABILITY "); + .append(catalog) + .append(" as PKTABLE_CAT, ") + .append(quotedSchema) + .append(" as PKTABLE_SCHEM, ") + .append(quote(target)) + .append(" as PKTABLE_NAME, ") + .append(hasImportedKey ? "pcn" : "''") + .append(" as PKCOLUMN_NAME, ") + .append(catalog) + .append(" as FKTABLE_CAT, ") + .append(quotedSchema) + .append(" as FKTABLE_SCHEM, ") + .append(hasImportedKey ? "fkt" : "''") + .append(" as FKTABLE_NAME, ") + .append(hasImportedKey ? "fcn" : "''") + .append(" as FKCOLUMN_NAME, ") + .append(hasImportedKey ? "ks" : "-1") + .append(" as KEY_SEQ, ") + .append(hasImportedKey ? "ur" : "3") + .append(" as UPDATE_RULE, ") + .append(hasImportedKey ? "dr" : "3") + .append(" as DELETE_RULE, ") + .append(hasImportedKey ? "fkn" : "''") + .append(" as FK_NAME, ") + .append(hasImportedKey ? "pkn" : "''") + .append(" as PK_NAME, ") + .append( + DatabaseMetaData + .importedKeyInitiallyDeferred) // FIXME: Check for pragma + // foreign_keys = true ? + .append(" as DEFERRABILITY "); if (hasImportedKey) { sql.append("from (") - .append(exportedKeysQuery) - .append(") ORDER BY FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, KEY_SEQ"); + .append(exportedKeysQuery) + .append(") ORDER BY FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, KEY_SEQ"); } else { sql.append("limit 0"); } @@ -1490,21 +1506,21 @@ public ResultSet getExportedKeys(String catalog, String schema, String table) } if (schemasNames.size() == 0) { sql.append("select\n") - .append("\tnull as PKTABLE_CAT,\n") - .append("\tnull as PKTABLE_SCHEM,\n") - .append("\tnull as PKTABLE_NAME,\n") - .append("\tnull as PKCOLUMN_NAME,\n") - .append("\tnull as FKTABLE_CAT,\n") - .append("\tnull as FKTABLE_SCHEM,\n") - .append("\t'null' as FKTABLE_NAME,\n") - .append("\tnull as FKCOLUMN_NAME,\n") - .append("\tnull as KEY_SEQ,\n") - .append("\tnull as UPDATE_RULE,\n") - .append("\tnull as DELETE_RULE,\n") - .append("\tnull as FK_NAME,\n") - .append("\tnull as PK_NAME,\n") - .append("\tnull as DEFERRABILITY\n") - .append("limit 0;"); + .append("\tnull as PKTABLE_CAT,\n") + .append("\tnull as PKTABLE_SCHEM,\n") + .append("\tnull as PKTABLE_NAME,\n") + .append("\tnull as PKCOLUMN_NAME,\n") + .append("\tnull as FKTABLE_CAT,\n") + .append("\tnull as FKTABLE_SCHEM,\n") + .append("\t'null' as FKTABLE_NAME,\n") + .append("\tnull as FKCOLUMN_NAME,\n") + .append("\tnull as KEY_SEQ,\n") + .append("\tnull as UPDATE_RULE,\n") + .append("\tnull as DELETE_RULE,\n") + .append("\tnull as FK_NAME,\n") + .append("\tnull as PK_NAME,\n") + .append("\tnull as DEFERRABILITY\n") + .append("limit 0;"); } return ((CoreStatement) stat).executeQuery(sql.toString(), true); } @@ -1535,21 +1551,21 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) for (int i = 0; i < schemasNames.size(); i++) { String schemaName = schemasNames.get(i); sql.append("select ") - .append(quote(catalog)) - .append(" as PKTABLE_CAT, ") - .append(quote(schemaName)) - .append(" as PKTABLE_SCHEM, ") - .append("ptn as PKTABLE_NAME, pcn as PKCOLUMN_NAME, ") - .append(quote(catalog)) - .append(" as FKTABLE_CAT, ") - .append(quote(schemaName)) - .append(" as FKTABLE_SCHEM, ") - .append(quote(table)) - .append(" as FKTABLE_NAME, ") - .append( - "fcn as FKCOLUMN_NAME, ks as KEY_SEQ, ur as UPDATE_RULE, dr as DELETE_RULE, fkn as FK_NAME, pkn as PK_NAME, ") - .append(DatabaseMetaData.importedKeyInitiallyDeferred) - .append(" as DEFERRABILITY from ("); + .append(quote(catalog)) + .append(" as PKTABLE_CAT, ") + .append(quote(schemaName)) + .append(" as PKTABLE_SCHEM, ") + .append("ptn as PKTABLE_NAME, pcn as PKCOLUMN_NAME, ") + .append(quote(catalog)) + .append(" as FKTABLE_CAT, ") + .append(quote(schemaName)) + .append(" as FKTABLE_SCHEM, ") + .append(quote(table)) + .append(" as FKTABLE_NAME, ") + .append( + "fcn as FKCOLUMN_NAME, ks as KEY_SEQ, ur as UPDATE_RULE, dr as DELETE_RULE, fkn as FK_NAME, pkn as PK_NAME, ") + .append(DatabaseMetaData.importedKeyInitiallyDeferred) + .append(" as DEFERRABILITY from ("); // Use a try catch block to avoid "query does not return ResultSet" error try { @@ -1596,47 +1612,47 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) if (fkNames.size() > keyId) fkName = fkNames.get(keyId).getFkName(); sql.append("select ") - .append(keySeq) - .append(" as ks,") - .append("'") - .append(escape(PKTabName)) - .append("' as ptn, '") - .append(escape(FKColName)) - .append("' as fcn, '") - .append(escape(PKColName)) - .append("' as pcn,") - .append("case '") - .append(escape(updateRule)) - .append("'") - .append(" when 'NO ACTION' then ") - .append(DatabaseMetaData.importedKeyNoAction) - .append(" when 'CASCADE' then ") - .append(DatabaseMetaData.importedKeyCascade) - .append(" when 'RESTRICT' then ") - .append(DatabaseMetaData.importedKeyRestrict) - .append(" when 'SET NULL' then ") - .append(DatabaseMetaData.importedKeySetNull) - .append(" when 'SET DEFAULT' then ") - .append(DatabaseMetaData.importedKeySetDefault) - .append(" end as ur, ") - .append("case '") - .append(escape(deleteRule)) - .append("'") - .append(" when 'NO ACTION' then ") - .append(DatabaseMetaData.importedKeyNoAction) - .append(" when 'CASCADE' then ") - .append(DatabaseMetaData.importedKeyCascade) - .append(" when 'RESTRICT' then ") - .append(DatabaseMetaData.importedKeyRestrict) - .append(" when 'SET NULL' then ") - .append(DatabaseMetaData.importedKeySetNull) - .append(" when 'SET DEFAULT' then ") - .append(DatabaseMetaData.importedKeySetDefault) - .append(" end as dr, ") - .append(fkName == null ? "''" : quote(fkName)) - .append(" as fkn, ") - .append(pkName == null ? "''" : quote(pkName)) - .append(" as pkn"); + .append(keySeq) + .append(" as ks,") + .append("'") + .append(escape(PKTabName)) + .append("' as ptn, '") + .append(escape(FKColName)) + .append("' as fcn, '") + .append(escape(PKColName)) + .append("' as pcn,") + .append("case '") + .append(escape(updateRule)) + .append("'") + .append(" when 'NO ACTION' then ") + .append(DatabaseMetaData.importedKeyNoAction) + .append(" when 'CASCADE' then ") + .append(DatabaseMetaData.importedKeyCascade) + .append(" when 'RESTRICT' then ") + .append(DatabaseMetaData.importedKeyRestrict) + .append(" when 'SET NULL' then ") + .append(DatabaseMetaData.importedKeySetNull) + .append(" when 'SET DEFAULT' then ") + .append(DatabaseMetaData.importedKeySetDefault) + .append(" end as ur, ") + .append("case '") + .append(escape(deleteRule)) + .append("'") + .append(" when 'NO ACTION' then ") + .append(DatabaseMetaData.importedKeyNoAction) + .append(" when 'CASCADE' then ") + .append(DatabaseMetaData.importedKeyCascade) + .append(" when 'RESTRICT' then ") + .append(DatabaseMetaData.importedKeyRestrict) + .append(" when 'SET NULL' then ") + .append(DatabaseMetaData.importedKeySetNull) + .append(" when 'SET DEFAULT' then ") + .append(DatabaseMetaData.importedKeySetDefault) + .append(" end as dr, ") + .append(fkName == null ? "''" : quote(fkName)) + .append(" as fkn, ") + .append(pkName == null ? "''" : quote(pkName)) + .append(" as pkn"); } rs.close(); @@ -1651,21 +1667,20 @@ public ResultSet getImportedKeys(String catalog, String schema, String table) } if (schemasNames.size() == 0) { sql.append("select\n") - .append("\tnull as PKTABLE_CAT,\n") - .append("\tnull as PKTABLE_SCHEM,\n") - .append("\tnull as PKTABLE_NAME,\n") - .append("\tnull as PKCOLUMN_NAME,\n") - .append("\tnull as FKTABLE_CAT,\n") - .append("\tnull as FKTABLE_SCHEM,\n") - .append("\tnull as FKTABLE_NAME,\n") - .append("\tnull as FKCOLUMN_NAME,\n") - .append("\tnull as KEY_SEQ,\n") - .append("\tnull as UPDATE_RULE,\n") - .append("\tnull as DELETE_RULE,\n") - .append("\tnull as FK_NAME,\n") - .append("\tnull as PK_NAME,\n") - .append("\tnull as DEFERRABILITY limit 0;"); - + .append("\tnull as PKTABLE_CAT,\n") + .append("\tnull as PKTABLE_SCHEM,\n") + .append("\tnull as PKTABLE_NAME,\n") + .append("\tnull as PKCOLUMN_NAME,\n") + .append("\tnull as FKTABLE_CAT,\n") + .append("\tnull as FKTABLE_SCHEM,\n") + .append("\tnull as FKTABLE_NAME,\n") + .append("\tnull as FKCOLUMN_NAME,\n") + .append("\tnull as KEY_SEQ,\n") + .append("\tnull as UPDATE_RULE,\n") + .append("\tnull as DELETE_RULE,\n") + .append("\tnull as FK_NAME,\n") + .append("\tnull as PK_NAME,\n") + .append("\tnull as DEFERRABILITY limit 0;"); } return ((CoreStatement) stat).executeQuery(sql.toString(), true); @@ -1686,18 +1701,23 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole // define the column header // this is from the JDBC spec, it is part of the driver protocol sql.append("select null as TABLE_CAT,'") - .append(escape(schemasNames.get(i))) - .append("' as TABLE_SCHEM, '") - .append(escape(table)) - .append("' as TABLE_NAME, un as NON_UNIQUE, null as INDEX_QUALIFIER, n as INDEX_NAME, ") - .append(Integer.toString(DatabaseMetaData.tableIndexOther)) - .append(" as TYPE, op as ORDINAL_POSITION, ") - .append( - "cn as COLUMN_NAME, null as ASC_OR_DESC, 0 as CARDINALITY, 0 as PAGES, null as FILTER_CONDITION from ("); + .append(escape(schemasNames.get(i))) + .append("' as TABLE_SCHEM, '") + .append(escape(table)) + .append( + "' as TABLE_NAME, un as NON_UNIQUE, null as INDEX_QUALIFIER, n as INDEX_NAME, ") + .append(Integer.toString(DatabaseMetaData.tableIndexOther)) + .append(" as TYPE, op as ORDINAL_POSITION, ") + .append( + "cn as COLUMN_NAME, null as ASC_OR_DESC, 0 as CARDINALITY, 0 as PAGES, null as FILTER_CONDITION from ("); // this always returns a result set now, previously threw exception - rs = stat.executeQuery("pragma " + prependSchemaPrefix(s, "index_list('" + escape(schemasNames.get(i)) + - "');")); + rs = + stat.executeQuery( + "pragma " + + prependSchemaPrefix( + s, + "index_list('" + escape(schemasNames.get(i)) + "');")); ArrayList> indexList = new ArrayList<>(); while (rs.next()) { @@ -1720,8 +1740,11 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole while (indexIterator.hasNext()) { currentIndex = indexIterator.next(); String indexName = currentIndex.get(0).toString(); - rs = stat.executeQuery( - "pragma " + prependSchemaPrefix(s, "index_info('" + escape(indexName) + "');")); + rs = + stat.executeQuery( + "pragma " + + prependSchemaPrefix( + s, "index_info('" + escape(indexName) + "');")); while (rs.next()) { @@ -1729,12 +1752,12 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole String colName = rs.getString(3); sqlRow.append("select ") - .append(1 - (Integer) currentIndex.get(1)) - .append(" as un,'") - .append(escape(indexName)) - .append("' as n,") - .append(rs.getInt(1) + 1) - .append(" as op,"); + .append(1 - (Integer) currentIndex.get(1)) + .append(" as un,'") + .append(escape(indexName)) + .append("' as n,") + .append(rs.getInt(1) + 1) + .append(" as op,"); if (colName == null) { // expression index sqlRow.append("null"); } else { @@ -1753,15 +1776,14 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole } if (i != schemasNames.size() - 1) { sql.append(") union all "); - } else { + } else { sql.append(");"); } } if (schemasNames.size() == 0) { sql.append("select null as un, null as n, null as op, null as cn limit 0"); } - return ((CoreStatement) stat) - .executeQuery(sql.toString(), true); + return ((CoreStatement) stat).executeQuery(sql.toString(), true); } /** @@ -1852,7 +1874,7 @@ public synchronized ResultSet getTables( ArrayList schemasNames = getSchemasNames(getSchemas(c, s)); StringBuilder sql = new StringBuilder(); for (int i = 0; i < schemasNames.size(); i++) { - appendSchemaTablesRequests(sql, schemasNames.get(i), tblNamePattern, types); + appendSchemaTablesRequests(sql, schemasNames.get(i), tblNamePattern, types); if (i != schemasNames.size() - 1) { sql.append("\nUNION ALL\n"); } else { @@ -1878,18 +1900,14 @@ public synchronized ResultSet getTables( } private StringBuilder appendSchemaTablesRequests( - StringBuilder sql, String s, String tblNamePattern, String[] types - ) { + StringBuilder sql, String s, String tblNamePattern, String[] types) { tblNamePattern = (tblNamePattern == null || "".equals(tblNamePattern)) ? "%" : escape(tblNamePattern); sql.append("SELECT").append("\n"); sql.append(" NULL AS TABLE_CAT,").append("\n"); - sql.append(" ") - .append(quote(s)) - .append(" AS TABLE_SCHEM,") - .append("\n"); + sql.append(" ").append(quote(s)).append(" AS TABLE_SCHEM,").append("\n"); sql.append(" NAME AS TABLE_NAME,").append("\n"); sql.append(" TYPE AS TABLE_TYPE,").append("\n"); sql.append(" NULL AS REMARKS,").append("\n"); diff --git a/src/test/java/org/sqlite/DBMetaDataTest.java b/src/test/java/org/sqlite/DBMetaDataTest.java index 549cb8a09..e33ac59a7 100644 --- a/src/test/java/org/sqlite/DBMetaDataTest.java +++ b/src/test/java/org/sqlite/DBMetaDataTest.java @@ -95,9 +95,9 @@ public void getTables() throws SQLException { assertThat(rs.next()).isTrue(); assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("main"); -// assertThat(rs.next()).isTrue(); -// assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); -// assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("temp"); + // assertThat(rs.next()).isTrue(); + // assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema"); + // assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("temp"); rs.close(); } @@ -1698,7 +1698,6 @@ public void testRequestsWithNonExistentSchemas() throws SQLException { assertThat(rsmeta.getColumnName(9)).isEqualTo("SELF_REFERENCING_COL_NAME"); assertThat(rsmeta.getColumnName(10)).isEqualTo("REF_GENERATION"); - rs = meta.getColumns(null, "nonexistent", null, null); assertThat(rs).isNotNull(); assertThat(rs.next()).isFalse(); @@ -1768,7 +1767,6 @@ public void testRequestsWithNonExistentSchemas() throws SQLException { assertThat(rsmeta.getColumnName(13)).isEqualTo("PK_NAME"); assertThat(rsmeta.getColumnName(14)).isEqualTo("DEFERRABILITY"); - rs = meta.getPrimaryKeys(null, "nonexistent", null); assertThat(rs).isNotNull(); assertThat(rs.next()).isFalse(); @@ -1779,7 +1777,6 @@ public void testRequestsWithNonExistentSchemas() throws SQLException { assertThat(rsmeta.getColumnName(4)).isEqualTo("COLUMN_NAME"); assertThat(rsmeta.getColumnName(5)).isEqualTo("KEY_SEQ"); assertThat(rsmeta.getColumnName(6)).isEqualTo("PK_NAME"); - } @Nested @@ -2074,7 +2071,6 @@ public void getColumnsForAttachedDatabaseTables() throws SQLException { assertThat(rs.getMetaData().getColumnCount()).isEqualTo(24); rs.close(); - rs = meta.getColumns(null, null, "%", "%"); assertReadsAllColumns(rs, 3); rs = meta.getColumns(null, "%", "%", "%"); @@ -2226,8 +2222,6 @@ public void getIndexInfoIndexedSingleExprForAttachedDatabase() throws SQLExcepti assertThat(rsmd).isNotNull(); } - - @Test public void getIndexInfoIndexedMultiForAttachedDatabase() throws SQLException { stat.executeUpdate(