diff --git a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java index a4fd91c..28d7ac0 100644 --- a/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java +++ b/libsql-jdbc/src/main/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaData.java @@ -284,9 +284,29 @@ private static String escapeLiteral(String value) { @Override public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types) throws SQLException { - StringBuilder sql = new StringBuilder("SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, name AS TABLE_NAME, UPPER(type) AS TABLE_TYPE, NULL AS REMARKS, NULL AS TYPE_CAT, NULL AS TYPE_SCHEM, NULL AS TYPE_NAME, NULL AS SELF_REFERENCING_COL_NAME, NULL AS REF_GENERATION FROM sqlite_master WHERE 1=1"); - if (tableNamePattern != null && !tableNamePattern.equals("%")) { - sql.append(" AND name LIKE '").append(escapeLiteral(tableNamePattern)).append("'"); + String pattern = tableNamePattern == null || tableNamePattern.isEmpty() + ? "%" + : escapeLiteral(tableNamePattern); + StringBuilder sql = new StringBuilder(); + sql.append("SELECT NULL AS TABLE_CAT, NULL AS TABLE_SCHEM, NAME AS TABLE_NAME, TYPE AS TABLE_TYPE, ") + .append("NULL AS REMARKS, NULL AS TYPE_CAT, NULL AS TYPE_SCHEM, NULL AS TYPE_NAME, ") + .append("NULL AS SELF_REFERENCING_COL_NAME, NULL AS REF_GENERATION FROM (") + .append("SELECT 'sqlite_schema' AS NAME, 'SYSTEM TABLE' AS TYPE") + .append(" UNION ALL SELECT NAME, UPPER(TYPE) AS TYPE FROM sqlite_schema") + .append(" WHERE NAME NOT LIKE 'sqlite\\_%' ESCAPE '\\' AND UPPER(TYPE) IN ('TABLE', 'VIEW')") + .append(" UNION ALL SELECT NAME, 'GLOBAL TEMPORARY' AS TYPE FROM sqlite_temp_master") + .append(" UNION ALL SELECT NAME, 'SYSTEM TABLE' AS TYPE FROM sqlite_schema") + .append(" WHERE NAME LIKE 'sqlite\\_%' ESCAPE '\\'") + .append(") WHERE TABLE_NAME LIKE '").append(pattern).append("' ESCAPE '\\'"); + if (types != null && types.length != 0) { + sql.append(" AND TABLE_TYPE IN ("); + for (int i = 0; i < types.length; i++) { + if (i > 0) { + sql.append(","); + } + sql.append("'").append(escapeLiteral(types[i].toUpperCase())).append("'"); + } + sql.append(")"); } sql.append(" ORDER BY TABLE_TYPE, TABLE_NAME"); return executeMetadataQuery(sql.toString()); diff --git a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaDataTest.java b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaDataTest.java index fc0a896..005aa88 100644 --- a/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaDataTest.java +++ b/libsql-jdbc/src/test/java/com/github/magnusp/libsql/jdbc/LibsqlDatabaseMetaDataTest.java @@ -43,8 +43,77 @@ void escapesSingleQuoteInTableNamePattern() throws SQLException { ResultSet resultSet = metadata.getTables(null, null, "O'Brien", null); resultSet.close(); - assertThat(connection.lastSql).contains("name LIKE 'O''Brien'"); - assertThat(connection.lastSql).doesNotContain("name LIKE 'O'Brien'"); + assertThat(connection.lastSql).contains("TABLE_NAME LIKE 'O''Brien'"); + assertThat(connection.lastSql).doesNotContain("TABLE_NAME LIKE 'O'Brien'"); + } + + @Test + void getTablesWithNullTypesReturnsOnlyTablesViewsAndSystemAndTemporaryRows() throws SQLException { + RecordingConnection connection = new RecordingConnection(emptyResult()); + + new LibsqlDatabaseMetaData(connection).getTables(null, null, null, null).close(); + + assertThat(connection.lastSql) + .contains("'sqlite_schema' AS NAME") + .contains("'SYSTEM TABLE' AS TYPE") + .contains("'GLOBAL TEMPORARY' AS TYPE") + .contains("FROM sqlite_temp_master") + .contains("UPPER(TYPE) IN ('TABLE', 'VIEW')") + .contains("TABLE_NAME LIKE '%'") + .doesNotContain("TABLE_TYPE IN") + .doesNotContain("INDEX") + .doesNotContain("TRIGGER"); + } + + @Test + void getTablesWithEmptyTypesBehavesLikeNullTypes() throws SQLException { + RecordingConnection connection = new RecordingConnection(emptyResult()); + + new LibsqlDatabaseMetaData(connection).getTables(null, null, null, new String[0]).close(); + + assertThat(connection.lastSql) + .contains("'SYSTEM TABLE' AS TYPE") + .contains("'GLOBAL TEMPORARY' AS TYPE") + .contains("UPPER(TYPE) IN ('TABLE', 'VIEW')") + .doesNotContain("TABLE_TYPE IN"); + } + + @Test + void getTablesWithTableTypeFiltersToTableRows() throws SQLException { + RecordingConnection connection = new RecordingConnection(emptyResult()); + + new LibsqlDatabaseMetaData(connection).getTables(null, null, null, new String[] {"TABLE"}).close(); + + assertThat(connection.lastSql) + .contains("UPPER(TYPE) IN ('TABLE', 'VIEW')") + .contains("AND TABLE_TYPE IN ('TABLE')"); + } + + @Test + void getTablesWithViewTypeFiltersToViewRows() throws SQLException { + RecordingConnection connection = new RecordingConnection(emptyResult()); + + new LibsqlDatabaseMetaData(connection).getTables(null, null, null, new String[] {"VIEW"}).close(); + + assertThat(connection.lastSql).contains("AND TABLE_TYPE IN ('VIEW')"); + } + + @Test + void getTablesWithMultipleTypesUppercasesThem() throws SQLException { + RecordingConnection connection = new RecordingConnection(emptyResult()); + + new LibsqlDatabaseMetaData(connection).getTables(null, null, null, new String[] {"table", "View"}).close(); + + assertThat(connection.lastSql).contains("AND TABLE_TYPE IN ('TABLE','VIEW')"); + } + + @Test + void getTablesWithEmptyTableNamePatternMatchesEverything() throws SQLException { + RecordingConnection connection = new RecordingConnection(emptyResult()); + + new LibsqlDatabaseMetaData(connection).getTables(null, null, "", null).close(); + + assertThat(connection.lastSql).contains("TABLE_NAME LIKE '%'"); } private static StmtResult emptyResult() {