Skip to content

Commit d6f4fdb

Browse files
committed
#908 JDBC 4.5: Firebird-specific implementation of various enquote methods on Connection
1 parent fc94a52 commit d6f4fdb

18 files changed

Lines changed: 680 additions & 146 deletions

src/docs/asciidoc/release_notes.adoc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,13 @@ The following was fixed or changed since Jaybird 6.0.4:
4141
+
4242
Applications referencing this constant may need to be recompiled to function correctly with Jaybird 6.0.5 and higher.
4343
If you use Java 26 or higher, we recommend using `java.sql.Type.DECFLOAT` and `java.sql.JDBCType.DECFLOAT` instead.
44+
* JDBC 4.5 support: implemented methods `enquoteIdentifier`, `enquoteLiteral`, `enquoteNCharLiteral`, and `isSimpleIdentifier` on `FBConnection`, and added them to interface `FirebirdConnection` so for older Java versions (https://github.com/FirebirdSQL/jaybird/issues/918[#918])
45+
+
46+
The methods of the same name in the `java.sql.Statement` implementations now call the methods on the connection.
47+
This results in two minor breaking changes:
48+
+
49+
** Reserved words are no longer considered simple identifiers by `enquoteIdentifier` and `isSimpleIdentifier` and will be quoted (or -- for dialect 1 -- result in a `SQLFeatureNotSupportedException`)
50+
** Presence of the NUL character (U+0000) in an identifier passed to `enquoteIdentifier` will result in a `SQLSyntaxErrorException`
4451

4552
=== Jaybird 6.0.4
4653

@@ -311,6 +318,8 @@ Addressed in Jaybird 6.0.5.
311318
+
312319
The default implementation provided is not sufficient for Firebird, especially not when connecting to a dialect 1 database.
313320
For the time being, we recommend using the equivalent JDBC 4.3 methods (with the same name) on a `java.sql.Statement` instance of the connection.
321+
+
322+
Addressed in Jaybird 6.0.5.
314323
* The JDBC escape to selectively disable escape processing ([.nowrap]`++{\ ... \}++`) is not yet implemented.
315324

316325
We plan to address these issues in Jaybird 5 and Jaybird 6 maintenance releases _after_ the release of Java 26 (tracked by https://github.com/FirebirdSQL/jaybird/issues/907[#907]).

src/main/org/firebirdsql/gds/JaybirdErrorCodes.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,9 @@ public interface JaybirdErrorCodes {
155155
int jb_socketFactoryClassNotFound = 337248347;
156156
int jb_socketFactoryConstructorNotFound = 337248348;
157157
int jb_socketFactoryFailedToCreateSocket = 337248349;
158+
int jb_invalidIdentifierLength = 337248350;
159+
int jb_invalidIdentifierName = 337248351;
160+
int jb_noDelimitedIdentifiersInDialect1 = 337248352;
158161

159162
@SuppressWarnings("unused")
160163
int jb_range_end = 337264639;

src/main/org/firebirdsql/jaybird/parser/FirebirdReservedWords.java

Lines changed: 131 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
package org.firebirdsql.jaybird.parser;
2020

2121
import org.firebirdsql.util.InternalApi;
22+
import org.jspecify.annotations.NullMarked;
2223

2324
import java.util.Arrays;
2425
import java.util.Collection;
@@ -33,12 +34,36 @@
3334
* @author Mark Rotteveel
3435
* @since 5
3536
*/
37+
@NullMarked
3638
@InternalApi
3739
public enum FirebirdReservedWords implements ReservedWords {
3840

39-
// TODO Add other versions if it turns out we do need reserved words per version
40-
41-
FIREBIRD_5_0("ADD", "ADMIN", "ALL", "ALTER", "AND", "ANY", "AS", "AT", "AVG", "BEGIN", "BETWEEN", "BIGINT",
41+
// Order is intentionally from higher versions to lower versions, see of(AbstractVersion) and latest()
42+
FIREBIRD_5_0(5, 0, "ADD", "ADMIN", "ALL", "ALTER", "AND", "ANY", "AS", "AT", "AVG", "BEGIN", "BETWEEN", "BIGINT",
43+
"BINARY", "BIT_LENGTH", "BLOB", "BOOLEAN", "BOTH", "BY", "CASE", "CAST", "CHAR", "CHARACTER",
44+
"CHARACTER_LENGTH", "CHAR_LENGTH", "CHECK", "CLOSE", "COLLATE", "COLUMN", "COMMENT", "COMMIT", "CONNECT",
45+
"CONSTRAINT", "CORR", "COUNT", "COVAR_POP", "COVAR_SAMP", "CREATE", "CROSS", "CURRENT",
46+
"CURRENT_CONNECTION", "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP",
47+
"CURRENT_TRANSACTION", "CURRENT_USER", "CURSOR", "DATE", "DAY", "DEC", "DECFLOAT", "DECIMAL", "DECLARE",
48+
"DEFAULT", "DELETE", "DELETING", "DETERMINISTIC", "DISCONNECT", "DISTINCT", "DOUBLE", "DROP", "ELSE", "END",
49+
"ESCAPE", "EXECUTE", "EXISTS", "EXTERNAL", "EXTRACT", "FALSE", "FETCH", "FILTER", "FLOAT", "FOR", "FOREIGN",
50+
"FROM", "FULL", "FUNCTION", "GDSCODE", "GLOBAL", "GRANT", "GROUP", "HAVING", "HOUR", "IN", "INDEX", "INNER",
51+
"INSENSITIVE", "INSERT", "INSERTING", "INT", "INT128", "INTEGER", "INTO", "IS", "JOIN", "LATERAL",
52+
"LEADING", "LEFT", "LIKE", "LOCAL", "LOCALTIME", "LOCALTIMESTAMP", "LONG", "LOWER", "MAX", "MERGE", "MIN",
53+
"MINUTE", "MONTH", "NATIONAL", "NATURAL", "NCHAR", "NO", "NOT", "NULL", "NUMERIC", "OCTET_LENGTH", "OF",
54+
"OFFSET", "ON", "ONLY", "OPEN", "OR", "ORDER", "OUTER", "OVER", "PARAMETER", "PLAN", "POSITION",
55+
"POST_EVENT", "PRECISION", "PRIMARY", "PROCEDURE", "PUBLICATION", "RDB$DB_KEY", "RDB$ERROR",
56+
"RDB$GET_CONTEXT", "RDB$GET_TRANSACTION_CN", "RDB$RECORD_VERSION", "RDB$ROLE_IN_USE", "RDB$SET_CONTEXT",
57+
"RDB$SYSTEM_PRIVILEGE", "REAL", "RECORD_VERSION", "RECREATE", "RECURSIVE", "REFERENCES", "REGR_AVGX",
58+
"REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2", "REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY",
59+
"RELEASE", "RESETTING", "RETURN", "RETURNING_VALUES", "RETURNS", "REVOKE", "RIGHT", "ROLLBACK", "ROW",
60+
"ROWS", "ROW_COUNT", "SAVEPOINT", "SCHEMA", "SCROLL", "SECOND", "SELECT", "SENSITIVE", "SET", "SIMILAR",
61+
"SMALLINT", "SOME", "SQLCODE", "SQLSTATE", "START", "STDDEV_POP", "STDDEV_SAMP", "SUM", "TABLE", "THEN",
62+
"TIME", "TIMESTAMP", "TIMEZONE_HOUR", "TIMEZONE_MINUTE", "TO", "TRAILING", "TRIGGER", "TRIM", "TRUE",
63+
"UNBOUNDED", "UNION", "UNIQUE", "UNKNOWN", "UPDATE", "UPDATING", "UPPER", "USER", "USING", "VALUE",
64+
"VALUES", "VARBINARY", "VARCHAR", "VARIABLE", "VARYING", "VAR_POP", "VAR_SAMP", "VIEW", "WHEN", "WHERE",
65+
"WHILE", "WINDOW", "WITH", "WITHOUT", "YEAR"),
66+
FIREBIRD_4_0(4, 0, "ADD", "ADMIN", "ALL", "ALTER", "AND", "ANY", "AS", "AT", "AVG", "BEGIN", "BETWEEN", "BIGINT",
4267
"BINARY", "BIT_LENGTH", "BLOB", "BOOLEAN", "BOTH", "BY", "CASE", "CAST", "CHAR", "CHARACTER",
4368
"CHARACTER_LENGTH", "CHAR_LENGTH", "CHECK", "CLOSE", "COLLATE", "COLUMN", "COMMENT", "COMMIT", "CONNECT",
4469
"CONSTRAINT", "CORR", "COUNT", "COVAR_POP", "COVAR_SAMP", "CREATE", "CROSS", "CURRENT",
@@ -62,14 +87,93 @@ public enum FirebirdReservedWords implements ReservedWords {
6287
"UNION", "UNIQUE", "UNKNOWN", "UPDATE", "UPDATING", "UPPER", "USER", "USING", "VALUE", "VALUES",
6388
"VARBINARY", "VARCHAR", "VARIABLE", "VARYING", "VAR_POP", "VAR_SAMP", "VIEW", "WHEN", "WHERE", "WHILE",
6489
"WINDOW", "WITH", "WITHOUT", "YEAR"),
90+
FIREBIRD_3_0(3, 0, "ADD", "ADMIN", "ALL", "ALTER", "AND", "ANY", "AS", "AT", "AVG", "BEGIN", "BETWEEN", "BIGINT",
91+
"BIT_LENGTH", "BLOB", "BOOLEAN", "BOTH", "BY", "CASE", "CAST", "CHAR", "CHARACTER", "CHARACTER_LENGTH",
92+
"CHAR_LENGTH", "CHECK", "CLOSE", "COLLATE", "COLUMN", "COMMIT", "CONNECT", "CONSTRAINT", "CORR", "COUNT",
93+
"COVAR_POP", "COVAR_SAMP", "CREATE", "CROSS", "CURRENT", "CURRENT_CONNECTION", "CURRENT_DATE",
94+
"CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP", "CURRENT_TRANSACTION", "CURRENT_USER", "CURSOR",
95+
"DATE", "DAY", "DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELETE", "DELETING", "DETERMINISTIC", "DISCONNECT",
96+
"DISTINCT", "DOUBLE", "DROP", "ELSE", "END", "ESCAPE", "EXECUTE", "EXISTS", "EXTERNAL", "EXTRACT", "FALSE",
97+
"FETCH", "FILTER", "FLOAT", "FOR", "FOREIGN", "FROM", "FULL", "FUNCTION", "GDSCODE", "GLOBAL", "GRANT",
98+
"GROUP", "HAVING", "HOUR", "IN", "INDEX", "INNER", "INSENSITIVE", "INSERT", "INSERTING", "INT", "INTEGER",
99+
"INTO", "IS", "JOIN", "LEADING", "LEFT", "LIKE", "LONG", "LOWER", "MAX", "MERGE", "MIN", "MINUTE", "MONTH",
100+
"NATIONAL", "NATURAL", "NCHAR", "NO", "NOT", "NULL", "NUMERIC", "OCTET_LENGTH", "OF", "OFFSET", "ON",
101+
"ONLY", "OPEN", "OR", "ORDER", "OUTER", "OVER", "PARAMETER", "PLAN", "POSITION", "POST_EVENT", "PRECISION",
102+
"PRIMARY", "PROCEDURE", "RDB$DB_KEY", "RDB$RECORD_VERSION", "REAL", "RECORD_VERSION", "RECREATE",
103+
"RECURSIVE", "REFERENCES", "REGR_AVGX", "REGR_AVGY", "REGR_COUNT", "REGR_INTERCEPT", "REGR_R2",
104+
"REGR_SLOPE", "REGR_SXX", "REGR_SXY", "REGR_SYY", "RELEASE", "RETURN", "RETURNING_VALUES", "RETURNS",
105+
"REVOKE", "RIGHT", "ROLLBACK", "ROW", "ROWS", "ROW_COUNT", "SAVEPOINT", "SCROLL", "SECOND", "SELECT",
106+
"SENSITIVE", "SET", "SIMILAR", "SMALLINT", "SOME", "SQLCODE", "SQLSTATE", "START", "STDDEV_POP",
107+
"STDDEV_SAMP", "SUM", "TABLE", "THEN", "TIME", "TIMESTAMP", "TO", "TRAILING", "TRIGGER", "TRIM", "TRUE",
108+
"UNION", "UNIQUE", "UNKNOWN", "UPDATE", "UPDATING", "UPPER", "USER", "USING", "VALUE", "VALUES", "VARCHAR",
109+
"VARIABLE", "VARYING", "VAR_POP", "VAR_SAMP", "VIEW", "WHEN", "WHERE", "WHILE", "WITH", "YEAR"),
110+
FIREBIRD_2_5(2, 5, "ADD", "ADMIN", "ALL", "ALTER", "AND", "ANY", "AS", "AT", "AVG", "BEGIN", "BETWEEN", "BIGINT",
111+
"BIT_LENGTH", "BLOB", "BOTH", "BY", "CASE", "CAST", "CHAR", "CHARACTER", "CHARACTER_LENGTH", "CHAR_LENGTH",
112+
"CHECK", "CLOSE", "COLLATE", "COLUMN", "COMMIT", "CONNECT", "CONSTRAINT", "COUNT", "CREATE", "CROSS",
113+
"CURRENT", "CURRENT_CONNECTION", "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_TIME", "CURRENT_TIMESTAMP",
114+
"CURRENT_TRANSACTION", "CURRENT_USER", "CURSOR", "DATE", "DAY", "DEC", "DECIMAL", "DECLARE", "DEFAULT",
115+
"DELETE", "DISCONNECT", "DISTINCT", "DOUBLE", "DROP", "ELSE", "END", "ESCAPE", "EXECUTE", "EXISTS",
116+
"EXTERNAL", "EXTRACT", "FETCH", "FILTER", "FLOAT", "FOR", "FOREIGN", "FROM", "FULL", "FUNCTION", "GDSCODE",
117+
"GLOBAL", "GRANT", "GROUP", "HAVING", "HOUR", "IN", "INDEX", "INNER", "INSENSITIVE", "INSERT", "INT",
118+
"INTEGER", "INTO", "IS", "JOIN", "LEADING", "LEFT", "LIKE", "LOCALTIME", "LOCALTIMESTAMP", "LONG", "LOWER",
119+
"MAX", "MAXIMUM_SEGMENT", "MERGE", "MIN", "MINUTE", "MONTH", "NATIONAL", "NATURAL", "NCHAR", "NO", "NOT",
120+
"NULL", "NUMERIC", "OCTET_LENGTH", "OF", "ON", "ONLY", "OPEN", "OR", "ORDER", "OUTER", "PARAMETER", "PLAN",
121+
"POSITION", "POST_EVENT", "PRECISION", "PRIMARY", "PROCEDURE", "RDB$DB_KEY", "REAL", "RECORD_VERSION",
122+
"RECREATE", "RECURSIVE", "REFERENCES", "RELEASE", "RETURNING_VALUES", "RETURNS", "REVOKE", "RIGHT",
123+
"ROLLBACK", "ROWS", "ROW_COUNT", "SAVEPOINT", "SECOND", "SELECT", "SENSITIVE", "SET", "SIMILAR", "SMALLINT",
124+
"SOME", "SQLCODE", "SQLSTATE", "START", "SUM", "TABLE", "THEN", "TIME", "TIMESTAMP", "TO", "TRAILING",
125+
"TRIGGER", "TRIM", "UNION", "UNIQUE", "UPDATE", "UPPER", "USER", "USING", "VALUE", "VALUES", "VARCHAR",
126+
"VARIABLE", "VARYING", "VIEW", "WHEN", "WHERE", "WHILE", "WITH", "YEAR"),
127+
FIREBIRD_2_1(2, 1, "ACTIVE", "ADD", "ADMIN", "AFTER", "ALL", "ALTER", "AND", "ANY", "AS", "ASC", "ASCENDING", "AT",
128+
"AUTO", "AVG", "BEFORE", "BEGIN", "BETWEEN", "BIGINT", "BIT_LENGTH", "BLOB", "BOTH", "BY", "CASE", "CAST",
129+
"CHAR", "CHARACTER", "CHARACTER_LENGTH", "CHAR_LENGTH", "CHECK", "CLOSE", "COLLATE", "COLUMN", "COMMIT",
130+
"COMMITTED", "COMPUTED", "CONDITIONAL", "CONNECT", "CONSTRAINT", "CONTAINING", "COUNT", "CREATE", "CROSS",
131+
"CSTRING", "CURRENT", "CURRENT_CONNECTION", "CURRENT_DATE", "CURRENT_ROLE", "CURRENT_TIME",
132+
"CURRENT_TIMESTAMP", "CURRENT_TRANSACTION", "CURRENT_USER", "CURSOR", "DATABASE", "DATE", "DAY", "DEBUG",
133+
"DEC", "DECIMAL", "DECLARE", "DEFAULT", "DELETE", "DESC", "DESCENDING", "DISCONNECT", "DISTINCT", "DO",
134+
"DOMAIN", "DOUBLE", "DROP", "ELSE", "END", "ENTRY_POINT", "ESCAPE", "EXCEPTION", "EXECUTE", "EXISTS",
135+
"EXIT", "EXTERNAL", "EXTRACT", "FETCH", "FILE", "FILTER", "FLOAT", "FOR", "FOREIGN", "FROM", "FULL",
136+
"FUNCTION", "GDSCODE", "GENERATOR", "GEN_ID", "GLOBAL", "GRANT", "GROUP", "HAVING", "HOUR", "IF", "IN",
137+
"INACTIVE", "INDEX", "INNER", "INPUT_TYPE", "INSENSITIVE", "INSERT", "INT", "INTEGER", "INTO", "IS",
138+
"ISOLATION", "JOIN", "KEY", "LEADING", "LEFT", "LENGTH", "LEVEL", "LIKE", "LONG", "LOWER", "MANUAL", "MAX",
139+
"MAXIMUM_SEGMENT", "MERGE", "MIN", "MINUTE", "MODULE_NAME", "MONTH", "NAMES", "NATIONAL", "NATURAL",
140+
"NCHAR", "NO", "NOT", "NULL", "NUMERIC", "OCTET_LENGTH", "OF", "ON", "ONLY", "OPEN", "OPTION", "OR",
141+
"ORDER", "OUTER", "OUTPUT_TYPE", "OVERFLOW", "PAGE", "PAGES", "PAGE_SIZE", "PARAMETER", "PASSWORD", "PLAN",
142+
"POSITION", "POST_EVENT", "PRECISION", "PRIMARY", "PRIVILEGES", "PROCEDURE", "PROTECTED", "RDB$DB_KEY",
143+
"READ", "REAL", "RECORD_VERSION", "RECREATE", "RECURSIVE", "REFERENCES", "RELEASE", "RESERV", "RESERVING",
144+
"RETAIN", "RETURNING_VALUES", "RETURNS", "REVOKE", "RIGHT", "ROLLBACK", "ROWS", "ROW_COUNT", "SAVEPOINT",
145+
"SCHEMA", "SECOND", "SEGMENT", "SELECT", "SENSITIVE", "SET", "SHADOW", "SHARED", "SINGULAR", "SIZE",
146+
"SMALLINT", "SNAPSHOT", "SOME", "SORT", "SQLCODE", "STABILITY", "START", "STARTING", "STARTS", "STATISTICS",
147+
"SUB_TYPE", "SUM", "SUSPEND", "TABLE", "THEN", "TIME", "TIMESTAMP", "TO", "TRAILING", "TRANSACTION",
148+
"TRIGGER", "TRIM", "UNCOMMITTED", "UNION", "UNIQUE", "UPDATE", "UPPER", "USER", "USING", "VALUE", "VALUES",
149+
"VARCHAR", "VARIABLE", "VARYING", "VIEW", "WAIT", "WHEN", "WHERE", "WHILE", "WITH", "WORK", "WRITE",
150+
"YEAR"),
65151
;
66152

153+
private final int major;
154+
private final int minor;
67155
private final Set<CharSequence> reservedWords;
68156

69-
FirebirdReservedWords(CharSequence... reservedWords) {
157+
FirebirdReservedWords(int major, int minor, CharSequence... reservedWords) {
158+
this.major = major;
159+
this.minor = minor;
70160
this.reservedWords = toUnmodifiableCaseInsensitiveSet(Arrays.asList(reservedWords));
71161
}
72162

163+
/**
164+
* @return Firebird major version
165+
*/
166+
int major() {
167+
return major;
168+
}
169+
170+
/**
171+
* @return Firebird minor version
172+
*/
173+
int minor() {
174+
return minor;
175+
}
176+
73177
/**
74178
* Reserved words for latest known Firebird version.
75179
*
@@ -79,14 +183,36 @@ public static FirebirdReservedWords latest() {
79183
return FIREBIRD_5_0;
80184
}
81185

186+
/**
187+
* Reserved words for the specified Firebird version.
188+
*
189+
* @param major
190+
* Firebird major version
191+
* @param minor Firebird minor version
192+
* @return reserved words for the specified version, or the closest known version
193+
* @since 6.0.5
194+
*/
195+
public static FirebirdReservedWords of(int major, int minor) {
196+
FirebirdReservedWords[] values = values();
197+
for (FirebirdReservedWords reservedWords : values) {
198+
if (major > reservedWords.major ||
199+
major == reservedWords.major && minor >= reservedWords.minor) {
200+
return reservedWords;
201+
}
202+
}
203+
// fall back to the lowest known version
204+
return values[values.length - 1];
205+
}
206+
82207
private static Set<CharSequence> toUnmodifiableCaseInsensitiveSet(Collection<CharSequence> values) {
83208
Set<CharSequence> set = new TreeSet<>(CharSequenceComparison.caseInsensitiveComparator());
84209
set.addAll(values);
85210
return unmodifiableSet(set);
86211
}
87212

88213
@Override
89-
public boolean isReservedWord(CharSequence tokenText) {
214+
public final boolean isReservedWord(CharSequence tokenText) {
90215
return reservedWords.contains(tokenText);
91216
}
217+
92218
}

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

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,45 @@ protected final void addWarning(SQLWarning warning) {
357357
}
358358
}
359359

360+
/**
361+
* Returns a {@code String} enclosed in single quotes. Any occurrence of a single quote within the string will be
362+
* replaced by two single quotes (for dialect 1, double quotes instead of single quotes).
363+
*
364+
* @see FirebirdConnection#enquoteLiteral(String)
365+
*/
366+
@Override
367+
public final String enquoteLiteral(String val) throws SQLException {
368+
return connection.enquoteLiteral(val);
369+
}
370+
371+
/**
372+
* @see #enquoteLiteral(String)
373+
*/
374+
@Override
375+
public final String enquoteNCharLiteral(String val) throws SQLException {
376+
return enquoteLiteral(val);
377+
}
378+
379+
/**
380+
* Returns a SQL identifier, appropriately delimited if needed.
381+
*
382+
* @see FirebirdConnection#enquoteIdentifier(String, boolean)
383+
*/
384+
@Override
385+
public final String enquoteIdentifier(String identifier, boolean alwaysDelimit) throws SQLException {
386+
return connection.enquoteIdentifier(identifier, alwaysDelimit);
387+
}
388+
389+
/**
390+
* Returns whether {@code identifier} is a simple SQL identifier.
391+
*
392+
* @see FirebirdConnection#isSimpleIdentifier(String)
393+
*/
394+
@Override
395+
public final boolean isSimpleIdentifier(String identifier) throws SQLException {
396+
return connection.isSimpleIdentifier(identifier);
397+
}
398+
360399
@Override
361400
public final int getLocalStatementId() {
362401
return localStatementId;

0 commit comments

Comments
 (0)