From 7930dd0ce117a8ce3be697612f08626ec8054266 Mon Sep 17 00:00:00 2001 From: Mark Rotteveel Date: Sat, 13 Apr 2019 11:07:52 +0200 Subject: [PATCH] JDBC-540 Add time zone bind DPB support Also add support for decfloat-related DPB items --- devdoc/jdp/jdp-2019-03-time-zone-support.md | 10 +- src/documentation/release_notes.md | 57 ++++++- .../org/firebirdsql/gds/ISCConstants.java | 11 +- .../DatabaseParameterBufferExtension.java | 2 - .../gds/ng/FbConnectionProperties.java | 8 +- src/resources/driver_property_info.properties | 13 +- src/resources/isc_dpb_types.properties | 5 +- src/resources/isc_error_msg.properties | 4 +- src/resources/isc_error_sqlstates.properties | 4 +- .../jdbc/TimeZoneBindLegacyTest.java | 122 ++++++++++++++ .../firebirdsql/jdbc/TimeZoneBindTest.java | 159 ++++++++++++++++++ 11 files changed, 370 insertions(+), 25 deletions(-) create mode 100644 src/test/org/firebirdsql/jdbc/TimeZoneBindLegacyTest.java create mode 100644 src/test_42/org/firebirdsql/jdbc/TimeZoneBindTest.java diff --git a/devdoc/jdp/jdp-2019-03-time-zone-support.md b/devdoc/jdp/jdp-2019-03-time-zone-support.md index 92f549615c..f9b702bc13 100644 --- a/devdoc/jdp/jdp-2019-03-time-zone-support.md +++ b/devdoc/jdp/jdp-2019-03-time-zone-support.md @@ -119,11 +119,9 @@ Jaybird 4 will support Java 7 and higher, and Java 7 does not include `java.time Simplifies implementation in some parts, avoids some ambiguity in Java code with mapping to `java.sql.Timestamp`/`java.sql.Time`. -7. Provide connection property to set the time zone bind (native (default) or - legacy). - - When the property is set to legacy, after connect to Firebird 4 or higher, - Jaybird will execute `SET TIME ZONE BIND LEGACY`. +7. Provide connection property `timeZoneBind` to set the time zone bind (native + (default) or legacy). This will map to Firebird DPB item + `isc_dpb_time_zone_bind`. Java 7 users will need to explicitly set this if they want to use `WITH TIME ZONE` types (including `CURRENT_TIME` and `CURRENT_TIMESTAMP`). @@ -278,5 +276,5 @@ Time zone support in Jaybird will not include the following: ## Consequences -*todo* +See [Decision] and [Rejected options]. \ No newline at end of file diff --git a/src/documentation/release_notes.md b/src/documentation/release_notes.md index b733143a94..042ce82813 100644 --- a/src/documentation/release_notes.md +++ b/src/documentation/release_notes.md @@ -654,6 +654,8 @@ applied: If you need other rounding and overflow behavior, make sure you round the values appropriately before you set them. + +*TODO*: Document decfloat bind/traps/round connection property. ### Notes ### @@ -785,11 +787,13 @@ ZONE` types. See the Firebird 4 release notes and `doc/sql.extensions/README.tim in the Firebird installation for details on these types. The time zone types are supported under Java 8 and higher, using the Java 8 (or -higher) version of Jaybird. Time zone types are not supported under Java 7 and -will require you to enable legacy time zone bind. With legacy time zone -bind (`SET TIME ZONE BIND LEGACY`), Firebird will convert to the equivalent -`TIME` and `TIMESTAMP` (`WITHOUT TIME ZONE`) types using the session time -zone. +higher) version of Jaybird. + +Time zone types are not supported under Java 7, you will need to enable legacy +time zone bind. With legacy time zone bind, Firebird will convert to the +equivalent `TIME` and `TIMESTAMP` (`WITHOUT TIME ZONE`) types using the session +time zone. Time zone bind can be configured with connection property +`timeZoneBind`, for more information see [Time zone bind configuration]. See also [jdp-2019-03 Time Zone Support](https://github.com/FirebirdSQL/jaybird/blob/master/devdoc/jdp/jdp-2019-03-time-zone-support.md) @@ -874,7 +878,7 @@ Compared to the `WITHOUT TIME ZONE` types, there may be small discrepancies in values as Jaybird uses 1970-01-01 for `WITHOUT TIME ZONE`, while for `WITH TIME ZONE` it uses the current date. If this is problematic, then either apply the necessary conversions yourself, enable legacy time zone bind, or define or cast -your columns as `TIME` or `TIMESTAMP`. +your columns to `TIME` or `TIMESTAMP`. #### No support for other java.time types #### @@ -889,6 +893,42 @@ Jaybird also does not support non-standard extensions like `java.time.Instant`, or `java.time.ZonedDateTime`. If there is interest, we may add them in the future. +### Time zone bind configuration ### + +The connection property `timeZoneBind` (alias `time_zone_bind`) is a connection +property to configure the time zone bind (see also `SET TIME ZONE BIND` in the +Firebird 4 release notes). + +The primary purpose of this property is to set the legacy time zone bind. This +needs to be explicitly set if you are using Java 7 and need to handle the +`WITH TIME ZONE` types. It can also be used for tools or applications that +expect `java.sql.Time`/`Timestamp` types and cannot use the +`java.time.OffsetTime`/`OffsetDateTime` types returned for the `WITH TIME ZONE` +types. + +Possible values (case insensitive): + +- `legacy` + + Firebird will convert a `WITH TIME ZONE` type to the equivalent `WITHOUT + TIME ZONE` type using the session time zone to derive the value. + + Result set columns and parameters on prepared statements will behave as the + equivalent `WITHOUT TIME ZONE` types. This conversion is not applied to the + database metadata which will always report `WITH TIME ZONE` information. + +- `native` + + Behaves as default (`WITH TIME ZONE` types supported), but value will be + explicitly set. + +Any other value will result in error `isc_time_zone_bind` (code 335545255, +message _"Invalid time zone bind mode <value>"_) on connect. + +**Important**: this feature requires Firebird 4 beta 2 or higher (or a snapshot +build version 4.0.0.1481 or later). It will be ignored in earlier builds as the +necessary database parameter buffer item does not exist in earlier versions. + ### Connection property sessionTimeZone ### The connection property `sessionTimeZone` (alias `session_time_zone`) does two @@ -903,6 +943,11 @@ By default, Jaybird will use the JVM default time zone as reported by the default is the best option in the light of JDBC requirements with regard to `java.sql.Time` and `java.sql.Timestamp` using the JVM default time zone. +Valid values are time zone names known by Firebird, we recommend to use the long +names (eg `Europe/Amsterdam`) and not the ambiguous short IDs (eg `CET`). +Although not required, we recommend to use time zone names that are known by +Firebird and Java (see [Session time zone for conversion] for caveats). + To use the default server time zone and the old behaviour to use the JVM default time zone, set the connection property to `server`. This will result in the conversion behaviour of Jaybird 3 and earlier. Be aware that this is diff --git a/src/main/org/firebirdsql/gds/ISCConstants.java b/src/main/org/firebirdsql/gds/ISCConstants.java index 3921ab8856..ab838f856e 100644 --- a/src/main/org/firebirdsql/gds/ISCConstants.java +++ b/src/main/org/firebirdsql/gds/ISCConstants.java @@ -166,6 +166,10 @@ public interface ISCConstants { // Firebird 4 constants int isc_dpb_session_time_zone = 91; int isc_dpb_set_db_replica = 92; + int isc_dpb_time_zone_bind = 93; + int isc_dpb_decfloat_bind = 94; + int isc_dpb_decfloat_round = 95; + int isc_dpb_decfloat_traps = 96; /* * Driver-specific DPB params that will be removed before sending them @@ -193,9 +197,7 @@ public interface ISCConstants { int isc_dpb_wire_crypt_level = 144; int isc_dpb_db_crypt_config = 145; int isc_dpb_generated_keys_enabled = 146; - // TODO Fix after CORE-6032 - int isc_dpb_time_zone_bind = 147; - int isc_dpb_ignore_procedure_type = 148; + int isc_dpb_ignore_procedure_type = 147; // Lowest Jaybird DPB extension value int jaybirdMinIscDpbValue = isc_dpb_socket_buffer_size; @@ -1827,6 +1829,9 @@ public interface ISCConstants { int isc_bad_repl_handle = 335545251; int isc_tra_snapshot_does_not_exist = 335545252; int isc_eds_input_prm_not_used = 335545253; + int isc_effective_user = 335545254; + int isc_time_zone_bind = 335545255; + int isc_decfloat_bind = 335545256; int isc_gfix_db_name = 335740929; int isc_gfix_invalid_sw = 335740930; int isc_gfix_incmp_sw = 335740932; diff --git a/src/main/org/firebirdsql/gds/impl/DatabaseParameterBufferExtension.java b/src/main/org/firebirdsql/gds/impl/DatabaseParameterBufferExtension.java index 9b5450439e..8a361a767f 100644 --- a/src/main/org/firebirdsql/gds/impl/DatabaseParameterBufferExtension.java +++ b/src/main/org/firebirdsql/gds/impl/DatabaseParameterBufferExtension.java @@ -60,7 +60,6 @@ public interface DatabaseParameterBufferExtension extends DatabaseParameterBuffe int WIRE_CRYPT_LEVEL = ISCConstants.isc_dpb_wire_crypt_level; int DB_CRYPT_CONFIG = ISCConstants.isc_dpb_db_crypt_config; int GENERATED_KEYS_ENABLED = ISCConstants.isc_dpb_generated_keys_enabled; - int TIME_ZONE_BIND = ISCConstants.isc_dpb_time_zone_bind; int IGNORE_PROCEDURE_TYPE = ISCConstants.isc_dpb_ignore_procedure_type; /** @@ -90,7 +89,6 @@ public interface DatabaseParameterBufferExtension extends DatabaseParameterBuffe WIRE_CRYPT_LEVEL, DB_CRYPT_CONFIG, GENERATED_KEYS_ENABLED, - TIME_ZONE_BIND, IGNORE_PROCEDURE_TYPE }; diff --git a/src/main/org/firebirdsql/gds/ng/FbConnectionProperties.java b/src/main/org/firebirdsql/gds/ng/FbConnectionProperties.java index da508fb479..42005d50e4 100644 --- a/src/main/org/firebirdsql/gds/ng/FbConnectionProperties.java +++ b/src/main/org/firebirdsql/gds/ng/FbConnectionProperties.java @@ -258,7 +258,13 @@ public void fromDpb(DatabaseParameterBuffer dpb) throws SQLException { "Unknown or unsupported parameter with type %d added to extra database parameters", parameterType)); } - parameter.copyTo(getExtraDatabaseParameters(), null); + // intentional fall-through; properties below don't need a warning + // TODO Consider using explicit properties in IConnectionProperties? + case isc_dpb_time_zone_bind: + case isc_dpb_decfloat_bind: + case isc_dpb_decfloat_round: + case isc_dpb_decfloat_traps: + parameter.copyTo(extraDatabaseParameters, null); dirtied(); } } diff --git a/src/resources/driver_property_info.properties b/src/resources/driver_property_info.properties index 684a924146..d0d5b83c21 100644 --- a/src/resources/driver_property_info.properties +++ b/src/resources/driver_property_info.properties @@ -1,6 +1,6 @@ # This file contains aliases of the DPB parameters as well as their descriptions -# Key of the resource is the alias, then after a delimiter (in our case it's tab -# character) comes name of the DPB parameter, and after that starting with the +# Key of the resource is the alias, then after a delimiter (in our case it's +# whitespace) comes name of the DPB parameter, and after that starting with the # hash character ('#') goes description user isc_dpb_user_name # Name of the user connecting to Firebird @@ -42,6 +42,11 @@ wireCrypt isc_dpb_wire_crypt_level # FB3+ wire crypt le dbCryptConfig isc_dpb_db_crypt_config # FB3+ database encryption config (format is plugin specific) authPlugins isc_dpb_auth_plugin_list # FB3+ database authentication plugins to try generatedKeysEnabled isc_dpb_generated_keys_enabled # Generated keys support configuration: default (or absent/empty), disabled, ignored or comma-separated list of statement types to enable (possible values: insert, update, delete, update_or_insert, merge) -timeZoneBind isc_dpb_time_zone_bind # FB4+ time zone bind setting: native (default) or legacy (Firebird converts with time zone types to without time zone types) -sessionTimeZone isc_dpb_session_time_zone # FB4+ session time zone (defaults to JVM default time zone), use 'server' to use server default time zone ignoreProcedureType isc_dpb_ignore_procedure_type # Ignore procedure type from metadata (defaults to executable stored procedure) + +sessionTimeZone isc_dpb_session_time_zone # FB4+ session time zone (defaults to JVM default time zone), use 'server' to use server default time zone +timeZoneBind isc_dpb_time_zone_bind # FB4+ time zone bind setting: native (default) or legacy (Firebird converts with time zone types to without time zone types) + +decfloatBind isc_dpb_decfloat_bind # FB4+ decloat bind setting: native (default), char or character, double precision, bigint (with optional comma-separated scale) +decfloatRound isc_dpb_decfloat_round # FB4+ decfloat rounding mode: ceiling, up, half_up (default), half_even, half_down, down, floor, reround +decfloatTrap isc_dpb_decfloat_traps # FB4+ decfloat traps (comma-separated list): Division_by_zero (default), Inexact, Invalid_operation (default), Overflow (default), Underflow diff --git a/src/resources/isc_dpb_types.properties b/src/resources/isc_dpb_types.properties index f9acd6e850..fd5b7497bc 100644 --- a/src/resources/isc_dpb_types.properties +++ b/src/resources/isc_dpb_types.properties @@ -21,6 +21,10 @@ isc_dpb_process_id int isc_dpb_process_name string isc_dpb_auth_plugin_list string isc_dpb_session_time_zone string +isc_dpb_time_zone_bind string +isc_dpb_decfloat_bind string +isc_dpb_decfloat_round string +isc_dpb_decfloat_traps string # following properties are extensions from Jaybird isc_dpb_socket_buffer_size int @@ -37,5 +41,4 @@ isc_dpb_use_firebird_autocommit boolean isc_dpb_wire_crypt_level string isc_dpb_db_crypt_config string isc_dpb_generated_keys_enabled string -isc_dpb_time_zone_bind string isc_dpb_ignore_procedure_type boolean diff --git a/src/resources/isc_error_msg.properties b/src/resources/isc_error_msg.properties index 20cf2ea02a..1721dc03c4 100644 --- a/src/resources/isc_error_msg.properties +++ b/src/resources/isc_error_msg.properties @@ -1,4 +1,4 @@ -#Sun Mar 24 09:27:02 CET 2019 +#Sat Apr 13 10:15:01 CEST 2019 335544320= 335544321=arithmetic exception, numeric overflow, or string truncation 335544322=invalid database key @@ -934,6 +934,8 @@ 335545252=Transaction's base snapshot number does not exist 335545253=Input parameter '{0}' is not used in SQL query text 335545254=Effective user is {0} +335545255=Invalid time zone bind mode {0} +335545256=Invalid decfloat bind mode {0} 335609856=expected type 335609857=bad block type 335609858=bad block size diff --git a/src/resources/isc_error_sqlstates.properties b/src/resources/isc_error_sqlstates.properties index d7a7cbf745..eb2662980b 100644 --- a/src/resources/isc_error_sqlstates.properties +++ b/src/resources/isc_error_sqlstates.properties @@ -1,4 +1,4 @@ -#Sun Mar 24 09:27:02 CET 2019 +#Sat Apr 13 10:15:01 CEST 2019 335544321=22000 335544322=HY000 335544323=HY000 @@ -933,6 +933,8 @@ 335545252=0B000 335545253=42000 335545254=28000 +335545255=42000 +335545256=42000 335740929=00000 335740930=00000 335740932=00000 diff --git a/src/test/org/firebirdsql/jdbc/TimeZoneBindLegacyTest.java b/src/test/org/firebirdsql/jdbc/TimeZoneBindLegacyTest.java new file mode 100644 index 0000000000..6946fda157 --- /dev/null +++ b/src/test/org/firebirdsql/jdbc/TimeZoneBindLegacyTest.java @@ -0,0 +1,122 @@ +/* + * 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; + +import org.firebirdsql.common.rules.UsesDatabase; +import org.junit.ClassRule; +import org.junit.Test; + +import java.sql.*; +import java.util.Properties; + +import static org.firebirdsql.common.FBTestProperties.*; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +/** + * See also Java 8 or higher companion test {@link TimeZoneBindTest}. + */ +public class TimeZoneBindLegacyTest { + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.usesDatabase(); + + /** + * Checks if {@code CURRENT_TIMESTAMP} returns a timestamp without time zone when time zone bind is set to legacy. + *

+ * NOTE: We are not checking the Firebird version as this property will be ignored on earlier versions, so the + * test should work on earlier versions as well. + *

+ */ + @Test + public void testCurrentTimestamp_legacyBind() throws Exception { + Properties props = getDefaultPropertiesForConnection(); + props.setProperty("timeZoneBind", "legacy"); + try (Connection connection = DriverManager.getConnection(getUrl(), props); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("select CURRENT_TIMESTAMP from RDB$DATABASE")) { + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals("Expected TIMESTAMP (WITHOUT TIME ZONE)", Types.TIMESTAMP, rsmd.getColumnType(1)); + assertTrue("Expected a row", rs.next()); + assertThat(rs.getObject(1), instanceOf(Timestamp.class)); + } + } + + /** + * Checks if {@code CURRENT_TIME} returns a time without time zone when time zone bind is set to legacy. + *

+ * NOTE: We are not checking the Firebird version as this property will be ignored on earlier versions, so the + * test should work on earlier versions as well. + *

+ */ + @Test + public void testCurrentTime_legacyBind() throws Exception { + Properties props = getDefaultPropertiesForConnection(); + props.setProperty("timeZoneBind", "legacy"); + try (Connection connection = DriverManager.getConnection(getUrl(), props); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("select CURRENT_TIME from RDB$DATABASE")) { + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals("Expected TIME (WITHOUT TIME ZONE)", Types.TIME, rsmd.getColumnType(1)); + assertTrue("Expected a row", rs.next()); + assertThat(rs.getObject(1), instanceOf(Time.class)); + } + } + + @Test + public void testRoundTrip_legacyBind() throws Exception { + assumeTrue("Requires time zone support", getDefaultSupportInfo().supportsTimeZones()); + Properties props = getDefaultPropertiesForConnection(); + props.setProperty("timeZoneBind", "legacy"); + try (Connection connection = DriverManager.getConnection(getUrl(), props)) { + try (Statement stmt = connection.createStatement()) { + stmt.execute("create table testtbl (" + + "id integer, " + + "timeval time with time zone, " + + "timestampval timestamp with time zone" + + ")"); + } + + long timeMillis = System.currentTimeMillis(); + Timestamp timestampVal = new Timestamp(timeMillis); + Time timeVal = new Time(timeMillis); + + try (PreparedStatement pstmt = connection.prepareStatement( + "insert into testtbl(id, timeval, timestampval) values (?, ?, ?)")) { + pstmt.setInt(1, 1); + pstmt.setTime(2, timeVal); + pstmt.setTimestamp(3, timestampVal); + + pstmt.execute(); + } + + try (Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery( + "select timeval, timestampval, cast(timestampval as varchar(50)) from testtbl where id = 1")) { + assertTrue("expected a row", rs.next()); + System.out.println(rs.getString(3)); + // Using toString to avoid 'date' differences + assertEquals("TIME", timeVal.toString(), rs.getTime(1).toString()); + assertEquals("TIMESTAMP", timestampVal, rs.getTimestamp(2)); + } + } + + } +} diff --git a/src/test_42/org/firebirdsql/jdbc/TimeZoneBindTest.java b/src/test_42/org/firebirdsql/jdbc/TimeZoneBindTest.java new file mode 100644 index 0000000000..784160573d --- /dev/null +++ b/src/test_42/org/firebirdsql/jdbc/TimeZoneBindTest.java @@ -0,0 +1,159 @@ +/* + * 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; + +import org.firebirdsql.common.rules.UsesDatabase; +import org.firebirdsql.gds.ISCConstants; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.sql.*; +import java.time.OffsetDateTime; +import java.util.Properties; + +import static org.firebirdsql.common.FBTestProperties.*; +import static org.firebirdsql.common.matchers.SQLExceptionMatchers.errorCodeEquals; +import static org.firebirdsql.common.matchers.SQLExceptionMatchers.fbMessageStartsWith; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.core.AllOf.allOf; +import static org.junit.Assert.*; +import static org.junit.Assume.assumeTrue; + +/** + * See also Java 7 companion test {@link TimeZoneBindLegacyTest}. + */ +public class TimeZoneBindTest { + + @ClassRule + public static final UsesDatabase usesDatabase = UsesDatabase.noDatabase(); + + @Rule + public final ExpectedException expectedException = ExpectedException.none(); + + @BeforeClass + public static void requireTimeZoneSupport() throws Exception { + assumeTrue("Test requires time zone support (Firebird 4+)", getDefaultSupportInfo().supportsTimeZones()); + usesDatabase.createDefaultDatabase(); + } + + @Test + public void testCurrentTimestamp_noBind() throws Exception { + Properties props = getDefaultPropertiesForConnection(); + try (Connection connection = DriverManager.getConnection(getUrl(), props); + Statement stmt = connection.createStatement(); + ResultSet rs = stmt.executeQuery("select CURRENT_TIMESTAMP from RDB$DATABASE")) { + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals("Expected TIMESTAMP (WITHOUT TIME ZONE)", Types.TIMESTAMP_WITH_TIMEZONE, rsmd.getColumnType(1)); + assertTrue("Expected a row", rs.next()); + assertThat(rs.getObject(1), instanceOf(OffsetDateTime.class)); + } + } + + @Test + public void testCurrentTimestamp_emptyBind() throws Exception { + checkForBindValue(""); + } + + @Test + public void testCurrentTimestamp_nativeBind() throws Exception { + checkForBindValue("native"); + } + + @Test + public void testCurrentTimestamp_NaTIVEBind() throws Exception { + checkForBindValue("NaTIVE"); + } + + @Test + public void testCurrentTimestamp_invalidBind() throws Exception { + expectedException.expect(allOf( + errorCodeEquals(ISCConstants.isc_time_zone_bind), + fbMessageStartsWith(ISCConstants.isc_time_zone_bind, "doesnotexist"))); + + Properties props = getDefaultPropertiesForConnection(); + props.setProperty("timeZoneBind", "doesnotexist"); + //noinspection EmptyTryBlock + try (Connection ignore = DriverManager.getConnection(getUrl(), props)) { + // ensure connection is closed if this doesn't fail + } + } + + @Test + public void verifySessionReset_retainsSetting() throws Exception { + Properties props = getDefaultPropertiesForConnection(); + props.setProperty("timeZoneBind", "legacy"); + try (Connection connection = DriverManager.getConnection(getUrl(), props); + Statement stmt = connection.createStatement()) { + stmt.execute("alter session reset"); + + verifyLegacyTimestamp(stmt); + } + } + + @Test + public void verifySessionReset_afterExplicitChange() throws Exception { + Properties props = getDefaultPropertiesForConnection(); + props.setProperty("timeZoneBind", "legacy"); + try (Connection connection = DriverManager.getConnection(getUrl(), props); + Statement stmt = connection.createStatement()) { + + verifyLegacyTimestamp(stmt); + + stmt.execute("set time zone bind native"); + + verifyTimestampWithTimezone(stmt); + + stmt.execute("alter session reset"); + + verifyLegacyTimestamp(stmt); + } + } + + private void checkForBindValue(String bindValue) throws Exception { + Properties props = getDefaultPropertiesForConnection(); + props.setProperty("timeZoneBind", bindValue); + try (Connection connection = DriverManager.getConnection(getUrl(), props)) { + try (Statement stmt = connection.createStatement()) { + verifyTimestampWithTimezone(stmt); + } + } + } + + private void verifyLegacyTimestamp(Statement stmt) throws SQLException { + verifyTimestampType(stmt, JDBCType.TIMESTAMP, Timestamp.class); + } + + private void verifyTimestampWithTimezone(Statement stmt) throws SQLException { + verifyTimestampType(stmt, JDBCType.TIMESTAMP_WITH_TIMEZONE, OffsetDateTime.class); + } + + private void verifyTimestampType(Statement stmt, JDBCType expectedJdbcType, Class expectedType) throws SQLException { + try (ResultSet rs = stmt.executeQuery("select CURRENT_TIMESTAMP from RDB$DATABASE")) { + ResultSetMetaData rsmd = rs.getMetaData(); + assertEquals("Expected " + expectedJdbcType, + expectedJdbcType.getVendorTypeNumber().intValue(), rsmd.getColumnType(1)); + assertTrue("Expected a row", rs.next()); + assertThat(rs.getObject(1), instanceOf(expectedType)); + } + } + +}