Skip to content

Schema support #883

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 43 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
9a084f6
#882 jdp-2025-06: Schema Support
mrotteveel Jun 18, 2025
0e56945
#882 Fix tests failing due to Firebird 6 schemas
mrotteveel Jun 18, 2025
b6821ee
#882 Implement to DatabaseMetaData.getSchemas return schemas
mrotteveel Jun 19, 2025
c6010b9
#882 Implement DatabaseMetaData informational methods to report schem…
mrotteveel Jun 19, 2025
83f77cc
Use isNullOrEmpty
mrotteveel Jun 19, 2025
eaffb6a
#882 Schema support for DatabaseMetaData.getProcedures
mrotteveel Jun 19, 2025
0f80c35
#882 Use SYSTEM unquoted due to dialect 1 support
mrotteveel Jun 19, 2025
63a073b
#882 Schema support for getBestRowIdentifier
mrotteveel Jun 21, 2025
c6c90ed
#882 Schema support for getColumnPrivileges
mrotteveel Jun 21, 2025
b000eab
#882 Schema support for getColumns
mrotteveel Jun 21, 2025
794ae4d
#882 Schema support for getImportedKeys/getExportedKeys/getCrossRefer…
mrotteveel Jun 21, 2025
f62e5d7
#882 Schema support for getFunctionColumns
mrotteveel Jun 22, 2025
2c63561
#882 Schema support for getFunctions
mrotteveel Jun 22, 2025
286f725
#882 Schema support for getIndexInfo
mrotteveel Jun 22, 2025
5329dad
#882 Schema support for getPrimaryKeys
mrotteveel Jun 22, 2025
5f3cb52
#882 Schema support for getProcedureColumns
mrotteveel Jun 22, 2025
647747f
#882 Schema support for getPseudoColumns
mrotteveel Jun 22, 2025
dea3c1a
#882 Schema support for getTablePrivileges
mrotteveel Jun 23, 2025
cd94e5e
#882 Schema support for getTables
mrotteveel Jun 23, 2025
044ae4b
#882 Schema support for getCatalogs
mrotteveel Jun 23, 2025
de4b2b7
Mark metadata classes sealed or final
mrotteveel Jun 23, 2025
955e37e
#882 Schema support for getXXXSourceCode
mrotteveel Jun 23, 2025
54d0713
Add ifSchemaElse also to FirebirdSupportInfo
mrotteveel Jun 23, 2025
08aac59
#882 Client props, xid detection
mrotteveel Jun 24, 2025
b4acae2
#882 Retrieve schema name in column information
mrotteveel Jun 24, 2025
5a4c29b
#882 Incomplete change to selectable procedure detection
mrotteveel Jun 24, 2025
3c8bb6f
#882 Misc wording of jdp-2025-06
mrotteveel Jun 24, 2025
73c5783
#882 Define connection property searchPath
mrotteveel Jun 24, 2025
7a012ae
Add supportInfoFor(FirebirdConnection) to avoid wrapper check
mrotteveel Jun 25, 2025
4d3da5b
Tighten up StoredProcedureMetaData, use support info
mrotteveel Jun 25, 2025
697c02c
#882 Implement Connection.get/setSchema
mrotteveel Jul 9, 2025
f9332d6
#882 Additional schema tests for FBResultSetMetaData
mrotteveel Jul 12, 2025
18596d9
#822 Modify FBRowUpdater to search for schema
mrotteveel Jul 13, 2025
6c6f18f
Deduplicate strings in StatementInfoProcessor
mrotteveel Jul 13, 2025
047c686
Add some todo for later
mrotteveel Jul 14, 2025
3317477
#822 Improve schema test coverage
mrotteveel Jul 17, 2025
00245b0
#822 Improve schema test coverage (getFunctions)
mrotteveel Jul 17, 2025
c3ea491
Replace QualifiedName with ObjectReference
mrotteveel Jul 18, 2025
5350741
Add schema support assumption to test
mrotteveel Jul 18, 2025
804dcf2
#822 Improve schema test coverage (getProcedures/getProcedureColumns)
mrotteveel Jul 22, 2025
f598527
#822 Improve schema test coverage (getIndexInfo/getPrimaryKeys)
mrotteveel Jul 22, 2025
cf4ac9c
#822 Improve schema test coverage (getPseudoColumns/getTables)
mrotteveel Jul 22, 2025
b2ab3d4
#822 Improve schema test coverage (getTablePrivileges)
mrotteveel Jul 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions devdoc/jdp/jdp-2025-06-schema-support.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
= jdp-2025-06: Schema Support

