Skip to content

Commit cd94e5e

Browse files
committed
#882 Schema support for getTables
1 parent dea3c1a commit cd94e5e

File tree

3 files changed

+146
-75
lines changed

3 files changed

+146
-75
lines changed

src/main/org/firebirdsql/jdbc/FBDatabaseMetaData.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1251,7 +1251,7 @@ public ResultSet getProcedureColumns(String catalog, String schemaPattern, Strin
12511251
@Override
12521252
public ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String[] types)
12531253
throws SQLException {
1254-
return createGetTablesInstance().getTables(tableNamePattern, types);
1254+
return createGetTablesInstance().getTables(schemaPattern, tableNamePattern, types);
12551255
}
12561256

12571257
private GetTables createGetTablesInstance() {

src/main/org/firebirdsql/jdbc/metadata/GetTables.java

Lines changed: 138 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
// SPDX-FileCopyrightText: Copyright 2001-2024 Firebird development team and individual contributors
2-
// SPDX-FileCopyrightText: Copyright 2022-2024 Mark Rotteveel
1+
// SPDX-FileCopyrightText: Copyright 2001-2025 Firebird development team and individual contributors
2+
// SPDX-FileCopyrightText: Copyright 2022-2025 Mark Rotteveel
33
// SPDX-License-Identifier: LGPL-2.1-or-later
44
package org.firebirdsql.jdbc.metadata;
55

@@ -9,7 +9,6 @@
99
import org.firebirdsql.jdbc.DbMetadataMediator;
1010
import org.firebirdsql.jdbc.DbMetadataMediator.MetadataQuery;
1111
import org.firebirdsql.jdbc.FBResultSet;
12-
import org.firebirdsql.util.InternalApi;
1312

1413
import java.sql.ResultSet;
1514
import java.sql.SQLException;
@@ -41,12 +40,19 @@
4140
* @author Mark Rotteveel
4241
* @since 5
4342
*/
44-
@InternalApi
45-
public abstract class GetTables extends AbstractMetadataMethod {
43+
public abstract sealed class GetTables extends AbstractMetadataMethod {
4644

45+
private static final String LEGACY_IS_TABLE = "rdb$relation_type is null and rdb$view_blr is null";
46+
private static final String LEGACY_IS_VIEW = "rdb$relation_type is null and rdb$view_blr is not null";
4747
private static final String TABLES = "TABLES";
4848
private static final String TABLE_TYPE = "TABLE_TYPE";
4949

50+
/**
51+
* All table types supported for Firebird 2.5 and higher
52+
*/
53+
private static final Set<String> ALL_TYPES = unmodifiableSet(new LinkedHashSet<>(
54+
Arrays.asList(GLOBAL_TEMPORARY, SYSTEM_TABLE, TABLE, VIEW)));
55+
5056
private static final RowDescriptor ROW_DESCRIPTOR = DbMetadataMediator.newRowDescriptorBuilder(12)
5157
.at(0).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "TABLE_CAT", TABLES).addField()
5258
.at(1).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "TABLE_SCHEM", TABLES).addField()
@@ -75,19 +81,19 @@ private GetTables(DbMetadataMediator mediator) {
7581
/**
7682
* @see java.sql.DatabaseMetaData#getTables(String, String, String, String[])
7783
*/
78-
public final ResultSet getTables(String tableNamePattern, String[] types) throws SQLException {
84+
public final ResultSet getTables(String schemaPattern, String tableNamePattern, String[] types) throws SQLException {
7985
if ("".equals(tableNamePattern) || types != null && types.length == 0) {
8086
// Matching table name not possible
8187
return createEmpty();
8288
}
83-
84-
MetadataQuery metadataQuery = createGetTablesQuery(tableNamePattern, toTypesSet(types));
89+
MetadataQuery metadataQuery = createGetTablesQuery(schemaPattern, tableNamePattern, toTypesSet(types));
8590
return createMetaDataResultSet(metadataQuery);
8691
}
8792

8893
@Override
8994
final RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) throws SQLException {
9095
return valueBuilder
96+
.at(1).setString(rs.getString("TABLE_SCHEM"))
9197
.at(2).setString(rs.getString("TABLE_NAME"))
9298
.at(3).setString(rs.getString(TABLE_TYPE))
9399
.at(4).setString(rs.getString("REMARKS"))
@@ -116,7 +122,7 @@ private Set<String> toTypesSet(String[] types) {
116122
return types != null ? new HashSet<>(Arrays.asList(types)) : allTableTypes();
117123
}
118124

119-
abstract MetadataQuery createGetTablesQuery(String tableNamePattern, Set<String> types);
125+
abstract MetadataQuery createGetTablesQuery(String schemaPattern, String tableNamePattern, Set<String> types);
120126

121127
/**
122128
* All supported table types.
@@ -126,7 +132,9 @@ private Set<String> toTypesSet(String[] types) {
126132
*
127133
* @return supported table types
128134
*/
129-
abstract Set<String> allTableTypes();
135+
Set<String> allTableTypes() {
136+
return ALL_TYPES;
137+
}
130138

131139
/**
132140
* The ODS of a Firebird 2.5 database.
@@ -135,13 +143,48 @@ private Set<String> toTypesSet(String[] types) {
135143

136144
public static GetTables create(DbMetadataMediator mediator) {
137145
// NOTE: Indirection through static method prevents unnecessary classloading
138-
if (mediator.getOdsVersion().compareTo(ODS_11_2) >= 0) {
146+
if (mediator.getFirebirdSupportInfo().isVersionEqualOrAbove(6)) {
147+
return FB6.createInstance(mediator);
148+
} else if (mediator.getOdsVersion().compareTo(ODS_11_2) >= 0) {
139149
return FB2_5.createInstance(mediator);
140150
} else {
141151
return FB2_1.createInstance(mediator);
142152
}
143153
}
144154

155+
void buildTypeCondition(StringBuilder sb, Set<String> types) {
156+
final int initialLength = sb.length();
157+
if (types.contains(SYSTEM_TABLE) && types.contains(TABLE)) {
158+
sb.append("(rdb$relation_type in (0, 2, 3) or " + LEGACY_IS_TABLE + ")");
159+
} else if (types.contains(SYSTEM_TABLE)) {
160+
// We assume that external tables are never system and that virtual tables are always system
161+
sb.append("(rdb$relation_type in (0, 3) or " + LEGACY_IS_TABLE + ") and rdb$system_flag = 1");
162+
} else if (types.contains(TABLE)) {
163+
// We assume that external tables are never system and that virtual tables are always system
164+
sb.append("(rdb$relation_type in (0, 2) or " + LEGACY_IS_TABLE + ") and rdb$system_flag = 0");
165+
}
166+
167+
if (types.contains(VIEW)) {
168+
if (sb.length() != initialLength) {
169+
sb.append(" or ");
170+
}
171+
// We assume (but don't check) that views are never system
172+
sb.append("(rdb$relation_type = 1 or " + LEGACY_IS_VIEW + ")");
173+
}
174+
175+
if (types.contains(GLOBAL_TEMPORARY)) {
176+
if (sb.length() != initialLength) {
177+
sb.append(" or ");
178+
}
179+
sb.append("rdb$relation_type in (4, 5)");
180+
}
181+
182+
if (sb.length() == initialLength) {
183+
// Requested types are unknown, query nothing
184+
sb.append("1 = 0");
185+
}
186+
}
187+
145188
@SuppressWarnings("java:S101")
146189
private static final class FB2_1 extends GetTables {
147190

@@ -150,7 +193,7 @@ private static final class FB2_1 extends GetTables {
150193
private static final String TABLE_COLUMNS_NORMAL_2_1 =
151194
formatTableQuery(TABLE, "RDB$SYSTEM_FLAG = 0 and rdb$view_blr is null");
152195
private static final String TABLE_COLUMNS_VIEW_2_1 = formatTableQuery(VIEW, "rdb$view_blr is not null");
153-
private static final String GET_TABLE_ORDER_BY_2_1 = "\norder by 2, 1";
196+
private static final String GET_TABLE_ORDER_BY_2_1 = "\norder by 3, 2";
154197

155198
private static final Map<String, String> QUERY_PER_TYPE;
156199
static {
@@ -176,7 +219,7 @@ private static GetTables createInstance(DbMetadataMediator mediator) {
176219
}
177220

178221
@Override
179-
MetadataQuery createGetTablesQuery(String tableNamePattern, Set<String> types) {
222+
MetadataQuery createGetTablesQuery(String schemaPattern, String tableNamePattern, Set<String> types) {
180223
var tableNameClause = new Clause("RDB$RELATION_NAME", tableNamePattern);
181224
var clauses = new ArrayList<Clause>(types.size());
182225
var queryBuilder = new StringBuilder(2000);
@@ -203,6 +246,7 @@ Set<String> allTableTypes() {
203246
private static String formatTableQuery(String tableType, String condition) {
204247
return String.format("""
205248
select
249+
cast(null as char(1)) as TABLE_SCHEM,
206250
RDB$RELATION_NAME as TABLE_NAME,
207251
cast('%s' as varchar(31)) as TABLE_TYPE,
208252
RDB$DESCRIPTION as REMARKS,
@@ -217,34 +261,27 @@ private static String formatTableQuery(String tableType, String condition) {
217261
@SuppressWarnings("java:S101")
218262
private static final class FB2_5 extends GetTables {
219263

220-
private static final String GET_TABLE_ORDER_BY_2_5 = "\norder by 2, 1";
264+
private static final String GET_TABLE_ORDER_BY_2_5 = "\norder by 3, 2";
221265

222266
//@formatter:off
223-
private static final String LEGACY_IS_TABLE = "rdb$relation_type is null and rdb$view_blr is null";
224-
private static final String LEGACY_IS_VIEW = "rdb$relation_type is null and rdb$view_blr is not null";
225-
226-
private static final String TABLE_COLUMNS_2_5 =
227-
"select\n"
228-
+ " trim(trailing from RDB$RELATION_NAME) as TABLE_NAME,\n"
229-
+ " trim(trailing from case"
230-
+ " when rdb$relation_type = 0 or " + LEGACY_IS_TABLE + " then case when RDB$SYSTEM_FLAG = 1 then '" + SYSTEM_TABLE + "' else '" + TABLE + "' end\n"
231-
+ " when rdb$relation_type = 1 or " + LEGACY_IS_VIEW + " then '" + VIEW + "'\n"
232-
+ " when rdb$relation_type = 2 then '" + TABLE + "'\n" // external table; assume as normal table
233-
+ " when rdb$relation_type = 3 then '" + SYSTEM_TABLE + "'\n" // virtual (monitoring) table: assume system
234-
+ " when rdb$relation_type in (4, 5) then '" + GLOBAL_TEMPORARY + "'\n"
235-
+ " end) as TABLE_TYPE,\n"
236-
+ " RDB$DESCRIPTION as REMARKS,\n"
237-
+ " trim(trailing from RDB$OWNER_NAME) as OWNER_NAME,\n"
238-
+ " RDB$RELATION_ID as JB_RELATION_ID\n"
239-
+ "from RDB$RELATIONS";
267+
private static final String TABLE_COLUMNS_2_5 = """
268+
select
269+
null as TABLE_SCHEM,
270+
trim(trailing from RDB$RELATION_NAME) as TABLE_NAME,
271+
trim(trailing from case
272+
""" +
273+
" when rdb$relation_type = 0 or " + LEGACY_IS_TABLE + " then case when RDB$SYSTEM_FLAG = 1 then '" + SYSTEM_TABLE + "' else '" + TABLE + "' end\n" +
274+
" when rdb$relation_type = 1 or " + LEGACY_IS_VIEW + " then '" + VIEW + "'\n" +
275+
" when rdb$relation_type = 2 then '" + TABLE + "' -- external table; assume as normal table\n" +
276+
" when rdb$relation_type = 3 then '" + SYSTEM_TABLE + "' -- virtual (monitoring) table: assume system\n" +
277+
" when rdb$relation_type in (4, 5) then '" + GLOBAL_TEMPORARY + "'\n" + """
278+
end) as TABLE_TYPE,
279+
RDB$DESCRIPTION as REMARKS,
280+
trim(trailing from RDB$OWNER_NAME) as OWNER_NAME,
281+
RDB$RELATION_ID as JB_RELATION_ID
282+
from RDB$RELATIONS""";
240283
//@formatter:on
241284

242-
/**
243-
* All table types supported for Firebird 2.5 and higher
244-
*/
245-
private static final Set<String> ALL_TYPES_2_5 = unmodifiableSet(new LinkedHashSet<>(
246-
Arrays.asList(GLOBAL_TEMPORARY, SYSTEM_TABLE, TABLE, VIEW)));
247-
248285
private FB2_5(DbMetadataMediator mediator) {
249286
super(mediator);
250287
}
@@ -254,7 +291,7 @@ private static GetTables createInstance(DbMetadataMediator mediator) {
254291
}
255292

256293
@Override
257-
MetadataQuery createGetTablesQuery(String tableNamePattern, Set<String> types) {
294+
MetadataQuery createGetTablesQuery(String schemaPattern, String tableNamePattern, Set<String> types) {
258295
var tableNameClause = new Clause("RDB$RELATION_NAME", tableNamePattern);
259296

260297
var queryBuilder = new StringBuilder(1000).append(TABLE_COLUMNS_2_5);
@@ -266,57 +303,86 @@ MetadataQuery createGetTablesQuery(String tableNamePattern, Set<String> types) {
266303
params = Collections.emptyList();
267304
}
268305

269-
if (!types.containsAll(ALL_TYPES_2_5)) {
306+
if (!types.containsAll(ALL_TYPES)) {
270307
// Only construct conditions when we don't query for all
271-
StringBuilder typeCondition = buildTypeCondition(types);
272308
if (tableNameClause.hasCondition()) {
273-
queryBuilder.append("\nand (").append(typeCondition).append(")");
309+
queryBuilder.append("\nand (");
310+
buildTypeCondition(queryBuilder, types);
311+
queryBuilder.append(")");
274312
} else {
275-
queryBuilder.append("\nwhere ").append(typeCondition);
313+
queryBuilder.append("\nwhere ");
314+
buildTypeCondition(queryBuilder, types);
276315
}
277316
}
278317
queryBuilder.append(GET_TABLE_ORDER_BY_2_5);
279318

280319
return new MetadataQuery(queryBuilder.toString(), params);
281320
}
282321

283-
private static StringBuilder buildTypeCondition(Set<String> types) {
284-
var typeCondition = new StringBuilder(120);
285-
if (types.contains(SYSTEM_TABLE) && types.contains(TABLE)) {
286-
typeCondition.append("(rdb$relation_type in (0, 2, 3) or " + LEGACY_IS_TABLE + ")");
287-
} else if (types.contains(SYSTEM_TABLE)) {
288-
// We assume that external tables are never system and that virtual tables are always system
289-
typeCondition.append("(rdb$relation_type in (0, 3) or " + LEGACY_IS_TABLE + ") and rdb$system_flag = 1");
290-
} else if (types.contains(TABLE)) {
291-
// We assume that external tables are never system and that virtual tables are always system
292-
typeCondition.append("(rdb$relation_type in (0, 2) or " + LEGACY_IS_TABLE + ") and rdb$system_flag = 0");
293-
}
322+
}
294323

295-
if (types.contains(VIEW)) {
296-
if (!typeCondition.isEmpty()) {
297-
typeCondition.append(" or ");
298-
}
299-
// We assume (but don't check) that views are never system
300-
typeCondition.append("(rdb$relation_type = 1 or " + LEGACY_IS_VIEW + ")");
324+
private static final class FB6 extends GetTables {
325+
326+
private static final String GET_TABLE_ORDER_BY_6 = "\norder by 3, 1, 2";
327+
328+
//@formatter:off
329+
private static final String TABLE_COLUMNS_6 = """
330+
select
331+
trim(trailing from RDB$SCHEMA_NAME) as TABLE_SCHEM,
332+
trim(trailing from RDB$RELATION_NAME) as TABLE_NAME,
333+
trim(trailing from case
334+
""" +
335+
" when rdb$relation_type = 0 or " + LEGACY_IS_TABLE + " then case when RDB$SYSTEM_FLAG = 1 then '" + SYSTEM_TABLE + "' else '" + TABLE + "' end\n" +
336+
" when rdb$relation_type = 1 or " + LEGACY_IS_VIEW + " then '" + VIEW + "'\n" +
337+
" when rdb$relation_type = 2 then '" + TABLE + "' -- external table; assume as normal table\n" +
338+
" when rdb$relation_type = 3 then '" + SYSTEM_TABLE + "' -- virtual (monitoring) table: assume system\n" +
339+
" when rdb$relation_type in (4, 5) then '" + GLOBAL_TEMPORARY + "'\n" + """
340+
end) as TABLE_TYPE,
341+
RDB$DESCRIPTION as REMARKS,
342+
trim(trailing from RDB$OWNER_NAME) as OWNER_NAME,
343+
RDB$RELATION_ID as JB_RELATION_ID
344+
from SYSTEM.RDB$RELATIONS""";
345+
//@formatter:on
346+
347+
private FB6(DbMetadataMediator mediator) {
348+
super(mediator);
349+
}
350+
351+
private static GetTables createInstance(DbMetadataMediator mediator) {
352+
return new FB6(mediator);
353+
}
354+
355+
@Override
356+
MetadataQuery createGetTablesQuery(String schemaPattern, String tableNamePattern, Set<String> types) {
357+
var clauses = List.of(
358+
new Clause("RDB$SCHEMA_NAME", schemaPattern),
359+
new Clause("RDB$RELATION_NAME", tableNamePattern));
360+
361+
var queryBuilder = new StringBuilder(1000).append(TABLE_COLUMNS_6);
362+
List<String> params;
363+
if (Clause.anyCondition(clauses)) {
364+
queryBuilder.append("\nwhere ").append(Clause.conjunction(clauses));
365+
params = Clause.parameters(clauses);
366+
} else {
367+
params = Collections.emptyList();
301368
}
302369

303-
if (types.contains(GLOBAL_TEMPORARY)) {
304-
if (!typeCondition.isEmpty()) {
305-
typeCondition.append(" or ");
370+
if (!types.containsAll(ALL_TYPES)) {
371+
// Only construct conditions when we don't query for all
372+
if (Clause.anyCondition(clauses)) {
373+
queryBuilder.append("\nand (");
374+
buildTypeCondition(queryBuilder, types);
375+
queryBuilder.append(")");
376+
} else {
377+
queryBuilder.append("\nwhere ");
378+
buildTypeCondition(queryBuilder, types);
306379
}
307-
typeCondition.append("rdb$relation_type in (4, 5)");
308380
}
381+
queryBuilder.append(GET_TABLE_ORDER_BY_6);
309382

310-
if (typeCondition.isEmpty()) {
311-
// Requested types are unknown, query nothing
312-
typeCondition.append("1 = 0");
313-
}
314-
return typeCondition;
383+
return new MetadataQuery(queryBuilder.toString(), params);
315384
}
316385

317-
@Override
318-
Set<String> allTableTypes() {
319-
return ALL_TYPES_2_5;
320-
}
321386
}
387+
322388
}

src/test/org/firebirdsql/jdbc/FBDatabaseMetaDataTablesTest.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// SPDX-FileCopyrightText: Copyright 2012-2023 Mark Rotteveel
1+
// SPDX-FileCopyrightText: Copyright 2012-2025 Mark Rotteveel
22
// SPDX-License-Identifier: LGPL-2.1-or-later
33
package org.firebirdsql.jdbc;
44

@@ -17,6 +17,7 @@
1717
import static java.lang.String.format;
1818
import static org.firebirdsql.common.FBTestProperties.getConnectionViaDriverManager;
1919
import static org.firebirdsql.common.FBTestProperties.getDefaultSupportInfo;
20+
import static org.firebirdsql.common.FBTestProperties.ifSchemaElse;
2021
import static org.hamcrest.CoreMatchers.anyOf;
2122
import static org.hamcrest.CoreMatchers.is;
2223
import static org.hamcrest.CoreMatchers.not;
@@ -35,6 +36,8 @@
3536
*/
3637
class FBDatabaseMetaDataTablesTest {
3738

39+
// TODO Add schema support: tests involving other schema
40+
3841
// Valid values for TABLE_TYPE (separate from those defined in FBDatabaseMetaData for testing)
3942
private static final String VIEW = "VIEW";
4043
private static final String TABLE = "TABLE";
@@ -233,6 +236,7 @@ private void validateTableMetaData_allSystemTables(String tableNamePattern) thro
233236
Set<String> expectedTables = new HashSet<>(Arrays.asList("RDB$FIELDS", "RDB$GENERATORS",
234237
"RDB$ROLES", "RDB$DATABASE", "RDB$TRIGGERS"));
235238
Map<TableMetaData, Object> rules = getDefaultValueValidationRules();
239+
rules.put(TableMetaData.TABLE_SCHEM, ifSchemaElse("SYSTEM", null));
236240
rules.put(TableMetaData.TABLE_TYPE, SYSTEM_TABLE);
237241
try (ResultSet tables = dbmd.getTables(null, null, tableNamePattern, new String[] { SYSTEM_TABLE })) {
238242
while (tables.next()) {
@@ -550,6 +554,7 @@ private void validateTableMetaDataNoRow(String tableNamePattern, String[] types)
550554
private void updateTableRules(String tableName, Map<TableMetaData, Object> rules) {
551555
rules.put(TableMetaData.TABLE_NAME, tableName);
552556
if (tableName.startsWith("RDB$") || tableName.startsWith("MON$") || tableName.startsWith("SEC$")) {
557+
rules.put(TableMetaData.TABLE_SCHEM, ifSchemaElse("SYSTEM", null));
553558
rules.put(TableMetaData.TABLE_TYPE, SYSTEM_TABLE);
554559
} else if (tableName.equals("TEST_NORMAL_TABLE") || tableName.equals("test_quoted_normal_table")
555560
|| tableName.equals("testquotedwith\\table")) {
@@ -570,7 +575,7 @@ private void updateTableRules(String tableName, Map<TableMetaData, Object> rules
570575
static {
571576
Map<TableMetaData, Object> defaults = new EnumMap<>(TableMetaData.class);
572577
defaults.put(TableMetaData.TABLE_CAT, null);
573-
defaults.put(TableMetaData.TABLE_SCHEM, null);
578+
defaults.put(TableMetaData.TABLE_SCHEM, ifSchemaElse("PUBLIC", null));
574579
defaults.put(TableMetaData.REMARKS, null);
575580
defaults.put(TableMetaData.TYPE_CAT, null);
576581
defaults.put(TableMetaData.TYPE_SCHEM, null);

0 commit comments

Comments
 (0)