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
4141 * @author Mark Rotteveel
4242 * @since 5
4343 */
44- public final class GetBestRowIdentifier extends AbstractMetadataMethod {
44+ public abstract class GetBestRowIdentifier extends AbstractMetadataMethod {
4545
4646 private static final String ROWIDENTIFIER = "ROWIDENTIFIER" ;
4747
@@ -56,31 +56,6 @@ public final class GetBestRowIdentifier extends AbstractMetadataMethod {
5656 .at (7 ).simple (SQL_SHORT , 0 , "PSEUDO_COLUMN" , ROWIDENTIFIER ).addField ()
5757 .toRowDescriptor ();
5858
59- //@formatter:off
60- private static final String GET_BEST_ROW_IDENT_START =
61- "select\n "
62- + " RF.RDB$FIELD_NAME as COLUMN_NAME,\n "
63- + " F.RDB$FIELD_TYPE as " + FIELD_TYPE + ",\n "
64- + " F.RDB$FIELD_SUB_TYPE as " + FIELD_SUB_TYPE + ",\n "
65- + " F.RDB$FIELD_PRECISION as " + FIELD_PRECISION + ",\n "
66- + " F.RDB$FIELD_SCALE as " + FIELD_SCALE + ",\n "
67- + " F.RDB$FIELD_LENGTH as " + FIELD_LENGTH + ",\n "
68- + " F.RDB$CHARACTER_LENGTH as " + CHAR_LEN + ",\n "
69- + " F.RDB$CHARACTER_SET_ID as " + CHARSET_ID + "\n "
70- + "from RDB$RELATION_CONSTRAINTS RC\n "
71- + "inner join RDB$INDEX_SEGMENTS IDX\n "
72- + " on IDX.RDB$INDEX_NAME = RC.RDB$INDEX_NAME\n "
73- + "inner join RDB$RELATION_FIELDS RF\n "
74- + " on RF.RDB$FIELD_NAME = IDX.RDB$FIELD_NAME and RF.RDB$RELATION_NAME = RC.RDB$RELATION_NAME\n "
75- + "inner join RDB$FIELDS F\n "
76- + " on F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE\n "
77- + "where RC.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'\n "
78- + "and " ;
79-
80- private static final String GET_BEST_ROW_IDENT_END =
81- "\n order by IDX.RDB$FIELD_POSITION" ;
82- //@formatter:on
83-
8459 private GetBestRowIdentifier (DbMetadataMediator mediator ) {
8560 super (ROW_DESCRIPTOR , mediator );
8661 }
@@ -93,7 +68,7 @@ public ResultSet getBestRowIdentifier(String catalog, String schema, String tabl
9368 }
9469
9570 RowValueBuilder valueBuilder = new RowValueBuilder (ROW_DESCRIPTOR );
96- List <RowValue > rows = getPrimaryKeyIdentifier (table , valueBuilder );
71+ List <RowValue > rows = getPrimaryKeyIdentifier (schema , table , valueBuilder );
9772
9873 // if no primary key exists, add RDB$DB_KEY as pseudo-column
9974 if (rows .isEmpty ()) {
@@ -105,8 +80,8 @@ public ResultSet getBestRowIdentifier(String catalog, String schema, String tabl
10580 return createEmpty ();
10681 }
10782
108- try (ResultSet pseudoColumns =
109- dbmd . getPseudoColumns ( catalog , schema , escapeWildcards (table ), "RDB$DB\\ _KEY" )) {
83+ try (ResultSet pseudoColumns = dbmd . getPseudoColumns (
84+ catalog , escapeWildcards ( schema ) , escapeWildcards (table ), "RDB$DB\\ _KEY" )) {
11085 if (!pseudoColumns .next ()) {
11186 return createEmpty ();
11287 }
@@ -118,7 +93,7 @@ public ResultSet getBestRowIdentifier(String catalog, String schema, String tabl
11893 .at (1 ).setString ("RDB$DB_KEY" )
11994 .at (2 ).setInt (Types .ROWID )
12095 .at (3 ).setString (getDataTypeName (char_type , 0 , CS_BINARY ))
121- .at (4 ).setInt (pseudoColumns .getInt (8 ))
96+ .at (4 ).setInt (pseudoColumns .getInt (6 ))
12297 .at (5 ).set (null )
12398 .at (6 ).set (null )
12499 .at (7 ).setShort (DatabaseMetaData .bestRowPseudo )
@@ -132,22 +107,20 @@ public ResultSet getBestRowIdentifier(String catalog, String schema, String tabl
132107 /**
133108 * Get primary key of the table as best row identifier.
134109 *
110+ * @param schema
111+ * name of the schema
135112 * @param table
136- * name of the table.
113+ * name of the table
137114 * @param valueBuilder
138115 * builder for row values
139116 * @return list of result set values, when empty, no primary key has been defined for a table or the table does not
140117 * exist. The returned list can be modified by caller if needed.
141118 * @throws SQLException
142119 * if something went wrong.
143120 */
144- private List <RowValue > getPrimaryKeyIdentifier (String table , RowValueBuilder valueBuilder ) throws SQLException {
145- Clause tableClause = Clause .equalsClause ("RC.RDB$RELATION_NAME" , table );
146- String sql = GET_BEST_ROW_IDENT_START
147- + tableClause .getCondition (false )
148- + GET_BEST_ROW_IDENT_END ;
149-
150- MetadataQuery metadataQuery = new MetadataQuery (sql , Clause .parameters (tableClause ));
121+ private List <RowValue > getPrimaryKeyIdentifier (String schema , String table , RowValueBuilder valueBuilder )
122+ throws SQLException {
123+ MetadataQuery metadataQuery = createGetPrimaryKeyIdentifierQuery (schema , table );
151124 try (ResultSet rs = mediator .performMetaDataQuery (metadataQuery )) {
152125 List <RowValue > rows = new ArrayList <>();
153126 while (rs .next ()) {
@@ -157,6 +130,8 @@ private List<RowValue> getPrimaryKeyIdentifier(String table, RowValueBuilder val
157130 }
158131 }
159132
133+ abstract MetadataQuery createGetPrimaryKeyIdentifierQuery (String schema , String table );
134+
160135 @ Override
161136 RowValue createMetadataRow (ResultSet rs , RowValueBuilder valueBuilder ) throws SQLException {
162137 TypeMetadata typeMetadata = TypeMetadata .builder (mediator .getFirebirdSupportInfo ())
@@ -176,6 +151,119 @@ RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) throws SQ
176151 }
177152
178153 public static GetBestRowIdentifier create (DbMetadataMediator mediator ) {
179- return new GetBestRowIdentifier (mediator );
154+ // NOTE: Indirection through static method prevents unnecessary classloading
155+ if (mediator .getFirebirdSupportInfo ().isVersionEqualOrAbove (6 )) {
156+ return FB6 .createInstance (mediator );
157+ } else {
158+ return FB5 .createInstance (mediator );
159+ }
180160 }
161+
162+ /**
163+ * Implementation for Firebird 5.0 and older.
164+ */
165+ private static final class FB5 extends GetBestRowIdentifier {
166+
167+ //@formatter:off
168+ private static final String GET_BEST_ROW_IDENT_START = """
169+ select
170+ RF.RDB$FIELD_NAME as COLUMN_NAME,
171+ """ +
172+ " F.RDB$FIELD_TYPE as " + FIELD_TYPE + ",\n " +
173+ " F.RDB$FIELD_SUB_TYPE as " + FIELD_SUB_TYPE + ",\n " +
174+ " F.RDB$FIELD_PRECISION as " + FIELD_PRECISION + ",\n " +
175+ " F.RDB$FIELD_SCALE as " + FIELD_SCALE + ",\n " +
176+ " F.RDB$FIELD_LENGTH as " + FIELD_LENGTH + ",\n " +
177+ " F.RDB$CHARACTER_LENGTH as " + CHAR_LEN + ",\n " +
178+ " F.RDB$CHARACTER_SET_ID as " + CHARSET_ID + "\n " + """
179+ from RDB$RELATION_CONSTRAINTS RC
180+ inner join RDB$INDEX_SEGMENTS IDX
181+ on IDX.RDB$INDEX_NAME = RC.RDB$INDEX_NAME
182+ inner join RDB$RELATION_FIELDS RF
183+ on RF.RDB$FIELD_NAME = IDX.RDB$FIELD_NAME and RF.RDB$RELATION_NAME = RC.RDB$RELATION_NAME
184+ inner join RDB$FIELDS F
185+ on F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE
186+ where RC.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'
187+ and\s """ ;
188+ //@formatter:on
189+
190+ private static final String GET_BEST_ROW_IDENT_END = "\n order by IDX.RDB$FIELD_POSITION" ;
191+
192+
193+ private FB5 (DbMetadataMediator mediator ) {
194+ super (mediator );
195+ }
196+
197+ private static GetBestRowIdentifier createInstance (DbMetadataMediator mediator ) {
198+ return new FB5 (mediator );
199+ }
200+
201+ @ Override
202+ MetadataQuery createGetPrimaryKeyIdentifierQuery (String schema , String table ) {
203+ Clause tableClause = Clause .equalsClause ("RC.RDB$RELATION_NAME" , table );
204+ String sql = GET_BEST_ROW_IDENT_START
205+ + tableClause .getCondition (false )
206+ + GET_BEST_ROW_IDENT_END ;
207+
208+ return new MetadataQuery (sql , Clause .parameters (tableClause ));
209+ }
210+ }
211+
212+ /**
213+ * Implementation for Firebird 6.0 and higher.
214+ */
215+ private static final class FB6 extends GetBestRowIdentifier {
216+
217+ //@formatter:off
218+ private static final String GET_BEST_ROW_IDENT_START_6 = """
219+ select
220+ RF.RDB$FIELD_NAME as COLUMN_NAME,
221+ """ +
222+ " F.RDB$FIELD_TYPE as " + FIELD_TYPE + ",\n " +
223+ " F.RDB$FIELD_SUB_TYPE as " + FIELD_SUB_TYPE + ",\n " +
224+ " F.RDB$FIELD_PRECISION as " + FIELD_PRECISION + ",\n " +
225+ " F.RDB$FIELD_SCALE as " + FIELD_SCALE + ",\n " +
226+ " F.RDB$FIELD_LENGTH as " + FIELD_LENGTH + ",\n " +
227+ " F.RDB$CHARACTER_LENGTH as " + CHAR_LEN + ",\n " +
228+ " F.RDB$CHARACTER_SET_ID as " + CHARSET_ID + "\n " + """
229+ from SYSTEM.RDB$RELATION_CONSTRAINTS RC
230+ inner join SYSTEM.RDB$INDEX_SEGMENTS IDX
231+ on IDX.RDB$SCHEMA_NAME = RC.RDB$SCHEMA_NAME and IDX.RDB$INDEX_NAME = RC.RDB$INDEX_NAME
232+ inner join SYSTEM.RDB$RELATION_FIELDS RF
233+ on RF.RDB$FIELD_NAME = IDX.RDB$FIELD_NAME and RF.RDB$SCHEMA_NAME = RC.RDB$SCHEMA_NAME and RF.RDB$RELATION_NAME = RC.RDB$RELATION_NAME
234+ inner join SYSTEM.RDB$FIELDS F
235+ on F.RDB$SCHEMA_NAME = RF.RDB$FIELD_SOURCE_SCHEMA_NAME and F.RDB$FIELD_NAME = RF.RDB$FIELD_SOURCE
236+ where RC.RDB$CONSTRAINT_TYPE = 'PRIMARY KEY'
237+ and\s """ ;
238+ //@formatter:on
239+
240+ // The order by schema name is to ensure a consistent order when this is called with schema = null, as that will
241+ // not narrow the search by schema, so can return columns of multiple same named tables in different schemas.
242+ private static final String GET_BEST_ROW_IDENT_END_6 = "\n order by RC.RDB$SCHEMA_NAME, IDX.RDB$FIELD_POSITION" ;
243+
244+ private FB6 (DbMetadataMediator mediator ) {
245+ super (mediator );
246+ }
247+
248+ private static GetBestRowIdentifier createInstance (DbMetadataMediator mediator ) {
249+ return new FB6 (mediator );
250+ }
251+
252+ @ Override
253+ MetadataQuery createGetPrimaryKeyIdentifierQuery (String schema , String table ) {
254+ List <Clause > clauses = new ArrayList <>(2 );
255+ if (schema != null ) {
256+ // NOTE: empty string will return no rows as required ("" retrieves those without a schema)
257+ clauses .add (Clause .equalsClause ("RC.RDB$SCHEMA_NAME" , schema ));
258+ }
259+ clauses .add (Clause .equalsClause ("RC.RDB$RELATION_NAME" , table ));
260+ String sql = GET_BEST_ROW_IDENT_START_6
261+ + Clause .conjunction (clauses )
262+ + GET_BEST_ROW_IDENT_END_6 ;
263+
264+ return new MetadataQuery (sql , Clause .parameters (clauses ));
265+ }
266+
267+ }
268+
181269}
0 commit comments