Skip to content

Commit

Permalink
JDBC-540 Add time zone bind DPB support
Browse files Browse the repository at this point in the history
Also add support for decfloat-related DPB items
  • Loading branch information
mrotteveel committed Apr 13, 2019
1 parent 25cb09a commit 7930dd0
Show file tree
Hide file tree
Showing 11 changed files with 370 additions and 25 deletions.
10 changes: 4 additions & 6 deletions devdoc/jdp/jdp-2019-03-time-zone-support.md
Expand Up @@ -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`).
Expand Down Expand Up @@ -278,5 +276,5 @@ Time zone support in Jaybird will not include the following:

## Consequences

*todo*
See [Decision] and [Rejected options].

57 changes: 51 additions & 6 deletions src/documentation/release_notes.md
Expand Up @@ -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 ###

Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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 ####

Expand All @@ -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
Expand All @@ -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
Expand Down
11 changes: 8 additions & 3 deletions src/main/org/firebirdsql/gds/ISCConstants.java
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -90,7 +89,6 @@ public interface DatabaseParameterBufferExtension extends DatabaseParameterBuffe
WIRE_CRYPT_LEVEL,
DB_CRYPT_CONFIG,
GENERATED_KEYS_ENABLED,
TIME_ZONE_BIND,
IGNORE_PROCEDURE_TYPE
};

Expand Down
8 changes: 7 additions & 1 deletion src/main/org/firebirdsql/gds/ng/FbConnectionProperties.java
Expand Up @@ -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();
}
}
Expand Down
13 changes: 9 additions & 4 deletions 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
Expand Down Expand Up @@ -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
5 changes: 4 additions & 1 deletion src/resources/isc_dpb_types.properties
Expand Up @@ -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
Expand All @@ -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
4 changes: 3 additions & 1 deletion 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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion 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
Expand Down Expand Up @@ -933,6 +933,8 @@
335545252=0B000
335545253=42000
335545254=28000
335545255=42000
335545256=42000
335740929=00000
335740930=00000
335740932=00000
Expand Down
122 changes: 122 additions & 0 deletions 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.
* <p>
* 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.
* </p>
*/
@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.
* <p>
* 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.
* </p>
*/
@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));
}
}

}
}

0 comments on commit 7930dd0

Please sign in to comment.