Skip to content

Commit 2d73767

Browse files
committed
#882 Schema support for getTablePrivileges
Add extra column JB_GRANTEE_SCHEMA to getTablePrivileges and getColumnPrivileges
1 parent 647747f commit 2d73767

File tree

6 files changed

+155
-53
lines changed

6 files changed

+155
-53
lines changed

src/docs/asciidoc/release_notes.adoc

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -516,16 +516,15 @@ Firebird 6.0 introduces schemas, and Jaybird 7 provides support for schemas as d
516516
Changes include:
517517

518518
* `DatabaseMetaData`
519-
** `getProcedures` now uses the `schemaPattern` to filter by schema, with the following caveats
520-
*** `schemaPattern` empty will return no rows on Firebird 6.0 and higher (all procedures are in a schema);
521-
use `null` or `"%"` to match all schemas
522-
*** `catalog` is (still) ignored if `useCatalogAsPackage` is `false`
519+
** Methods accepting a `schema` (exact match if not `null`) or `schemaPattern` (`LIKE` match if not `null`) will return no rows for value empty (`""`) on Firebird 6.0 and higher;
520+
use `null` or -- `schemaPattern` only -- `"%"` to match all schemas
523521
** `getSchemas()` returns all defined schemas
524522
** `getSchemas(String catalog, String schemaPattern)` returns all schemas matching the `LIKE` pattern `schemaPattern`, with the following caveats
525523
*** `catalog` non-empty will return no rows;
526524
we recommend to always use `null` for `catalog`
527-
*** `schemaPattern` empty will return no rows (there are no schemas with an empty name);
528-
use `null` or `"%"` to match all schemas
525+
** `getColumnPrivileges` and `getTablePrivileges` received an additional column, `JB_GRANTEE_SCHEMA`, which is non-``null`` for grantees that are schema-bound (e.g. a procedure).
526+
+
527+
As this is a non-standard column, we recommend to always retrieve it by name.
529528
530529
// TODO add major changes
531530

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

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1327,10 +1327,12 @@ public ResultSet getColumns(String catalog, String schemaPattern, String tableNa
13271327
* {@inheritDoc}
13281328
*
13291329
* <p>
1330-
* Jaybird defines an additional column:
1330+
* Jaybird defines these additional columns:
13311331
* <ol start="9">
13321332
* <li><b>JB_GRANTEE_TYPE</b> String =&gt; Object type of {@code GRANTEE} (<b>NOTE: Jaybird specific column;
13331333
* retrieve by name!</b>).</li>
1334+
* <li><b>JB_GRANTEE_SCHEMA</b> String =&gt; Schema of {@code GRANTEE} if it's a schema-bound object (<b>NOTE:
1335+
* Jaybird specific column; retrieve by name!</b>).</li>
13341336
* </ol>
13351337
* </p>
13361338
* <p>
@@ -1358,10 +1360,12 @@ public ResultSet getColumnPrivileges(String catalog, String schema, String table
13581360
* {@inheritDoc}
13591361
*
13601362
* <p>
1361-
* Jaybird defines an additional column:
1363+
* Jaybird defines these additional columns:
13621364
* <ol start="8">
13631365
* <li><b>JB_GRANTEE_TYPE</b> String =&gt; Object type of {@code GRANTEE} (<b>NOTE: Jaybird specific column;
13641366
* retrieve by name!</b>).</li>
1367+
* <li><b>JB_GRANTEE_SCHEMA</b> String =&gt; Schema of {@code GRANTEE} if it's a schema-bound object (<b>NOTE:
1368+
* Jaybird specific column; retrieve by name!</b>).</li>
13651369
* </ol>
13661370
* </p>
13671371
* <p>
@@ -1373,7 +1377,7 @@ public ResultSet getColumnPrivileges(String catalog, String schema, String table
13731377
@Override
13741378
public ResultSet getTablePrivileges(String catalog, String schemaPattern, String tableNamePattern)
13751379
throws SQLException {
1376-
return GetTablePrivileges.create(getDbMetadataMediator()).getTablePrivileges(tableNamePattern);
1380+
return GetTablePrivileges.create(getDbMetadataMediator()).getTablePrivileges(schemaPattern, tableNamePattern);
13771381
}
13781382

13791383
/**

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
public abstract class GetColumnPrivileges extends AbstractMetadataMethod {
3434

3535
private static final String COLUMNPRIV = "COLUMNPRIV";
36-
private static final RowDescriptor ROW_DESCRIPTOR = DbMetadataMediator.newRowDescriptorBuilder(9)
36+
private static final RowDescriptor ROW_DESCRIPTOR = DbMetadataMediator.newRowDescriptorBuilder(10)
3737
.at(0).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "TABLE_CAT", COLUMNPRIV).addField()
3838
.at(1).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "TABLE_SCHEM", COLUMNPRIV).addField()
3939
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_NAME", COLUMNPRIV).addField()
@@ -43,6 +43,7 @@ public abstract class GetColumnPrivileges extends AbstractMetadataMethod {
4343
.at(6).simple(SQL_VARYING, 31, "PRIVILEGE", COLUMNPRIV).addField()
4444
.at(7).simple(SQL_VARYING, 3, "IS_GRANTABLE", COLUMNPRIV).addField()
4545
.at(8).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "JB_GRANTEE_TYPE", COLUMNPRIV).addField()
46+
.at(9).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "JB_GRANTEE_SCHEMA", COLUMNPRIV).addField()
4647
.toRowDescriptor();
4748

4849
GetColumnPrivileges(DbMetadataMediator mediator) {
@@ -74,6 +75,7 @@ RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) throws SQ
7475
.at(6).setString(mapPrivilege(rs.getString("PRIVILEGE")))
7576
.at(7).setString(rs.getBoolean("IS_GRANTABLE") ? "YES" : "NO")
7677
.at(8).setString(rs.getString("JB_GRANTEE_TYPE"))
78+
.at(9).setString(rs.getString("JB_GRANTEE_SCHEMA"))
7779
.toRowValue(false);
7880
}
7981

@@ -100,6 +102,7 @@ private static final class FB5 extends GetColumnPrivileges {
100102
UP.RDB$PRIVILEGE as PRIVILEGE,
101103
UP.RDB$GRANT_OPTION as IS_GRANTABLE,
102104
T.RDB$TYPE_NAME as JB_GRANTEE_TYPE
105+
cast(null as char(1)) as JB_GRANTEE_SCHEMA
103106
from RDB$RELATION_FIELDS RF
104107
inner join RDB$USER_PRIVILEGES UP
105108
on UP.RDB$RELATION_NAME = RF.RDB$RELATION_NAME
@@ -149,7 +152,8 @@ private static final class FB6 extends GetColumnPrivileges {
149152
trim(trailing from UP.RDB$USER) as GRANTEE,
150153
UP.RDB$PRIVILEGE as PRIVILEGE,
151154
UP.RDB$GRANT_OPTION as IS_GRANTABLE,
152-
T.RDB$TYPE_NAME as JB_GRANTEE_TYPE
155+
trim(trailing from T.RDB$TYPE_NAME) as JB_GRANTEE_TYPE,
156+
trim(trailing from UP.RDB$USER_SCHEMA_NAME) as JB_GRANTEE_SCHEMA
153157
from SYSTEM.RDB$RELATION_FIELDS RF
154158
inner join SYSTEM.RDB$USER_PRIVILEGES UP
155159
on UP.RDB$RELATION_SCHEMA_NAME = RF.RDB$SCHEMA_NAME and UP.RDB$RELATION_NAME = RF.RDB$RELATION_NAME
@@ -162,7 +166,8 @@ private static final class FB6 extends GetColumnPrivileges {
162166

163167
// NOTE: Sort by user and schema is not defined in JDBC, but we do this to ensure a consistent order for tests
164168
private static final String GET_COLUMN_PRIVILEGES_END_6 =
165-
"\norder by RF.RDB$FIELD_NAME, UP.RDB$PRIVILEGE, UP.RDB$USER, RF.RDB$SCHEMA_NAME";
169+
"\norder by RF.RDB$FIELD_NAME, UP.RDB$PRIVILEGE, UP.RDB$USER_SCHEMA_NAME nulls first, UP.RDB$USER, "
170+
+ "RF.RDB$SCHEMA_NAME";
166171

167172
private FB6(DbMetadataMediator mediator) {
168173
super(mediator);
Lines changed: 112 additions & 36 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

@@ -10,6 +10,7 @@
1010

1111
import java.sql.ResultSet;
1212
import java.sql.SQLException;
13+
import java.util.List;
1314

1415
import static org.firebirdsql.gds.ISCConstants.SQL_VARYING;
1516
import static org.firebirdsql.jdbc.metadata.FbMetadataConstants.OBJECT_NAME_LENGTH;
@@ -27,11 +28,11 @@
2728
* @author Mark Rotteveel
2829
* @since 5
2930
*/
30-
public final class GetTablePrivileges extends AbstractMetadataMethod {
31+
public abstract sealed class GetTablePrivileges extends AbstractMetadataMethod {
3132

3233
private static final String TABLEPRIV = "TABLEPRIV";
3334

34-
private static final RowDescriptor ROW_DESCRIPTOR = DbMetadataMediator.newRowDescriptorBuilder(8)
35+
private static final RowDescriptor ROW_DESCRIPTOR = DbMetadataMediator.newRowDescriptorBuilder(9)
3536
.at(0).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "TABLE_CAT", TABLEPRIV).addField()
3637
.at(1).simple(SQL_VARYING | 1, OBJECT_NAME_LENGTH, "TABLE_SCHEM", TABLEPRIV).addField()
3738
.at(2).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "TABLE_NAME", TABLEPRIV).addField()
@@ -40,63 +41,138 @@ public final class GetTablePrivileges extends AbstractMetadataMethod {
4041
.at(5).simple(SQL_VARYING, 31, "PRIVILEGE", TABLEPRIV).addField()
4142
.at(6).simple(SQL_VARYING, 3, "IS_GRANTABLE", TABLEPRIV).addField()
4243
.at(7).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "JB_GRANTEE_TYPE", TABLEPRIV).addField()
44+
.at(8).simple(SQL_VARYING, OBJECT_NAME_LENGTH, "JB_GRANTEE_SCHEMA", TABLEPRIV).addField()
4345
.toRowDescriptor();
4446

45-
//@formatter:off
46-
private static final String GET_TABLE_PRIVILEGES_START =
47-
// Distinct is needed as we're selecting privileges for the table and columns of the table
48-
"select distinct\n"
49-
+ " UP.RDB$RELATION_NAME as TABLE_NAME,\n"
50-
+ " UP.RDB$GRANTOR as GRANTOR,\n"
51-
+ " UP.RDB$USER as GRANTEE,\n"
52-
+ " UP.RDB$PRIVILEGE as PRIVILEGE,\n"
53-
+ " UP.RDB$GRANT_OPTION as IS_GRANTABLE,\n"
54-
+ " T.RDB$TYPE_NAME as JB_GRANTEE_TYPE\n"
55-
+ "from RDB$USER_PRIVILEGES UP\n"
56-
+ "left join RDB$TYPES T\n"
57-
+ " on T.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE' and T.RDB$TYPE = UP.RDB$USER_TYPE \n"
58-
// Other privileges don't make sense for table privileges
59-
// TODO Consider including ALTER/DROP privileges
60-
+ "where UP.RDB$PRIVILEGE in ('A', 'D', 'I', 'R', 'S', 'U')\n"
61-
// Only tables and views
62-
+ "and UP.RDB$OBJECT_TYPE in (0, 1)\n";
63-
64-
// NOTE: Sort by user is not defined in JDBC, but we do this to ensure a consistent order for tests
65-
private static final String GET_TABLE_PRIVILEGES_END = "order by RDB$RELATION_NAME, RDB$PRIVILEGE, RDB$USER";
66-
//@formatter:on
67-
6847
private GetTablePrivileges(DbMetadataMediator mediator) {
6948
super(ROW_DESCRIPTOR, mediator);
7049
}
7150

72-
public ResultSet getTablePrivileges(String tableNamePattern) throws SQLException {
51+
public final ResultSet getTablePrivileges(String schemaPattern, String tableNamePattern) throws SQLException {
7352
if ("".equals(tableNamePattern)) {
7453
return createEmpty();
7554
}
76-
Clause tableClause = new Clause("RDB$RELATION_NAME", tableNamePattern);
77-
78-
String sql = GET_TABLE_PRIVILEGES_START
79-
+ tableClause.getCondition("and ", "\n")
80-
+ GET_TABLE_PRIVILEGES_END;
81-
MetadataQuery metadataQuery = new MetadataQuery(sql, Clause.parameters(tableClause));
55+
MetadataQuery metadataQuery = createGetTablePrivilegesQuery(schemaPattern, tableNamePattern);
8256
return createMetaDataResultSet(metadataQuery);
8357
}
8458

59+
abstract MetadataQuery createGetTablePrivilegesQuery(String schemaPattern, String tableNamePattern);
60+
8561
@Override
8662
RowValue createMetadataRow(ResultSet rs, RowValueBuilder valueBuilder) throws SQLException {
8763
return valueBuilder
8864
.at(0).set(null)
89-
.at(1).set(null)
65+
.at(1).setString(rs.getString("TABLE_SCHEM"))
9066
.at(2).setString(rs.getString("TABLE_NAME"))
9167
.at(3).setString(rs.getString("GRANTOR"))
9268
.at(4).setString(rs.getString("GRANTEE"))
9369
.at(5).setString(mapPrivilege(rs.getString("PRIVILEGE")))
9470
.at(6).setString(rs.getBoolean("IS_GRANTABLE") ? "YES" : "NO")
9571
.at(7).setString(rs.getString("JB_GRANTEE_TYPE"))
72+
.at(8).setString(rs.getString("JB_GRANTEE_SCHEMA"))
9673
.toRowValue(false);
9774
}
9875

9976
public static GetTablePrivileges create(DbMetadataMediator mediator) {
100-
return new GetTablePrivileges(mediator);
77+
// NOTE: Indirection through static method prevents unnecessary classloading
78+
if (mediator.getFirebirdSupportInfo().isVersionEqualOrAbove(6)) {
79+
return FB6.createInstance(mediator);
80+
} else {
81+
return FB5.createInstance(mediator);
82+
}
83+
}
84+
85+
/**
86+
* Implementation for Firebird 5.0 and older.
87+
*/
88+
private static final class FB5 extends GetTablePrivileges {
89+
90+
// Distinct is needed as we're selecting privileges for the table and columns of the table
91+
private static final String GET_TABLE_PRIVILEGES_START_5 = """
92+
select distinct
93+
cast(null as char(1)) as TABLE_SCHEM,
94+
UP.RDB$RELATION_NAME as TABLE_NAME,
95+
UP.RDB$GRANTOR as GRANTOR,
96+
UP.RDB$USER as GRANTEE,
97+
UP.RDB$PRIVILEGE as PRIVILEGE,
98+
UP.RDB$GRANT_OPTION as IS_GRANTABLE,
99+
T.RDB$TYPE_NAME as JB_GRANTEE_TYPE,
100+
cast(null as char(1)) as JB_GRANTEE_SCHEMA
101+
from RDB$USER_PRIVILEGES UP
102+
left join RDB$TYPES T
103+
on T.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE' and T.RDB$TYPE = UP.RDB$USER_TYPE
104+
where UP.RDB$PRIVILEGE in ('A', 'D', 'I', 'R', 'S', 'U') -- privileges relevant for tables
105+
and UP.RDB$OBJECT_TYPE in (0, 1) -- Only tables and views""";
106+
107+
// NOTE: Sort by user is not defined in JDBC, but we do this to ensure a consistent order for tests
108+
private static final String GET_TABLE_PRIVILEGES_END_5 =
109+
"\norder by UP.RDB$RELATION_NAME, UP.RDB$PRIVILEGE, UP.RDB$USER";
110+
111+
private FB5(DbMetadataMediator mediator) {
112+
super(mediator);
113+
}
114+
115+
private static GetTablePrivileges createInstance(DbMetadataMediator mediator) {
116+
return new FB5(mediator);
117+
}
118+
119+
@Override
120+
MetadataQuery createGetTablePrivilegesQuery(String schemaPattern, String tableNamePattern) {
121+
Clause tableClause = new Clause("UP.RDB$RELATION_NAME", tableNamePattern);
122+
String sql = GET_TABLE_PRIVILEGES_START_5
123+
+ tableClause.getCondition("\nand ", "")
124+
+ GET_TABLE_PRIVILEGES_END_5;
125+
return new MetadataQuery(sql, Clause.parameters(tableClause));
126+
}
127+
128+
}
129+
130+
/**
131+
* Implementation for Firebird 6.0 and newer.
132+
*/
133+
private static final class FB6 extends GetTablePrivileges {
134+
135+
// Distinct is needed as we're selecting privileges for the table and columns of the table
136+
private static final String GET_TABLE_PRIVILEGES_START_6 = """
137+
select distinct
138+
trim(trailing from UP.RDB$RELATION_SCHEMA_NAME) as TABLE_SCHEM,
139+
trim(trailing from UP.RDB$RELATION_NAME) as TABLE_NAME,
140+
trim(trailing from UP.RDB$GRANTOR) as GRANTOR,
141+
trim(trailing from UP.RDB$USER) as GRANTEE,
142+
UP.RDB$PRIVILEGE as PRIVILEGE,
143+
UP.RDB$GRANT_OPTION as IS_GRANTABLE,
144+
trim(trailing from T.RDB$TYPE_NAME) as JB_GRANTEE_TYPE,
145+
trim(trailing from UP.RDB$USER_SCHEMA_NAME) as JB_GRANTEE_SCHEMA
146+
from SYSTEM.RDB$USER_PRIVILEGES UP
147+
left join SYSTEM.RDB$TYPES T
148+
on T.RDB$FIELD_NAME = 'RDB$OBJECT_TYPE' and T.RDB$TYPE = UP.RDB$USER_TYPE
149+
where UP.RDB$PRIVILEGE in ('A', 'D', 'I', 'R', 'S', 'U') -- privileges relevant for tables
150+
and UP.RDB$OBJECT_TYPE in (0, 1) -- Only tables and views""";
151+
152+
// NOTE: Sort by user schema and user is not defined in JDBC, but we do this to ensure a consistent order for tests
153+
private static final String GET_TABLE_PRIVILEGES_END_6 =
154+
"\norder by UP.RDB$RELATION_SCHEMA_NAME, UP.RDB$RELATION_NAME, UP.RDB$PRIVILEGE, "
155+
+ "UP.RDB$USER_SCHEMA_NAME nulls first, UP.RDB$USER";
156+
157+
private FB6(DbMetadataMediator mediator) {
158+
super(mediator);
159+
}
160+
161+
private static GetTablePrivileges createInstance(DbMetadataMediator mediator) {
162+
return new FB6(mediator);
163+
}
164+
165+
@Override
166+
MetadataQuery createGetTablePrivilegesQuery(String schemaPattern, String tableNamePattern) {
167+
var clauses = List.of(
168+
new Clause("UP.RDB$RELATION_SCHEMA_NAME", schemaPattern),
169+
new Clause("UP.RDB$RELATION_NAME", tableNamePattern));
170+
String sql = GET_TABLE_PRIVILEGES_START_6
171+
+ (Clause.anyCondition(clauses) ? "\nand " + Clause.conjunction(clauses) : "")
172+
+ GET_TABLE_PRIVILEGES_END_6;
173+
return new MetadataQuery(sql, Clause.parameters(clauses));
174+
}
175+
101176
}
177+
102178
}

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ private void validateExpectedColumnPrivileges(String schema, String table, Strin
231231
defaults.put(ColumnPrivilegesMetadata.TABLE_SCHEM, ifSchemaElse("PUBLIC", null));
232232
defaults.put(ColumnPrivilegesMetadata.GRANTOR, SYSDBA);
233233
defaults.put(ColumnPrivilegesMetadata.JB_GRANTEE_TYPE, "USER");
234+
defaults.put(ColumnPrivilegesMetadata.JB_GRANTEE_SCHEMA, null);
234235

235236
DEFAULT_COLUMN_PRIVILEGES_VALUES = Collections.unmodifiableMap(defaults);
236237
}
@@ -248,7 +249,8 @@ private enum ColumnPrivilegesMetadata implements MetaDataInfo {
248249
GRANTEE(6),
249250
PRIVILEGE(7),
250251
IS_GRANTABLE(8),
251-
JB_GRANTEE_TYPE(9);
252+
JB_GRANTEE_TYPE(9),
253+
JB_GRANTEE_SCHEMA(10);
252254

253255
private final int position;
254256

0 commit comments

Comments
 (0)