Skip to content
Open
30 changes: 30 additions & 0 deletions src/main/java/org/sqlite/core/CoreDatabaseMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,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. */
Expand Down
127 changes: 87 additions & 40 deletions src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -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, null 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, ")
Comment on lines +967 to +968
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about that.

The function should return the actual schema of the table, no? But it seems we just feed back the parameter that was passed.

If we have 2 tables with the same name in 2 different schemas, and we call this function without specifying the schema s, then that function should return columns from both tables, but we would need to have TABLE_SCHEM to differentiate them.

.append(
"cn as COLUMN_NAME, ct as DATA_TYPE, tn as TYPE_NAME, colSize as COLUMN_SIZE, ")
.append(
Expand Down Expand Up @@ -950,8 +952,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_master "
+ "WHERE LOWER(name) = LOWER('"
"SELECT LIKE('%autoincrement%', LOWER(sql)) FROM "
+ prependSchemaPrefix(
s, "sqlite_master WHERE LOWER(name) = LOWER('")
+ escape(tableName)
+ "') AND TYPE IN ('table', 'view')");
rsColAutoinc.next();
Expand All @@ -974,7 +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 table_xinfo('" + escape(tableName) + "')";
String pragmaStatement =
"PRAGMA "
+ prependSchemaPrefix(
s, "table_xinfo('" + escape(tableName) + "')");
try (Statement colstat = conn.createStatement();
ResultSet rscol = colstat.executeQuery(pragmaStatement)) {

Expand Down Expand Up @@ -1175,7 +1181,7 @@ 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;");
}

return getSchemas.executeQuery();
Expand All @@ -1195,12 +1201,14 @@ 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 == null ? "main" : s))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar as above, we should return the actual schema of the retrieved PK's table, not the parameter that was passed to the function

.append(" as TABLE_SCHEM, '")
.append(escape(table))
.append("' as TABLE_NAME, cn as COLUMN_NAME, ks as KEY_SEQ, pk as PK_NAME from (");

Expand Down Expand Up @@ -1245,12 +1253,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) : quote("main");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar as above, we should return the actual schema of the retrieved PK's table, not the parameter that was passed to the function


StringBuilder exportedKeysQuery = new StringBuilder(512);

Expand All @@ -1260,7 +1269,10 @@ public ResultSet getExportedKeys(String catalog, String schema, String table)
// retrieve table list
ArrayList<String> tableList;
try (ResultSet rs =
stat.executeQuery("select name from sqlite_master where type = 'table'")) {
stat.executeQuery(
"select name from "
+ prependSchemaPrefix(
schema, "sqlite_master where type = " + "'table'"))) {
tableList = new ArrayList<>();

while (rs.next()) {
Expand All @@ -1276,7 +1288,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<ForeignKey> fkNames = impFkFinder.getFkList();

for (ForeignKey foreignKey : fkNames) {
Expand Down Expand Up @@ -1340,15 +1352,15 @@ 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, ")
.append(hasImportedKey ? "pcn" : "''")
.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, ")
Expand Down Expand Up @@ -1404,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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar as above, we should return the actual schema of the retrieved PK's table, not the parameter that was passed to the function

.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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar as above, we should return the actual schema of the retrieved PK's table, not the parameter that was passed to the function

.append(" as FKTABLE_SCHEM, ")
.append(quote(table))
.append(" as FKTABLE_NAME, ")
Expand All @@ -1426,7 +1438,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<ForeignKey> fkNames = impFkFinder.getFkList();

int i = 0;
Expand All @@ -1439,7 +1451,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];
Expand Down Expand Up @@ -1523,7 +1535,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 == null ? "main" : s))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar as above, we should return the actual schema of the retrieved PK's table, not the parameter that was passed to the function

.append(" as TABLE_SCHEM, '")
.append(escape(table))
.append(
"' as TABLE_NAME, un as NON_UNIQUE, null as INDEX_QUALIFIER, n as INDEX_NAME, ")
Expand All @@ -1533,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 index_list('" + escape(table) + "');");
rs =
stat.executeQuery(
"pragma " + prependSchemaPrefix(s, "index_list('" + escape(table) + "');"));

ArrayList<ArrayList<Object>> indexList = new ArrayList<>();
while (rs.next()) {
Expand All @@ -1557,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 index_info('" + escape(indexName) + "');");
rs =
stat.executeQuery(
"pragma "
+ prependSchemaPrefix(
s, "index_info('" + escape(indexName) + "');"));

while (rs.next()) {

Expand Down Expand Up @@ -1685,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))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar as above, we should return the actual schema of the retrieved PK's table, not the parameter that was passed to the function

.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");
Expand All @@ -1704,7 +1727,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");
Expand All @@ -1719,7 +1743,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");
Expand Down Expand Up @@ -1962,6 +1987,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;

Expand All @@ -1971,15 +1997,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;
if (table == null || table.trim().length() == 0) {
throw new SQLException("Invalid table name: '" + this.table + "'");
}
Expand All @@ -1988,10 +2019,13 @@ public PrimaryKeyFinder(String table) throws SQLException {
// read create SQL script for table
ResultSet rs =
stat.executeQuery(
"select sql from 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 + "'");

Expand All @@ -2008,7 +2042,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)};
}
Expand Down Expand Up @@ -2046,21 +2084,28 @@ class ImportedKeyFinder {
private final List<ForeignKey> 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 + "'");
}

this.fkTableName = table;

List<String> fkNames = getForeignKeyNames(this.fkTableName);
List<String> 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;
Expand Down Expand Up @@ -2097,19 +2142,21 @@ public ImportedKeyFinder(String table) throws SQLException {
}
}

private List<String> getForeignKeyNames(String tbl) throws SQLException {
private List<String> getForeignKeyNames(String tbl, String schema) throws SQLException {
List<String> 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)
+ "')")) {

"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));

Expand Down
Loading