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_` and `` mapping is now supported +- Contrary to the specification, we allow explicit length or precision and +scale parameters +- `(SQL_)VARCHAR`, `(SQL_)NVARCHAR` (and _value_ not a parameter (`?`)) +without explicit length is converted using `TRIM(TRAILING FROM value)`, which +means the result is `VARCHAR` except for blobs where this will result in a blob; +national character set will be lost. If _value_ is a parameter (`?`), and no +length is specified, then a length of 50 will be applied (cast to +`(N)VARCHAR(50)`). +- `(SQL_)CHAR`, `(SQL_)NCHAR` without explicit length will be cast to +`(N)CHAR(50)` +- `(SQL_)BINARY`, and `(SQL_)VARBINARY` without explicit length will be cast +to `(VAR)CHAR(50) CHARACTER SET OCTETS`. With explicit length, +`CHARACTER SET OCTETS` is appended. +- `(SQL_)LONGVARCHAR`, `(SQL_)LONGNVARCHAR`, `(SQL_)CLOB`, `(SQL_)NCLOB` will +be cast to `BLOB SUB_TYPE TEXT`, national character set will be lost +- `(SQL_)LONGVARBINARY`, `(SQL_)BLOB` will be cast to `BLOB SUB_TYPE BINARY` +- `(SQL_)TINYINT` is mapped to `SMALLINT` +- `(SQL_)ROWID` is not supported as length of `DB_KEY` values depend on the +context +- `(SQL_)DECIMAL` and `(SQL_)NUMERIC` without precision and scale are passed +as is, in current Firebird versions, this means the value will be equivalent to +`DECIMAL(9,0)` (which is equivalent to `INTEGER`) +- Unsupported/unknown _SQLtype_ values (or invalid length or precision and +scale) are passed as is to cast, resulting in an error from the Firebird engine +if the resulting cast is invalid + Potentially breaking changes ---------------------------- @@ -706,6 +808,13 @@ The following methods will be removed in Jaybird 5: - `TraceManager.loadConfigurationFromFile(String)`, use standard Java functionality like `new String(Files.readAllBytes(Paths.get(fileName)), )` +### Removal of deprecated constants ### + +The following constants will be removed in Jaybird 5: + +- All `SQL_STATE_*` constants in `FBSQLParseException` will be removed. Use equivalent + constants in `org.firebirdsql.jdbc.SQLStateConstants`. + Compatibility notes =================== diff --git a/src/main/org/firebirdsql/jdbc/SQLStateConstants.java b/src/main/org/firebirdsql/jdbc/SQLStateConstants.java index 9123bd187f..b8df235816 100644 --- a/src/main/org/firebirdsql/jdbc/SQLStateConstants.java +++ b/src/main/org/firebirdsql/jdbc/SQLStateConstants.java @@ -64,6 +64,7 @@ public final class SQLStateConstants { public static final String SQL_STATE_COMM_LINK_FAILURE = "08S01"; public static final String SQL_STATE_SYNTAX_ERROR = "42000"; + public static final String SQL_STATE_INVALID_ESCAPE_SEQ = SQL_STATE_SYNTAX_ERROR; private SQLStateConstants() { // no instances diff --git a/src/main/org/firebirdsql/jdbc/escape/CharacterLengthFunction.java b/src/main/org/firebirdsql/jdbc/escape/CharacterLengthFunction.java new file mode 100644 index 0000000000..70829eac56 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/CharacterLengthFunction.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 CHAR_LENGTH} and {@code CHARACTER_LENGTH} JDBC escape + * + * @author Mark Rotteveel + * @since 4.0 + */ +final class CharacterLengthFunction implements SQLFunction { + + private static final SQLFunction CHAR_LENGTH_FUNCTION = new PatternSQLFunction("CHAR_LENGTH({0})"); + private static final SQLFunction OCTET_LENGTH_FUNCTION = new PatternSQLFunction("OCTET_LENGTH({0})"); + + @Override + public String apply(String... parameters) throws FBSQLParseException { + switch (parameters.length) { + case 1: + return CHAR_LENGTH_FUNCTION.apply(parameters); + case 2: + String typeParam = parameters[1].trim(); + if ("CHARACTERS".equalsIgnoreCase(typeParam)) { + return CHAR_LENGTH_FUNCTION.apply(parameters); + } else if ("OCTETS".equalsIgnoreCase(typeParam)) { + return OCTET_LENGTH_FUNCTION.apply(parameters); + } else { + throw new FBSQLParseException( + "Second parameter for CHAR(ACTER)_LENGTH must be OCTETS or CHARACTERS, was " + parameters[1]); + } + default: + throw new FBSQLParseException( + "Expected 1 or 2 parameters for CHAR(ACTER)_LENGTH, received " + parameters.length); + } + } +} diff --git a/src/main/org/firebirdsql/jdbc/escape/ConstantSQLFunction.java b/src/main/org/firebirdsql/jdbc/escape/ConstantSQLFunction.java new file mode 100644 index 0000000000..63e05b7f92 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/ConstantSQLFunction.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; + +/** + * Implementation of {@link SQLFunction} for constants or functions without parameters. + * + * @author Mark Rotteveel + * @since 4.0 + */ +final class ConstantSQLFunction implements SQLFunction { + + private final String functionConstant; + + ConstantSQLFunction(String functionConstant) { + this.functionConstant = functionConstant; + } + + @Override + public String apply(String... parameters) throws FBSQLParseException { + if (parameters.length > 0) { + throw new FBSQLParseException( + "Invalid number of arguments, expected no arguments, received " + parameters.length); + } + return functionConstant; + } +} diff --git a/src/main/org/firebirdsql/jdbc/escape/ConvertFunction.java b/src/main/org/firebirdsql/jdbc/escape/ConvertFunction.java new file mode 100644 index 0000000000..f807bcfdc6 --- /dev/null +++ b/src/main/org/firebirdsql/jdbc/escape/ConvertFunction.java @@ -0,0 +1,145 @@ +/* + * 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.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Implements the {@code CONVERT} JDBC escape with some caveats. + *

+ * Most important caveats: + *

+ *
    + *
  • Contrary to the specification, we allow explicit length or precision and scale parameters
  • + *
  • {@code VARCHAR}, {@code NVARCHAR} (and value not a parameter ({@code ?})) without explicit length is converted + * using {@code TRIM(TRAILING FROM value)}, which means the result is {@code VARCHAR} except for blobs where this will + * result in a blob; national character set will be lost. If value is a parameter ({@code ?}), and no length is + * specified, then a length of 50 will be applied.
  • + *
  • {@code CHAR}, {@code NCHAR} without explicit length will be cast to {@code (N)CHAR(50)}
  • + *
  • {@code BINARY}, and {@code VARBINARY} without explicit length will be cast to + * {@code (VAR)CHAR(50) CHARACTER SET OCTETS}, with explicit length, {@code CHARACTER SET OCTETS} is appended
  • + *
  • {@code LONGVARCHAR}, {@code LONGNVARCHAR}, {@code CLOB}, {@code NCLOB} will be cast to + * {@code BLOB SUB_TYPE TEXT}, national character set will be lost
  • + *
  • {@code LONGVARBINARY}, {@code BLOB} will be cast to {@code BLOB SUB_TYPE BINARY}
  • + *
  • {@code TINYINT} is mapped to {@code SMALLINT}
  • + *
  • {@code ROWID} is not supported as length of {@code DB_KEY} values depend on the context + * TODO: consider cast to CHAR(8) character set binary or maybe multiples of 8?
  • + *
  • {@code `(SQL_)DECIMAL`} and {@code `(SQL_)NUMERIC`} without precision and scale are passed as is, in current + * Firebird versions, this means the value will be equivalent to {@code DECIMAL(9,0)} (which is equivalent to + * {@code INTEGER})
  • + *
  • Unsupported/unknown datatypes (or invalid length or precision and scale) are passed as is to cast, resulting in + * an error from the Firebird engine if the resulting cast is invalid
  • + *
+ * + * @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 Map FUNCTION_MAP; - + private static final Map FUNCTION_MAP; + /** * Supported numeric functions */ private static final Set SUPPORTED_NUMERIC_FUNCTIONS; - + /** * Supported string functions */ private static final Set SUPPORTED_STRING_FUNCTIONS; - + /** * Supported time and date functions */ private static final Set SUPPORTED_TIME_DATE_FUNCTIONS; - + /** * Supported system functions */ private static final Set SUPPORTED_SYSTEM_FUNCTIONS; - + static { - Map functionMap = new HashMap(); + final Map functionMap = new HashMap<>(71); /* Numeric Functions */ + Map numericFunctionMap = getNumericFunctions(); + SUPPORTED_NUMERIC_FUNCTIONS = Collections.unmodifiableSet(new HashSet<>(numericFunctionMap.keySet())); + functionMap.putAll(numericFunctionMap); + + /* String Functions */ + Map stringFunctionMap = getStringFunctions(); + SUPPORTED_STRING_FUNCTIONS = Collections.unmodifiableSet(new HashSet<>(stringFunctionMap.keySet())); + functionMap.putAll(stringFunctionMap); + + /* Time and Date Functions */ + Map timeDateFunctionMap = getTimeDateFunctions(); + SUPPORTED_TIME_DATE_FUNCTIONS = Collections.unmodifiableSet(new HashSet<>(timeDateFunctionMap.keySet())); + functionMap.putAll(timeDateFunctionMap); + + /* System Functions */ + Map systemFunctionMap = getSystemFunctions(); + SUPPORTED_SYSTEM_FUNCTIONS = Collections.unmodifiableSet(new HashSet<>(systemFunctionMap.keySet())); + functionMap.putAll(systemFunctionMap); + + /* Conversion Functions */ + functionMap.put("CONVERT", new ConvertFunction()); + + // Unsupported functions defined in appendix D that might accidentally work due to UDFs + // Numerics + functionMap.put("RAND", null); + + // String + functionMap.put("DIFFERENCE", null); + functionMap.put("SOUNDEX", null); + + // Time and date + functionMap.put("DAYNAME", null); // TODO Implement with DECODE or CASE? + functionMap.put("MONTHNAME", null); // TODO Implement with DECODE or CASE? + + // System + functionMap.put("DATABASE", null); // TODO Implement with RDB$GET_CONTEXT + + FUNCTION_MAP = Collections.unmodifiableMap(new HashMap<>(functionMap)); + } + + private static Map getNumericFunctions() { + Map functionMap = new HashMap<>(32); functionMap.put("ABS", null); functionMap.put("ACOS", null); functionMap.put("ASIN", null); @@ -68,118 +110,99 @@ public class FBEscapedFunctionHelper { functionMap.put("CEILING", null); functionMap.put("COS", null); functionMap.put("COT", null); + functionMap.put("DEGREES", new PatternSQLFunction("(({0})*180.0/PI())")); functionMap.put("EXP", null); functionMap.put("FLOOR", null); - functionMap.put("LOG", "LN({0})"); + functionMap.put("LOG", new PatternSQLFunction("LN({0})")); functionMap.put("LOG10", null); functionMap.put("MOD", null); functionMap.put("PI", null); functionMap.put("POWER", null); + functionMap.put("RADIANS", new PatternSQLFunction("(({0})*PI()/180.0)")); functionMap.put("ROUND", null); functionMap.put("SIGN", null); functionMap.put("SIN", null); functionMap.put("SQRT", null); functionMap.put("TAN", null); - functionMap.put("TRUNCATE", "TRUNC({0},{1})"); - - SUPPORTED_NUMERIC_FUNCTIONS = Collections.unmodifiableSet(new HashSet(functionMap.keySet())); - - /* String Functions */ - functionMap.put("ASCII", "ASCII_VAL({0})"); - functionMap.put("CHAR", "ASCII_CHAR({0})"); - // TODO support difference between CHARACTER and OCTETS optional param - functionMap.put("CHAR_LENGTH", "CHAR_LENGTH({0})"); - functionMap.put("CHARACTER_LENGTH", "CHAR_LENGTH({0})"); - functionMap.put("CONCAT", "{0}||{1}"); - functionMap.put("INSERT", "OVERLAY({0} PLACING {3} FROM {1} FOR {2})"); - functionMap.put("LCASE", "LOWER({0})"); + functionMap.put("TRUNCATE", new PatternSQLFunction("TRUNC({0},{1})")); + + return functionMap; + } + + private static Map getStringFunctions() { + Map functionMap = new HashMap<>(32); + functionMap.put("ASCII", new PatternSQLFunction("ASCII_VAL({0})")); + functionMap.put("CHAR", new PatternSQLFunction("ASCII_CHAR({0})")); + CharacterLengthFunction characterLengthFunction = new CharacterLengthFunction(); + functionMap.put("CHAR_LENGTH", characterLengthFunction); + functionMap.put("CHARACTER_LENGTH", characterLengthFunction); + functionMap.put("CONCAT", new PatternSQLFunction("({0}||{1})")); + functionMap.put("INSERT", new PatternSQLFunction("OVERLAY({0} PLACING {3} FROM {1} FOR {2})")); + functionMap.put("LCASE", new PatternSQLFunction("LOWER({0})")); functionMap.put("LEFT", null); - // TODO support difference between CHARACTER and OCTETS optional param - functionMap.put("LENGTH", "CHAR_LENGTH(TRIM(TRAILING FROM {0}))"); - // TODO Support variant without start position (required for JavaEE compliance see 6.2 of JDBC 4.1 spec - functionMap.put("LOCATE", "POSITION({0},{1},{2})"); - functionMap.put("LTRIM", "TRIM(LEADING FROM {0})"); + functionMap.put("LENGTH", new LengthFunction()); + functionMap.put("LOCATE", new LocateFunction()); + functionMap.put("LTRIM", new PatternSQLFunction("TRIM(LEADING FROM {0})")); functionMap.put("OCTET_LENGTH", null); - functionMap.put("POSITION", null); - functionMap.put("REPEAT", "RPAD('''',{1},{0})"); + // NOTE We're only supporting CHARACTERS optional parameter (OCTETS unclear or technically not possible) + functionMap.put("POSITION", new PositionFunction()); + // Doubling of single quotes due to MessageFormat requirements + functionMap.put("REPEAT", new PatternSQLFunction("RPAD('''',{1},{0})")); functionMap.put("REPLACE", null); functionMap.put("RIGHT", null); - functionMap.put("RTRIM", "TRIM(TRAILING FROM {0})"); - functionMap.put("SPACE", "RPAD('''',{0})"); - functionMap.put("SUBSTRING", "SUBSTRING({0} FROM {1} FOR {2})"); - functionMap.put("UCASE", "UPPER({0})"); - - Set supportedStringFunctions = new HashSet(functionMap.keySet()); - supportedStringFunctions.removeAll(SUPPORTED_NUMERIC_FUNCTIONS); - SUPPORTED_STRING_FUNCTIONS = Collections.unmodifiableSet(new HashSet(supportedStringFunctions)); - - /* Time and Date Functions */ - functionMap.put("CURRENT_DATE", "CURRENT_DATE"); - functionMap.put("CURRENT_TIME", "CURRENT_TIME"); - functionMap.put("CURRENT_TIMESTAMP", "CURRENT_TIMESTAMP"); - functionMap.put("CURDATE", "CURRENT_DATE"); - functionMap.put("CURTIME", "CURRENT_TIME"); - functionMap.put("DAYOFMONTH", "EXTRACT(DAY FROM {0})"); - functionMap.put("DAYOFWEEK", "EXTRACT(WEEKDAY FROM {0})+1"); - functionMap.put("DAYOFYEAR", "EXTRACT(YEARDAY FROM {0})+1"); + functionMap.put("RTRIM", new PatternSQLFunction("TRIM(TRAILING FROM {0})")); + // Doubling of single quotes due to MessageFormat requirements + functionMap.put("SPACE", new PatternSQLFunction("RPAD('''',{0})")); + // NOTE We're not discerning between optional CHARACTERS / OCTETS parameter (technically not possible?) + functionMap.put("SUBSTRING", new PatternSQLFunction("SUBSTRING({0} FROM {1} FOR {2})")); + functionMap.put("UCASE", new PatternSQLFunction("UPPER({0})")); + + return functionMap; + } + + private static Map getTimeDateFunctions() { + Map functionMap = new HashMap<>(32); + ConstantSQLFunction currentDate = new ConstantSQLFunction("CURRENT_DATE"); + functionMap.put("CURRENT_DATE", currentDate); + ConstantSQLFunction currentTime = new ConstantSQLFunction("CURRENT_TIME"); + functionMap.put("CURRENT_TIME", currentTime); + ConstantSQLFunction currentTimestamp = new ConstantSQLFunction("CURRENT_TIMESTAMP"); + functionMap.put("CURRENT_TIMESTAMP", currentTimestamp); + functionMap.put("CURDATE", currentDate); + functionMap.put("CURTIME", currentTime); + functionMap.put("DAYOFMONTH", new PatternSQLFunction("EXTRACT(DAY FROM {0})")); + functionMap.put("DAYOFWEEK", new PatternSQLFunction("EXTRACT(WEEKDAY FROM {0})+1")); + functionMap.put("DAYOFYEAR", new PatternSQLFunction("EXTRACT(YEARDAY FROM {0})+1")); functionMap.put("EXTRACT", null); - functionMap.put("HOUR", "EXTRACT(HOUR FROM {0})"); - functionMap.put("MINUTE", "EXTRACT(MINUTE FROM {0})"); - functionMap.put("MONTH", "EXTRACT(MONTH FROM {0})"); - functionMap.put("NOW", "CURRENT_TIMESTAMP"); - functionMap.put("SECOND", "EXTRACT(SECOND FROM {0})"); - functionMap.put("TIMESTAMPADD", null); - functionMap.put("TIMESTAMPDIFF", null); - functionMap.put("WEEK", "EXTRACT(WEEK FROM {0})"); - functionMap.put("YEAR", "EXTRACT(YEAR FROM {0})"); - - Set supportedTimeDateFunctions = new HashSet(functionMap.keySet()); - supportedTimeDateFunctions.removeAll(SUPPORTED_NUMERIC_FUNCTIONS); - supportedTimeDateFunctions.removeAll(SUPPORTED_STRING_FUNCTIONS); - SUPPORTED_TIME_DATE_FUNCTIONS = Collections.unmodifiableSet(new HashSet(supportedTimeDateFunctions)); - - /* System Functions */ - functionMap.put("IFNULL", "COALESCE({0}, {1})"); - functionMap.put("USER", "USER"); - - Set supportedSystemFunctions = new HashSet(functionMap.keySet()); - supportedSystemFunctions.removeAll(SUPPORTED_NUMERIC_FUNCTIONS); - supportedSystemFunctions.removeAll(SUPPORTED_STRING_FUNCTIONS); - supportedSystemFunctions.removeAll(SUPPORTED_TIME_DATE_FUNCTIONS); - SUPPORTED_SYSTEM_FUNCTIONS = Collections.unmodifiableSet(new HashSet(supportedSystemFunctions)); - - /* Conversion Functions */ - // TODO This does not support the conversion with SQL_ prefix in appendix D - // TODO Should work without specifying size on CHAR and VARCHAR - functionMap.put("CONVERT", "CAST({0} AS {1})"); - - // Unsupported functions defined in appendix D that might accidentally work due to UDFs - // Numerics - functionMap.put("DEGREES", null); - functionMap.put("RADIANS", null); - functionMap.put("RAND", null); - - // String - functionMap.put("DIFFERENCE", null); - functionMap.put("SOUNDEX", null); - - // Time and date - functionMap.put("DAYNAME", null); - functionMap.put("MONTHNAME", null); - functionMap.put("QUARTER", null); - - // System - functionMap.put("DATABASE", null); - - FUNCTION_MAP = Collections.unmodifiableMap(functionMap); + functionMap.put("HOUR", new PatternSQLFunction("EXTRACT(HOUR FROM {0})")); + functionMap.put("MINUTE", new PatternSQLFunction("EXTRACT(MINUTE FROM {0})")); + functionMap.put("MONTH", new PatternSQLFunction("EXTRACT(MONTH FROM {0})")); + functionMap.put("NOW", currentTimestamp); + functionMap.put("QUARTER", new PatternSQLFunction("(1+(EXTRACT(MONTH FROM {0})-1)/3)")); + functionMap.put("SECOND", new PatternSQLFunction("EXTRACT(SECOND FROM {0})")); + functionMap.put("TIMESTAMPADD", new TimestampAddFunction()); + functionMap.put("TIMESTAMPDIFF", new TimestampDiffFunction()); + functionMap.put("WEEK", new PatternSQLFunction("EXTRACT(WEEK FROM {0})")); + functionMap.put("YEAR", new PatternSQLFunction("EXTRACT(YEAR FROM {0})")); + + return functionMap; } - + + private static Map getSystemFunctions() { + Map functionMap = new HashMap<>(2, 1.0f); + functionMap.put("IFNULL", new PatternSQLFunction("COALESCE({0}, {1})")); + functionMap.put("USER", new ConstantSQLFunction("USER")); + + return functionMap; + } + /** * Simple syntax check if function is specified in form "name(...)". - * - * @param functionCall string representing function call. - * - * @throws FBSQLParseException if simple syntax check failed. + * + * @param functionCall + * string representing function call. + * @throws FBSQLParseException + * if simple syntax check failed. */ private static void checkSyntax(String functionCall) throws FBSQLParseException { // NOTE: Some function calls don't require parenthesis eg CURRENT_TIMESTAMP @@ -187,33 +210,33 @@ private static void checkSyntax(String functionCall) throws FBSQLParseException if (parenthesisStart != -1 && functionCall.charAt(functionCall.length() - 1) != ')') throw new FBSQLParseException("No closing parenthesis found, not a function call."); } - + /** * Extract function name from the function call. - * - * @param functionCall escaped function call. - * + * + * @param functionCall + * escaped function call. * @return name of the function. - * - * @throws FBSQLParseException if parse error occurs. + * @throws FBSQLParseException + * if parse error occurs. */ public static String parseFunction(String functionCall) throws FBSQLParseException { functionCall = functionCall.trim(); checkSyntax(functionCall); int parenthesisStart = functionCall.indexOf('('); - + return parenthesisStart != -1 ? functionCall.substring(0, parenthesisStart) : functionCall; } - + /** - * Extract function arguments from the function call. This method parses + * Extract function arguments from the function call. This method parses * escaped function call string and extracts function parameters from it. - * - * @param functionCall escaped function call. - * + * + * @param functionCall + * escaped function call. * @return list of parameters of the function. - * - * @throws FBSQLParseException if parse error occurs. + * @throws FBSQLParseException + * if parse error occurs. */ public static List parseArguments(String functionCall) throws FBSQLParseException { functionCall = functionCall.trim(); @@ -225,7 +248,7 @@ public static List parseArguments(String functionCall) throws FBSQLParse } final String paramsString = functionCall.substring(parenthesisStart + 1, functionCall.length() - 1); - final List params = new ArrayList(); + final List params = new ArrayList<>(); final StringBuilder sb = new StringBuilder(); boolean inQuotes = false; boolean inDoubleQuotes = false; @@ -250,13 +273,13 @@ public static List parseArguments(String functionCall) throws FBSQLParse case '\'': sb.append(currentChar); if (!inDoubleQuotes) - inQuotes = !inQuotes; + inQuotes = !inQuotes; coalesceSpace = false; break; case '"': sb.append(currentChar); if (!inQuotes) - inDoubleQuotes = !inDoubleQuotes; + inDoubleQuotes = !inDoubleQuotes; coalesceSpace = false; break; case '(': @@ -297,7 +320,7 @@ public static List parseArguments(String functionCall) throws FBSQLParse // add last parameter if present if (sb.length() > 0) - params.add(sb.toString()); + params.add(sb.toString()); // after processing all parameters all string literals should be closed if (inQuotes || inDoubleQuotes) { @@ -309,21 +332,21 @@ public static List parseArguments(String functionCall) throws FBSQLParse return params; } - + /** * Convert escaped function call using function template. - * - * @param functionCall escaped function call. - * + * + * @param functionCall + * escaped function call. * @return server-side representation of the function call or null * 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 Set getSupportedNumericFunctions() { return SUPPORTED_NUMERIC_FUNCTIONS; } - + /** * @return Set of JDBC string functions supported (as defined in appendix D.2 of JDBC 4.1) */ public static Set getSupportedStringFunctions() { return SUPPORTED_STRING_FUNCTIONS; } - + /** * @return Set of JDBC time and date functions supported (as defined in appendix D.3 of JDBC 4.1) */ public static Set getSupportedTimeDateFunctions() { return SUPPORTED_TIME_DATE_FUNCTIONS; } - + /** * @return Set of JDBC system functions supported (as defined in appendix D.4 of JDBC 4.1) */ diff --git a/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java b/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java index 3183c82172..5c2c0171aa 100644 --- a/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java +++ b/src/main/org/firebirdsql/jdbc/escape/FBEscapedParser.java @@ -55,7 +55,7 @@ public final class FBEscapedParser { /** * Regular expression to check for existence of JDBC escapes, is used to * stop processing the entire SQL statement if it does not contain any of - * the substrings. + * the escape introducers. */ private static final Pattern CHECK_ESCAPE_PATTERN = Pattern.compile( "\\{(?:(?:\\?\\s*=\\s*)?call|d|ts?|escape|fn|oj|limit)\\s", @@ -69,8 +69,8 @@ public final class FBEscapedParser { * Creates a parser for JDBC escaped strings. * * @param mode - * One of {@link EscapeParserMode#USE_BUILT_IN} or - * {@link EscapeParserMode#USE_STANDARD_UDF} + * One of {@link EscapeParserMode#USE_BUILT_IN} or + * {@link EscapeParserMode#USE_STANDARD_UDF} */ public FBEscapedParser(EscapeParserMode mode) { this.mode = mode; @@ -85,9 +85,9 @@ public FBEscapedParser(EscapeParserMode mode) { * perform complete SQL parsing. * * @param sql - * to test + * to test * @return true 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 *

* * @param limitClause - * Limit clause + * Limit clause */ private void convertLimitString(final StringBuilder target, final CharSequence limitClause) throws FBSQLParseException { @@ -348,11 +348,11 @@ private void convertLimitString(final StringBuilder target, final CharSequence l * we do not change anything here, we hope that all UDF are defined. * * @param target - * Target StringBuilder to append to. + * Target StringBuilder to append to. * @param escapedFunction - * escaped function call + * escaped function call * @throws FBSQLParseException - * if something was wrong. + * if something was wrong. */ private void convertEscapedFunction(final StringBuilder target, final CharSequence escapedFunction) throws FBSQLParseException { @@ -481,10 +481,10 @@ protected ParserState nextState(char inputChar) throws FBSQLParseException { * Decides on the next ParserState based on the input character. * * @param inputChar - * Input character + * Input character * @return Next state * @throws FBSQLParseException - * For incorrect character for current state during parsing + * For incorrect character for current state during parsing */ protected abstract ParserState nextState(char inputChar) throws FBSQLParseException; } @@ -493,7 +493,7 @@ public enum EscapeParserMode { /** * Use built-in functions if available. *

- * 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 convertTestCases() { + return Arrays.asList( +//@formatter:off + // JDBC 4.3 Appendix D cases + /* 0 */ testCase("val", "SQL_BIGINT", "CAST(val AS BIGINT)"), + /* 1 */ testCase("val", "sql_bigint", "CAST(val AS BIGINT)"), + /* 2 */ testCase("val", "BIGINT", "CAST(val AS BIGINT)"), + /* 3 */ testCase("val", "bigint", "CAST(val AS BIGINT)"), + // Case sensitivity considered tested sufficiently, further tests will only check uppercase + /* 4 */ testCase("val", "SQL_BINARY", "CAST(val AS CHAR(50) CHARACTER SET OCTETS)"), + /* 5 */ testCase("val", "BINARY", "CAST(val AS CHAR(50) CHARACTER SET OCTETS)"), + // NOTE: BIT not supported by Firebird + /* 6 */ testCase("val", "SQL_BIT", "CAST(val AS BIT)"), + /* 7 */ testCase("val", "BIT", "CAST(val AS BIT)"), + /* 8 */ testCase("val", "SQL_BLOB", "CAST(val AS BLOB SUB_TYPE BINARY)"), + /* 9 */ testCase("val", "BLOB", "CAST(val AS BLOB SUB_TYPE BINARY)"), + /* 10 */ testCase("val", "SQL_BOOLEAN", "CAST(val AS BOOLEAN)"), + /* 11 */ testCase("val", "BOOLEAN", "CAST(val AS BOOLEAN)"), + /* 12 */ testCase("val", "SQL_CHAR", "CAST(val AS CHAR(50))"), + /* 13 */ testCase("val", "CHAR", "CAST(val AS CHAR(50))"), + /* 14 */ testCase("val", "SQL_CLOB", "CAST(val AS BLOB SUB_TYPE TEXT)"), + /* 15 */ testCase("val", "CLOB", "CAST(val AS BLOB SUB_TYPE TEXT)"), + /* 16 */ testCase("val", "SQL_DATE", "CAST(val AS DATE)"), + /* 17 */ testCase("val", "DATE", "CAST(val AS DATE)"), + /* 18 */ testCase("val", "SQL_DECIMAL", "CAST(val AS DECIMAL)"), + /* 19 */ testCase("val", "DECIMAL", "CAST(val AS DECIMAL)"), + // NOTE: DATALINK not supported by Firebird + /* 20 */ testCase("val", "SQL_DATALINK", "CAST(val AS DATALINK)"), + /* 21 */ testCase("val", "DATALINK", "CAST(val AS DATALINK)"), + /* 22 */ testCase("val", "SQL_DOUBLE", "CAST(val AS DOUBLE PRECISION)"), + /* 23 */ testCase("val", "DOUBLE", "CAST(val AS DOUBLE PRECISION)"), + /* 24 */ testCase("val", "SQL_FLOAT", "CAST(val AS FLOAT)"), + /* 25 */ testCase("val", "FLOAT", "CAST(val AS FLOAT)"), + /* 26 */ testCase("val", "SQL_INTEGER", "CAST(val AS INTEGER)"), + /* 27 */ testCase("val", "INTEGER", "CAST(val AS INTEGER)"), + /* 28 */ testCase("val", "SQL_LONGVARBINARY", "CAST(val AS BLOB SUB_TYPE BINARY)"), + /* 29 */ testCase("val", "LONGVARBINARY", "CAST(val AS BLOB SUB_TYPE BINARY)"), + /* 30 */ testCase("val", "SQL_LONGNVARCHAR", "CAST(val AS BLOB SUB_TYPE TEXT)"), + /* 31 */ testCase("val", "LONGNVARCHAR", "CAST(val AS BLOB SUB_TYPE TEXT)"), + /* 32 */ testCase("val", "SQL_LONGVARCHAR", "CAST(val AS BLOB SUB_TYPE TEXT)"), + /* 33 */ testCase("val", "LONGVARCHAR", "CAST(val AS BLOB SUB_TYPE TEXT)"), + /* 34 */ testCase("val", "SQL_NCHAR", "CAST(val AS NCHAR(50))"), + /* 35 */ testCase("val", "NCHAR", "CAST(val AS NCHAR(50))"), + /* 36 */ testCase("val", "SQL_NCLOB", "CAST(val AS BLOB SUB_TYPE TEXT)"), + /* 37 */ testCase("val", "NCLOB", "CAST(val AS BLOB SUB_TYPE TEXT)"), + /* 38 */ testCase("val", "SQL_NUMERIC", "CAST(val AS NUMERIC)"), + /* 39 */ testCase("val", "NUMERIC", "CAST(val AS NUMERIC)"), + // See also cases 76, 77 + /* 40 */ testCase("val", "SQL_NVARCHAR", "TRIM(TRAILING FROM val)"), + /* 41 */ testCase("val", "NVARCHAR", "TRIM(TRAILING FROM val)"), + /* 42 */ testCase("val", "SQL_REAL", "CAST(val AS REAL)"), + /* 43 */ testCase("val", "REAL", "CAST(val AS REAL)"), + // TODO: Change to CHAR(8) CHARACTER SET OCTETS? + /* 44 */ testCase("val", "SQL_ROWID", "CAST(val AS ROWID)"), + /* 45 */ testCase("val", "ROWID", "CAST(val AS ROWID)"), + // NOTE: SQLXML not supported by Firebird + /* 46 */ testCase("val", "SQL_SQLXML", "CAST(val AS SQLXML)"), + /* 47 */ testCase("val", "SQLXML", "CAST(val AS SQLXML)"), + /* 48 */ testCase("val", "SQL_SMALLINT", "CAST(val AS SMALLINT)"), + /* 49 */ testCase("val", "SMALLINT", "CAST(val AS SMALLINT)"), + /* 50 */ testCase("val", "SQL_TIME", "CAST(val AS TIME)"), + /* 51 */ testCase("val", "TIME", "CAST(val AS TIME)"), + /* 52 */ testCase("val", "SQL_TIMESTAMP", "CAST(val AS TIMESTAMP)"), + /* 53 */ testCase("val", "TIMESTAMP", "CAST(val AS TIMESTAMP)"), + /* 54 */ testCase("val", "SQL_TINYINT", "CAST(val AS SMALLINT)"), + /* 55 */ testCase("val", "TINYINT", "CAST(val AS SMALLINT)"), + /* 56 */ testCase("val", "SQL_VARBINARY", "CAST(val AS VARCHAR(50) CHARACTER SET OCTETS)"), + /* 57 */ testCase("val", "VARBINARY", "CAST(val AS VARCHAR(50) CHARACTER SET OCTETS)"), + // See also cases 74, 75 + /* 58 */ testCase("val", "SQL_VARCHAR", "TRIM(TRAILING FROM val)"), + /* 59 */ testCase("val", "VARCHAR", "TRIM(TRAILING FROM val)"), + // Jaybird-specific extensions; not checking SQL_-prefix variant + /* 60 */ testCase("val", "BINARY(10)", "CAST(val AS CHAR(10) CHARACTER SET OCTETS)"), + // Pass-through datatype that doesn't match pattern + /* 61 */ testCase("val", "BLOB SUB_TYPE -1", "CAST(val AS BLOB SUB_TYPE -1)"), + /* 62 */ testCase("val", "CHAR(9)", "CAST(val AS CHAR(9))"), + // Pass-through datatype that doesn't match pattern + /* 63 */ testCase("val", "CHAR(9) CHARACTER SET UTF8", "CAST(val AS CHAR(9) CHARACTER SET UTF8)"), + /* 64 */ testCase("val", "DECIMAL(12,2)", "CAST(val AS DECIMAL(12,2))"), + /* 65 */ testCase("val", "FLOAT(11)", "CAST(val AS FLOAT(11))"), + /* 66 */ testCase("val", "NCHAR(15)", "CAST(val AS NCHAR(15))"), + /* 67 */ testCase("val", "NUMERIC(8, 2)", "CAST(val AS NUMERIC(8, 2))"), + /* 68 */ testCase("val", "NVARCHAR(33)", "CAST(val AS NVARCHAR(33))"), + /* 69 */ testCase("val", "VARBINARY(99)", "CAST(val AS VARCHAR(99) CHARACTER SET OCTETS)"), + // Pass-through datatype that doesn't match pattern + /* 70 */ testCase("val", "VARBINARY(99) invalid", "CAST(val AS VARBINARY(99) invalid)"), + /* 71 */ testCase("val", "VARCHAR(100)", "CAST(val AS VARCHAR(100))"), + // Pass-through datatype that doesn't match pattern + /* 72 */ testCase("val", "VARCHAR(75) CHARACTER SET UTF8", "CAST(val AS VARCHAR(75) CHARACTER SET UTF8)"), + // Some other exceptional behaviour + // Pass-through datatype that doesn't match pattern + /* 73 */ testCase("val", "DOUBLE PRECISION", "CAST(val AS DOUBLE PRECISION)"), + /* 74 */ testCase("?", "VARCHAR", "CAST(? AS VARCHAR(50))"), + /* 75 */ testCase("?", "VARCHAR(75)", "CAST(? AS VARCHAR(75))"), + /* 76 */ testCase("?", "NVARCHAR", "CAST(? AS NVARCHAR(50))"), + /* 77 */ testCase("?", "NVARCHAR(75)", "CAST(? AS NVARCHAR(75))") +//@formatter:on + ); + } + + private static Object[] testCase(String value, String dataType, String expectedResult) { + return new Object[] { value, dataType, expectedResult }; + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/ConvertFunctionTest.java b/src/test/org/firebirdsql/jdbc/escape/ConvertFunctionTest.java new file mode 100644 index 0000000000..bd6b3affa6 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/ConvertFunctionTest.java @@ -0,0 +1,63 @@ +/* + * 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; + +/** + * Tests for {@link ConvertFunction}. + *

+ * Also see {@link ConvertFunctionParameterizedTest}. + *

+ */ +public class ConvertFunctionTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + private static final ConvertFunction function = new ConvertFunction(); + + // Happy path tested in ConvertFunctionParameterizedTest + + @Test + public void testZeroParameters_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 2 parameters for CONVERT, received 0"); + + function.apply(); + } + + @Test + public void testOneParameter_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 2 parameters for CONVERT, received 1"); + + function.apply("val"); + } + + @Test + public void testThreeParameter_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 2 parameters for CONVERT, received 3"); + + function.apply("val", "BIGINT", "XYZ"); + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedCallParser.java b/src/test/org/firebirdsql/jdbc/escape/FBEscapedCallParserTest.java similarity index 97% rename from src/test/org/firebirdsql/jdbc/escape/TestFBEscapedCallParser.java rename to src/test/org/firebirdsql/jdbc/escape/FBEscapedCallParserTest.java index d3ea236013..9788a2a0a1 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedCallParser.java +++ b/src/test/org/firebirdsql/jdbc/escape/FBEscapedCallParserTest.java @@ -27,7 +27,8 @@ import java.sql.SQLException; import java.sql.Types; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; /** * Tests for {@link FBEscapedCallParser}. @@ -35,7 +36,7 @@ * @author Roman Rokytskyy * @author Mark Rotteveel */ -public class TestFBEscapedCallParser { +public class FBEscapedCallParserTest { public static final String CALL_TEST_1 = "{call my_proc(?, {d 01-12-11})}"; diff --git a/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedFunctionHelper.java b/src/test/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelperTest.java similarity index 59% rename from src/test/org/firebirdsql/jdbc/escape/TestFBEscapedFunctionHelper.java rename to src/test/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelperTest.java index 5b6bc0b3b9..1d8a98b7e4 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedFunctionHelper.java +++ b/src/test/org/firebirdsql/jdbc/escape/FBEscapedFunctionHelperTest.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,81 +12,67 @@ * 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 org.junit.Test; + import java.sql.SQLException; -import java.util.ArrayList; +import java.util.Arrays; import java.util.List; -import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; +import static org.junit.Assert.assertEquals; -import junit.framework.TestCase; - -/** - * @author Roman Rokytskyy - */ -public class TestFBEscapedFunctionHelper extends TestCase { +public class FBEscapedFunctionHelperTest { /** - * Create instance of this class. - * - * @param name name of the test case. - */ - public TestFBEscapedFunctionHelper(String name) { - super(name); - } - - /** - * Test function call that contains quoted identifiers as well as + * Test function call that contains quoted identifiers as well as * commas and double quotes in string literals. */ - public static final String ESCAPED_FUNCTION_CALL = "test(\"arg1\", 12, ',\"')"; - public static final String ESCAPED_FUNCTION_NAME = "test"; - public static final List ESCAPED_FUNCTION_PARAMS = new ArrayList(); - static { - ESCAPED_FUNCTION_PARAMS.add("\"arg1\""); - ESCAPED_FUNCTION_PARAMS.add("12"); - ESCAPED_FUNCTION_PARAMS.add("',\"'"); - } - + private static final String ESCAPED_FUNCTION_CALL = "test(\"arg1\", 12, ',\"')"; + private static final String ESCAPED_FUNCTION_NAME = "test"; + private static final List ESCAPED_FUNCTION_PARAMS = Arrays.asList("\"arg1\"", "12", "',\"'"); + private static final String LCASE_FUNCTION_CALL = "{fn lcase('some name')}"; + private static final String LCASE_FUNCTION_TEST = "LOWER('some name')"; + private static final String UCASE_FUNCTION_CALL = "{fn ucase(some_identifier)}"; + private static final String UCASE_FUNCTION_TEST = "UPPER(some_identifier)"; + + @Test public void testParseArguments() throws SQLException { List parsedParams = FBEscapedFunctionHelper.parseArguments(ESCAPED_FUNCTION_CALL); - - assertEquals("Parsed params should be equal to the test ones.", - ESCAPED_FUNCTION_PARAMS, parsedParams); + + assertEquals("Parsed params should be equal to the test ones.", + ESCAPED_FUNCTION_PARAMS, parsedParams); } - + /** * Test if function name is parsed correctly. */ + @Test public void testParseName() throws SQLException { String name = FBEscapedFunctionHelper.parseFunction(ESCAPED_FUNCTION_CALL); - + assertEquals("Parsed function name should be equal to the test one.", - ESCAPED_FUNCTION_NAME, name); + ESCAPED_FUNCTION_NAME, name); } - - public static final String LCASE_FUNCTION_CALL = "{fn lcase('some name')}"; - public static final String LCASE_FUNCTION_TEST = "LOWER('some name')"; - - public static final String UCASE_FUNCTION_CALL = "{fn ucase(some_identifier)}"; - public static final String UCASE_FUNCTION_TEST = "UPPER(some_identifier)"; - + + @Test public void testEscapedFunctionCall() throws SQLException { FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); - + String ucaseTest = parser.parse(UCASE_FUNCTION_CALL); assertEquals("ucase function parsing should be correct", UCASE_FUNCTION_TEST, ucaseTest); } - + + @Test public void testUseStandardUdf() throws SQLException { FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_STANDARD_UDF); - + String lcaseTest = parser.parse(LCASE_FUNCTION_CALL); assertEquals("lcase function parsing should be correct", LCASE_FUNCTION_TEST, lcaseTest); diff --git a/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java b/src/test/org/firebirdsql/jdbc/escape/FBEscapedParserTest.java similarity index 96% rename from src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java rename to src/test/org/firebirdsql/jdbc/escape/FBEscapedParserTest.java index 1a02a1ca39..85d07205f5 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestFBEscapedParser.java +++ b/src/test/org/firebirdsql/jdbc/escape/FBEscapedParserTest.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,138 +12,138 @@ * 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 static org.junit.Assert.*; - import org.firebirdsql.jdbc.escape.FBEscapedParser.EscapeParserMode; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; +import static org.junit.Assert.assertEquals; + /** * Tests for {@link FBEscapedParser}. - * + * * @author Mark Rotteveel */ -public class TestFBEscapedParser { +public class FBEscapedParserTest { @Rule public final ExpectedException expectedException = ExpectedException.none(); - + @Test public void testStringWithoutEscapes() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table WHERE x = 'xyz'"; - + String parseResult = parser.parse(input); assertEquals("Expected output identical to input for string without escapes", input, parseResult); } - + @Test public void testEscapeEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' {escape '&'}"; final String expectedOutput = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' ESCAPE '&'"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {escape ..}", expectedOutput, parseResult); } - + @Test public void testFunctionEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table WHERE {fn abs(x)} = ?"; final String expectedOutput = "SELECT * FROM some_table WHERE abs(x) = ?"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {fn ..}", expectedOutput, parseResult); } - + @Test public void testDateEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table WHERE x = {d '2012-12-28'}"; final String expectedOutput = "SELECT * FROM some_table WHERE x = DATE '2012-12-28'"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {d ..}", expectedOutput, parseResult); } - + @Test public void testTimeEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table WHERE x = {t '22:15:28'}"; final String expectedOutput = "SELECT * FROM some_table WHERE x = TIME '22:15:28'"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {t ..}", expectedOutput, parseResult); } - + @Test public void testTimestampEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table WHERE x = {ts '2012-12-28 22:15:28'}"; final String expectedOutput = "SELECT * FROM some_table WHERE x = TIMESTAMP '2012-12-28 22:15:28'"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {ts ..}", expectedOutput, parseResult); } - + @Test public void testOuterjoinEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM {oj some_table FULL OUTER JOIN some_other_table ON some_table.x = some_other_table.x}"; final String expectedOutput = "SELECT * FROM some_table FULL OUTER JOIN some_other_table ON some_table.x = some_other_table.x"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {oj ..}", expectedOutput, parseResult); } - + @Test public void testSimpleLimitEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table {limit 10}"; final String expectedOutput = "SELECT * FROM some_table ROWS 10"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {limit ..}", expectedOutput, parseResult); } - + @Test public void testExtendedLimitEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table {limit 10 offset 15}"; final String expectedOutput = "SELECT * FROM some_table ROWS 15 TO 15+10"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {limit ..}", expectedOutput, parseResult); } - + @Test public void testSimpleLimitEscapeWithParameter() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table {limit ?}"; final String expectedOutput = "SELECT * FROM some_table ROWS ?"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {limit ..}", expectedOutput, parseResult); } - + @Test public void testExtendedLimitEscapeRowsParameter() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table {limit ? offset 15}"; final String expectedOutput = "SELECT * FROM some_table ROWS 15 TO 15+?"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {limit ..}", expectedOutput, parseResult); } - + /** * Test of limit escape with the rows and row_offset parameter with a literal value for rows and a parameter for offset_rows. *

@@ -155,10 +155,10 @@ public void testExtendedLimitEscapeOffsetParameter() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table {limit 10 offset ?}"; expectedException.expect(FBSQLParseException.class); - + parser.parse(input); } - + /** * Test of limit escape with the rows and row_offset parameter with a parameter for rows and offset_rows. *

@@ -170,108 +170,108 @@ public void testExtendedLimitEscapeRowsAndOffsetParameter() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table {limit ? offset ?}"; expectedException.expect(FBSQLParseException.class); - + parser.parse(input); } - + @Test public void testCallEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "{call FUNCTION(?,?)}"; final String expectedOutput = "EXECUTE PROCEDURE FUNCTION(?,?)"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {call ..}", expectedOutput, parseResult); } - + @Test public void testQuestionmarkCallEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "{?=call FUNCTION(?,?)}"; final String expectedOutput = "EXECUTE PROCEDURE FUNCTION(?,?,?)"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {call ..}", expectedOutput, parseResult); } - + @Test public void testQuestionmarkCallEscapeExtraWhitespace() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "{? = call FUNCTION(?,?)}"; final String expectedOutput = "EXECUTE PROCEDURE FUNCTION(?,?,?)"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {call ..}", expectedOutput, parseResult); } - + @Test public void testUnsupportedKeyword() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); // NOTE: need to include an existent keyword, otherwise string isn't parsed at all final String input = "{fn ABS(?)} {doesnotexist xyz}"; expectedException.expect(FBSQLParseException.class); - + parser.parse(input); } - + @Test public void testTooManyCurlyBraceOpen() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "{escape '&'"; expectedException.expect(FBSQLParseException.class); - + parser.parse(input); } - + @Test public void testTooManyCurlyBraceClose() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "{escape '&'}}"; expectedException.expect(FBSQLParseException.class); - + parser.parse(input); } - + @Test public void testCurlyBraceOpenClose() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); // NOTE: need to include an existent keyword, otherwise string isn't parsed at all final String input = "{escape '&'} {}"; expectedException.expect(FBSQLParseException.class); - + parser.parse(input); } - + @Test public void testAdditionalWhitespaceBetweenEscapeAndParameter() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' {escape '&'}"; final String expectedOutput = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' ESCAPE '&'"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {escape ..}", expectedOutput, parseResult); } - + @Test public void testAdditionalWhitespaceAfterParameter() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' {escape '&' }"; final String expectedOutput = "SELECT * FROM some_table WHERE x LIKE '_x&_yz' ESCAPE '&'"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for {escape ..}", expectedOutput, parseResult); } - + @Test public void testNestedEscapes() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "{fn LTRIM({fn RTRIM(' abc ')})}"; final String expectedOutput = "TRIM(LEADING FROM TRIM(TRAILING FROM ' abc '))"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); } - + /** * Tests if the parser preserves whitespace inside parameters (implementation coalesces multiple whitespace characters into one space) */ @@ -280,11 +280,11 @@ public void testWhitespaceInParameter() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}"; final String expectedOutput = "TRIM(LEADING FROM CAST( ? AS VARCHAR(10)))"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); } - + /** * Tests if the parser does not process JDBC escapes inside a line comment */ @@ -293,11 +293,11 @@ public void testEscapeInLineComment() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "{fn LTRIM(CAST( ?\tAS VARCHAR(10)))} --{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}\n{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}"; final String expectedOutput = "TRIM(LEADING FROM CAST( ? AS VARCHAR(10))) --{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}\nTRIM(LEADING FROM CAST( ? AS VARCHAR(10)))"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); } - + /** * Tests if the parser does not process JDBC escapes inside a block comment */ @@ -306,11 +306,11 @@ public void testEscapeInBlockComment() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "{fn LTRIM(CAST( ?\tAS VARCHAR(10)))} /*{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}\n*/{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}"; final String expectedOutput = "TRIM(LEADING FROM CAST( ? AS VARCHAR(10))) /*{fn LTRIM(CAST( ?\tAS VARCHAR(10)))}\n*/TRIM(LEADING FROM CAST( ? AS VARCHAR(10)))"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); } - + /** * Tests if the parser correctly processes an escape that is directly after a '-' (potential start of line comment). */ @@ -319,11 +319,11 @@ public void testLineCommentStartFollowedByEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "6-{fn EXP(2)}"; final String expectedOutput = "6-EXP(2)"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); } - + /** * Tests if the parser correctly processes an escape that is directly after a '/' (potential start of block comment). */ @@ -332,7 +332,7 @@ public void testBlockCommentStartFollowedByEscape() throws Exception { final FBEscapedParser parser = new FBEscapedParser(EscapeParserMode.USE_BUILT_IN); final String input = "6/{fn EXP(2)}"; final String expectedOutput = "6/EXP(2)"; - + String parseResult = parser.parse(input); assertEquals("Unexpected output for nested escapes", expectedOutput, parseResult); } diff --git a/src/test/org/firebirdsql/jdbc/escape/LengthFunctionTest.java b/src/test/org/firebirdsql/jdbc/escape/LengthFunctionTest.java new file mode 100644 index 0000000000..90a49e7a4f --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/LengthFunctionTest.java @@ -0,0 +1,96 @@ +/* + * 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 LengthFunctionTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + private static final LengthFunction function = new LengthFunction(); + + @Test + public void testSingleParameter_rendersChar_Length() throws Exception { + assertEquals("CHAR_LENGTH(TRIM(TRAILING FROM 'abc'))", function.apply("'abc'")); + } + + @Test + public void testSecondParameter_CHARACTERS_rendersChar_Length() throws Exception { + assertEquals("CHAR_LENGTH(TRIM(TRAILING FROM 'abc'))", function.apply("'abc'", "CHARACTERS")); + } + + @Test + public void testSecondParameter_characters_rendersChar_Length() throws Exception { + assertEquals("CHAR_LENGTH(TRIM(TRAILING FROM 'abc'))", function.apply("'abc'", "characters")); + } + + @Test + public void testSecondParameter_characters_whitespace_rendersChar_Length() throws Exception { + assertEquals("CHAR_LENGTH(TRIM(TRAILING FROM 'abc'))", function.apply("'abc'", " characters ")); + } + + @Test + public void testSecondParameter_OCTETS_rendersOctet_Length() throws Exception { + assertEquals("OCTET_LENGTH(TRIM(TRAILING FROM 'abc'))", function.apply("'abc'", "OCTETS")); + } + + @Test + public void testSecondParameter_octets_rendersChar_Length() throws Exception { + assertEquals("OCTET_LENGTH(TRIM(TRAILING FROM 'abc'))", function.apply("'abc'", "octets")); + } + + @Test + public void testSecondParameter_octets_whitespace_rendersChar_Length() throws Exception { + assertEquals("OCTET_LENGTH(TRIM(TRAILING FROM '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 LENGTH, received 0"); + + function.apply(); + } + + @Test + public void testThreeParameters_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage( + "Expected 1 or 2 parameters for LENGTH, received 3"); + + function.apply("'abc'", "invalid", "xyz"); + } + +} diff --git a/src/test/org/firebirdsql/jdbc/escape/TestLikeEscape.java b/src/test/org/firebirdsql/jdbc/escape/LikeEscapeTest.java similarity index 50% rename from src/test/org/firebirdsql/jdbc/escape/TestLikeEscape.java rename to src/test/org/firebirdsql/jdbc/escape/LikeEscapeTest.java index 273fe3f56e..b5cd54862f 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestLikeEscape.java +++ b/src/test/org/firebirdsql/jdbc/escape/LikeEscapeTest.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,65 +12,62 @@ * 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 static org.junit.Assert.*; +import org.firebirdsql.common.FBTestProperties; +import org.firebirdsql.common.rules.UsesDatabase; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; +import java.sql.*; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.Set; -import org.firebirdsql.common.FBJUnit4TestBase; -import org.firebirdsql.common.FBTestProperties; -import org.firebirdsql.common.JdbcResourceHelper; -import org.junit.Before; -import org.junit.Test; +import static org.junit.Assert.assertEquals; /** * Tests for support of the LIKE escape character escape as defined * in section 13.4.5 of the JDBC 4.1 specification. - * + * * @author Mark Rotteveel */ -public class TestLikeEscape extends FBJUnit4TestBase { +public class LikeEscapeTest { + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.usesDatabase(); private static final String[] TEST_DATA = { "abcdef", "abc_ef", "abc%ef", "abc&%ef", "abc&_ef" }; - @Before - public void setupTestData() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); + @BeforeClass + public static void setupTestData() throws Exception { + try (Connection con = FBTestProperties.getConnectionViaDriverManager()) { + try (Statement stmt = con.createStatement()) { //@formatter:off - stmt.execute( + stmt.execute( "CREATE TABLE TAB1 (" + " ID INT NOT NULL CONSTRAINT PK_TAB1 PRIMARY KEY," + " VAL VARCHAR(30)" + ")"); //@formatter:on - stmt.close(); + } con.setAutoCommit(false); - PreparedStatement pstmt = con.prepareStatement("INSERT INTO TAB1 (ID, VAL) VALUES (?, ?)"); - for (int idx = 0; idx < TEST_DATA.length; idx++) { - pstmt.setInt(1, idx + 1); - pstmt.setString(2, TEST_DATA[idx]); - pstmt.addBatch(); + try (PreparedStatement pstmt = con.prepareStatement("INSERT INTO TAB1 (ID, VAL) VALUES (?, ?)")) { + for (int idx = 0; idx < TEST_DATA.length; idx++) { + pstmt.setInt(1, idx + 1); + pstmt.setString(2, TEST_DATA[idx]); + pstmt.addBatch(); + } + pstmt.executeBatch(); } - pstmt.executeBatch(); con.commit(); - } finally { - JdbcResourceHelper.closeQuietly(con); } } @@ -80,18 +77,13 @@ public void setupTestData() throws Exception { */ @Test public void testSimpleLike_percent() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc%' {escape '&'}"); - Set expectedStrings = Collections.unmodifiableSet(new HashSet(Arrays.asList(TEST_DATA))); + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc%' {escape '&'}")) { - assertEquals("Unexpected result for LIKE 'abc%' {escape '&'}", expectedStrings, getStrings(rs, 1)); + Set expectedStrings = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(TEST_DATA))); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); + assertEquals("Unexpected result for LIKE 'abc%' {escape '&'}", expectedStrings, getStrings(rs, 1)); } } @@ -101,18 +93,14 @@ public void testSimpleLike_percent() throws Exception { */ @Test public void testSimpleLike_underscore() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc_ef' {escape '&'}"); - Set expectedStrings = Collections.unmodifiableSet(new HashSet(Arrays.asList("abcdef", "abc_ef", "abc%ef"))); + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc_ef' {escape '&'}")) { - assertEquals("Unexpected result for LIKE 'abc_ef' {escape '&'}", expectedStrings, getStrings(rs, 1)); + Set expectedStrings = + Collections.unmodifiableSet(new HashSet<>(Arrays.asList("abcdef", "abc_ef", "abc%ef"))); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); + assertEquals("Unexpected result for LIKE 'abc_ef' {escape '&'}", expectedStrings, getStrings(rs, 1)); } } @@ -121,18 +109,14 @@ public void testSimpleLike_underscore() throws Exception { */ @Test public void testEscapedLike_percent() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc&%ef' {escape '&'}"); - Set expectedStrings = Collections.unmodifiableSet(new HashSet(Arrays.asList("abc%ef"))); + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc&%ef' {escape '&'}")) { - assertEquals("Unexpected result for LIKE 'abc&%ef' {escape '&'}", expectedStrings, getStrings(rs, 1)); + Set expectedStrings = + Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("abc%ef"))); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); + assertEquals("Unexpected result for LIKE 'abc&%ef' {escape '&'}", expectedStrings, getStrings(rs, 1)); } } @@ -141,34 +125,29 @@ public void testEscapedLike_percent() throws Exception { */ @Test public void testEscapedLike_underscore() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc&_ef' {escape '&'}"); - Set expectedStrings = Collections.unmodifiableSet(new HashSet(Arrays.asList("abc_ef"))); + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT VAL FROM TAB1 WHERE VAL LIKE 'abc&_ef' {escape '&'}")) { - assertEquals("Unexpected result for LIKE 'abc&_ef' {escape '&'}", expectedStrings, getStrings(rs, 1)); + Set expectedStrings = + Collections.unmodifiableSet(new HashSet<>(Collections.singletonList("abc_ef"))); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); + assertEquals("Unexpected result for LIKE 'abc&_ef' {escape '&'}", expectedStrings, getStrings(rs, 1)); } } /** * Helper method to sequentially process a ResultSet and add all strings in * columnIndex into a Set. - * + * * @param rs - * ResultSet to process + * ResultSet to process * @param columnIndex - * Index of the column + * Index of the column * @return Set of strings in columnIndex. - * @throws SQLException */ private Set getStrings(ResultSet rs, int columnIndex) throws SQLException { - Set strings = new HashSet(); + Set strings = new HashSet<>(); while (rs.next()) { strings.add(rs.getString(columnIndex)); } diff --git a/src/test/org/firebirdsql/jdbc/escape/LimitEscapeTest.java b/src/test/org/firebirdsql/jdbc/escape/LimitEscapeTest.java new file mode 100644 index 0000000000..c83efd8f74 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/LimitEscapeTest.java @@ -0,0 +1,149 @@ +/* + * 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.firebirdsql.common.FBTestProperties; +import org.firebirdsql.common.rules.UsesDatabase; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.junit.Assert.assertEquals; + +/** + * Tests for support of the Limiting Returned Rows Escape as defined in section + * 13.4.6 of the JDBC 4.1 specification. + * + * @author Mark Rotteveel + */ +public class LimitEscapeTest { + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.usesDatabase(); + + private static final int ROW_COUNT = 25; + + @BeforeClass + public static void setupTestData() throws Exception { + try (Connection con = FBTestProperties.getConnectionViaDriverManager()) { + try (Statement stmt = con.createStatement()) { +//@formatter:off + stmt.execute("CREATE TABLE TAB1 (" + + " ID INT NOT NULL CONSTRAINT PK_TAB1 PRIMARY KEY" + + ")"); +//@formatter:on + } + + con.setAutoCommit(false); + try (PreparedStatement pstmt = con.prepareStatement("INSERT INTO TAB1 (ID) VALUES (?)")) { + for (int id = 1; id <= ROW_COUNT; id++) { + pstmt.setInt(1, id); + pstmt.addBatch(); + } + pstmt.executeBatch(); + } + con.commit(); + } + } + + /** + * Test of limit escape with only the rows parameter with a literal value + */ + @Test + public void testLimitLiteralWithoutOffset() throws Exception { + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT ID FROM TAB1 ORDER BY ID {limit 10}")) { + + int id = 0; + while (rs.next()) { + id++; + assertEquals(id, rs.getInt(1)); + } + + assertEquals("Unexpected final value for ID returned", 10, id); + } + } + + /** + * Test of limit escape with only the rows parameter with a parametrized value + */ + @Test + public void testLimitParametrizedWithoutOffset() throws Exception { + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + PreparedStatement stmt = con.prepareStatement("SELECT ID FROM TAB1 ORDER BY ID {limit ?}")) { + + stmt.setInt(1, 10); + try (ResultSet rs = stmt.executeQuery()) { + int id = 0; + while (rs.next()) { + id++; + assertEquals(id, rs.getInt(1)); + } + + assertEquals("Unexpected final value for ID returned", 10, id); + } + } + } + + /** + * Test of limit escape with the rows and row_offset parameter with a literal value for both + */ + @Test + public void testLimitLiteralWithOffset() throws Exception { + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT ID FROM TAB1 ORDER BY ID {limit 10 offset 5}")) { + + int id = 4; + while (rs.next()) { + id++; + assertEquals(id, rs.getInt(1)); + } + + assertEquals("Unexpected final value for ID returned", 15, id); + } + } + + /** + * Test of limit escape with the rows and row_offset parameter with a parameter for rows and a literal value for offset_rows. + */ + @Test + public void testLimitRowsParameter() throws Exception { + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + PreparedStatement pstmt = con.prepareStatement("SELECT ID FROM TAB1 ORDER BY ID {limit ? offset 10}")) { + + pstmt.setInt(1, 8); + try (ResultSet rs = pstmt.executeQuery()) { + int id = 9; + while (rs.next()) { + id++; + assertEquals(id, rs.getInt(1)); + } + + assertEquals("Unexpected final value for ID returned", 18, id); + } + } + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/LocateFunctionTest.java b/src/test/org/firebirdsql/jdbc/escape/LocateFunctionTest.java new file mode 100644 index 0000000000..57dc6dbaa5 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/LocateFunctionTest.java @@ -0,0 +1,61 @@ +/* + * 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 LocateFunctionTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + private static final LocateFunction function = new LocateFunction(); + + @Test + public void testTwoArgument() throws Exception { + assertEquals("POSITION(a,b)", function.apply("a", "b")); + } + + @Test + public void testThreeArgument() throws Exception { + assertEquals("POSITION(a,b,c)", function.apply("a", "b", "c")); + } + + @Test + public void testSingleArgument_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage( + "Expected 2 or 3 parameters for LOCATE, received 1"); + + function.apply("a"); + } + + @Test + public void testFourArgument_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage( + "Expected 2 or 3 parameters for LOCATE, received 4"); + + function.apply("a", "b", "c", "d"); + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/TestOuterJoinEscapes.java b/src/test/org/firebirdsql/jdbc/escape/OuterJoinEscapesTest.java similarity index 61% rename from src/test/org/firebirdsql/jdbc/escape/TestOuterJoinEscapes.java rename to src/test/org/firebirdsql/jdbc/escape/OuterJoinEscapesTest.java index 6ffd1eb4d2..4496b4ce2c 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestOuterJoinEscapes.java +++ b/src/test/org/firebirdsql/jdbc/escape/OuterJoinEscapesTest.java @@ -18,20 +18,16 @@ */ package org.firebirdsql.jdbc.escape; -import static org.junit.Assert.*; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.Statement; -import java.sql.Types; - -import org.firebirdsql.common.FBJUnit4TestBase; import org.firebirdsql.common.FBTestProperties; -import org.firebirdsql.common.JdbcResourceHelper; -import org.junit.Before; +import org.firebirdsql.common.rules.UsesDatabase; +import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; +import java.sql.*; + +import static org.junit.Assert.*; + /** * Tests for support of the outer join escapes as defined in section 13.4.3 of * the JDBC 4.1 specification. @@ -39,136 +35,127 @@ * NOTE: We only test the basics, because Firebird supports the full join syntax * required, and we just strip the {oj ...} from the query. *

- * + * * @author Mark Rotteveel */ -public class TestOuterJoinEscapes extends FBJUnit4TestBase { +public class OuterJoinEscapesTest { + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.usesDatabase(); - @Before - public void setupTestData() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); + @BeforeClass + public static void setupTestData() throws Exception { + try (Connection con = FBTestProperties.getConnectionViaDriverManager()) { + try (Statement stmt = con.createStatement()) { //@formatter:off - stmt.execute( + stmt.execute( "CREATE TABLE TAB1 (" + " ID INT NOT NULL CONSTRAINT PK_TAB1 PRIMARY KEY" + ")"); - stmt.execute( + stmt.execute( "CREATE TABLE TAB2 (" + " ID INT NOT NULL CONSTRAINT PK_TAB2 PRIMARY KEY," + " TAB1_ID INT CONSTRAINT FK_TAB2_TAB1 REFERENCES TAB1 (ID)" + ")"); //@formatter:on - stmt.close(); + } con.setAutoCommit(false); - PreparedStatement pstmt = con.prepareStatement("INSERT INTO TAB1 (ID) VALUES (?)"); - pstmt.setInt(1, 1); - pstmt.executeUpdate(); - pstmt.setInt(1, 3); - pstmt.executeUpdate(); - pstmt.close(); - - pstmt = con.prepareStatement("INSERT INTO TAB2 (ID, TAB1_ID) VALUES (?, ?)"); - pstmt.setInt(1, 1); - pstmt.setInt(2, 1); - pstmt.executeUpdate(); - pstmt.setInt(1, 2); - pstmt.setNull(2, Types.INTEGER); - pstmt.executeUpdate(); - pstmt.close(); + try (PreparedStatement pstmt = con.prepareStatement("INSERT INTO TAB1 (ID) VALUES (?)")) { + pstmt.setInt(1, 1); + pstmt.execute(); + + pstmt.setInt(1, 3); + pstmt.execute(); + } + + try (PreparedStatement pstmt = con.prepareStatement("INSERT INTO TAB2 (ID, TAB1_ID) VALUES (?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setInt(2, 1); + pstmt.execute(); + + pstmt.setInt(1, 2); + pstmt.setNull(2, Types.INTEGER); + pstmt.execute(); + } con.commit(); - } finally { - JdbcResourceHelper.closeQuietly(con); } } @Test public void testFullOuterJoinEscape() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); //@formatter:off - ResultSet rs = stmt.executeQuery( + ResultSet rs = stmt.executeQuery( "SELECT TAB1.ID, TAB2.ID " + "FROM {oj TAB1 FULL OUTER JOIN TAB2 ON TAB2.TAB1_ID = TAB1.ID} " + - "ORDER BY TAB1.ID NULLS LAST"); + "ORDER BY TAB1.ID NULLS LAST")) { //@formatter:on assertTrue("Expected first row", rs.next()); assertEquals(1, rs.getInt(1)); assertEquals(1, rs.getInt(2)); + assertTrue("Expected second row", rs.next()); assertEquals(3, rs.getInt(1)); assertEquals(0, rs.getInt(2)); assertTrue(rs.wasNull()); + assertTrue("Expected third row", rs.next()); assertEquals(0, rs.getInt(1)); assertTrue(rs.wasNull()); assertEquals(2, rs.getInt(2)); - assertFalse("No more rows expected", rs.next()); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); + assertFalse("No more rows expected", rs.next()); } } @Test public void testLeftOuterJoinEscape() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); //@formatter:off - ResultSet rs = stmt.executeQuery( + ResultSet rs = stmt.executeQuery( "SELECT TAB1.ID, TAB2.ID " + "FROM {oj TAB1 LEFT OUTER JOIN TAB2 ON TAB2.TAB1_ID = TAB1.ID} " + - "ORDER BY TAB1.ID NULLS LAST"); + "ORDER BY TAB1.ID NULLS LAST")) { //@formatter:on assertTrue("Expected first row", rs.next()); assertEquals(1, rs.getInt(1)); assertEquals(1, rs.getInt(2)); + assertTrue("Expected second row", rs.next()); assertEquals(3, rs.getInt(1)); assertEquals(0, rs.getInt(2)); assertTrue(rs.wasNull()); - assertFalse("No more rows expected", rs.next()); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); + assertFalse("No more rows expected", rs.next()); } } @Test public void testRightOuterJoinEscape() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); //@formatter:off - ResultSet rs = stmt.executeQuery( + ResultSet rs = stmt.executeQuery( "SELECT TAB1.ID, TAB2.ID " + "FROM {oj TAB1 RIGHT OUTER JOIN TAB2 ON TAB2.TAB1_ID = TAB1.ID} " + - "ORDER BY TAB1.ID NULLS LAST"); + "ORDER BY TAB1.ID NULLS LAST")) { //@formatter:on assertTrue("Expected first row", rs.next()); assertEquals(1, rs.getInt(1)); assertEquals(1, rs.getInt(2)); + assertTrue("Expected second row", rs.next()); assertEquals(0, rs.getInt(1)); assertTrue(rs.wasNull()); assertEquals(2, rs.getInt(2)); - assertFalse("No more rows expected", rs.next()); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); + assertFalse("No more rows expected", rs.next()); } } } diff --git a/src/test/org/firebirdsql/jdbc/escape/PatternSQLFunctionTest.java b/src/test/org/firebirdsql/jdbc/escape/PatternSQLFunctionTest.java new file mode 100644 index 0000000000..2b600345c1 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/PatternSQLFunctionTest.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; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class PatternSQLFunctionTest { + + private static final PatternSQLFunction function = new PatternSQLFunction("func({0}, {1})"); + + @Test + public void testParameterCountEqualToArgumentCount() { + assertEquals("func(?, 'abc')", function.apply("?", "'abc'")); + } + + @Test + public void testParameterCountLessThanArgumentCount() { + assertEquals("func(xyz, {1})", function.apply("xyz")); + } + + @Test + public void testParameterCountGreaterThanArgumentCount() { + assertEquals("func(?, 'abc')", function.apply("?", "'abc'", "xyz")); + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/PositionFunctionTest.java b/src/test/org/firebirdsql/jdbc/escape/PositionFunctionTest.java new file mode 100644 index 0000000000..0d8e516f57 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/PositionFunctionTest.java @@ -0,0 +1,84 @@ +/* + * 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; +import static org.junit.Assert.assertNull; + +public class PositionFunctionTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + private static final PositionFunction function = new PositionFunction(); + + @Test + public void testSingleParameter() throws Exception { + assertEquals("POSITION('abc' in 'defabc')", function.apply("'abc' in 'defabc'")); + } + + @Test + public void testTwoParameters_CHARACTERS() throws Exception { + assertEquals("POSITION('abc' in 'defabc')", function.apply("'abc' in 'defabc'", "CHARACTERS")); + } + + @Test + public void testTwoParameters_characters() throws Exception { + assertEquals("POSITION('abc' in 'defabc')", function.apply("'abc' in 'defabc'", "characters")); + } + + @Test + public void testTwoParameters_characters_whitespace() throws Exception { + assertEquals("POSITION('abc' in 'defabc')", function.apply("'abc' in 'defabc'", " characters ")); + } + + + @Test + public void testTwoParameters_OCTETS_returnsNullForPassthrough() throws Exception { + assertNull(function.apply("'abc' in 'defabc'", "OCTETS")); + } + + @Test + public void testTwoParameters_invalid_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Second parameter for POSITION must be OCTETS or CHARACTERS, was invalid"); + + function.apply("'abc' in 'defabc'", "invalid"); + } + + @Test + public void testZeroParameters_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 1 or 2 parameters for POSITION, received 0"); + + function.apply(); + } + + @Test + public void testThreeParameters_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 1 or 2 parameters for POSITION, received 3"); + + function.apply("abc", "defabc", "CHARACTERS"); + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java b/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java deleted file mode 100644 index 0af1e889bf..0000000000 --- a/src/test/org/firebirdsql/jdbc/escape/TestLimitEscape.java +++ /dev/null @@ -1,167 +0,0 @@ -/* - * Firebird Open Source J2ee 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 CVS history command. - * - * All rights reserved. - */ -package org.firebirdsql.jdbc.escape; - -import static org.junit.Assert.*; - -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.Statement; - -import org.firebirdsql.common.FBJUnit4TestBase; -import org.firebirdsql.common.FBTestProperties; -import org.firebirdsql.common.JdbcResourceHelper; -import org.junit.Before; -import org.junit.Test; - -/** - * Tests for support of the Limiting Returned Rows Escape as defined in section - * 13.4.6 of the JDBC 4.1 specification. - * - * @author Mark Rotteveel - */ -public class TestLimitEscape extends FBJUnit4TestBase { - - private static final int ROW_COUNT = 25; - - @Before - public void setupTestData() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); -//@formatter:off - stmt.execute("CREATE TABLE TAB1 (" + - " ID INT NOT NULL CONSTRAINT PK_TAB1 PRIMARY KEY" + - ")"); -//@formatter:on - stmt.close(); - - con.setAutoCommit(false); - PreparedStatement pstmt = con.prepareStatement("INSERT INTO TAB1 (ID) VALUES (?)"); - for (int id = 1; id <= ROW_COUNT; id++) { - pstmt.setInt(1, id); - pstmt.addBatch(); - } - pstmt.executeBatch(); - pstmt.close(); - con.commit(); - } finally { - JdbcResourceHelper.closeQuietly(con); - } - } - - /** - * Test of limit escape with only the rows parameter with a literal value - */ - @Test - public void testLimitLiteralWithoutOffset() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT ID FROM TAB1 ORDER BY ID {limit 10}"); - - int id = 0; - while (rs.next()) { - id++; - assertEquals(id, rs.getInt(1)); - } - - assertEquals("Unexpected final value for ID returned", 10, id); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); - } - } - - /** - * Test of limit escape with only the rows parameter with a parametrized value - */ - @Test - public void testLimitParametrizedWithoutOffset() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - PreparedStatement stmt = con.prepareStatement("SELECT ID FROM TAB1 ORDER BY ID {limit ?}"); - stmt.setInt(1, 10); - ResultSet rs = stmt.executeQuery(); - - int id = 0; - while (rs.next()) { - id++; - assertEquals(id, rs.getInt(1)); - } - - assertEquals("Unexpected final value for ID returned", 10, id); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); - } - } - - /** - * Test of limit escape with the rows and row_offset parameter with a literal value for both - */ - @Test - public void testLimitLiteralWithOffset() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT ID FROM TAB1 ORDER BY ID {limit 10 offset 5}"); - - int id = 4; - while (rs.next()) { - id++; - assertEquals(id, rs.getInt(1)); - } - - assertEquals("Unexpected final value for ID returned", 15, id); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); - } - } - - /** - * Test of limit escape with the rows and row_offset parameter with a parameter for rows and a literal value for offset_rows. - */ - @Test - public void testLimitRowsParameter() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - PreparedStatement pstmt = con.prepareStatement("SELECT ID FROM TAB1 ORDER BY ID {limit ? offset 10}"); - pstmt.setInt(1, 8); - ResultSet rs = pstmt.executeQuery(); - - int id = 9; - while (rs.next()) { - id++; - assertEquals(id, rs.getInt(1)); - } - - assertEquals("Unexpected final value for ID returned", 18, id); - rs.close(); - pstmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); - } - } -} diff --git a/src/test/org/firebirdsql/jdbc/escape/TestScalarNumericFunctions.java b/src/test/org/firebirdsql/jdbc/escape/TestScalarNumericFunctions.java index 55c19074a9..e01ad5c5ea 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestScalarNumericFunctions.java +++ b/src/test/org/firebirdsql/jdbc/escape/TestScalarNumericFunctions.java @@ -1,6 +1,4 @@ /* - * $Id$ - * * Firebird Open Source JavaEE connector - JDBC driver * * Distributable under LGPL license. @@ -20,9 +18,14 @@ */ package org.firebirdsql.jdbc.escape; -import static org.firebirdsql.common.FBTestProperties.*; -import static org.firebirdsql.common.JdbcResourceHelper.closeQuietly; -import static org.junit.Assert.*; +import org.firebirdsql.common.rules.UsesDatabase; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; import java.sql.Connection; import java.sql.ResultSet; @@ -31,51 +34,45 @@ import java.util.Arrays; import java.util.Collection; -import org.firebirdsql.management.FBManager; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import static org.firebirdsql.common.FBTestProperties.getConnectionViaDriverManager; +import static org.firebirdsql.common.JdbcResourceHelper.closeQuietly; +import static org.junit.Assert.*; /** * Tests for support of the scalar numeric function escapes as defined in * appendix D.1 of the JDBC 4.1 specification. - * + * * @author Mark Rotteveel */ @RunWith(Parameterized.class) public class TestScalarNumericFunctions { - - private static FBManager fbManager; + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.usesDatabase(); + private static Connection con; private static Statement stmt; - + private final String functionCall; private final double expectedResult; private final boolean supported; - + @BeforeClass public static void setUp() throws Exception { - fbManager = createFBManager(); - defaultDatabaseSetUp(fbManager); // We create a connection and statement for all tests executed for performance reasons con = getConnectionViaDriverManager(); stmt = con.createStatement(); } - + @AfterClass - public static void tearDown() throws Exception { + public static void tearDown() { closeQuietly(stmt); closeQuietly(con); - defaultDatabaseTearDown(fbManager); - fbManager = null; } - + /** * Testcase - * + * * @param functionCall * JDBC function call (without {fn .. }) * @param expectedResult @@ -89,10 +86,10 @@ public TestScalarNumericFunctions(String functionCall, Double expectedResult, Bo this.expectedResult = expectedResult; this.supported = supported; } - - @Parameters + + @Parameters(name = "{index}: value {0} ({1})") public static Collection numericFunctionTestcases() { - return Arrays.asList(new Object[][] { + return Arrays.asList( //@formatter:off /* 0*/ testcase("ABS(-513)", 513), /* 1*/ testcase("ACOS(-1)", Math.PI), @@ -102,7 +99,7 @@ public static Collection numericFunctionTestcases() { /* 5*/ testcase("CEILING(2.13)", 3), /* 6*/ testcase("COS(3.141592654)", -1), /* 7*/ testcase("COT(0.5)", 1.830488), - /* 8*/ unsupported("DEGREES(3.1414)"), // TODO Add support using number * 180.0 / PI()? + /* 8*/ testcase("DEGREES(PI())", 180), /* 9*/ testcase("EXP(2)", Math.exp(2)), /*10*/ testcase("FLOOR(2.13)", 2), /*11*/ testcase("LOG(7.389056099)", 2), @@ -110,7 +107,7 @@ public static Collection numericFunctionTestcases() { /*13*/ testcase("MOD(513, 132)", 117), /*14*/ testcase("PI()", Math.PI), /*15*/ testcase("POWER(3, 4)", 81), - /*16*/ unsupported("RADIANS(90)"), // TODO Add support using number * PI() / 180.0? + /*16*/ testcase("RADIANS(90)", Math.PI / 2), /*17*/ unsupported("RAND(14232)"), // Firebird only supports RAND() without parameter /*18*/ testcase("ROUND(1.123456, 3)", 1.123), /*19*/ testcase("SIGN(-3487)", -1), @@ -119,16 +116,14 @@ public static Collection numericFunctionTestcases() { /*22*/ testcase("SIN(2.322)", Math.sin(2.322)), /*23*/ testcase("SQRT(25.25)", 5.024938), /*24*/ testcase("TAN(1.2333)", Math.tan(1.2333)), - /*25*/ testcase("TRUNCATE(2345.12556, 2)", 2345.12), + /*25*/ testcase("TRUNCATE(2345.12556, 2)", 2345.12) //@formatter:on - }); + ); } - + @Test public void testScalarFunction() throws Exception { - ResultSet rs = null; - try { - rs = stmt.executeQuery(createQuery()); + try (ResultSet rs = stmt.executeQuery(createQuery())) { if (!supported) { fail(String.format("Expected function call %s to be unsupported", functionCall)); } else { @@ -142,34 +137,32 @@ public void testScalarFunction() throws Exception { // TODO validate exception? //fail("Validation of unsupported functions not yet implemented"); } - } finally { - closeQuietly(rs); } } - + private String failureMessage() { return String.format("Unexpected result for function escape %s", functionCall); } - + private String createQuery() { return String.format("SELECT {fn %s} FROM RDB$DATABASE", functionCall); } - + /** * Convenience method to create object array for testcase (ensures correct * types). - * + * * @param functionCall - * JDBC function call (with out {fn .. }) + * JDBC function call (with out {fn .. }) * @param expectedResult - * Expected value as result of using the function against the - * database + * Expected value as result of using the function against the + * database * @return Object[] testcase */ private static Object[] testcase(String functionCall, double expectedResult) { return new Object[] { functionCall, expectedResult, true }; } - + private static Object[] unsupported(String functionCall) { return new Object[] { functionCall, Double.NaN, false }; } diff --git a/src/test/org/firebirdsql/jdbc/escape/TestScalarStringFunctions.java b/src/test/org/firebirdsql/jdbc/escape/TestScalarStringFunctions.java index a162d73730..0be35f988f 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestScalarStringFunctions.java +++ b/src/test/org/firebirdsql/jdbc/escape/TestScalarStringFunctions.java @@ -1,6 +1,4 @@ /* - * $Id$ - * * Firebird Open Source JavaEE connector - JDBC driver * * Distributable under LGPL license. @@ -20,9 +18,14 @@ */ package org.firebirdsql.jdbc.escape; -import static org.firebirdsql.common.FBTestProperties.*; -import static org.firebirdsql.common.JdbcResourceHelper.closeQuietly; -import static org.junit.Assert.*; +import org.firebirdsql.common.rules.UsesDatabase; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; import java.sql.Connection; import java.sql.ResultSet; @@ -31,51 +34,45 @@ import java.util.Arrays; import java.util.Collection; -import org.firebirdsql.management.FBManager; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameters; +import static org.firebirdsql.common.FBTestProperties.getConnectionViaDriverManager; +import static org.firebirdsql.common.JdbcResourceHelper.closeQuietly; +import static org.junit.Assert.*; /** * Tests for support of the scalar string function escapes as defined in * appendix D.2 of the JDBC 4.1 specification. - * + * * @author Mark Rotteveel */ @RunWith(Parameterized.class) public class TestScalarStringFunctions { - - private static FBManager fbManager; + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.usesDatabase(); + private static Connection con; private static Statement stmt; - + private final String functionCall; private final String expectedResult; private final boolean supported; - + @BeforeClass public static void setUp() throws Exception { - fbManager = createFBManager(); - defaultDatabaseSetUp(fbManager); // We create a connection and statement for all tests executed for performance reasons con = getConnectionViaDriverManager(); stmt = con.createStatement(); } - + @AfterClass - public static void tearDown() throws Exception { + public static void tearDown() { closeQuietly(stmt); closeQuietly(con); - defaultDatabaseTearDown(fbManager); - fbManager = null; } - + /** * Testcase - * + * * @param functionCall * JDBC function call (without {fn .. }) * @param expectedResult @@ -89,19 +86,19 @@ public TestScalarStringFunctions(String functionCall, String expectedResult, Boo this.expectedResult = expectedResult; this.supported = supported; } - - @Parameters + + @Parameters(name = "{index}: value {0} ({1})") public static Collection stringFunctionTestcases() { - return Arrays.asList(new Object[][] { + return Arrays.asList( //@formatter:off /* 0*/ testcase("ASCII('A')", "65"), /* 1*/ testcase("CHAR(65)", "A"), /* 2*/ testcase("CHAR_LENGTH('123456')", "6"), /* 3*/ testcase("CHAR_LENGTH('123456', CHARACTERS)", "6"), - /* 4*/ testcase("CHAR_LENGTH('123456', OCTETS)", "6"), // TODO Add support using OCTET_LENGTH(param1) + /* 4*/ testcase("CHAR_LENGTH('123456', OCTETS)", "6"), /* 5*/ testcase("CHARACTER_LENGTH('123456')", "6"), /* 6*/ testcase("CHARACTER_LENGTH('123456', CHARACTERS)", "6"), - /* 7*/ testcase("CHARACTER_LENGTH('123456', OCTETS)", "6"), // TODO Add support using OCTET_LENGTH(param1) + /* 7*/ testcase("CHARACTER_LENGTH('123456', OCTETS)", "6"), /* 8*/ testcase("CONCAT('abc', 'def')", "abcdef"), /* 9*/ unsupported("DIFFERENCE('Robert', 'Rubin')"), /*10*/ testcase("INSERT('Goodbye', 2, 3, 'Hello')", "GHellobye"), @@ -109,12 +106,12 @@ public static Collection stringFunctionTestcases() { /*12*/ testcase("LEFT('1234567890', 4)", "1234"), /*13*/ testcase("LENGTH(' 234 ')", "4"), /*14*/ testcase("LENGTH(' 234 ', CHARACTERS)", "4"), - /*15*/ testcase("LENGTH(' 234 ', OCTETS)", "4"), // TODO Add support using OCTET_LENGTH() - /*16*/ unsupported("LOCATE('def', 'abcdefabcdef')"/*, "4"*/), // TODO Currently fails, implement using POSITION + /*15*/ testcase("LENGTH(' 234 ', OCTETS)", "4"), + /*16*/ testcase("LOCATE('def', 'abcdefabcdef')", "4"), /*17*/ testcase("LOCATE('def', 'abcdefabcdef', 5)", "10"), /*18*/ testcase("LTRIM(' abc ')", "abc "), /*19*/ testcase("POSITION('def' IN 'abcdefabcdef')", "4"), - /*20*/ unsupported("POSITION('def' IN 'abcdefabcdef', CHARACTERS)"/*, "4"*/), // TODO Currently fails, implement by removing , CHARACTERS + /*20*/ testcase("POSITION('def' IN 'abcdefabcdef', CHARACTERS)", "4"), /*21*/ unsupported("POSITION('def' IN 'abcdefabcdef', OCTETS)"), // TODO Unclear what semantics are, so don't support, or as normal POSITION? /*22*/ testcase("REPEAT('a', 4)", "aaaa"), /*23*/ testcase("REPLACE('abcdefabcdef', 'def', 'xyz')", "abcxyzabcxyz"), @@ -125,16 +122,14 @@ public static Collection stringFunctionTestcases() { /*28*/ testcase("SUBSTRING('1234567890', 3, 3)", "345"), /*29*/ testcase("SUBSTRING('1234567890', 3, 3, CHARACTERS)", "345"), /*30*/ testcase("SUBSTRING('1234567890', 3, 3, OCTETS)", "345"), // TODO Verify behaviour with multi-byte characters - /*31*/ testcase("UCASE('abcdef')", "ABCDEF"), + /*31*/ testcase("UCASE('abcdef')", "ABCDEF") //@formatter:on - }); + ); } - + @Test public void testScalarFunction() throws Exception { - ResultSet rs = null; - try { - rs = stmt.executeQuery(createQuery()); + try (ResultSet rs = stmt.executeQuery(createQuery())) { if (!supported) { fail(String.format("Expected function call %s to be unsupported", functionCall)); } else { @@ -148,34 +143,32 @@ public void testScalarFunction() throws Exception { // TODO validate exception? //fail("Validation of unsupported functions not yet implemented"); } - } finally { - closeQuietly(rs); } } - + private String failureMessage() { return String.format("Unexpected result for function escape %s", functionCall); } - + private String createQuery() { return String.format("SELECT {fn %s} FROM RDB$DATABASE", functionCall); } - + /** * Convenience method to create object array for testcase (ensures correct * types). - * + * * @param functionCall - * JDBC function call (with out {fn .. }) + * JDBC function call (with out {fn .. }) * @param expectedResult - * Expected value as result of using the function against the - * database + * Expected value as result of using the function against the + * database * @return Object[] testcase */ private static Object[] testcase(String functionCall, String expectedResult) { return new Object[] { functionCall, expectedResult, true }; } - + private static Object[] unsupported(String functionCall) { return new Object[] { functionCall, "", false }; } diff --git a/src/test/org/firebirdsql/jdbc/escape/TestScalarSystemFunctions.java b/src/test/org/firebirdsql/jdbc/escape/TestScalarSystemFunctions.java index 2575a36bc1..e163357612 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestScalarSystemFunctions.java +++ b/src/test/org/firebirdsql/jdbc/escape/TestScalarSystemFunctions.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,73 +12,67 @@ * 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 static org.junit.Assert.*; +import org.firebirdsql.common.FBTestProperties; +import org.firebirdsql.common.rules.UsesDatabase; +import org.junit.ClassRule; +import org.junit.Test; import java.sql.Connection; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; -import org.firebirdsql.common.FBJUnit4TestBase; -import org.firebirdsql.common.FBTestProperties; -import org.firebirdsql.common.JdbcResourceHelper; -import org.junit.Test; +import static org.junit.Assert.*; /** * Tests for support of the scalar system function escapes as defined in * appendix D.4 of the JDBC 4.1 specification. - * + * * @author Mark Rotteveel */ -public class TestScalarSystemFunctions extends FBJUnit4TestBase { - +public class TestScalarSystemFunctions { + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.usesDatabase(); + @Test - public void testDatabase() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { + public void testDatabase() { + try (Connection con = FBTestProperties.getConnectionViaDriverManager()) { Statement stmt = con.createStatement(); stmt.executeQuery("SELECT {fn DATABASE()} FROM RDB$DATABASE"); // TODO Consider implementing using RDB$GET_CONTEXT('SYSTEM', 'DB_NAME') fail("JDBC escape DATABASE() not supported"); } catch (SQLException ex) { // TODO validate exception? - //fail("Validation of unsupported functions not yet implemented"); - } finally { - JdbcResourceHelper.closeQuietly(con); + //fail("Validation of unsupported functions not yet implemented"); } } - + @Test public void testIfNull() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { + try (Connection con = FBTestProperties.getConnectionViaDriverManager()) { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT {fn IFNULL(NULL, 'abcd')} FROM RDB$DATABASE"); assertTrue("Expected at least one row", rs.next()); assertEquals("Unexpected result for function escape IFNULL(NULL, 'abcd')", "abcd", rs.getString(1)); - } finally { - JdbcResourceHelper.closeQuietly(con); } } - + @Test public void testUser() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { + try (Connection con = FBTestProperties.getConnectionViaDriverManager()) { Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery("SELECT {fn USER()} FROM RDB$DATABASE"); assertTrue("Expected at least one row", rs.next()); - assertEquals("Unexpected result for function escape USER()", + assertEquals("Unexpected result for function escape USER()", FBTestProperties.DB_USER.toUpperCase(), rs.getString(1)); - } finally { - JdbcResourceHelper.closeQuietly(con); } } - + } diff --git a/src/test/org/firebirdsql/jdbc/escape/TestScalarTimeDateFunctions.java b/src/test/org/firebirdsql/jdbc/escape/TestScalarTimeDateFunctions.java index a12790dda8..c100677456 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestScalarTimeDateFunctions.java +++ b/src/test/org/firebirdsql/jdbc/escape/TestScalarTimeDateFunctions.java @@ -1,6 +1,4 @@ /* - * $Id$ - * * Firebird Open Source JavaEE connector - JDBC driver * * Distributable under LGPL license. @@ -20,118 +18,171 @@ */ package org.firebirdsql.jdbc.escape; -import static org.firebirdsql.common.FBTestProperties.*; -import static org.firebirdsql.common.JdbcResourceHelper.closeQuietly; -import static org.junit.Assert.*; - -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.text.SimpleDateFormat; -import java.util.Arrays; -import java.util.Collection; - -import org.firebirdsql.management.FBManager; +import org.firebirdsql.common.rules.UsesDatabase; import org.junit.AfterClass; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; +import java.sql.*; +import java.text.SimpleDateFormat; +import java.util.Arrays; +import java.util.Collection; + +import static org.firebirdsql.common.FBTestProperties.getConnectionViaDriverManager; +import static org.firebirdsql.common.JdbcResourceHelper.closeQuietly; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + /** * Tests for support of the scalar time and date function escapes as defined in * appendix D.3 of the JDBC 4.1 specification. - * + * * @author Mark Rotteveel */ @RunWith(Parameterized.class) public class TestScalarTimeDateFunctions { - private static FBManager fbManager; + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.usesDatabase(); + private static Connection con; private static Statement stmt; - + private final String functionCall; private final Validator validator; private final boolean supported; - + @BeforeClass public static void setUp() throws Exception { - fbManager = createFBManager(); - defaultDatabaseSetUp(fbManager); // We create a connection and statement for all tests executed for performance reasons con = getConnectionViaDriverManager(); stmt = con.createStatement(); } - + @AfterClass - public static void tearDown() throws Exception { + public static void tearDown() { closeQuietly(stmt); closeQuietly(con); - defaultDatabaseTearDown(fbManager); - fbManager = null; } - + /** * Testcase - * + * * @param functionCall - * JDBC function call (without {fn .. }) + * JDBC function call (without {fn .. }) * @param validator - * {@link Validator} to test the result of using the function against the - * database + * {@link Validator} to test the result of using the function against the + * database * @param supported - * true function is supported, false when not supported + * true function is supported, false when not supported */ public TestScalarTimeDateFunctions(String functionCall, Validator validator, Boolean supported) { this.functionCall = functionCall; this.validator = validator; this.supported = supported; } - - @Parameters + + @Parameters(name = "{index}: value {0}") public static Collection timeDateFunctionTestcases() { - return Arrays.asList(new Object[][] { -//@formatter:off - /* 0*/ testcase("CURRENT_DATE", new CurrentDateValidator()), - /* 1*/ testcase("CURRENT_DATE()", new CurrentDateValidator()), - /* 2*/ testcase("CURRENT_TIME", new CurrentTimeValidator()), - /* 3*/ testcase("CURRENT_TIME()", new CurrentTimeValidator()), - /* 4*/ testcase("CURRENT_TIMESTAMP", new CurrentTimestampValidator()), - /* 5*/ testcase("CURRENT_TIMESTAMP()", new CurrentTimestampValidator()), - /* 6*/ testcase("CURDATE()", new CurrentDateValidator()), - /* 7*/ testcase("CURTIME()", new CurrentTimeValidator()), - /* 8*/ unsupported("DAYNAME(CURRENT_DATE)"), - /* 9*/ testcase("DAYOFMONTH(DATE '2012-12-22')", new SimpleValidator(22)), - /*10*/ testcase("DAYOFWEEK(DATE '2012-12-22')", new SimpleValidator(7)), - /*11*/ testcase("DAYOFWEEK(DATE '2012-12-23')", new SimpleValidator(1)), - /*12*/ testcase("DAYOFYEAR(DATE '2012-12-22')", new SimpleValidator(357)), - /*13*/ testcase("EXTRACT(YEAR FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(2012)), - /*14*/ testcase("EXTRACT(MONTH FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(12)), - /*15*/ testcase("EXTRACT(DAY FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(22)), - /*16*/ testcase("EXTRACT(HOUR FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(19)), - /*17*/ testcase("EXTRACT(MINUTE FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(13)), - /*18*/ testcase("EXTRACT(SECOND FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(5)), - /*19*/ testcase("HOUR(TIME '19:13:05')", new SimpleValidator(19)), - /*20*/ testcase("MINUTE(TIME '19:13:05')", new SimpleValidator(13)), - /*21*/ testcase("MONTH(DATE '2012-12-22')", new SimpleValidator(12)), - /*22*/ unsupported("MONTHNAME(CURRENT_DATE)"), - /*23*/ testcase("NOW()", new CurrentTimestampValidator()), - /*24*/ unsupported("QUARTER(DATE '2012-12-22')"), // TODO Can be implemented as 1+(EXTRACT(MONTH FROM ...)-1)/3 - /*25*/ testcase("SECOND(TIME '19:13:05')", new SimpleValidator(5)), - // TODO tests for TIMESTAMPADD and TIMESTAMPDIFF (maybe as separate test class) - /*26*/ testcase("WEEK(DATE '2012-12-22')", new SimpleValidator(51)), - /*27*/ testcase("YEAR(DATE '2012-12-22')", new SimpleValidator(2012)), + return Arrays.asList( //@formatter:off - }); + /* 0*/ testcase("CURRENT_DATE", new CurrentDateValidator()), + /* 1*/ testcase("CURRENT_DATE()", new CurrentDateValidator()), + /* 2*/ testcase("CURRENT_TIME", new CurrentTimeValidator()), + /* 3*/ testcase("CURRENT_TIME()", new CurrentTimeValidator()), + /* 4*/ testcase("CURRENT_TIMESTAMP", new CurrentTimestampValidator()), + /* 5*/ testcase("CURRENT_TIMESTAMP()", new CurrentTimestampValidator()), + /* 6*/ testcase("CURDATE()", new CurrentDateValidator()), + /* 7*/ testcase("CURTIME()", new CurrentTimeValidator()), + /* 8*/ unsupported("DAYNAME(CURRENT_DATE)"), + /* 9*/ testcase("DAYOFMONTH(DATE '2012-12-22')", new SimpleValidator(22)), + /*10*/ testcase("DAYOFWEEK(DATE '2012-12-22')", new SimpleValidator(7)), + /*11*/ testcase("DAYOFWEEK(DATE '2012-12-23')", new SimpleValidator(1)), + /*12*/ testcase("DAYOFYEAR(DATE '2012-12-22')", new SimpleValidator(357)), + /*13*/ testcase("EXTRACT(YEAR FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(2012)), + /*14*/ testcase("EXTRACT(MONTH FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(12)), + /*15*/ testcase("EXTRACT(DAY FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(22)), + /*16*/ testcase("EXTRACT(HOUR FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(19)), + /*17*/ testcase("EXTRACT(MINUTE FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(13)), + /*18*/ testcase("EXTRACT(SECOND FROM TIMESTAMP '2012-12-22 19:13:05')", new SimpleValidator(5)), + /*19*/ testcase("HOUR(TIME '19:13:05')", new SimpleValidator(19)), + /*20*/ testcase("MINUTE(TIME '19:13:05')", new SimpleValidator(13)), + /*21*/ testcase("MONTH(DATE '2012-12-22')", new SimpleValidator(12)), + /*22*/ unsupported("MONTHNAME(CURRENT_DATE)"), + /*23*/ testcase("NOW()", new CurrentTimestampValidator()), + /*24*/ testcase("QUARTER(DATE '2012-01-01')", new SimpleValidator(1)), + /*25*/ testcase("QUARTER(DATE '2012-03-31')", new SimpleValidator(1)), + /*26*/ testcase("QUARTER(DATE '2012-04-01')", new SimpleValidator(2)), + /*27*/ testcase("QUARTER(DATE '2012-06-30')", new SimpleValidator(2)), + /*28*/ testcase("QUARTER(DATE '2012-07-01')", new SimpleValidator(3)), + /*29*/ testcase("QUARTER(DATE '2012-09-30')", new SimpleValidator(3)), + /*20*/ testcase("QUARTER(DATE '2012-10-01')", new SimpleValidator(4)), + /*31*/ testcase("QUARTER(DATE '2012-12-31')", new SimpleValidator(4)), + /*32*/ testcase("SECOND(TIME '19:13:05')", new SimpleValidator(5)), + /*33*/ testcase("TIMESTAMPADD(SQL_TSI_FRAC_SECOND, 5000000, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2017-11-23 12:00:00.005"))), + /*34*/ testcase("TIMESTAMPADD(SQL_TSI_SECOND, 13, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2017-11-23 12:00:13"))), + /*35*/ testcase("TIMESTAMPADD(SQL_TSI_MINUTE, 4, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2017-11-23 12:04:00"))), + /*36*/ testcase("TIMESTAMPADD(SQL_TSI_HOUR, 1, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2017-11-23 13:00:00"))), + /*37*/ testcase("TIMESTAMPADD(SQL_TSI_DAY, 1, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2017-11-24 12:00:00"))), + /*38*/ testcase("TIMESTAMPADD(SQL_TSI_WEEK, 1, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2017-11-30 12:00:00"))), + /*39*/ testcase("TIMESTAMPADD(SQL_TSI_MONTH, 1, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2017-12-23 12:00:00"))), + /*40*/ testcase("TIMESTAMPADD(SQL_TSI_QUARTER, 1, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2018-02-23 12:00:00"))), + /*41*/ testcase("TIMESTAMPADD(SQL_TSI_YEAR, 1, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2018-11-23 12:00:00"))), + // Check if expressions work correctly for FRAC_SECOND (check correct evaluation order) + /*42*/ testcase("TIMESTAMPADD(SQL_TSI_FRAC_SECOND, 2500000+2500000, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2017-11-23 12:00:00.005"))), + // Check if expressions work correctly for QUARTER (check correct evaluation order) + /*43*/ testcase("TIMESTAMPADD(SQL_TSI_QUARTER, 1+1, TIMESTAMP'2017-11-23 12:00:00')", + new SimpleValidator(Timestamp.valueOf("2018-05-23 12:00:00"))), + /*44*/ testcase( + "TIMESTAMPDIFF(SQL_TSI_FRAC_SECOND,TIMESTAMP'2017-11-23 12:00:00',TIMESTAMP'2017-11-23 12:00:00.999')", + new SimpleValidator(999 * 1_000_000L)), + /*45*/ testcase( + "TIMESTAMPDIFF(SQL_TSI_SECOND,TIMESTAMP'2017-11-23 12:00:00',TIMESTAMP'2017-11-23 12:00:03')", + new SimpleValidator(3L)), + /*46*/ testcase( + "TIMESTAMPDIFF(SQL_TSI_MINUTE,TIMESTAMP'2017-11-23 12:00:00',TIMESTAMP'2017-11-23 13:01:03')", + new SimpleValidator(61L)), + /*47*/ testcase( + "TIMESTAMPDIFF(SQL_TSI_HOUR,TIMESTAMP'2017-11-23 12:00:00',TIMESTAMP'2017-11-23 13:59:59')", + new SimpleValidator(1L)), + /*48*/ testcase( + "TIMESTAMPDIFF(SQL_TSI_DAY,TIMESTAMP'2017-11-23 12:00:00',TIMESTAMP'2017-11-25 11:00:03')", + new SimpleValidator(2L)), + /*49*/ testcase( + "TIMESTAMPDIFF(SQL_TSI_WEEK,TIMESTAMP'2017-11-23 12:00:00',TIMESTAMP'2017-11-30 12:00:03')", + new SimpleValidator(1L)), + /*50*/ testcase( + "TIMESTAMPDIFF(SQL_TSI_MONTH,TIMESTAMP'2017-11-23 12:00:00',TIMESTAMP'2018-02-01 12:00:03')", + new SimpleValidator(3L)), + /*51*/ testcase( + "TIMESTAMPDIFF(SQL_TSI_QUARTER,TIMESTAMP'2017-11-23 12:00:00',TIMESTAMP'2018-02-01 12:00:03')", + new SimpleValidator(1L)), + /*52*/ testcase( + "TIMESTAMPDIFF(SQL_TSI_YEAR,TIMESTAMP'2017-11-23 12:00:00',TIMESTAMP'2019-01-01 12:00:03')", + new SimpleValidator(2L)), + /*53*/ testcase("WEEK(DATE '2012-12-22')", new SimpleValidator(51)), + /*54*/ testcase("YEAR(DATE '2012-12-22')", new SimpleValidator(2012)) +//@formatter:on + ); } - + @Test public void testScalarFunction() throws Exception { - ResultSet rs = null; - try { - rs = stmt.executeQuery(createQuery()); + try (ResultSet rs = stmt.executeQuery(createQuery())) { if (!supported) { fail(String.format("Expected function call %s to be unsupported", functionCall)); } else { @@ -148,30 +199,28 @@ public void testScalarFunction() throws Exception { // TODO validate exception? //fail("Validation of unsupported functions not yet implemented"); } - } finally { - closeQuietly(rs); } } - + private String createQuery() { return String.format("SELECT {fn %s} FROM RDB$DATABASE", functionCall); } - + /** * Convenience method to create object array for testcase (ensures correct * types). - * + * * @param functionCall - * JDBC function call (with out {fn .. }) + * JDBC function call (with out {fn .. }) * @param validator - * {@link Validator} to test the result of using the function against the - * database + * {@link Validator} to test the result of using the function against the + * database * @return Object[] testcase */ private static Object[] testcase(final String functionCall, final Validator validator) { return new Object[] { functionCall, validator, true }; } - + private static Object[] unsupported(final String functionCall) { return new Object[] { functionCall, new Validator() { @Override @@ -180,21 +229,21 @@ public String validate(Object objectToValidate, String functionCall) { } }, false }; } - + private interface Validator { - + /** * Validates object - * + * * @param objectToValidate - * Object to validate + * Object to validate * @param functionCall - * Text of function call (for use in validation message) + * Text of function call (for use in validation message) * @return null if validated, validation message if not validated */ String validate(Object objectToValidate, String functionCall); } - + private static class CurrentDateValidator implements Validator { @Override public String validate(Object objectToValidate, String functionCall) { @@ -213,7 +262,7 @@ public String validate(Object objectToValidate, String functionCall) { } } } - + private static class CurrentTimeValidator implements Validator { @Override public String validate(Object objectToValidate, String functionCall) { @@ -232,7 +281,7 @@ public String validate(Object objectToValidate, String functionCall) { } } } - + private static class CurrentTimestampValidator implements Validator { @Override public String validate(Object objectToValidate, String functionCall) { @@ -251,15 +300,15 @@ public String validate(Object objectToValidate, String functionCall) { return "Expected result of type java.sql.Timestamp"; } } - + private static class SimpleValidator implements Validator { - + private final Object expectedValue; - + private SimpleValidator(Object expectedValue) { this.expectedValue = expectedValue; } - + @Override public String validate(Object objectToValidate, String functionCall) { if (equals(expectedValue, objectToValidate)) { @@ -268,7 +317,7 @@ public String validate(Object objectToValidate, String functionCall) { return String.format("Unexpected value %s, expected %s for function call %s", objectToValidate, expectedValue, functionCall); } - + private boolean equals(Object o1, Object o2) { if (o1 == o2) { return true; diff --git a/src/test/org/firebirdsql/jdbc/escape/TestTimeDateLiteralEscapes.java b/src/test/org/firebirdsql/jdbc/escape/TimeDateLiteralEscapesTest.java similarity index 57% rename from src/test/org/firebirdsql/jdbc/escape/TestTimeDateLiteralEscapes.java rename to src/test/org/firebirdsql/jdbc/escape/TimeDateLiteralEscapesTest.java index b10821c90e..2f8401d538 100644 --- a/src/test/org/firebirdsql/jdbc/escape/TestTimeDateLiteralEscapes.java +++ b/src/test/org/firebirdsql/jdbc/escape/TimeDateLiteralEscapesTest.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,114 +12,98 @@ * 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 static org.junit.Assert.*; +import org.firebirdsql.common.FBTestProperties; +import org.firebirdsql.common.rules.UsesDatabase; +import org.junit.ClassRule; +import org.junit.Test; import java.sql.Connection; import java.sql.ResultSet; import java.sql.Statement; -import org.firebirdsql.common.FBJUnit4TestBase; -import org.firebirdsql.common.FBTestProperties; -import org.firebirdsql.common.JdbcResourceHelper; -import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; /** * Tests for support of the time and date literal escapes as defined in * section 13.4.2 of the JDBC 4.1 specification. - * + * * @author Mark Rotteveel */ -public class TestTimeDateLiteralEscapes extends FBJUnit4TestBase { +public class TimeDateLiteralEscapesTest { + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.usesDatabase(); /** - * Test of the {d 'yyyy-mm-dd'} escape. + * Test of the {d 'yyyy-mm-dd'} escape. */ @Test public void testDateEscape() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT {d '2012-12-22'} FROM RDB$DATABASE"); + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT {d '2012-12-22'} FROM RDB$DATABASE")) { + assertTrue("Expected one row", rs.next()); Object column1 = rs.getObject(1); assertTrue("Expected result of {d escape} to be of type java.sql.Date", column1 instanceof java.sql.Date); assertEquals("Unexpected value for {d escape}", "2012-12-22", column1.toString()); - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); } } - + /** - * Test of the {t 'hh:mm:ss'} escape + * Test of the {t 'hh:mm:ss'} escape */ @Test public void testTimeEscape() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT {t '15:05:56'} FROM RDB$DATABASE"); - + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT {t '15:05:56'} FROM RDB$DATABASE")) { + assertTrue("Expected one row", rs.next()); Object column1 = rs.getObject(1); assertTrue("Expected result of {t escape} to be of type java.sql.Time", column1 instanceof java.sql.Time); assertEquals("Unexpected value for {t escape}", "15:05:56", column1.toString()); - - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); } } - + /** * Test of the {ts 'yyyy-mm-dd hh:mm:ss'} escape (without fractional seconds) */ @Test public void testTimestampEscape() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT {ts '2012-12-22 15:05:56'} FROM RDB$DATABASE"); - + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT {ts '2012-12-22 15:05:56'} FROM RDB$DATABASE")) { + assertTrue("Expected one row", rs.next()); Object column1 = rs.getObject(1); - assertTrue("Expected result of {t escape} to be of type java.sql.Timestamp", column1 instanceof java.sql.Timestamp); + assertTrue("Expected result of {t escape} to be of type java.sql.Timestamp", + column1 instanceof java.sql.Timestamp); assertEquals("Unexpected value for {ts escape}", "2012-12-22 15:05:56.0", column1.toString()); - - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); } } - + /** * Test of the {ts 'yyyy-mm-dd hh:mm:ss.f..'} escape (with fractional seconds) */ @Test public void testTimestampEscapeMillisecond() throws Exception { - Connection con = FBTestProperties.getConnectionViaDriverManager(); - try { - Statement stmt = con.createStatement(); - ResultSet rs = stmt.executeQuery("SELECT {ts '2012-12-22 15:05:56.123'} FROM RDB$DATABASE"); - + try (Connection con = FBTestProperties.getConnectionViaDriverManager(); + Statement stmt = con.createStatement(); + ResultSet rs = stmt.executeQuery("SELECT {ts '2012-12-22 15:05:56.123'} FROM RDB$DATABASE")) { + assertTrue("Expected one row", rs.next()); Object column1 = rs.getObject(1); - assertTrue("Expected result of {t escape} to be of type java.sql.Timestamp", column1 instanceof java.sql.Timestamp); + assertTrue("Expected result of {t escape} to be of type java.sql.Timestamp", + column1 instanceof java.sql.Timestamp); assertEquals("Unexpected value for {ts escape}", "2012-12-22 15:05:56.123", column1.toString()); - - rs.close(); - stmt.close(); - } finally { - JdbcResourceHelper.closeQuietly(con); } } } diff --git a/src/test/org/firebirdsql/jdbc/escape/TimestampAddFunctionParameterizedTest.java b/src/test/org/firebirdsql/jdbc/escape/TimestampAddFunctionParameterizedTest.java new file mode 100644 index 0000000000..2a22a138aa --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/TimestampAddFunctionParameterizedTest.java @@ -0,0 +1,84 @@ +/* + * 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 TimestampAddFunction} for the conversion specified in JDBC 4.3 Appendix D. + *

+ * For behaviour with invalid lengths, see {@link TimestampAddFunctionTest} + *

+ * + * @author Mark Rotteveel + */ +@RunWith(Parameterized.class) +public class TimestampAddFunctionParameterizedTest { + + private static final TimestampAddFunction function = new TimestampAddFunction(); + + private final String interval; + private final String count; + private final String timestamp; + private final String expectedResult; + + public TimestampAddFunctionParameterizedTest(String interval, String count, String timestamp, + String expectedResult) { + this.interval = interval; + this.count = count; + this.timestamp = timestamp; + this.expectedResult = expectedResult; + } + + @Test + public void testConvert() throws Exception { + assertEquals(expectedResult, function.apply(interval, count, timestamp)); + } + + @Parameterized.Parameters(name = "{index}: timestampadd({0}, {1}, {2}) : {3}") + public static Collection convertTestCases() { + return Arrays.asList( +//@formatter:off + // JDBC 4.3 Appendix D cases + /* 0 */ testCase("SQL_TSI_FRAC_SECOND", "5000000", "STAMP", "DATEADD(MILLISECOND,1.0e-6*(5000000),STAMP)"), + /* 1 */ testCase("SQL_TSI_SECOND", "5", "STAMP", "DATEADD(SECOND,5,STAMP)"), + /* 2 */ testCase("SQL_TSI_MINUTE", "15", "CURRENT_TIMESTAMP", "DATEADD(MINUTE,15,CURRENT_TIMESTAMP)"), + /* 3 */ testCase("SQL_TSI_HOUR", "1", "STAMP", "DATEADD(HOUR,1,STAMP)"), + /* 4 */ testCase("SQL_TSI_DAY", "5", "STAMP", "DATEADD(DAY,5,STAMP)"), + /* 5 */ testCase("SQL_TSI_WEEK", "2", "STAMP", "DATEADD(WEEK,2,STAMP)"), + /* 6 */ testCase("SQL_TSI_MONTH", "4", "STAMP", "DATEADD(MONTH,4,STAMP)"), + /* 7 */ testCase("SQL_TSI_QUARTER", "1", "STAMP", "DATEADD(MONTH,3*(1),STAMP)"), + /* 8 */ testCase("SQL_TSI_YEAR", "9", "STAMP", "DATEADD(YEAR,9,STAMP)"), + // Unsupported / unknown values passed through as-is + /* 9 */ testCase("INCORRECT_VAL", "5", "STAMP", "DATEADD(INCORRECT_VAL,5,STAMP)") +//@formatter:on + ); + } + + private static Object[] testCase(String interval, String count, String timestamp, String expectedResult) { + return new Object[] { interval, count, timestamp, expectedResult }; + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/TimestampAddFunctionTest.java b/src/test/org/firebirdsql/jdbc/escape/TimestampAddFunctionTest.java new file mode 100644 index 0000000000..6908d809e2 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/TimestampAddFunctionTest.java @@ -0,0 +1,74 @@ +/* + * 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; + +/** + * Tests for {@link TimestampAddFunction}. + *

+ * See also {@link TimestampAddFunctionParameterizedTest} + *

+ * + * @author Mark Rotteveel + */ +public class TimestampAddFunctionTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + private static final TimestampAddFunction function = new TimestampAddFunction(); + + // Happy path tested in TimestampAddFunctionParameterizedTest + + @Test + public void testZeroParameters_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 3 parameters for TIMESTAMPADD, received 0"); + + function.apply(); + } + + @Test + public void testOneParameter_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 3 parameters for TIMESTAMPADD, received 1"); + + function.apply("SQL_TSI_MINUTE"); + } + + @Test + public void testTwoParameter_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 3 parameters for TIMESTAMPADD, received 2"); + + function.apply("SQL_TSI_MINUTE", "5"); + } + + @Test + public void testFourParameter_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 3 parameters for TIMESTAMPADD, received 4"); + + function.apply("SQL_TSI_MINUTE", "5", "CURRENT_TIMESTAMP", "extra"); + } + +} diff --git a/src/test/org/firebirdsql/jdbc/escape/TimestampDiffFunctionParameterizedTest.java b/src/test/org/firebirdsql/jdbc/escape/TimestampDiffFunctionParameterizedTest.java new file mode 100644 index 0000000000..9c7d299b6c --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/TimestampDiffFunctionParameterizedTest.java @@ -0,0 +1,84 @@ +/* + * 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 TimestampAddFunction} for the conversion specified in JDBC 4.3 Appendix D. + *

+ * For behaviour with invalid lengths, see {@link TimestampAddFunctionTest} + *

+ * + * @author Mark Rotteveel + */ +@RunWith(Parameterized.class) +public class TimestampDiffFunctionParameterizedTest { + + private static final TimestampDiffFunction function = new TimestampDiffFunction(); + + private final String interval; + private final String timestamp1; + private final String timestamp2; + private final String expectedResult; + + public TimestampDiffFunctionParameterizedTest(String interval, String timestamp1, String timestamp2, + String expectedResult) { + this.interval = interval; + this.timestamp1 = timestamp1; + this.timestamp2 = timestamp2; + this.expectedResult = expectedResult; + } + + @Test + public void testConvert() throws Exception { + assertEquals(expectedResult, function.apply(interval, timestamp1, timestamp2)); + } + + @Parameterized.Parameters(name = "{index}: timestampadd({0}, {1}, {2}) : {3}") + public static Collection convertTestCases() { + return Arrays.asList( +//@formatter:off + // JDBC 4.3 Appendix D cases + /* 0 */ testCase("SQL_TSI_FRAC_SECOND", "STAMP1", "STAMP2", "CAST(DATEDIFF(MILLISECOND,STAMP1,STAMP2)*1.0e6 AS BIGINT)"), + /* 1 */ testCase("SQL_TSI_SECOND", "STAMP1", "STAMP2", "DATEDIFF(SECOND,STAMP1,STAMP2)"), + /* 2 */ testCase("SQL_TSI_MINUTE", "STAMP", "CURRENT_TIMESTAMP", "DATEDIFF(MINUTE,STAMP,CURRENT_TIMESTAMP)"), + /* 3 */ testCase("SQL_TSI_HOUR", "STAMP1", "STAMP2", "DATEDIFF(HOUR,STAMP1,STAMP2)"), + /* 4 */ testCase("SQL_TSI_DAY", "STAMP1", "STAMP2", "DATEDIFF(DAY,STAMP1,STAMP2)"), + /* 5 */ testCase("SQL_TSI_WEEK", "STAMP1", "STAMP2", "DATEDIFF(WEEK,STAMP1,STAMP2)"), + /* 6 */ testCase("SQL_TSI_MONTH", "STAMP1", "STAMP2", "DATEDIFF(MONTH,STAMP1,STAMP2)"), + /* 7 */ testCase("SQL_TSI_QUARTER", "STAMP1", "STAMP2", "(DATEDIFF(MONTH,STAMP1,STAMP2)/3)"), + /* 8 */ testCase("SQL_TSI_YEAR", "STAMP1", "STAMP2", "DATEDIFF(YEAR,STAMP1,STAMP2)"), + // Unsupported / unknown values passed through as-is + /* 9 */ testCase("INCORRECT_VAL", "STAMP1", "STAMP2", "DATEDIFF(INCORRECT_VAL,STAMP1,STAMP2)") +//@formatter:on + ); + } + + private static Object[] testCase(String interval, String timestamp1, String timestamp2, String expectedResult) { + return new Object[] { interval, timestamp1, timestamp2, expectedResult }; + } +} diff --git a/src/test/org/firebirdsql/jdbc/escape/TimestampDiffFunctionTest.java b/src/test/org/firebirdsql/jdbc/escape/TimestampDiffFunctionTest.java new file mode 100644 index 0000000000..c433c1e28b --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/escape/TimestampDiffFunctionTest.java @@ -0,0 +1,74 @@ +/* + * 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; + +/** + * Tests for {@link TimestampDiffFunction}. + *

+ * See also {@link TimestampDiffFunctionParameterizedTest} + *

+ * + * @author Mark Rotteveel + */ +public class TimestampDiffFunctionTest { + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + private static final TimestampDiffFunction function = new TimestampDiffFunction(); + + // Happy path tested in TimestampAddFunctionParameterizedTest + + @Test + public void testZeroParameters_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 3 parameters for TIMESTAMPDIFF, received 0"); + + function.apply(); + } + + @Test + public void testOneParameter_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 3 parameters for TIMESTAMPDIFF, received 1"); + + function.apply("SQL_TSI_MINUTE"); + } + + @Test + public void testTwoParameter_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 3 parameters for TIMESTAMPDIFF, received 2"); + + function.apply("SQL_TSI_MINUTE", "STAMP"); + } + + @Test + public void testFourParameter_throwsException() throws Exception { + expectedException.expect(FBSQLParseException.class); + expectedException.expectMessage("Expected 3 parameters for TIMESTAMPDIFF, received 4"); + + function.apply("SQL_TSI_MINUTE", "STAMP", "CURRENT_TIMESTAMP", "extra"); + } + +}