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
44package org .firebirdsql .jdbc .metadata ;
55
99import org .firebirdsql .jdbc .DbMetadataMediator ;
1010import org .firebirdsql .jdbc .DbMetadataMediator .MetadataQuery ;
1111import org .firebirdsql .jdbc .FBResultSet ;
12- import org .firebirdsql .util .InternalApi ;
1312
1413import java .sql .ResultSet ;
1514import java .sql .SQLException ;
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 = "\n order by 2, 1 " ;
196+ private static final String GET_TABLE_ORDER_BY_2_1 = "\n order 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 = "\n order by 2, 1 " ;
264+ private static final String GET_TABLE_ORDER_BY_2_5 = "\n order 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 ("\n and (" ).append (typeCondition ).append (")" );
309+ queryBuilder .append ("\n and (" );
310+ buildTypeCondition (queryBuilder , types );
311+ queryBuilder .append (")" );
274312 } else {
275- queryBuilder .append ("\n where " ).append (typeCondition );
313+ queryBuilder .append ("\n where " );
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 = "\n order 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 ("\n where " ).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 ("\n and (" );
374+ buildTypeCondition (queryBuilder , types );
375+ queryBuilder .append (")" );
376+ } else {
377+ queryBuilder .append ("\n where " );
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}
0 commit comments