From ccd5fc34c86332a0840c39a31be2ce53d5556c88 Mon Sep 17 00:00:00 2001
From: Mark Rotteveel
Date: Sun, 11 Mar 2018 13:05:44 +0100
Subject: [PATCH] JDBC-293, JDBC-294, JDBC-523, JDBC-524 Improve JDBC function
escapes
- Support optional parameters for CHAR_LENGTH, CHARACTER_LENGTH, LENGTH, POSITION, and LOCATE as defined in JDBC specification
- Improve CONVERT support to be compliant with JDBC 4.1 and higher
- Add DEGREES/RADIANS
- Add TIMESTAMPADD/TIMESTAMPDIFF
---
src/documentation/release_notes.md | 109 +++++
.../firebirdsql/jdbc/SQLStateConstants.java | 1 +
.../jdbc/escape/CharacterLengthFunction.java | 52 ++
.../jdbc/escape/ConstantSQLFunction.java | 43 ++
.../jdbc/escape/ConvertFunction.java | 145 ++++++
.../jdbc/escape/FBEscapedFunctionHelper.java | 459 +++++++++---------
.../jdbc/escape/FBEscapedParser.java | 54 +--
.../jdbc/escape/FBSQLParseException.java | 22 +-
.../jdbc/escape/IntervalMapping.java | 75 +++
.../jdbc/escape/LengthFunction.java | 52 ++
.../jdbc/escape/LocateFunction.java | 43 ++
.../jdbc/escape/PatternSQLFunction.java | 56 +++
.../jdbc/escape/PositionFunction.java | 55 +++
.../firebirdsql/jdbc/escape/SQLFunction.java | 39 ++
.../jdbc/escape/TimestampAddFunction.java | 52 ++
.../jdbc/escape/TimestampDiffFunction.java | 54 +++
.../escape/CharacterLengthFunctionTest.java | 95 ++++
.../jdbc/escape/ConstantSQLFunctionTest.java | 45 ++
.../ConvertFunctionParameterizedTest.java | 162 +++++++
.../jdbc/escape/ConvertFunctionTest.java | 63 +++
...rser.java => FBEscapedCallParserTest.java} | 5 +-
....java => FBEscapedFunctionHelperTest.java} | 76 ++-
...edParser.java => FBEscapedParserTest.java} | 124 ++---
.../jdbc/escape/LengthFunctionTest.java | 96 ++++
...estLikeEscape.java => LikeEscapeTest.java} | 129 +++--
.../jdbc/escape/LimitEscapeTest.java | 149 ++++++
.../jdbc/escape/LocateFunctionTest.java | 61 +++
...Escapes.java => OuterJoinEscapesTest.java} | 123 +++--
.../jdbc/escape/PatternSQLFunctionTest.java | 43 ++
.../jdbc/escape/PositionFunctionTest.java | 84 ++++
.../jdbc/escape/TestLimitEscape.java | 167 -------
.../escape/TestScalarNumericFunctions.java | 85 ++--
.../escape/TestScalarStringFunctions.java | 91 ++--
.../escape/TestScalarSystemFunctions.java | 50 +-
.../escape/TestScalarTimeDateFunctions.java | 231 +++++----
...s.java => TimeDateLiteralEscapesTest.java} | 92 ++--
...TimestampAddFunctionParameterizedTest.java | 84 ++++
.../jdbc/escape/TimestampAddFunctionTest.java | 74 +++
...imestampDiffFunctionParameterizedTest.java | 84 ++++
.../escape/TimestampDiffFunctionTest.java | 74 +++
40 files changed, 2660 insertions(+), 938 deletions(-)
create mode 100644 src/main/org/firebirdsql/jdbc/escape/CharacterLengthFunction.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/ConstantSQLFunction.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/ConvertFunction.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/IntervalMapping.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/LengthFunction.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/LocateFunction.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/PatternSQLFunction.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/PositionFunction.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/SQLFunction.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/TimestampAddFunction.java
create mode 100644 src/main/org/firebirdsql/jdbc/escape/TimestampDiffFunction.java
create mode 100644 src/test/org/firebirdsql/jdbc/escape/CharacterLengthFunctionTest.java
create mode 100644 src/test/org/firebirdsql/jdbc/escape/ConstantSQLFunctionTest.java
create mode 100644 src/test/org/firebirdsql/jdbc/escape/ConvertFunctionParameterizedTest.java
create mode 100644 src/test/org/firebirdsql/jdbc/escape/ConvertFunctionTest.java
rename src/test/org/firebirdsql/jdbc/escape/{TestFBEscapedCallParser.java => FBEscapedCallParserTest.java} (97%)
rename src/test/org/firebirdsql/jdbc/escape/{TestFBEscapedFunctionHelper.java => FBEscapedFunctionHelperTest.java} (59%)
rename src/test/org/firebirdsql/jdbc/escape/{TestFBEscapedParser.java => FBEscapedParserTest.java} (96%)
create mode 100644 src/test/org/firebirdsql/jdbc/escape/LengthFunctionTest.java
rename src/test/org/firebirdsql/jdbc/escape/{TestLikeEscape.java => LikeEscapeTest.java} (50%)
create mode 100644 src/test/org/firebirdsql/jdbc/escape/LimitEscapeTest.java
create mode 100644 src/test/org/firebirdsql/jdbc/escape/LocateFunctionTest.java
rename src/test/org/firebirdsql/jdbc/escape/{TestOuterJoinEscapes.java => OuterJoinEscapesTest.java} (61%)
create mode 100644 src/test/org/firebirdsql/jdbc/escape/PatternSQLFunctionTest.java
create mode 100644 src/test/org/firebirdsql/jdbc/escape/PositionFunctionTest.java
delete mode 100644 src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java
rename src/test/org/firebirdsql/jdbc/escape/{TestTimeDateLiteralEscapes.java => TimeDateLiteralEscapesTest.java} (57%)
create mode 100644 src/test/org/firebirdsql/jdbc/escape/TimestampAddFunctionParameterizedTest.java
create mode 100644 src/test/org/firebirdsql/jdbc/escape/TimestampAddFunctionTest.java
create mode 100644 src/test/org/firebirdsql/jdbc/escape/TimestampDiffFunctionParameterizedTest.java
create mode 100644 src/test/org/firebirdsql/jdbc/escape/TimestampDiffFunctionTest.java
diff --git a/src/documentation/release_notes.md b/src/documentation/release_notes.md
index 54f5ec76bd..6b9dbd8275 100644
--- a/src/documentation/release_notes.md
+++ b/src/documentation/release_notes.md
@@ -548,6 +548,108 @@ set or retrieved using `String` or the `DecimalXX` types, or the result of
rounding. This behaviour is subject to change, and future releases may 'round'
to `0` (aka `+0`).
+Improved JDBC function escape support
+-------------------------------------
+
+Revised support for JDBC function escapes with optional parameters, and added
+support for a number of previously unsupported functions or options.
+
+If you only target Firebird, then we suggest you do not use JDBC function
+escapes.
+
+### New JDBC function escapes ###
+
+- `DEGREES(number)` - Degrees in _number_ radians; implemented as
+`((number)*180.0/PI())`
+- `RADIANS(number)` - Radians in _number_ degrees; implemented as
+`((number)*PI()/180.0)`
+- `QUARTER(date)` - Quarter of year for date; implemented as
+`(1+(EXTRACT(MONTH FROM date)-1)/3)`
+- `TIMESTAMPADD(interval, count, timestamp)` - Implemented using `DATEADD`
+with the following caveats:
+
+ - _interval_ `SQL_TSI_FRAC_SECOND` unit is nanoseconds and will be
+ simulated by using `MILLISECOND` and the count multiplied by `1.0e-6` to
+ convert the value to milliseconds.
+ - _interval_ `SQL_TSI_QUARTER` will be simulated by using `MONTH` and
+ _count_ multiplied by 3.
+ - _interval_ values that are not specified in JDBC will be passed as is,
+ resulting in an error from the Firebird engine if it is an invalid interval
+ name for `DATEADD`.
+- `TIMESTAMPDIFF(interval, timestamp1, timestamp2)` - Implemented using
+`DATEDIFF` with the following caveats:
+
+ - _interval_ `SQL_TSI_FRAC_SECOND` unit is nanoseconds and will be
+ simulated by using `MILLISECOND` and the result multiplied by `1.0e6` and
+ cast to `BIGINT` to convert the value to nanoseconds.
+ - Value `SQL_TSI_QUARTER` will be simulated by using `MONTH` and the
+ result divided by 3.
+ - Contrary to specified in the JDBC specification, the resulting value
+ will be `BIGINT`, not `INTEGER`.
+ - _interval_ values that are not specified in JDBC will be passed as is,
+ resulting in an error from the Firebird engine if it is an invalid interval
+ name for `DATEDIFF`.
+
+### Improved JDBC function escapes ###
+
+- `CHAR_LENGTH(string[, CHARACTERS|OCTETS])` - The optional second parameter
+with `CHARACTERS` or `OCTETS` is now supported.
+
+ Absence of second parameter, or `CHARACTERS` maps to `CHAR_LENGTH`, `OCTETS` maps to
+`OCTET_LENGTH`
+- `CHARACTER_LENGTH(string[, CHARACTERS|OCTETS])` - see `CHAR_LENGTH`
+- `CONCAT(string1, string2)` - Added parentheses around expression to prevent
+ambiguity or incorrect evaluation order.
+- `LENGTH(string[, CHARACTERS|OCTETS])` - The optional second parameter with
+`CHARACTERS` or `OCTETS` is now supported.
+
+ The JDBC specification specifies _Number of characters in string, excluding
+trailing blanks_, we right-trim (`TRIM(TRAILING FROM value)`) the string before
+passing the value to either `CHAR_LENGTH` or `OCTETS_LENGTH`. As a result, the
+interpretation of what is a blank depends on the type of _value_. Is the value a
+normal `(VAR)CHAR` (non-octets), then the blank is space (0x20), for a
+`VAR(CHAR)CHARACTER SET OCTETS / (VAR)BINARY` the blank is NUL (0x00). This means
+that the optional `CHARACTERS|OCTETS` parameter has no influence on which blanks
+are trimmed, but only whether we count characters or bytes after trimming.
+- `LOCATE(string1, string2[, start])` - The third parameter _start_ is now
+optional.
+- `POSITION(substring IN string[, CHARACTERS|OCTETS])` - The optional second
+parameter is now supported if `CHARACTERS`. `OCTETS` is not supported.
+- `CONVERT(value, SQLtype)` - See [Improved CONVERT support].
+
+### Improved CONVERT support ###
+
+In Jaybird 3, `CONVERT(value, SQLtype)` would map directly to
+`CAST(value as SQLtype)`, we have improved support to better conform to the JDBC
+requirements, with some caveats:
+
+- Both the `SQL_
+ * Most important caveats:
+ *
+ *
+ *
+ * @author Mark Rotteveel
+ * @since 4.0
+ */
+final class ConvertFunction implements SQLFunction {
+
+ private static final Pattern TYPE_PATTERN =
+ Pattern.compile("(?:SQL_)?(\\w+)(?:\\s*(\\([^)]*\\)))?", Pattern.CASE_INSENSITIVE);
+
+ @Override
+ public String apply(String... parameters) throws FBSQLParseException {
+ if (parameters.length != 2) {
+ throw new FBSQLParseException("Expected 2 parameters for CONVERT, received " + parameters.length);
+ }
+ final String value = parameters[0];
+ final String sqlType = parameters[1];
+ final Matcher typeMatcher = TYPE_PATTERN.matcher(sqlType);
+ if (!typeMatcher.matches()) {
+ return renderCast(value, sqlType);
+ }
+
+ return renderCast(value, typeMatcher);
+ }
+
+ private String renderCast(final String value, final String sqlType) {
+ return "CAST(" + value + " AS " + sqlType + ")";
+ }
+
+ private String renderCast(final String value, final Matcher typeMatcher) {
+ String dataType = typeMatcher.group(1).toUpperCase();
+ String parameters = typeMatcher.group(2);
+ switch (dataType) {
+ case "TINYINT":
+ dataType = "SMALLINT";
+ break;
+ case "DOUBLE":
+ dataType = "DOUBLE PRECISION";
+ break;
+ case "CHAR":
+ case "NCHAR":
+ // Caveat: without parameters, size fixed at 50 (seems a reasonable trade off)
+ if (parameters == null) {
+ parameters = "(50)";
+ }
+ break;
+ case "VARCHAR":
+ case "NVARCHAR":
+ // Caveat: for blob use of TRIM results in a blob, not VARCHAR
+ // Caveat: for NVARCHAR without parameters, this results in a VARCHAR
+ // Caveat: if value is a parameter, size fixed at 50 (seems a reasonable trade off)
+ if (parameters == null) {
+ if (!"?".equals(value)) {
+ return "TRIM(TRAILING FROM " + value + ")";
+ } else {
+ parameters = "(50)";
+ }
+ }
+ break;
+ case "BINARY":
+ // Caveat: without parameters, size fixed at 50 (seems a reasonable trade off)
+ if (parameters == null) {
+ dataType = "CHAR";
+ parameters = "(50) CHARACTER SET OCTETS";
+ } else {
+ dataType = "CHAR";
+ parameters += " CHARACTER SET OCTETS";
+ }
+ break;
+ case "VARBINARY":
+ // Caveat: without parameters, size fixed at 50 (seems a reasonable trade off)
+ if (parameters == null) {
+ dataType = "VARCHAR";
+ parameters = "(50) CHARACTER SET OCTETS";
+ } else {
+ dataType = "VARCHAR";
+ parameters += " CHARACTER SET OCTETS";
+ }
+ break;
+ case "LONGVARCHAR":
+ case "LONGNVARCHAR":
+ case "CLOB":
+ case "NCLOB":
+ // Caveat: LONGNVARCHAR / NCLOB doesn't apply Firebird N(VAR)CHAR semantics of ISO-8859-1 charset
+ dataType = "BLOB SUB_TYPE TEXT";
+ parameters = null;
+ break;
+ case "LONGVARBINARY":
+ case "BLOB":
+ dataType = "BLOB SUB_TYPE BINARY";
+ parameters = null;
+ break;
+ }
+ return renderCast(value, parameters == null ? dataType : dataType + parameters);
+ }
+
+}
diff --git a/src/main/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelper.java b/src/main/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelper.java
index 386a57d3dd..df74c20bb0 100644
--- a/src/main/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelper.java
+++ b/src/main/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelper.java
@@ -1,5 +1,5 @@
/*
- * Firebird Open Source J2ee connector - jdbc driver
+ * Firebird Open Source JavaEE connector - JDBC driver
*
* Distributable under LGPL license.
* You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html
@@ -12,54 +12,96 @@
* This file was created by members of the firebird development team.
* All individual contributions remain the Copyright (C) of those
* individuals. Contributors to this file are either listed here or
- * can be obtained from a CVS history command.
+ * can be obtained from a source control history command.
*
* All rights reserved.
*/
package org.firebirdsql.jdbc.escape;
+import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode;
+
import java.lang.reflect.Method;
-import java.text.MessageFormat;
import java.util.*;
-import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode;
-
/**
* Helper class for escaped functions.
- *
+ *
* @author Roman Rokytskyy
+ * @author Mark Rotteveel
*/
public class FBEscapedFunctionHelper {
-
+
/**
* This map contains mapping between JDBC function names and Firebird ones.
* Mapping to null means: execute as is (might fail if there is no built-in or UDF)
*/
- private static final Mapnull
* if no template found.
- *
- * @throws FBSQLParseException if escaped function call has incorrect syntax.
+ * @throws FBSQLParseException
+ * if escaped function call has incorrect syntax.
*/
public static String convertTemplate(final String functionCall, final EscapeParserMode mode) throws FBSQLParseException {
final String functionName = parseFunction(functionCall).toUpperCase();
final String[] params = parseArguments(functionCall).toArray(new String[0]);
-
+
if (!FUNCTION_MAP.containsKey(functionName)) {
/* See 13.4.1 of JDBC 4.1 spec:
* "The escape syntax for scalar functions must only be used to invoke the scalar
@@ -333,18 +356,20 @@ public static String convertTemplate(final String functionCall, final EscapePars
// TODO Consider throwing SQLFeatureNotSupported or a different SQLException
throw new FBSQLParseException("Unsupported JDBC function escape: " + functionName);
}
-
- final String firebirdTemplate = FUNCTION_MAP.get(functionName);
- if (firebirdTemplate != null)
- return MessageFormat.format(firebirdTemplate, (Object[]) params);
-
- if (mode == EscapeParserMode.USE_STANDARD_UDF)
+ final SQLFunction firebirdTemplate = FUNCTION_MAP.get(functionName);
+
+ if (firebirdTemplate != null) {
+ return firebirdTemplate.apply(params);
+ }
+
+ if (mode == EscapeParserMode.USE_STANDARD_UDF) {
return convertUsingStandardUDF(functionName, params);
-
+ }
+
return null;
}
-
+
/*
* Functions below are conversion routines of the escaped function calls
* into the standard UDF library functions. The conversion function must
@@ -353,30 +378,32 @@ public static String convertTemplate(final String functionCall, final EscapePars
* the FBSQLParseException and must be declared as static and have public
* visibility. It should return a string of the converted function call.
*/
-
+
+ // TODO Replace with PatternSQLFunction?
+
private static String convertUsingStandardUDF(String name, String[] params) throws FBSQLParseException {
try {
name = name.toLowerCase();
- Method method = FBEscapedFunctionHelper.class.getMethod(name, new Class[] { String[].class});
- return (String)method.invoke(null, new Object[]{params});
- } catch(NoSuchMethodException ex) {
+ Method method = FBEscapedFunctionHelper.class.getMethod(name, new Class[] { String[].class });
+ return (String) method.invoke(null, new Object[] { params });
+ } catch (NoSuchMethodException ex) {
return null;
} catch (Exception ex) {
- throw new FBSQLParseException("Error when converting function "
- + name + ". Error " + ex.getClass().getName() +
- " : " + ex.getMessage());
+ throw new FBSQLParseException("Error when converting function "
+ + name + ". Error " + ex.getClass().getName() +
+ " : " + ex.getMessage());
}
}
-
+
/*
* Mathematical functions
*/
-
+
/**
- * Produce a function call for the abs
UDF function.
- * The syntax of the abs
function is
+ * Produce a function call for the abs
UDF function.
+ * The syntax of the abs
function is
* {fn abs(number)}
.
- *
+ *
* @param params The parameters to be used in the call
* @throws FBSQLParseException if there is an error with the parameters
*/
@@ -384,13 +411,13 @@ public static String abs(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function abs : " + params.length);
-
+
return "abs(" + params[0] + ")";
}
-
+
/**
- * Produce a function call for the acos
UDF function.
- * The syntax of the acos
function is
+ * Produce a function call for the acos
UDF function.
+ * The syntax of the acos
function is
* {fn acos(float)}
.
*
* @param params The parameters to be used in the call
@@ -400,13 +427,13 @@ public static String acos(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function acos : " + params.length);
-
+
return "acos(" + params[0] + ")";
}
-
+
/**
- * Produce a function call for the asin
UDF function.
- * The syntax of the asin
function is
+ * Produce a function call for the asin
UDF function.
+ * The syntax of the asin
function is
* {fn asin(float)}
.
*
* @param params The parameters to be used in the call
@@ -416,13 +443,13 @@ public static String asin(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function asin : " + params.length);
-
+
return "asin(" + params[0] + ")";
}
-
+
/**
- * Produce a function call for the atan
UDF function.
- * The syntax of the atan
function is
+ * Produce a function call for the atan
UDF function.
+ * The syntax of the atan
function is
* {fn atan(float)}
.
*
* @param params The parameters to be used in the call
@@ -432,13 +459,13 @@ public static String atan(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function atan : " + params.length);
-
+
return "atan(" + params[0] + ")";
}
-
+
/**
- * Produce a function call for the atan2
UDF function.
- * The syntax of the atan2
function is
+ * Produce a function call for the atan2
UDF function.
+ * The syntax of the atan2
function is
* {fn atan2(float1, float2)}
.
*
* @param params The parameters to be used in the call
@@ -448,15 +475,15 @@ public static String atan2(String[] params) throws FBSQLParseException {
if (params.length != 2)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function atan2 : " + params.length);
-
+
return "atan2(" + params[0] + ", " + params[1] + ")";
}
-
+
/**
- * Produce a function call for the ceiling
UDF function.
- * The syntax of the ceiling
function is
+ * Produce a function call for the ceiling
UDF function.
+ * The syntax of the ceiling
function is
* {fn ceiling(number)}
.
- *
+ *
* @param params The parameters to be used in the call
* @throws FBSQLParseException if there is an error with the parameters
*/
@@ -464,13 +491,13 @@ public static String ceiling(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function ceiling : " + params.length);
-
+
return "ceiling(" + params[0] + ")";
}
/**
- * Produce a function call for the cos
UDF function.
- * The syntax of the cos
function is
+ * Produce a function call for the cos
UDF function.
+ * The syntax of the cos
function is
* {fn cos(float)}
.
*
* @param params The parameters to be used in the call
@@ -480,15 +507,15 @@ public static String cos(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function cos : " + params.length);
-
+
return "cos(" + params[0] + ")";
}
/**
- * Produce a function call for the cot
UDF function.
- * The syntax of the cot
function is
+ * Produce a function call for the cot
UDF function.
+ * The syntax of the cot
function is
* {fn cot(float)}
.
- *
+ *
* @param params The parameters to be used in the call
* @throws FBSQLParseException if there is an error with the parameters
*/
@@ -496,15 +523,15 @@ public static String cot(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function cot : " + params.length);
-
+
return "cot(" + params[0] + ")";
}
-
+
/**
- * Produce a function call for the floor
UDF function.
- * The syntax of the floor
function is
+ * Produce a function call for the floor
UDF function.
+ * The syntax of the floor
function is
* {fn floor(number)}
.
- *
+ *
* @param params The parameters to be used in the call
* @throws FBSQLParseException if there is an error with the parameters
*/
@@ -512,13 +539,13 @@ public static String floor(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function floor : " + params.length);
-
+
return "floor(" + params[0] + ")";
}
-
+
/**
- * Produce a function call for the log10
UDF function.
- * The syntax of the log10
function is
+ * Produce a function call for the log10
UDF function.
+ * The syntax of the log10
function is
* {fn log10(number)}
.
*
* @param params The parameters to be used in the call
@@ -528,13 +555,13 @@ public static String log10(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function log10 : " + params.length);
-
+
return "log10(" + params[0] + ")";
}
-
+
/**
- * Produce a function call for the mod
UDF function.
- * The syntax of the mod
function is
+ * Produce a function call for the mod
UDF function.
+ * The syntax of the mod
function is
* {fn mod(integer1, integer2)}
.
*
* @param params The parameters to be used in the call
@@ -544,12 +571,12 @@ public static String mod(String[] params) throws FBSQLParseException {
if (params.length != 2)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function mod : " + params.length);
-
+
return "mod(" + params[0] + ", " + params[1] + ")";
}
-
+
/**
- * Produce a function call for the pi
UDF function.
+ * Produce a function call for the pi
UDF function.
* The syntax of the pi
function is {fn pi()}
.
*
* @param params The parameters to be used in the call
@@ -559,13 +586,13 @@ public static String pi(String[] params) throws FBSQLParseException {
if (params.length != 0)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function pi : " + params.length);
-
+
return "pi()";
}
-
+
/**
- * Produce a function call for the rand
UDF function.
- * The syntax for the rand
function is
+ * Produce a function call for the rand
UDF function.
+ * The syntax for the rand
function is
* {fn rand()}
.
*
* @param params The parameters to be used in the call
@@ -575,13 +602,13 @@ public static String rand(String[] params) throws FBSQLParseException {
if (params.length != 0)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function rand : " + params.length);
-
+
return "rand()";
}
-
+
/**
- * Produce a function call for the sign
UDF function.
- * The syntax for the sign
function is
+ * Produce a function call for the sign
UDF function.
+ * The syntax for the sign
function is
* {fn sign(number)}
.
*
* @param params The parameters to be used in the call
@@ -591,13 +618,13 @@ public static String sign(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function sign : " + params.length);
-
+
return "sign(" + params[0] + ")";
}
-
+
/**
* Produce a function call for the sin
UDF function.
- * The syntax for the sin
function is
+ * The syntax for the sin
function is
* {fn sin(float)}
.
*
* @param params The parameters to be used in the call
@@ -607,10 +634,10 @@ public static String sin(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function sin : " + params.length);
-
+
return "sin(" + params[0] + ")";
}
-
+
/**
* Produce a function call for the sqrt
UDF function.
* The syntax for the sqrt
function is
@@ -623,10 +650,10 @@ public static String sqrt(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function sqrt : " + params.length);
-
+
return "sqrt(" + params[0] + ")";
}
-
+
/**
* Produce a function call for the tan UDF function.
* The syntax for the
tan
function is
@@ -639,31 +666,31 @@ public static String tan(String[] params) throws FBSQLParseException {
if (params.length != 1)
throw new FBSQLParseException("Incorrect number of " +
"parameters of function tan : " + params.length);
-
+
return "tan(" + params[0] + ")";
}
-
+
/**
* @return Set of JDBC numeric functions supported (as defined in appendix D.1 of JDBC 4.1)
*/
public static Settrue
if the sql
is suspected to contain
- * escaped syntax.
+ * escaped syntax.
*/
private boolean checkForEscapes(String sql) {
return CHECK_ESCAPE_PATTERN.matcher(sql).find();
@@ -97,7 +97,7 @@ private boolean checkForEscapes(String sql) {
* Converts escaped parts in the passed SQL to native representation.
*
* @param sql
- * to parse
+ * to parse
* @return native form of the sql
.
*/
public String parse(final String sql) throws SQLException {
@@ -176,9 +176,9 @@ private void processEscaped(final String escaped, final StringBuilder keyword, f
* Firebird SQL syntax.
*
* @param target
- * Target StringBuilder to append to.
+ * Target StringBuilder to append to.
* @param escaped
- * the part of escaped SQL between the '{' and '}'.
+ * the part of escaped SQL between the '{' and '}'.
*/
private void escapeToNative(final StringBuilder target, final String escaped) throws SQLException {
final StringBuilder keyword = new StringBuilder();
@@ -229,9 +229,9 @@ private void escapeToNative(final StringBuilder target, final String escaped) th
* understandable format.
*
* @param target
- * Target StringBuilder to append to.
+ * Target StringBuilder to append to.
* @param dateStr
- * the date in the 'yyyy-mm-dd' format.
+ * the date in the 'yyyy-mm-dd' format.
*/
private void toDateString(final StringBuilder target, final CharSequence dateStr) throws FBSQLParseException {
// use shorthand date cast (using just the string will not work in all contexts)
@@ -243,9 +243,9 @@ private void toDateString(final StringBuilder target, final CharSequence dateStr
* understandable format.
*
* @param target
- * Target StringBuilder to append to.
+ * Target StringBuilder to append to.
* @param timeStr
- * the date in the 'hh:mm:ss' format.
+ * the date in the 'hh:mm:ss' format.
*/
private void toTimeString(final StringBuilder target, final CharSequence timeStr) throws FBSQLParseException {
// use shorthand time cast (using just the string will not work in all contexts)
@@ -257,9 +257,9 @@ private void toTimeString(final StringBuilder target, final CharSequence timeStr
* Firebird understandable format.
*
* @param target
- * Target StringBuilder to append to.
+ * Target StringBuilder to append to.
* @param timestampStr
- * the date in the 'yyyy-mm-dd hh:mm:ss' format.
+ * the date in the 'yyyy-mm-dd hh:mm:ss' format.
*/
private void toTimestampString(final StringBuilder target, final CharSequence timestampStr)
throws FBSQLParseException {
@@ -272,10 +272,10 @@ private void toTimestampString(final StringBuilder target, final CharSequence ti
* procedure call.
*
* @param target
- * Target StringBuilder to append native procedure call to.
+ * Target StringBuilder to append native procedure call to.
* @param procedureCall
- * part of {call proc_name(...)} without curly braces and "call"
- * word.
+ * part of {call proc_name(...)} without curly braces and "call"
+ * word.
*/
private void convertProcedureCall(final StringBuilder target, final String procedureCall) throws SQLException {
FBEscapedCallParser tempParser = new FBEscapedCallParser(mode);
@@ -290,9 +290,9 @@ private void convertProcedureCall(final StringBuilder target, final String proce
* syntax is the same.
*
* @param target
- * Target StringBuilder to append to.
+ * Target StringBuilder to append to.
* @param outerJoin
- * Outer join text
+ * Outer join text
*/
private void convertOuterJoin(final StringBuilder target, final CharSequence outerJoin) throws FBSQLParseException {
target.append(outerJoin);
@@ -303,7 +303,7 @@ private void convertOuterJoin(final StringBuilder target, final CharSequence out
* escape clause for Firebird.
*
* @param escapeString
- * escape string to convert
+ * escape string to convert
*/
private void convertEscapeString(final StringBuilder target, final CharSequence escapeString) {
target.append("ESCAPE ").append(escapeString);
@@ -325,7 +325,7 @@ private void convertEscapeString(final StringBuilder target, final CharSequence
*
- * This may still result in a UDF function beign used if the UDF matches + * This may still result in a UDF function being used if the UDF matches * naming and arguments of the function (or function template) *
*/ diff --git a/src/main/org/firebirdsql/jdbc/escape/FBSQLParseException.java b/src/main/org/firebirdsql/jdbc/escape/FBSQLParseException.java index 522dddde16..d2e872b634 100644 --- a/src/main/org/firebirdsql/jdbc/escape/FBSQLParseException.java +++ b/src/main/org/firebirdsql/jdbc/escape/FBSQLParseException.java @@ -1,5 +1,5 @@ /* - * Firebird Open Source J2ee connector - jdbc driver + * Firebird Open Source JavaEE connector - JDBC driver * * Distributable under LGPL license. * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html @@ -12,27 +12,33 @@ * This file was created by members of the firebird development team. * All individual contributions remain the Copyright (C) of those * individuals. Contributors to this file are either listed here or - * can be obtained from a CVS history command. + * can be obtained from a source control history command. * * All rights reserved. */ package org.firebirdsql.jdbc.escape; +import org.firebirdsql.jdbc.SQLStateConstants; + import java.sql.SQLSyntaxErrorException; /** * This exception is thrown by FBEscapedParser when it cannot parse the * escaped syntax. - * + * * @author Roman Rokytskyy */ public class FBSQLParseException extends SQLSyntaxErrorException { - + private static final long serialVersionUID = 4217078230221445003L; - public static final String SQL_STATE_INVALID_ESCAPE_SEQ = "42000"; - - public FBSQLParseException(String msg) { - super(msg, SQL_STATE_INVALID_ESCAPE_SEQ); + /** + * @deprecated Use {@link SQLStateConstants#SQL_STATE_INVALID_ESCAPE_SEQ}; will be removed in Jaybird 5. + */ + @Deprecated + public static final String SQL_STATE_INVALID_ESCAPE_SEQ = SQLStateConstants.SQL_STATE_INVALID_ESCAPE_SEQ; + + public FBSQLParseException(String msg) { + super(msg, SQLStateConstants.SQL_STATE_INVALID_ESCAPE_SEQ); } } diff --git a/src/main/org/firebirdsql/jdbc/escape/IntervalMapping.java b/src/main/org/firebirdsql/jdbc/escape/IntervalMapping.java new file mode 100644 index 0000000000..c6dd063972 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/IntervalMapping.java @@ -0,0 +1,75 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +/** + * Helper class to map JDBC interval names to Firebird interval names. + * + * @author Mark Rotteveel + * @since 4.0 + */ +final class IntervalMapping { + + private IntervalMapping() { + // no instances + } + + /** + * Maps the JDBC interval name to a Firebird interval name, unknown values are returned as is. + *+ * This function will map {@code SQL_TSI_QUARTER} to {@code QUARTER}, but current Firebird versions do not + * support {@code QUARTER}. Users of this function should explicitly handle this. + *
+ *+ * Value {@code SQL_TSI_FRAC_SECOND} is not mapped and will be returned as is, users of this function should + * explicitly handle this. The unit of {@code SQL_TSI_FRAC_SECOND} is one billionth of a second (or in other words: + * nanoseconds). See + * ODBC: Time, Date, and Interval Functions. + *
+ * + * @param jdbcIntervalName + * JDBC interval name, value must already be trimmed and upper-cased; {@code null} not supported. + * @return Firebird interval name, or original value if unsupported or unknown + */ + static String getFirebirdInterval(String jdbcIntervalName) { + switch (jdbcIntervalName) { + case "SQL_TSI_SECOND": + return "SECOND"; + case "SQL_TSI_MINUTE": + return "MINUTE"; + case "SQL_TSI_HOUR": + return "HOUR"; + case "SQL_TSI_DAY": + return "DAY"; + case "SQL_TSI_WEEK": + return "WEEK"; + case "SQL_TSI_MONTH": + return "MONTH"; + case "SQL_TSI_QUARTER": + // NOTE QUARTER not supported by Firebird + return "QUARTER"; + case "SQL_TSI_YEAR": + return "YEAR"; + case "SQL_TSI_FRAC_SECOND": + // explicitly not supported, passing as-is + default: + return jdbcIntervalName; + } + } +} diff --git a/src/main/org/firebirdsql/jdbc/escape/LengthFunction.java b/src/main/org/firebirdsql/jdbc/escape/LengthFunction.java new file mode 100644 index 0000000000..d4458832e1 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/LengthFunction.java @@ -0,0 +1,52 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +/** + * Implements the {@code LENGTH} JDBC escape. + *+ * The JDBC specification specifies Number of characters in string, excluding trailing blanks, we right-trim + * ({@code TRIM(TRAILING FROM value)}) the string before passing the value to either {@code CHAR_LENGTH} or + * {@code OCTETS_LENGTH}. As a result, the interpretation of what is a blank depends on the type of value. Is the value + * a normal {@code (VAR)CHAR} (non-octets), then the blank is space (0x20), for a + * {@code VAR(CHAR)CHARACTER SET OCTETS / (VAR)BINARY} the blank is NUL (0x00). This means that the optional + * {@code CHARACTERS|OCTETS} parameter has no influence on which blanks are trimmed, but only whether we count + * characters or bytes after trimming. + *
+ * + * @author Mark Rotteveel + * @since 4.0 + */ +final class LengthFunction implements SQLFunction { + + private static final SQLFunction TRIM_TRAILING = new PatternSQLFunction("TRIM(TRAILING FROM {0})"); + private static final SQLFunction CHARACTER_LENGTH = new CharacterLengthFunction(); + + @Override + public String apply(String... parameters) throws FBSQLParseException { + if (parameters.length < 1 || parameters.length > 2) { + throw new FBSQLParseException( + "Expected 1 or 2 parameters for LENGTH, received " + parameters.length); + } + String[] clonedParameters = parameters.clone(); + clonedParameters[0] = TRIM_TRAILING.apply(parameters); + return CHARACTER_LENGTH.apply(clonedParameters); + } + +} diff --git a/src/main/org/firebirdsql/jdbc/escape/LocateFunction.java b/src/main/org/firebirdsql/jdbc/escape/LocateFunction.java new file mode 100644 index 0000000000..a9ab80cfe0 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/LocateFunction.java @@ -0,0 +1,43 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +/** + * Implements the {@code LOCATE} JDBC escape + * + * @author Mark Rotteveel + * @since 4.0 + */ +final class LocateFunction implements SQLFunction { + + private static final SQLFunction POSITION_FROM_START = new PatternSQLFunction("POSITION({0},{1})"); + private static final SQLFunction POSITION_FROM_INDEX = new PatternSQLFunction("POSITION({0},{1},{2})"); + + @Override + public String apply(String... parameters) throws FBSQLParseException { + switch (parameters.length) { + case 2: + return POSITION_FROM_START.apply(parameters); + case 3: + return POSITION_FROM_INDEX.apply(parameters); + default: + throw new FBSQLParseException("Expected 2 or 3 parameters for LOCATE, received " + parameters.length); + } + } +} diff --git a/src/main/org/firebirdsql/jdbc/escape/PatternSQLFunction.java b/src/main/org/firebirdsql/jdbc/escape/PatternSQLFunction.java new file mode 100644 index 0000000000..a117ce60d7 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/PatternSQLFunction.java @@ -0,0 +1,56 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +import java.text.MessageFormat; + +/** + * Implementation of {@link SQLFunction} with a function pattern. + *+ * The pattern is a {@link java.text.MessageFormat} pattern string. + *
+ * + * @author Mark Rotteveel + * @since 4.0 + */ +final class PatternSQLFunction implements SQLFunction { + + private final String functionPattern; + + /** + * Creates the pattern-based SQL function. + * + * @param functionPattern + * {@link MessageFormat} pattern for the function + */ + PatternSQLFunction(String functionPattern) { + this.functionPattern = functionPattern; + } + + /** + * {@inheritDoc} + *+ * This implementation will not throw a {@code FBSQLParseException} if the wrong number of parameters are passed. + *
+ */ + @Override + public String apply(String... parameters) { + return MessageFormat.format(functionPattern, (Object[]) parameters); + } +} diff --git a/src/main/org/firebirdsql/jdbc/escape/PositionFunction.java b/src/main/org/firebirdsql/jdbc/escape/PositionFunction.java new file mode 100644 index 0000000000..c3fe63686b --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/PositionFunction.java @@ -0,0 +1,55 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +/** + * Implements the {@code POSITION} JDBC escape. + *+ * This implementation only supports single parameter or two parameter variant with value {@code CHARACTERS}. + * If second parameter is {@code OCTETS}, no processing is done. + *
+ * + * @author Mark Rotteveel + * @since 4.0 + */ +final class PositionFunction implements SQLFunction { + + private static final SQLFunction POSITION = new PatternSQLFunction("POSITION({0})"); + + @Override + public String apply(String... parameters) throws FBSQLParseException { + switch (parameters.length) { + case 1: + return POSITION.apply(parameters); + case 2: + String typeParam = parameters[1].trim(); + if (typeParam.equalsIgnoreCase("CHARACTERS")) { + return POSITION.apply(parameters); + } else if (typeParam.equalsIgnoreCase("OCTETS")) { + // We can't handle OCTETS, returning null to pass original (without {fn ...} decoration to server + return null; + } else { + throw new FBSQLParseException( + "Second parameter for POSITION must be OCTETS or CHARACTERS, was " + typeParam); + } + default: + throw new FBSQLParseException("Expected 1 or 2 parameters for POSITION, received " + parameters.length); + } + } +} diff --git a/src/main/org/firebirdsql/jdbc/escape/SQLFunction.java b/src/main/org/firebirdsql/jdbc/escape/SQLFunction.java new file mode 100644 index 0000000000..3c2f94a527 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/SQLFunction.java @@ -0,0 +1,39 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +/** + * SQL function call for processing JDBC function escapes + * + * @author Mark Rotteveel + * @since 4.0 + */ +interface SQLFunction { + + /** + * Render this function call with the supplied parameters. + * + * @param parameters + * Parameters for the function call. + * @return Rendered function call, or {@code null} to fallback to server-side handling + * @throws FBSQLParseException + * Optionally, if the number of parameters or values of parameters are invalid + */ + String apply(String... parameters) throws FBSQLParseException; +} diff --git a/src/main/org/firebirdsql/jdbc/escape/TimestampAddFunction.java b/src/main/org/firebirdsql/jdbc/escape/TimestampAddFunction.java new file mode 100644 index 0000000000..21a0e1e627 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/TimestampAddFunction.java @@ -0,0 +1,52 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +/** + * Implements the {@code TIMESTAMPADD} JDBC escape. + *+ * Value {@code SQL_TSI_FRAC_SECOND} unit is nanoseconds and will be simulated by using {@code MILLISECOND} and + * the count multiplied by {@code 1.0e-6} to convert the value to milliseconds. + *
+ *+ * Value {@code SQL_TSI_QUARTER} will be simulated by using {@code MONTH} and the count multiplied by {@code 3}. + *
+ * + * @author Mark Rotteveel + * @since 4.0 + */ +final class TimestampAddFunction implements SQLFunction { + + @Override + public String apply(String... parameters) throws FBSQLParseException { + if (parameters.length != 3) { + throw new FBSQLParseException("Expected 3 parameters for TIMESTAMPADD, received " + parameters.length); + } + String jdbcIntervalName = parameters[0].trim().toUpperCase(); + if ("SQL_TSI_QUARTER".equals(jdbcIntervalName)) { + return "DATEADD(MONTH,3*(" + parameters[1] + ")," + parameters[2] + ")"; + } else if ("SQL_TSI_FRAC_SECOND".equals(jdbcIntervalName)) { + // See ODBC spec: "where fractional seconds are expressed in billionths of a second." + return "DATEADD(MILLISECOND,1.0e-6*(" + parameters[1] + ")," + parameters[2] + ")"; + } + String fbIntervalName = IntervalMapping.getFirebirdInterval(jdbcIntervalName); + return "DATEADD(" + fbIntervalName + "," + parameters[1] + "," + parameters[2] + ")"; + } + +} diff --git a/src/main/org/firebirdsql/jdbc/escape/TimestampDiffFunction.java b/src/main/org/firebirdsql/jdbc/escape/TimestampDiffFunction.java new file mode 100644 index 0000000000..86a8b09fa2 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/TimestampDiffFunction.java @@ -0,0 +1,54 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +/** + * Implements the {@code TIMESTAMPDIFF} JDBC escape. + *+ * Value {@code SQL_TSI_FRAC_SECOND} unit is nanoseconds and will be simulated by using {@code MILLISECOND} and + * the result multiplied by {@code 1.0e6} to convert the value to nanoseconds. + *
+ *+ * Value {@code SQL_TSI_QUARTER} will be simulated by using {@code MONTH} and the result divided by {@code 3}. + *
+ *+ * Contrary to specified in the JDBC specification, the resulting value will be {@code BIGINT}, not {@code INTEGER}. + *
+ * + * @author Mark Rotteveel + * @since 4.0 + */ +final class TimestampDiffFunction implements SQLFunction { + + @Override + public String apply(String... parameters) throws FBSQLParseException { + if (parameters.length != 3) { + throw new FBSQLParseException("Expected 3 parameters for TIMESTAMPDIFF, received " + parameters.length); + } + String jdbcIntervalName = parameters[0].trim().toUpperCase(); + if ("SQL_TSI_QUARTER".equals(jdbcIntervalName)) { + return "(DATEDIFF(MONTH," + parameters[1] + "," + parameters[2] + ")/3)"; + } else if ("SQL_TSI_FRAC_SECOND".equals(jdbcIntervalName)) { + // See ODBC spec: "where fractional seconds are expressed in billionths of a second." + return "CAST(DATEDIFF(MILLISECOND," + parameters[1] + "," + parameters[2] + ")*1.0e6 AS BIGINT)"; + } + String fbIntervalName = IntervalMapping.getFirebirdInterval(jdbcIntervalName); + return "DATEDIFF(" + fbIntervalName + "," + parameters[1] + "," + parameters[2] + ")"; + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/CharacterLengthFunctionTest.java b/src/test/org/firebirdsql/jdbc/escape/CharacterLengthFunctionTest.java new file mode 100644 index 0000000000..11dd5b3dd2 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/CharacterLengthFunctionTest.java @@ -0,0 +1,95 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertEquals; + +public class CharacterLengthFunctionTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + private static final CharacterLengthFunction function = new CharacterLengthFunction(); + + @Test + public void testSingleParameter_rendersChar_Length() throws Exception { + assertEquals("CHAR_LENGTH('abc')", function.apply("'abc'")); + } + + @Test + public void testSecondParameter_CHARACTERS_rendersChar_Length() throws Exception { + assertEquals("CHAR_LENGTH('abc')", function.apply("'abc'", "CHARACTERS")); + } + + @Test + public void testSecondParameter_characters_rendersChar_Length() throws Exception { + assertEquals("CHAR_LENGTH('abc')", function.apply("'abc'", "characters")); + } + + @Test + public void testSecondParameter_characters_whitespace_rendersChar_Length() throws Exception { + assertEquals("CHAR_LENGTH('abc')", function.apply("'abc'", " characters ")); + } + + @Test + public void testSecondParameter_OCTETS_rendersOctet_Length() throws Exception { + assertEquals("OCTET_LENGTH('abc')", function.apply("'abc'", "OCTETS")); + } + + @Test + public void testSecondParameter_octets_rendersChar_Length() throws Exception { + assertEquals("OCTET_LENGTH('abc')", function.apply("'abc'", "octets")); + } + + @Test + public void testSecondParameter_octets_whitespace_rendersChar_Length() throws Exception { + assertEquals("OCTET_LENGTH('abc')", function.apply("'abc'", " octets ")); + } + + @Test + public void testSecondParameter_wrongValue_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage( + "Second parameter for CHAR(ACTER)_LENGTH must be OCTETS or CHARACTERS, was invalid"); + + function.apply("'abc'", "invalid"); + } + + @Test + public void testZeroParameters_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage( + "Expected 1 or 2 parameters for CHAR(ACTER)_LENGTH, received 0"); + + function.apply(); + } + + @Test + public void testThreeParameters_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage( + "Expected 1 or 2 parameters for CHAR(ACTER)_LENGTH, received 3"); + + function.apply("'abc'", "invalid", "xyz"); + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/ConstantSQLFunctionTest.java b/src/test/org/firebirdsql/jdbc/escape/ConstantSQLFunctionTest.java new file mode 100644 index 0000000000..28d7a16852 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/ConstantSQLFunctionTest.java @@ -0,0 +1,45 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertEquals; + +public class ConstantSQLFunctionTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + private static final ConstantSQLFunction function = new ConstantSQLFunction("name"); + + @Test + public void testWithoutArguments() throws Exception { + assertEquals("name", function.apply()); + } + + @Test + public void testWithArguments() throws Exception { + expectedException.expect(FBSQLParseException.class); + + assertEquals("name", function.apply("argument")); + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/ConvertFunctionParameterizedTest.java b/src/test/org/firebirdsql/jdbc/escape/ConvertFunctionParameterizedTest.java new file mode 100644 index 0000000000..b38d13347b --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/ConvertFunctionParameterizedTest.java @@ -0,0 +1,162 @@ +/* + * Firebird Open Source JavaEE Connector - JDBC Driver + * + * Distributable under LGPL license. + * You may obtain a copy of the License at http://www.gnu.org/copyleft/lgpl.html + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * LGPL License for more details. + * + * This file was created by members of the firebird development team. + * All individual contributions remain the Copyright (C) of those + * individuals. Contributors to this file are either listed here or + * can be obtained from a source control history command. + * + * All rights reserved. + */ +package org.firebirdsql.jdbc.escape; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for {@link ConvertFunction} for the conversion specified in JDBC 4.3 Appendix D and Jaybird extensions. + *+ * For behaviour with invalid lengths, see {@link ConvertFunctionTest} + *
+ * + * @author Mark Rotteveel + */ +@RunWith(Parameterized.class) +public class ConvertFunctionParameterizedTest { + + private static final ConvertFunction function = new ConvertFunction(); + + private final String value; + private final String dataType; + private final String expectedResult; + + public ConvertFunctionParameterizedTest(String value, String dataType, String expectedResult) { + this.value = value; + this.dataType = dataType; + this.expectedResult = expectedResult; + } + + @Test + public void testConvert() throws Exception { + assertEquals(expectedResult, function.apply(value, dataType)); + } + + @Parameterized.Parameters(name = "{index}: convert({0}, {1}) : {2}") + public static Collection