1- // SPDX-FileCopyrightText: Copyright 2001-2024 Firebird development team and individual contributors
2- // SPDX-FileCopyrightText: Copyright 2019-2024 Mark Rotteveel
1+ // SPDX-FileCopyrightText: Copyright 2001-2025 Firebird development team and individual contributors
2+ // SPDX-FileCopyrightText: Copyright 2019-2025 Mark Rotteveel
33// SPDX-License-Identifier: LGPL-2.1-or-later
44package org .firebirdsql .jdbc .metadata ;
55
1313import java .sql .ResultSet ;
1414import java .sql .SQLException ;
1515import java .util .ArrayList ;
16+ import java .util .List ;
1617
1718import static java .sql .DatabaseMetaData .functionNoTable ;
1819import static org .firebirdsql .gds .ISCConstants .SQL_SHORT ;
3031public abstract class GetFunctions extends AbstractMetadataMethod {
3132
3233 private static final String FUNCTIONS = "FUNCTIONS" ;
34+ private static final String COLUMN_CATALOG_NAME = "RDB$PACKAGE_NAME" ;
35+ private static final String COLUMN_SCHEMA_NAME = "RDB$SCHEMA_NAME" ;
3336 private static final String COLUMN_FUNCTION_NAME = "RDB$FUNCTION_NAME" ;
34-
37+
3538 private static final RowDescriptor ROW_DESCRIPTOR = DbMetadataMediator .newRowDescriptorBuilder (11 )
3639 .at (0 ).simple (SQL_VARYING | 1 , OBJECT_NAME_LENGTH , "FUNCTION_CAT" , FUNCTIONS ).addField ()
3740 .at (1 ).simple (SQL_VARYING | 1 , OBJECT_NAME_LENGTH , "FUNCTION_SCHEM" , FUNCTIONS ).addField ()
@@ -56,27 +59,29 @@ private GetFunctions(DbMetadataMediator mediator) {
5659 /**
5760 * @see java.sql.DatabaseMetaData#getFunctions(String, String, String)
5861 */
59- public final ResultSet getFunctions (String catalog , String functionNamePattern ) throws SQLException {
62+ public final ResultSet getFunctions (String catalog , String schemaPattern , String functionNamePattern )
63+ throws SQLException {
6064 if ("" .equals (functionNamePattern )) {
6165 // Matching function name not possible
6266 return createEmpty ();
6367 }
6468
65- MetadataQuery metadataQuery = createGetFunctionsQuery (catalog , functionNamePattern );
69+ MetadataQuery metadataQuery = createGetFunctionsQuery (catalog , schemaPattern , functionNamePattern );
6670 return createMetaDataResultSet (metadataQuery );
6771 }
6872
6973 @ Override
7074 final RowValue createMetadataRow (ResultSet rs , RowValueBuilder valueBuilder ) throws SQLException {
7175 String catalog = rs .getString ("FUNCTION_CAT" );
76+ String schema = rs .getString ("FUNCTION_SCHEM" );
7277 String functionName = rs .getString ("FUNCTION_NAME" );
7378 return valueBuilder
7479 .at (0 ).setString (catalog )
75- .at (1 ).set ( null )
80+ .at (1 ).setString ( schema )
7681 .at (2 ).setString (functionName )
7782 .at (3 ).setString (rs .getString ("REMARKS" ))
7883 .at (4 ).setShort (functionNoTable )
79- .at (5 ).setString (toSpecificName (catalog , functionName ))
84+ .at (5 ).setString (toSpecificName (catalog , schema , functionName ))
8085 .at (6 ).setString (rs .getString ("JB_FUNCTION_SOURCE" ))
8186 .at (7 ).setString (rs .getString ("JB_FUNCTION_KIND" ))
8287 .at (8 ).setString (rs .getString ("JB_MODULE_NAME" ))
@@ -85,7 +90,7 @@ final RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) thr
8590 .toRowValue (false );
8691 }
8792
88- abstract MetadataQuery createGetFunctionsQuery (String catalog , String functionNamePattern );
93+ abstract MetadataQuery createGetFunctionsQuery (String catalog , String schemaPattern , String functionNamePattern );
8994
9095 /**
9196 * Creates an instance of {@code GetFunctions}.
@@ -97,7 +102,12 @@ final RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) thr
97102 public static GetFunctions create (DbMetadataMediator mediator ) {
98103 FirebirdSupportInfo firebirdSupportInfo = mediator .getFirebirdSupportInfo ();
99104 // NOTE: Indirection through static method prevents unnecessary classloading
100- if (firebirdSupportInfo .isVersionEqualOrAbove (3 )) {
105+ if (firebirdSupportInfo .isVersionEqualOrAbove (6 )) {
106+ if (mediator .isUseCatalogAsPackage ()) {
107+ return FB6CatalogAsPackage .createInstance (mediator );
108+ }
109+ return FB6 .createInstance (mediator );
110+ } else if (firebirdSupportInfo .isVersionEqualOrAbove (3 )) {
101111 if (mediator .isUseCatalogAsPackage ()) {
102112 return FB3CatalogAsPackage .createInstance (mediator );
103113 }
@@ -115,7 +125,8 @@ private static final class FB2_5 extends GetFunctions {
115125
116126 private static final String GET_FUNCTIONS_FRAGMENT_2_5 = """
117127 select
118- null as FUNCTION_CAT,
128+ cast(null as char(1)) as FUNCTION_CAT,
129+ cast(null as char(1)) as FUNCTION_SCHEM,
119130 RDB$FUNCTION_NAME as FUNCTION_NAME,
120131 RDB$DESCRIPTION as REMARKS,
121132 cast(null as blob sub_type text) as JB_FUNCTION_SOURCE,
@@ -137,7 +148,7 @@ private static GetFunctions createInstance(DbMetadataMediator mediator) {
137148 }
138149
139150 @ Override
140- MetadataQuery createGetFunctionsQuery (String catalog , String functionNamePattern ) {
151+ MetadataQuery createGetFunctionsQuery (String catalog , String schemaPattern , String functionNamePattern ) {
141152 Clause functionNameClause = new Clause (COLUMN_FUNCTION_NAME , functionNamePattern );
142153 String queryText = GET_FUNCTIONS_FRAGMENT_2_5
143154 + functionNameClause .getCondition ("\n where " , "" )
@@ -154,6 +165,7 @@ private static final class FB3 extends GetFunctions {
154165 private static final String GET_FUNCTIONS_FRAGMENT_3 = """
155166 select
156167 null as FUNCTION_CAT,
168+ null as FUNCTION_SCHEM,
157169 trim(trailing from RDB$FUNCTION_NAME) as FUNCTION_NAME,
158170 RDB$DESCRIPTION as REMARKS,
159171 RDB$FUNCTION_SOURCE as JB_FUNCTION_SOURCE,
@@ -180,7 +192,7 @@ private static GetFunctions createInstance(DbMetadataMediator mediator) {
180192 }
181193
182194 @ Override
183- MetadataQuery createGetFunctionsQuery (String catalog , String functionNamePattern ) {
195+ MetadataQuery createGetFunctionsQuery (String catalog , String schemaPattern , String functionNamePattern ) {
184196 Clause functionNameClause = new Clause (COLUMN_FUNCTION_NAME , functionNamePattern );
185197 String queryText = GET_FUNCTIONS_FRAGMENT_3
186198 + functionNameClause .getCondition ("\n and " , "" )
@@ -194,6 +206,7 @@ private static final class FB3CatalogAsPackage extends GetFunctions {
194206 private static final String GET_FUNCTIONS_FRAGMENT_3_W_PKG = """
195207 select
196208 coalesce(trim(trailing from RDB$PACKAGE_NAME), '') as FUNCTION_CAT,
209+ null as FUNCTION_SCHEM,
197210 trim(trailing from RDB$FUNCTION_NAME) as FUNCTION_NAME,
198211 RDB$DESCRIPTION as REMARKS,
199212 RDB$FUNCTION_SOURCE as JB_FUNCTION_SOURCE,
@@ -210,8 +223,6 @@ private static final class FB3CatalogAsPackage extends GetFunctions {
210223 private static final String GET_FUNCTIONS_ORDER_BY_3_W_PKG =
211224 "\n order by RDB$PACKAGE_NAME nulls first, RDB$FUNCTION_NAME" ;
212225
213- private static final String COLUMN_CATALOG_NAME = "RDB$PACKAGE_NAME" ;
214-
215226 private FB3CatalogAsPackage (DbMetadataMediator mediator ) {
216227 super (mediator );
217228 }
@@ -221,7 +232,7 @@ private static GetFunctions createInstance(DbMetadataMediator mediator) {
221232 }
222233
223234 @ Override
224- MetadataQuery createGetFunctionsQuery (String catalog , String functionNamePattern ) {
235+ MetadataQuery createGetFunctionsQuery (String catalog , String schemaPattern , String functionNamePattern ) {
225236 var clauses = new ArrayList <Clause >(2 );
226237 if (catalog != null ) {
227238 // To quote from the JDBC API: "" retrieves those without a catalog; null means that the catalog name
@@ -234,14 +245,109 @@ MetadataQuery createGetFunctionsQuery(String catalog, String functionNamePattern
234245 }
235246 }
236247 clauses .add (new Clause (COLUMN_FUNCTION_NAME , functionNamePattern ));
237- //@formatter:off
238248 String sql = GET_FUNCTIONS_FRAGMENT_3_W_PKG
239- + (Clause .anyCondition (clauses )
240- ? "\n where " + Clause .conjunction (clauses )
241- : "" )
249+ + (Clause .anyCondition (clauses ) ? "\n where " + Clause .conjunction (clauses ) : "" )
242250 + GET_FUNCTIONS_ORDER_BY_3_W_PKG ;
243- //@formatter:on
244251 return new MetadataQuery (sql , Clause .parameters (clauses ));
245252 }
246253 }
254+
255+ private static final class FB6 extends GetFunctions {
256+
257+ private static final String GET_FUNCTIONS_FRAGMENT_6 = """
258+ select
259+ null as FUNCTION_CAT,
260+ trim(trailing from RDB$SCHEMA_NAME) as FUNCTION_SCHEM,
261+ trim(trailing from RDB$FUNCTION_NAME) as FUNCTION_NAME,
262+ RDB$DESCRIPTION as REMARKS,
263+ RDB$FUNCTION_SOURCE as JB_FUNCTION_SOURCE,
264+ case
265+ when RDB$LEGACY_FLAG = 1 then 'UDF'
266+ when RDB$ENGINE_NAME is not null then 'UDR'
267+ else 'PSQL'
268+ end as JB_FUNCTION_KIND,
269+ trim(trailing from RDB$MODULE_NAME) as JB_MODULE_NAME,
270+ trim(trailing from RDB$ENTRYPOINT) as JB_ENTRYPOINT,
271+ trim(trailing from RDB$ENGINE_NAME) as JB_ENGINE_NAME
272+ from SYSTEM.RDB$FUNCTIONS
273+ where RDB$PACKAGE_NAME is null""" ;
274+
275+ // NOTE: Including RDB$PACKAGE_NAME so index can be used to sort
276+ private static final String GET_FUNCTIONS_ORDER_BY_6 =
277+ "\n order by RDB$SCHEMA_NAME, RDB$PACKAGE_NAME, RDB$FUNCTION_NAME" ;
278+
279+ private FB6 (DbMetadataMediator mediator ) {
280+ super (mediator );
281+ }
282+
283+ private static GetFunctions createInstance (DbMetadataMediator mediator ) {
284+ return new FB6 (mediator );
285+ }
286+
287+ @ Override
288+ MetadataQuery createGetFunctionsQuery (String catalog , String schemaPattern , String functionNamePattern ) {
289+ var clauses = List .of (
290+ new Clause (COLUMN_SCHEMA_NAME , schemaPattern ),
291+ new Clause (COLUMN_FUNCTION_NAME , functionNamePattern ));
292+ String queryText = GET_FUNCTIONS_FRAGMENT_6
293+ + (Clause .anyCondition (clauses ) ? "\n and " + Clause .conjunction (clauses ) : "" )
294+ + GET_FUNCTIONS_ORDER_BY_6 ;
295+ return new MetadataQuery (queryText , Clause .parameters (clauses ));
296+ }
297+
298+ }
299+
300+ private static final class FB6CatalogAsPackage extends GetFunctions {
301+
302+ private static final String GET_FUNCTIONS_FRAGMENT_6_W_PKG = """
303+ select
304+ coalesce(trim(trailing from RDB$PACKAGE_NAME), '') as FUNCTION_CAT,
305+ trim(trailing from RDB$SCHEMA_NAME) as FUNCTION_SCHEM,
306+ trim(trailing from RDB$FUNCTION_NAME) as FUNCTION_NAME,
307+ RDB$DESCRIPTION as REMARKS,
308+ RDB$FUNCTION_SOURCE as JB_FUNCTION_SOURCE,
309+ case
310+ when RDB$LEGACY_FLAG = 1 then 'UDF'
311+ when RDB$ENGINE_NAME is not null then 'UDR'
312+ else 'PSQL'
313+ end as JB_FUNCTION_KIND,
314+ trim(trailing from RDB$MODULE_NAME) as JB_MODULE_NAME,
315+ trim(trailing from RDB$ENTRYPOINT) as JB_ENTRYPOINT,
316+ trim(trailing from RDB$ENGINE_NAME) as JB_ENGINE_NAME
317+ from SYSTEM.RDB$FUNCTIONS""" ;
318+
319+ private static final String GET_FUNCTIONS_ORDER_BY_6_W_PKG =
320+ "\n order by RDB$PACKAGE_NAME nulls first, RDB$SCHEMA_NAME, RDB$FUNCTION_NAME" ;
321+
322+ private FB6CatalogAsPackage (DbMetadataMediator mediator ) {
323+ super (mediator );
324+ }
325+
326+ private static GetFunctions createInstance (DbMetadataMediator mediator ) {
327+ return new FB6CatalogAsPackage (mediator );
328+ }
329+
330+ @ Override
331+ MetadataQuery createGetFunctionsQuery (String catalog , String schemaPattern , String functionNamePattern ) {
332+ var clauses = new ArrayList <Clause >(3 );
333+ clauses .add (new Clause (COLUMN_SCHEMA_NAME , schemaPattern ));
334+ if (catalog != null ) {
335+ // To quote from the JDBC API: "" retrieves those without a catalog; null means that the catalog name
336+ // should not be used to narrow the search
337+ if (catalog .isEmpty ()) {
338+ clauses .add (Clause .isNullClause (COLUMN_CATALOG_NAME ));
339+ } else {
340+ // Exact matches only
341+ clauses .add (Clause .equalsClause (COLUMN_CATALOG_NAME , catalog ));
342+ }
343+ }
344+ clauses .add (new Clause (COLUMN_FUNCTION_NAME , functionNamePattern ));
345+ String sql = GET_FUNCTIONS_FRAGMENT_6_W_PKG
346+ + (Clause .anyCondition (clauses ) ? "\n where " + Clause .conjunction (clauses ) : "" )
347+ + GET_FUNCTIONS_ORDER_BY_6_W_PKG ;
348+ return new MetadataQuery (sql , Clause .parameters (clauses ));
349+ }
350+
351+ }
352+
247353}
0 commit comments