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

Conversation

Destrolaric
Copy link

@Destrolaric Destrolaric commented Jan 16, 2023

Hi SQLite team!
Here are my proposed changes to the reading of metadata. To allow to read of metadata from attached databases
https://www.sqlite.org/lang_attach.html
This functionality will be optional and disabled by default because it adds more layers [schema].[table] for metadata reading.
The reason why this functionality will be useful:
It isn't easy to work with attached databases using JDBC API because all metadata reading assumes that we are in the main schema. This is fixable by adding schema information to the existing queries.

@gotson
Copy link
Collaborator

gotson commented Jan 17, 2023

Hi, i don't think we should add a pragma for that. The JDBC methods accept a schema, and if it is null then it should not be used for filtering.

@Destrolaric
Copy link
Author

Hi, pragma is used only for getSchemas to mark if we read them at all. Other parts just check for null.

@gotson
Copy link
Collaborator

gotson commented Jan 17, 2023

Hi, pragma is used only for getSchemas to mark if we read them at all. Other parts just check for null.

i don't think the switch is necessary, we can always retrieve them all

@Destrolaric
Copy link
Author

@gotson I have removed pragma now the schema read is by default

Copy link
Collaborator

@gotson gotson left a comment

Choose a reason for hiding this comment

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

i did a very quick first review

src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java Outdated Show resolved Hide resolved
src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java Outdated Show resolved Hide resolved
@gotson
Copy link
Collaborator

gotson commented Jan 18, 2023

Code formatting is failing, can you run mvn spotless:apply ?

src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java Outdated Show resolved Hide resolved
src/test/java/org/sqlite/DBMetaDataTest.java Outdated Show resolved Hide resolved
src/test/java/org/sqlite/DBMetaDataTest.java Outdated Show resolved Hide resolved
src/test/java/org/sqlite/DBMetaDataTest.java Outdated Show resolved Hide resolved
src/test/java/org/sqlite/DBMetaDataTest.java Show resolved Hide resolved
@Destrolaric Destrolaric force-pushed the add_metadata_read_for_attached_db branch from 5e41cc5 to 4820bfd Compare January 18, 2023 11:03
@Destrolaric
Copy link
Author

Hi, I Added 2 test cases for the default DB and for attaching an additional one after getschemas() call. Also fixed formatting issues and now null schema replaced with main

@Destrolaric Destrolaric force-pushed the add_metadata_read_for_attached_db branch from 4820bfd to 6e59227 Compare January 18, 2023 11:13
Comment on lines +919 to +920
.append(quote(s == null ? "main" : s))
.append(" as TABLE_SCHEM, tblname as TABLE_NAME, ")
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.

src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java Outdated Show resolved Hide resolved
@@ -1198,7 +1206,9 @@ public ResultSet getPrimaryKeys(String c, String s, String table) throws SQLExce

Statement stat = conn.createStatement();
StringBuilder sql = new StringBuilder(512);
sql.append("select null as TABLE_CAT, ").append(quote(s)).append(" as TABLE_SCHEM, '")
sql.append("select null as TABLE_CAT, ")
.append(quote(s == null ? "main" : s))
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

@@ -1249,7 +1259,7 @@ public ResultSet getExportedKeys(String catalog, String schema, String table)

catalog = (catalog != null) ? quote(catalog) : null;

String quotedSchema = (schema != null) ? quote(schema) : null;
String quotedSchema = (schema != null) ? quote(schema) : quote("main");
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

@@ -1406,12 +1416,12 @@ public ResultSet getImportedKeys(String catalog, String schema, String table)
sql.append("select ")
.append(quote(catalog))
.append(" as PKTABLE_CAT, ")
.append(quote(schema))
.append(quote(schema == null ? "main" : schema))
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

