Skip to content

Commit 2c63561

Browse files
committed
#882 Schema support for getFunctions
1 parent f62e5d7 commit 2c63561

File tree

4 files changed

+136
-25
lines changed

4 files changed

+136
-25
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1780,7 +1780,7 @@ public ResultSet getFunctionColumns(String catalog, String schemaPattern, String
17801780
@Override
17811781
public ResultSet getFunctions(String catalog, String schemaPattern, String functionNamePattern)
17821782
throws SQLException {
1783-
return GetFunctions.create(getDbMetadataMediator()).getFunctions(catalog, functionNamePattern);
1783+
return GetFunctions.create(getDbMetadataMediator()).getFunctions(catalog, schemaPattern, functionNamePattern);
17841784
}
17851785

17861786
@Override

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

Lines changed: 126 additions & 20 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 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
44
package org.firebirdsql.jdbc.metadata;
55

@@ -13,6 +13,7 @@
1313
import java.sql.ResultSet;
1414
import java.sql.SQLException;
1515
import java.util.ArrayList;
16+
import java.util.List;
1617

1718
import static java.sql.DatabaseMetaData.functionNoTable;
1819
import static org.firebirdsql.gds.ISCConstants.SQL_SHORT;
@@ -30,8 +31,10 @@
3031
public 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("\nwhere ", "")
@@ -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("\nand ", "")
@@ -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
"\norder 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-
? "\nwhere " + Clause.conjunction(clauses)
241-
: "")
249+
+ (Clause.anyCondition(clauses) ? "\nwhere " + 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+
"\norder 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) ? "\nand " + 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+
"\norder 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) ? "\nwhere " + Clause.conjunction(clauses) : "")
347+
+ GET_FUNCTIONS_ORDER_BY_6_W_PKG;
348+
return new MetadataQuery(sql, Clause.parameters(clauses));
349+
}
350+
351+
}
352+
247353
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
*/
3737
class FBDatabaseMetaDataFunctionColumnsTest {
3838

39+
// TODO Add schema support: tests involving other schema
40+
3941
private static final String PSQL_EXAMPLE_1 = "PSQL$EXAMPLE$1";
4042
private static final String PSQL_EXAMPLE_2 = "PSQL$EXAMPLE$2";
4143
private static final String UDF_EXAMPLE_1 = "UDF$EXAMPLE$1";

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static org.firebirdsql.common.FBTestProperties.getDefaultPropertiesForConnection;
2525
import static org.firebirdsql.common.FBTestProperties.getDefaultSupportInfo;
2626
import static org.firebirdsql.common.FBTestProperties.getUrl;
27+
import static org.firebirdsql.common.FBTestProperties.ifSchemaElse;
2728
import static org.junit.jupiter.api.Assertions.assertFalse;
2829
import static org.junit.jupiter.api.Assertions.assertTrue;
2930
import static org.junit.jupiter.api.Assertions.fail;
@@ -36,6 +37,8 @@
3637
*/
3738
class FBDatabaseMetaDataFunctionsTest {
3839

40+
// TODO Add schema support: tests involving other schema
41+
3942
private static final String CREATE_UDF_EXAMPLE = """
4043
declare external function UDF$EXAMPLE
4144
int by descriptor, int by descriptor
@@ -274,7 +277,7 @@ private void validatePsqlExample(ResultSet functions, boolean useCatalogAsPackag
274277
rules.put(FunctionMetaData.FUNCTION_CAT, "");
275278
}
276279
rules.put(FunctionMetaData.FUNCTION_NAME, "PSQL$EXAMPLE");
277-
rules.put(FunctionMetaData.SPECIFIC_NAME, "PSQL$EXAMPLE");
280+
rules.put(FunctionMetaData.SPECIFIC_NAME, ifSchemaElse("\"PUBLIC\".\"PSQL$EXAMPLE\"", "PSQL$EXAMPLE"));
278281
if (supportsComments) {
279282
rules.put(FunctionMetaData.REMARKS, "Comment on PSQL$EXAMPLE");
280283
}
@@ -298,7 +301,7 @@ private void validateUdfExample(ResultSet functions, boolean useCatalogAsPackage
298301
rules.put(FunctionMetaData.FUNCTION_CAT, "");
299302
}
300303
rules.put(FunctionMetaData.FUNCTION_NAME, "UDF$EXAMPLE");
301-
rules.put(FunctionMetaData.SPECIFIC_NAME, "UDF$EXAMPLE");
304+
rules.put(FunctionMetaData.SPECIFIC_NAME, ifSchemaElse("\"PUBLIC\".\"UDF$EXAMPLE\"", "UDF$EXAMPLE"));
302305
if (supportsComments) {
303306
rules.put(FunctionMetaData.REMARKS, "Comment on UDF$EXAMPLE");
304307
}
@@ -312,7 +315,7 @@ private void validatePackageFunctionExample(ResultSet functions) throws SQLExcep
312315
Map<FunctionMetaData, Object> rules = getDefaultValidationRules();
313316
rules.put(FunctionMetaData.FUNCTION_CAT, "WITH$FUNCTION");
314317
rules.put(FunctionMetaData.FUNCTION_NAME, "IN$PACKAGE");
315-
rules.put(FunctionMetaData.SPECIFIC_NAME, "\"WITH$FUNCTION\".\"IN$PACKAGE\"");
318+
rules.put(FunctionMetaData.SPECIFIC_NAME, ifSchemaElse("\"PUBLIC\".", "") + "\"WITH$FUNCTION\".\"IN$PACKAGE\"");
316319
// Stored with package
317320
rules.put(FunctionMetaData.JB_FUNCTION_SOURCE, null);
318321
rules.put(FunctionMetaData.JB_FUNCTION_KIND, "PSQL");
@@ -351,7 +354,7 @@ class Ignored {
351354
static {
352355
Map<FunctionMetaData, Object> defaults = new EnumMap<>(FunctionMetaData.class);
353356
defaults.put(FunctionMetaData.FUNCTION_CAT, null);
354-
defaults.put(FunctionMetaData.FUNCTION_SCHEM, null);
357+
defaults.put(FunctionMetaData.FUNCTION_SCHEM, ifSchemaElse("PUBLIC", null));
355358
defaults.put(FunctionMetaData.REMARKS, null);
356359
defaults.put(FunctionMetaData.FUNCTION_TYPE, (short) DatabaseMetaData.functionNoTable);
357360
defaults.put(FunctionMetaData.JB_FUNCTION_SOURCE, null);

0 commit comments

Comments
 (0)