// SPDX-FileCopyrightText: Copyright 2025 Mark Rotteveel
// SPDX-License-Identifier: LicenseRef-PDL-1.0

== Status

* Draft
* Proposed for: Jaybird 7, potential backport to Jaybird 6 and/or Jaybird 5

== Type

* Feature-Specification

== Context

Firebird 6.0 introduces support for schemas.
To quote from `README.schemas.md` (of an early snapshot):

____
Firebird 6.0 introduces support for schemas in the database.
Schemas are not an optional feature, so every Firebird 6 database has at least a `SYSTEM` schema, reserved for Firebird system objects (`RDB$*` and `MON$*`).

User objects live in different schemas, which may be the automatically created `PUBLIC` schema or user-defined ones.
It is not allowed (except for indexes) to create or modify objects in the `SYSTEM` schema.
____

Important details related to schemas:

* Search path, defaults to `PUBLIC, SYSTEM`.
The session default can be configured with `isc_dpb_search_path` (string DPB item).
The current search path can be altered with `SET SEARCH_PATH TO ...`.
`ALTER SESSION RESET` reverts to the session default.
* If `SYSTEM` is not on the search path, it is automatically searched last
* The "`current`" schema cannot be set separately;
the first valid (i.e. existing) schema listed in the search path is considered the current schema.
* `CURRENT_SCHEMA` and `RDB$GET_CONTEXT('SYSTEM', 'CURRENT_SCHEMA')` return the first valid schema from the search path
* `RDB$GET_CONTEXT('SYSTEM', 'SEARCH_PATH')` returns the current search path
* Objects not qualified with a schema name will be resolved using the current search path.
This is done -- with some exceptions -- at prepare time.
* TPB has new item `isc_tpb_lock_table_schema` to specify the schema of a table to be locked (1 byte length + string data)
* Gbak has additional options to include/exclude (skip) schema data in backup or restore, similar to existing options to include/exclude tables
* Gstat has additional options to specify a schema for operations involving tables
* For validation, `val_sch_incl` and `val_sch_excl` (I don't think we use the equivalent,`val_tab_incl`/`val_tab_excl` in Jaybird, so might not be relevant)

JDBC defines various methods, parameters, and return values or result set columns that are related to schemas.

Jaybird 5 is the "`long-term support`" version for Java 8.

[NOTE]
====
This document is in flux, and will be updated during implementation of the feature.
====

== Decision

Jaybird 7 will implement schema support for Firebird 6.0.
When Jaybird 7 is used on Firebird 5.0 or older, it will behave as before (no schemas at all).

Further details can be found in <<consequences>>.

Decision on backport to Jaybird 6 and/or Jaybird 5 is pending, and may be the subject of a separate JDP.

[#consequences]
== Consequences

The following changes are made to Jaybird to support schemas when connecting to Firebird 6.0 or higher:

* Connection property `searchPath` (alias `search_path`, `isc_dpb_search_path`) to configure the default session search path.
+
On Firebird 5.0 and older, this will be silently ignored.
* In internal queries in Jaybird, and fully qualified object names, we'll use the regular -- unquoted -- identifier `SYSTEM`, even though `SYSTEM` is a SQL:2023 reserved word, to preserve dialect 1 compatibility.
* `Connection.getSchema()` will return the result of `select CURRENT_SCHEMA from SYSTEM.RDB$DATABASE`;
the connection will not store this value
* `Connection.setSchema(String)` will query the current search path, if not previously called, it will prepend the schema name to the search path, otherwise it will _replace_ the previously prepended schema name.
The schema name is stored _only_ for this replacement operation (i.e. it will not be returned by `getSchema`!)
+
** The name must match exactly as is stored in the metadata (it is always case-sensitive!)
** Jaybird will take care of quoting, and will always quote on dialect 3
** Existence of the schema is **not** checked, so it is possible the current schema does not change with this operation, as `CURRENT_SCHEMA` reports the first _valid_ schema
** JDBC specifies that "`__Calling ``setSchema`` has no effect on previously created or prepared Statement objects.__`";
Jaybird cannot honour this requirement for plain `Statement`, as schema resolution is on prepare time (which for plain `Statement` is on execute), and not always for `CallableStatement` (as the implementation may delay actual prepare until execution).
* Request `isc_info_sql_relation_schema` after preparing a query, record it in `FieldDescriptor`, and return it were relevant for JDBC (e.g. `ResultSetMetaData.getSchemaName(int)`)
** For Firebird 5.0 and older, we need to ensure that JDBC methods continue to report the correct value (i.e. `""` for schema-less objects)
* A Firebird 6.0 variant of the `DatabaseMetaData` and other internal metadata queries needs to be written to address at least the following things:
** Explicitly qualify metadata tables with `SYSTEM`, so the queries will work even if another schema on the search path contains tables with the same names as the system tables.
** Returning schema names, and qualified object names where relevant (e.g. in `DatabaseMetaData` result sets)
** Include schema names in joins to ensure matching the right objects
** Allow searching for schema or schema pattern as specified in JDBC, or were needed for internal metadata queries
** `getCatalogs`: TODO: Maybe add a custom column with a list of schema names for `useCatalogAsPackage=true`?
* `FirebirdConnection`
** Added method `String getSearchPath()` to obtain the search path as reported by `RBB$GET_CONTEXT('SYSTEM', 'SEARCH_PATH')`, or `null` if schemas are not supported
** Added method `List<String> getSearchPatList()` to obtain the search path as a list of unquoted object names, or empty list if schemas are not supported
* TODO: Define effects for management API
* TODO: Redesign retrieval of selectable procedure information (`StoredProcedureMetaDataFactory`) to be able to find stored procedures by schema
* TODO: Add information to Jaybird manual

Note to self: use `// TODO Add schema support` in places that you identify need to get/improve schema support, while working on schema support elsewhere

[appendix]
== License Notice

The contents of this Documentation are subject to the Public Documentation License Version 1.0 (the “License”);
you may only use this Documentation if you comply with the terms of this License.
A copy of the License is available at https://firebirdsql.org/en/public-documentation-license/.

The Original Documentation is "`jdp-2025-06: Schema Support`".
The Initial Writer of the Original Documentation is Mark Rotteveel, Copyright © 2025.
All Rights Reserved.
(Initial Writer contact(s): mark (at) lawinegevaar (dot) nl).

////
Contributor(s): ______________________________________.
Portions created by ______ are Copyright © _________ [Insert year(s)].
All Rights Reserved.
(Contributor contact(s): ________________ [Insert hyperlink/alias]).
////

The exact file history is recorded in our Git repository;
see https://github.com/FirebirdSQL/jaybird
40 changes: 39 additions & 1 deletion src/docs/asciidoc/release_notes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -508,6 +508,43 @@ Artificial testing on local WiFi with small blobs (200 bytes) shows a 30,000-45,

This optimization was backported to Jaybird 5.0.8 and Jaybird 6.0.2.

[#schemas]
=== Schema support

Firebird 6.0 introduces schemas, and Jaybird 7 provides support for schemas as defined in the JDBC specification.

Changes include:

* Connection property `searchPath` sets the initial search path of the connection.
The search path is the list of schemas that will be searched for schema-bound objects if they are not explicitly qualified with a schema name.
The first _valid_ schema is the current schema of the connection.
+
The value of `searchPath` is a comma-separated list of schema names.
Schema names that are case-sensitive or otherwise non-regular identifiers, must be quoted.
Unknown schema names are ignored.
If `SYSTEM` is not included, the server will automatically add it as the last schema.
* `DatabaseMetaData`
** 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;
use `null` or -- `schemaPattern` only -- `"%"` to match all schemas
** `getCatalogs` -- when `useCatalogAsPackage=true` -- returns all (distinct) package names over all schemas.
Within the limitations and specification of the JDBC API, this method cannot be used to find out which schema(s) contain a specific package name.
// TODO Maybe add a custom column with a list of schema names?
** `getColumnPrivileges` and `getTablePrivileges` received an additional column, `JB_GRANTEE_SCHEMA`, which is non-``null`` for grantees that are schema-bound (e.g. procedures).
+
As this is a non-standard column, we recommend to always retrieve it by name.
** `getProcedureSourceCode`/`getTriggerSourceCode`/`getViewSourceCode` now also have an overload accepting the schema;
the overloads without a `schema` parameter, or `schema` is `null` will return the source code of the first match found.
The `schema` parameter is ignored on Firebird 5.0 and older.
** `getSchemas()` returns all defined schemas
** `getSchemas(String catalog, String schemaPattern)` returns all schemas matching the `LIKE` pattern `schemaPattern`, with the following caveats
*** `catalog` non-empty will return no rows -- even if `useCatalogAsPackage` is `true`;
we recommend to always use `null` for `catalog`
* `ResultSetMetaData`
** `getSchemaName` reports the schema if the column is backed by a table, otherwise empty string (`""`)
* `FirebirdConnection`/`FBConnection`
** Added method `String getSearchPath()` to obtain the search path as reported by `RBB$GET_CONTEXT('SYSTEM', 'SEARCH_PATH')`, or `null` if schemas are not supported
** Added method `List<String> getSearchPatList()` to obtain the search path as a list of unquoted object names, or empty list if schemas are not supported

// TODO add major changes

[#other-fixes-and-changes]
Expand Down Expand Up @@ -648,13 +685,14 @@ If you are confronted with such a change, let us know on {firebird-java}[firebir
* `FbWireOperations`
** The `ProcessAttachCallback` parameter of `authReceiveResponse` was removed, as all implementations did nothing, and since protocol 13, it wasn't only called for the attach response
** Interface `ProcessAttachCallback` was removed
* Interface `StoredProcedureMetaData` was made package-private

[#breaking-changes-unlikely]
=== Unlikely breaking changes

The following changes might cause issues, though we think this is unlikely:

// TODO Document unlikely breaking changes, or remove section
// TODO Document unlikely breaking changes, or comment out this section

[#breaking-changes-for-jaybird-8]
=== Breaking changes for Jaybird 8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,16 @@ public void setMaxBlobCacheSize(int maxBlobCacheSize) {
FirebirdConnectionProperties.super.setMaxBlobCacheSize(maxBlobCacheSize);
}

@Override
public String getSearchPath() {
return FirebirdConnectionProperties.super.getSearchPath();
}

@Override
public void setSearchPath(String searchPath) {
FirebirdConnectionProperties.super.setSearchPath(searchPath);
}

@SuppressWarnings("deprecation")
@Deprecated(since = "5")
@Override
Expand Down
3 changes: 3 additions & 0 deletions src/main/org/firebirdsql/gds/ISCConstants.java
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ public interface ISCConstants {
int isc_info_sql_stmt_timeout_user = 28;
int isc_info_sql_stmt_timeout_run = 29;
int isc_info_sql_stmt_blob_align = 30;
int isc_info_sql_exec_path_blr_bytes = 31;
int isc_info_sql_exec_path_blr_text = 32;
int isc_info_sql_relation_schema = 33;

// SQL information return values

Expand Down
59 changes: 59 additions & 0 deletions src/main/org/firebirdsql/gds/ng/ServerVersionInformation.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,22 @@ public byte[] getStatementInfoRequestItems() {
public byte[] getParameterDescriptionInfoRequestItems() {
return Constants.V_2_0_PARAMETER_INFO.clone();
}
},
/**
* Information for Version 6.0 and higher
*
* @since 7
*/
VERSION_6_0(6, 0) {
@Override
public byte[] getStatementInfoRequestItems() {
return Constants.V_6_0_STATEMENT_INFO.clone();
}

@Override
public byte[] getParameterDescriptionInfoRequestItems() {
return Constants.V_6_0_PARAMETER_INFO.clone();
}
};

private final int majorVersion;
Expand Down Expand Up @@ -213,6 +229,49 @@ private static final class Constants {
isc_info_sql_describe_end
};

static final byte[] V_6_0_STATEMENT_INFO = new byte[] {
isc_info_sql_stmt_type,
isc_info_sql_select,
isc_info_sql_describe_vars,
isc_info_sql_sqlda_seq,
isc_info_sql_type, isc_info_sql_sub_type,
isc_info_sql_scale, isc_info_sql_length,
isc_info_sql_field,
isc_info_sql_alias,
isc_info_sql_relation_schema,
isc_info_sql_relation,
isc_info_sql_relation_alias,
isc_info_sql_owner,
isc_info_sql_describe_end,

isc_info_sql_bind,
isc_info_sql_describe_vars,
isc_info_sql_sqlda_seq,
isc_info_sql_type, isc_info_sql_sub_type,
isc_info_sql_scale, isc_info_sql_length,
// TODO: Information not available in normal queries, check for procedures, otherwise remove
//isc_info_sql_field,
//isc_info_sql_alias,
//isc_info_sql_relation_schema,
//isc_info_sql_relation,
//isc_info_sql_relation_alias,
//isc_info_sql_owner,
isc_info_sql_describe_end
};
static final byte[] V_6_0_PARAMETER_INFO = new byte[] {
isc_info_sql_describe_vars,
isc_info_sql_sqlda_seq,
isc_info_sql_type, isc_info_sql_sub_type,
isc_info_sql_scale, isc_info_sql_length,
isc_info_sql_field,
isc_info_sql_alias,
isc_info_sql_relation_schema,
isc_info_sql_relation,
isc_info_sql_relation_alias,
isc_info_sql_owner,
isc_info_sql_describe_end
};

private Constants() {
// no instances
}
Expand Down
17 changes: 12 additions & 5 deletions src/main/org/firebirdsql/gds/ng/StatementInfoProcessor.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
// SPDX-FileCopyrightText: Copyright 2013-2024 Mark Rotteveel
// SPDX-FileCopyrightText: Copyright 2013-2025 Mark Rotteveel
// SPDX-License-Identifier: LGPL-2.1-or-later
package org.firebirdsql.gds.ng;

import org.firebirdsql.gds.ISCConstants;
import org.firebirdsql.gds.ng.fields.RowDescriptor;
import org.firebirdsql.gds.ng.fields.RowDescriptorBuilder;
import org.firebirdsql.jaybird.util.StringDeduplicator;

import java.sql.SQLException;
import java.util.List;

import static org.firebirdsql.gds.VaxEncoding.iscVaxInteger;
import static org.firebirdsql.gds.VaxEncoding.iscVaxInteger2;
Expand All @@ -21,9 +23,11 @@
public final class StatementInfoProcessor implements InfoProcessor<InfoProcessor.StatementInfo> {

private static final System.Logger log = System.getLogger(StatementInfoProcessor.class.getName());
private static final List<String> DEDUPLICATOR_PRESET = List.of("SYSTEM", "PUBLIC", "SYSDBA");

private final AbstractFbStatement statement;
private final FbDatabase database;
private final StringDeduplicator stringDeduplicator = StringDeduplicator.of(DEDUPLICATOR_PRESET);

/**
* Creates an instance of this class.
Expand Down Expand Up @@ -85,10 +89,8 @@ private void handleTruncatedInfo(final StatementInfo info) throws SQLException {
newInfoItems[newIndex++] = 2; // size of short
newInfoItems[newIndex++] = (byte) (descriptorIndex & 0xFF);
newInfoItems[newIndex++] = (byte) (descriptorIndex >> 8);
newInfoItems[newIndex++] = infoItem;
} else {
newInfoItems[newIndex++] = infoItem;
}
newInfoItems[newIndex++] = infoItem;
}
assert newIndex == newInfoItems.length : "newInfoItems size too long";
// Doubling request buffer up to the maximum
Expand Down Expand Up @@ -166,6 +168,10 @@ private void processDescriptors(final StatementInfo info, final RowDescriptorBui
rdb.setFieldName(readStringValue(info));
break;

case ISCConstants.isc_info_sql_relation_schema:
rdb.setOriginalSchema(readStringValue(info));
break;

case ISCConstants.isc_info_sql_relation:
rdb.setOriginalTableName(readStringValue(info));
break;
Expand Down Expand Up @@ -208,10 +214,11 @@ private int readIntValue(StatementInfo info) {
private String readStringValue(StatementInfo info) {
int len = iscVaxInteger2(info.buffer, info.currentIndex);
info.currentIndex += 2;
if (len == 0) return "";
// TODO Is it correct to use the connection encoding here, or should we use UTF-8 always?
String value = database.getEncoding().decodeFromCharset(info.buffer, info.currentIndex, len);
info.currentIndex += len;
return value;
return stringDeduplicator.get(value);
}

/**
Expand Down
Loading