@@ -1526,7 +1536,7 @@ public ResultSet getIndexInfo(String c, String s, String table, boolean u, boole
// define the column header
// this is from the JDBC spec, it is part of the driver protocol
sql.append("select null as TABLE_CAT,")
.append(quote(s))
.append(quote(s == null ? "main" : s))
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

@@ -1689,7 +1705,10 @@ public synchronized ResultSet getTables(
StringBuilder sql = new StringBuilder();
sql.append("SELECT").append("\n");
sql.append(" NULL AS TABLE_CAT,").append("\n");
sql.append(" NULL AS TABLE_SCHEM,").append("\n");
sql.append(" ")
.append(quote(s == null ? "main" : s))
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

@gotson
Copy link
Collaborator

gotson commented Jan 19, 2023

I think the logic should be enhance to retrieve various entities.

The way the schema parameter work in most of the DB Metadata function is as follows:

schemaPattern - a schema name pattern; must match the schema name as it is stored in the database; "" retrieves those without a schema; null means that the schema name should not be used to narrow the search

For all those functions, we probably need to apply that logic:

  1. if schemaPattern is "", then we retrieve objects from the main database. That's a subset of 2.
  2. if schemaPattern is null, then we should retrieve objects from all schemas. That means we need to loop through all schemas (getSchemas), and run the statements like table_info, table_xinfo and select from sqlite_master for all those schemas, and join the results. The returned TABLE_SCHEM should be the correct one, not the parameter that was passed to the function.
  3. if schemaPattern is a String, we should match it against getSchemas. If found, we get items for that schema only. That's a subset of 2.

That means we need to change the logic for all those functions to loop through all known schemas, optionnaly filtered by the schemaPattern parameter, or using main, in order to call the sqlite functions or special tables with the schema, then join everything.

@Destrolaric
Copy link
Author

@gotson Ok, this one looks doable and will probably, be ready sometime next week.

@Destrolaric
Copy link
Author

Destrolaric commented Feb 13, 2023

@gotson Hi, sorry for waiting; unfortunately, I had no time to finish the pr before. I have a question. JDBC has some functions like getExportedKeys that require using full schema name and returns everything if null. Do we want to read the data from all existing schemas or to use old behaviour?

@Destrolaric
Copy link
Author

Added schema(String, String) for pattern matching, which still requires some unit tests and discussion.

@Destrolaric Destrolaric force-pushed the add_metadata_read_for_attached_db branch from 5b4b2ad to e5e4f1d Compare February 13, 2023 17:42
@Destrolaric
Copy link
Author

Destrolaric commented Feb 13, 2023

        sql.append("    SELECT").append("\n");
        sql.append("      NAME,").append("\n");
        sql.append("      'GLOBAL TEMPORARY' AS TYPE").append("\n");
        sql.append("    FROM").append("\n");
        sql.append("      sqlite_temp_master").append("\n"); 

Also, what should I do with GLOBAL TEMPORARY tables? They kind of have their scheme now.
Converting pr to draft until all this is covered with tests.

@Destrolaric Destrolaric marked this pull request as draft February 13, 2023 19:27
@Destrolaric
Copy link
Author

@gotson, Hi, again, can we discuss acceptance criteria regarding this pr? Currently, it needs to add some unit tests, and we can probably discuss if anything else is required. Also it looks like this one leads to the significant change of default behaviour, so it probably may require more accurate handling.

@gotson
Copy link
Collaborator

gotson commented Feb 22, 2023

@gotson, Hi, again, can we discuss acceptance criteria regarding this pr? Currently, it needs to add some unit tests, and we can probably discuss if anything else is required. Also it looks like this one leads to the significant change of default behaviour, so it probably may require more accurate handling.

Hi, unit tests would be needed yes. What kind of significant changes of default behavior would you foresee though ?

@Destrolaric
Copy link
Author

@gotson Well, there are some. Most importantly, if the value of the schema pattern is null, it will show all DBs instead of only 'main', that may look like a significant API change.

@gotson
Copy link
Collaborator

gotson commented Feb 22, 2023

@gotson Well, there are some. Most importantly, if the value of the schema pattern is null, it will show all DBs instead of only 'main', that may look like a significant API change.

It's fine, it is more correct

…nto add_metadata_read_for_attached_db

� Conflicts:
�	src/main/java/org/sqlite/jdbc3/JDBC3DatabaseMetaData.java
�	src/test/java/org/sqlite/DBMetaDataTest.java
@Destrolaric
Copy link
Author

@gotson Sorry, could you look at the current state of pr and see if anything else is required?

@Destrolaric Destrolaric marked this pull request as ready for review April 18, 2023 14:59
@Destrolaric Destrolaric force-pushed the add_metadata_read_for_attached_db branch from 469887c to 61f5344 Compare April 18, 2023 15:08
@gotson
Copy link
Collaborator

gotson commented Apr 19, 2023

@gotson Sorry, could you look at the current state of pr and see if anything else is required?

The PR has conflict, you would need to fix those before I can have a look.

@Destrolaric
Copy link
Author

@gotson I resolved the conflict with the recent two commits

@gotson
Copy link
Collaborator

gotson commented Apr 19, 2023

@gotson I resolved the conflict with the recent two commits

Thank you. I don't have much time these days, but I will look at this when I have time.

@gotson gotson self-assigned this Apr 19, 2023
@gotson
Copy link
Collaborator

gotson commented Apr 19, 2023

I still see conflicts though. Are you sure you pushed your latest changes?

@Destrolaric
Copy link
Author

image
Weird, for me branch have no conflicts

@gotson
Copy link
Collaborator

gotson commented Apr 19, 2023

Weird indeed

SmartSelect_20230419_220608_Chrome

@gotson
Copy link
Collaborator

gotson commented Apr 19, 2023

My bad, it's only the rebase (which was selected by default) that can't be done.

Squash and merge is ok (and it's what we want).

SmartSelect_20230419_220755_Chrome

@gotson
Copy link
Collaborator

gotson commented Apr 27, 2023

code formatting is failing, can you fix it with mvn spotless:apply ?

@Destrolaric
Copy link
Author

@gotson Fixed

@Destrolaric Destrolaric requested a review from gotson May 4, 2023 15:28
@Destrolaric
Copy link
Author

@gotson Hi there! I hope you're doing well. I wanted to reach out and kindly ask if you had a chance to review the pull request.

@gotson
Copy link
Collaborator

gotson commented May 15, 2023

@gotson Hi there! I hope you're doing well. I wanted to reach out and kindly ask if you had a chance to review the pull request.

It's on my list, unfortunately time is scarce these days. Hopefully i have some time this week to look at it!

Copy link
Collaborator

@gotson gotson left a comment

Choose a reason for hiding this comment

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

For this review i mostly focused on the test class. I think we should have more coverage on dynamic attachment, ie for all functions:

  • test the behaviour
  • attach 1 db
  • test the behaviour
  • detach the db
  • test the behaviour

Is that something you could add ?

Comment on lines +98 to +100
// assertThat(rs.next()).isTrue();
// assertThat(rs.getString("TABLE_NAME")).isEqualTo("sqlite_schema");
// assertThat(rs.getString("TABLE_SCHEM")).isEqualTo("temp");
Copy link
Collaborator

Choose a reason for hiding this comment

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

What's with those commented lines?

}
}

private void assertSystemSchema(ResultSet rs) throws SQLException {
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't that also check for TABLE_SCHEM and compare it to a parameter passed in the function?

// SYSTEM TABLE "sqlite_schema" for main
assertSystemSchema(rs);

// SYSTEM TABLE "sqlite_schema" for temp
Copy link
Collaborator

Choose a reason for hiding this comment

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

where is that temp database attached? i can't find any mention to it

Copy link
Author

@Destrolaric Destrolaric May 16, 2023

Choose a reason for hiding this comment

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

temp is for the sqlite temporary files, I think are returned as system external schema if any exist.

@@ -1001,7 +999,7 @@ public void columnOrderOfgetTables() throws SQLException {
ResultSet rsTables =
meta.getTables(
null,
null,
"",
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, i don't think passing "" should match any schema, that's what passing null is for.

@@ -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();
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't we have more assertions to verify the content of the result set?

Comment on lines +1841 to +1857
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();
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't we have the same suite of tests for db2 for consistency?

Comment on lines +1867 to +1874
boolean schemaFound = false;
while (schemas.next()) {
schemaFound = "db3".equals(schemas.getString("TABLE_SCHEM"));
if (schemaFound) {
break;
}
}
assertThat(schemaFound).isTrue();
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe put all schemas in a list and verify that it contains all the values we expect, ie db2,db3 and main.
If we expect some ordering, we should also test for that here.

}

@Test
public void testGetSchemasForAttachedDatabases() throws SQLException, IOException {
Copy link
Collaborator

@gotson gotson May 16, 2023

Choose a reason for hiding this comment

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

the above function name implies it's about dynamic attachment, but this one doesn't

assertThat(schemaFound).isTrue();

} finally {
stat.executeUpdate("detach database db3;");
Copy link
Collaborator

Choose a reason for hiding this comment

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

should we also test after the detach that the schemas are properly returned?

Comment on lines +1244 to +1246
if ("".equals(schemaPattern)) {
schemaPattern = "main";
}
Copy link
Collaborator

@gotson gotson May 16, 2023

Choose a reason for hiding this comment

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

I don't think we should have that behaviour. The documentation says:

schemaPattern – a schema name; must match the schema name as it is stored in the database; null means schema name should not be used to narrow down the search.

There is no mention of a special handling when passing ""

@gotson gotson added the enhancement:JDBC Enhancement specific to the JDBC standard label May 23, 2023
@gotson gotson removed their assignment Jul 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement:JDBC Enhancement specific to the JDBC standard
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants