Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add an ability to read metadata for attached databases #829

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
29 changes: 29 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,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. */
Expand Down
113 changes: 64 additions & 49 deletions src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java
Original file line number Diff line number Diff line change
Expand Up @@ -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, ")
gotson marked this conversation as resolved.
Show resolved Hide resolved
.append(
"cn as COLUMN_NAME, ct as DATA_TYPE, tn as TYPE_NAME, colSize as COLUMN_SIZE, ")
.append(
Expand Down Expand Up @@ -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 {
Expand All @@ -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)) {

Expand Down Expand Up @@ -1173,9 +1172,8 @@ public ResultSet getCrossReference(
/** @see java.sql.DatabaseMetaData#getSchemas() */
gotson marked this conversation as resolved.
Show resolved Hide resolved
public ResultSet getSchemas() throws SQLException {
if (getSchemas == null) {
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();
Expand All @@ -1195,12 +1193,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 (");

Expand Down Expand Up @@ -1245,12 +1243,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);

Expand All @@ -1259,8 +1258,11 @@ public ResultSet getExportedKeys(String catalog, String schema, String table)
if (pkColumns != null) {
// retrieve table list
ArrayList<String> 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()) {
Expand All @@ -1276,7 +1278,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 +1342,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 @@ -1426,7 +1428,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 +1441,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 +1525,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, ")
Expand All @@ -1533,7 +1537,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<ArrayList<Object>> indexList = new ArrayList<>();
while (rs.next()) {
Expand All @@ -1557,7 +1561,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()) {

Expand Down Expand Up @@ -1704,7 +1708,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 +1724,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 +1968,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 +1978,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 + "'");
}
Expand All @@ -1988,10 +2000,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 + "'");

Expand All @@ -2007,8 +2019,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)};
}
Expand Down Expand Up @@ -2046,21 +2060,26 @@ 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())
+ "')")) {
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;
Expand Down Expand Up @@ -2097,19 +2116,15 @@ 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)
+ "')")) {

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

Expand Down
Loading