diff --git a/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java b/src/main/java/org/sqlite/core/CoreDatabaseMetaData.java index 1c6a4fdf6..456b286ae 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. * @@ -184,6 +199,36 @@ 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 6e31e0c6c..41b061a77 100644 --- a/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java +++ b/src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java @@ -913,9 +913,59 @@ 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 null as TABLE_CAT, null as TABLE_SCHEM, tblname as TABLE_NAME, ") + 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 "); + } else { + sql.append("\n) order by TABLE_NAME, TABLE_SCHEM, 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); + } + + 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, ") .append( "cn as COLUMN_NAME, ct as DATA_TYPE, tn as TYPE_NAME, colSize as COLUMN_SIZE, ") .append( @@ -949,8 +999,9 @@ public ResultSet getColumns(String c, String s, String tblNamePattern, String co statColAutoinc = conn.createStatement(); rsColAutoinc = statColAutoinc.executeQuery( - "SELECT LIKE('%autoincrement%', LOWER(sql)) FROM sqlite_schema " - + "WHERE LOWER(name) = LOWER('" + "SELECT LIKE('%autoincrement%', LOWER(sql)) FROM " + + prependSchemaPrefix( + s, "sqlite_schema WHERE LOWER(name) = LOWER('") + escape(tableName) + "') AND TYPE IN ('table', 'view')"); rsColAutoinc.next(); @@ -973,7 +1024,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 table_xinfo('" + escape(tableName) + "')"; + String pragmaStatement = + "PRAGMA " + + prependSchemaPrefix( + s, "table_xinfo('" + escape(tableName) + "')"); try (Statement colstat = conn.createStatement(); ResultSet rscol = colstat.executeQuery(pragmaStatement)) { @@ -1124,14 +1178,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; } /** @@ -1175,12 +1229,33 @@ public ResultSet getSchemas() throws SQLException { if (getSchemas == null) { getSchemas = conn.prepareStatement( - "select null as TABLE_SCHEM, null as TABLE_CATALOG limit 0;"); + "select name as TABLE_SCHEM, null as TABLE_CATALOG from pragma_database_list order by " + + "TABLE_SCHEM;"); } 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() + + "' order by " + + "TABLE_SCHEM;"; + return stat.executeQuery(sql); + } + /** @see java.sql.DatabaseMetaData#getCatalogs() */ public ResultSet getCatalogs() throws SQLException { if (getCatalogs == null) { @@ -1195,19 +1270,52 @@ public ResultSet getCatalogs() throws SQLException { * java.lang.String) */ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLException { - PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(table); - String[] columns = pkFinder.getColumns(); Statement stat = conn.createStatement(); StringBuilder sql = new StringBuilder(512); - sql.append("select null as TABLE_CAT, null as TABLE_SCHEM, '") + 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 "); + } else { + 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); + } + + 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)) + .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(); @@ -1225,8 +1333,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(") "); } private static final Map RULE_MAP = new HashMap<>(); @@ -1245,137 +1352,176 @@ 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); - String[] pkColumns = pkFinder.getColumns(); Statement stat = conn.createStatement(); - - catalog = (catalog != null) ? quote(catalog) : null; - schema = (schema != null) ? quote(schema) : null; - - StringBuilder exportedKeysQuery = new StringBuilder(512); - - String target = null; - int count = 0; - if (pkColumns != null) { - // retrieve table list - ArrayList tableList; - try (ResultSet rs = - stat.executeQuery("select name from sqlite_schema 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; + StringBuilder sql = new StringBuilder(2048); + + 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(); + + catalog = (catalog != null) ? quote(catalog) : null; + + String quotedSchema = (schema != null) ? quote(schemasNames.get(i)) : quote("main"); + + StringBuilder exportedKeysQuery = new StringBuilder(512); + + 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_schema 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); - 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 = + (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, "); + + String fkName = foreignKey.getFkName(); + + if (fkName != null) { + exportedKeysQuery + .append("'") + .append(escape(fkName)) + .append("' as fkn"); + } else { + exportedKeysQuery.append("'' as fkn"); } - } - String pkName = - (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, "); - - String fkName = foreignKey.getFkName(); - - 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 ") - .append(catalog) - .append(" as PKTABLE_CAT, ") - .append(schema) - .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(schema) - .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"); - } else { - sql.append("limit 0"); + 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 "); + + if (hasImportedKey) { + sql.append("from (") + .append(exportedKeysQuery) + .append(") ORDER BY FKTABLE_CAT, FKTABLE_SCHEM, FKTABLE_NAME, KEY_SEQ"); + } else { + sql.append("limit 0"); + } + if (i != schemasNames.size() - 1) { + sql.append(" union all "); + } else { + 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); } @@ -1387,7 +1533,7 @@ private StringBuilder appendDummyForeignKeyList(StringBuilder sql) { .append(" as dr, ") .append(" '' as fkn, ") .append(" '' as pkn ") - .append(") limit 0;"); + .append(" limit 0"); return sql; } @@ -1399,113 +1545,142 @@ 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++) { + 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 ("); + + // 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 "); + } else { + sql.append(";"); + } + continue; + } - sql.append("select ") - .append(quote(catalog)) - .append(" as PKTABLE_CAT, ") - .append(quote(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(" 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 ("); + final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(table, schemaName); + List fkNames = impFkFinder.getFkList(); - // 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); - } + 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); - final ImportedKeyFinder impFkFinder = new ImportedKeyFinder(table); - List fkNames = impFkFinder.getFkList(); + String pkName = null; + try { + PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(PKTabName, schemaName); + pkName = pkFinder.getName(); + if (PKColName == null) { + PKColName = pkFinder.getColumns()[0]; + } + } catch (SQLException ignored) { + } - 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); + String updateRule = rs.getString(6); + String deleteRule = rs.getString(7); - String pkName = null; - try { - PrimaryKeyFinder pkFinder = new PrimaryKeyFinder(PKTabName); - pkName = pkFinder.getName(); - if (PKColName == null) { - PKColName = pkFinder.getColumns()[0]; + if (j > 0) { + sql.append(" union all "); } - } catch (SQLException ignored) { - } - - String updateRule = rs.getString(6); - String deleteRule = rs.getString(7); - if (i > 0) { - sql.append(" union all "); + String fkName = null; + 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"); } + rs.close(); - String fkName = null; - 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"); + 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;"); + } } - rs.close(); - - if (i == 0) { - sql = appendDummyForeignKeyList(sql); - } 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;"); } return ((CoreStatement) stat).executeQuery(sql.toString(), true); @@ -1517,78 +1692,98 @@ 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, null 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 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 - - Iterator> indexIterator = indexList.iterator(); - ArrayList currentIndex; - - ArrayList unionAll = new ArrayList<>(); - - while (indexIterator.hasNext()) { - currentIndex = indexIterator.next(); - String indexName = currentIndex.get(0).toString(); - rs = stat.executeQuery("pragma index_info('" + escape(indexName) + "');"); - - while (rs.next()) { + 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(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)) + "');")); + + 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 + + Iterator> indexIterator = indexList.iterator(); + ArrayList currentIndex; + + ArrayList unionAll = new ArrayList<>(); + + while (indexIterator.hasNext()) { + currentIndex = indexIterator.next(); + String indexName = currentIndex.get(0).toString(); + rs = + stat.executeQuery( + "pragma " + + prependSchemaPrefix( + s, "index_info('" + escape(indexName) + "');")); + + 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("'"); + } + sqlRow.append(" as cn"); - 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("'"); + 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); + } + if (i != schemasNames.size() - 1) { + sql.append(") union all "); + } else { + sql.append(");"); } - - String sqlBlock = StringUtils.join(unionAll, " union all "); - - return ((CoreStatement) stat) - .executeQuery(sql.append(sqlBlock).append(");").toString(), true); } + 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); } /** @@ -1632,7 +1827,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(); @@ -1676,16 +1871,43 @@ 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;"); + } + } + 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); + } + 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(" NULL 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"); @@ -1698,13 +1920,14 @@ public synchronized ResultSet getTables( 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"); sql.append(" UPPER(TYPE) AS TYPE").append("\n"); sql.append(" FROM").append("\n"); - sql.append(" sqlite_schema").append("\n"); + sql.append(" "); + prependSchemaPrefix(sql, s, "sqlite_schema\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 +1942,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_schema").append("\n"); + sql.append(" "); + prependSchemaPrefix(sql, s, "sqlite_schema\n"); sql.append(" WHERE").append("\n"); sql.append(" NAME LIKE 'sqlite\\_%' ESCAPE '\\'").append("\n"); sql.append(" )").append("\n"); @@ -1737,10 +1961,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() */ @@ -1962,6 +2183,7 @@ public ResultSet getFunctionColumns(String a, String b, String c, String d) /** Parses the sqlite_schema table for a table's primary key */ class PrimaryKeyFinder { + String schema; /** The table name. */ String table; @@ -1971,14 +2193,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 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; // specific handling for sqlite_schema and synonyms, so that // getExportedKeys/getPrimaryKeys return an empty ResultSet instead of throwing an @@ -1993,10 +2221,13 @@ public PrimaryKeyFinder(String table) throws SQLException { // read create SQL script for table ResultSet rs = stat.executeQuery( - "select sql from sqlite_schema where" - + " lower(name) = lower('" - + escape(table) - + "') and type in ('table', 'view')")) { + "select sql from " + + prependSchemaPrefix( + schema, + "sqlite_schema where" + + " lower(name) = lower('" + + escape(table) + + "') and type in ('table', 'view')"))) { if (!rs.next()) throw new SQLException("Table not found: '" + table + "'"); @@ -2013,7 +2244,11 @@ public PrimaryKeyFinder(String table) throws SQLException { if (pkColumns == null) { try (ResultSet rs2 = - stat.executeQuery("pragma table_info('" + escape(table) + "');")) { + stat.executeQuery( + "pragma " + + prependSchemaPrefix( + schema, + "table_info('" + escape(table) + "');"))) { while (rs2.next()) { if (rs2.getBoolean(6)) pkColumns = new String[] {rs2.getString(2)}; } @@ -2051,6 +2286,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 + "'"); @@ -2058,14 +2297,17 @@ 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()) - + "')")) { + "pragma " + + prependSchemaPrefix( + schema, + "foreign_key_list('" + + escape(this.fkTableName.toLowerCase()) + + "')"))) { int prevFkId = -1; int count = 0; @@ -2102,7 +2344,7 @@ 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; @@ -2110,11 +2352,13 @@ private List getForeignKeyNames(String tbl) throws SQLException { try (Statement stat2 = conn.createStatement(); ResultSet rs = stat2.executeQuery( - "select sql from sqlite_schema where" - + " lower(name) = lower('" - + escape(tbl) - + "')")) { - + "select sql from " + + prependSchemaPrefix( + schema, + "sqlite_schema where" + + " lower(name) = lower('" + + escape(tbl) + + "')"))) { if (rs.next()) { Matcher matcher = FK_NAMED_PATTERN.matcher(rs.getString(1)); 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 0c8ef7c83..e33ac59a7 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,10 +19,7 @@ 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.Nested; -import org.junit.jupiter.api.Test; +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; @@ -94,7 +94,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(); } @@ -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"); @@ -1001,7 +999,7 @@ public void columnOrderOfgetTables() throws SQLException { ResultSet rsTables = meta.getTables( null, - null, + "", null, new String[] {"TABLE", "VIEW", "GLOBAL TEMPORARY", "SYSTEM TABLE"}); @@ -1158,7 +1156,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"); @@ -1443,7 +1441,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); @@ -1471,10 +1469,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("main"); assertThat(importedKeys.getString("FKTABLE_CAT")).isEqualTo("default"); assertThat(importedKeys.getString("PKTABLE_NAME")).isEqualTo("person"); assertThat(importedKeys.getString("PKCOLUMN_NAME")).isEqualTo("id"); @@ -1496,12 +1494,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("main"); assertThat(exportedKeys.getString("FKTABLE_CAT")).isEqualTo("default"); - assertThat(exportedKeys.getString("FKTABLE_SCHEM")).isEqualTo("global"); + assertThat(exportedKeys.getString("FKTABLE_SCHEM")).isEqualTo("main"); assertThat(exportedKeys.getString("PK_NAME")).isNotNull(); assertThat(exportedKeys.getString("FK_NAME")).isNotNull(); @@ -1669,4 +1667,593 @@ public void getPrimaryKeys(String table) throws SQLException { assertThat(primaryKeys.next()).isFalse(); } } + + @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; + + @BeforeEach + public void init() throws IOException, SQLException { + 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(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 + 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, "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, "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, "main", 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; + 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")); + 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"); + 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.close(); + + 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.close(); + + 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.close(); + + 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.close(); + + 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.close(); + + 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.close(); + + 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.close(); + + 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.close(); + + rs = meta.getColumns(null, "db2", "%", "%"); + assertSystemSchema(rs); + + // 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.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, 3); + rs = meta.getColumns(null, "%", "%", "%"); + assertReadsAllColumns(rs, 3); + rs = meta.getColumns(null, "\\%", "%", "%"); + assertThat(rs.next()).isFalse(); + } + + 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"); + 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()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); + // 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"); + assertThat(rs.next()).isTrue(); + assertThat(rs.getString("COLUMN_NAME")).isEqualTo("charvalue"); + // 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 + public void getTablesForDefaultSchema() throws SQLException { + 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"); + 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))"); + + 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 { + stat.executeUpdate("Detach database db2;"); + 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"); + } }