Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a timezone as region option #81

Merged
merged 3 commits into from
Sep 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,13 +290,17 @@ supported by Oracle R2DBC:
- Out of band (OOB) breaks effect statement timeouts. Set this to "true" if statement timeouts are not working correctly.
- [oracle.jdbc.enableQueryResultCache](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_ENABLE_QUERY_RESULT_CACHE)
- Cached query results can cause phantom reads even if the serializable
transaction isolation level is set. Set this to "false" if using the
transaction isolation level is set. Set this to "false" if using the
serializable isolation level.
- [v$session.terminal](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_VSESSION_TERMINAL)
- [v$session.machine](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_VSESSION_MACHINE)
- [v$session.osuser](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_VSESSION_OSUSER)
- [v$session.program](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_VSESSION_PROGRAM)
- [v$session.process](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_THIN_VSESSION_PROCESS)
- (For inclusion in the next release) [oracle.jdbc.timeZoneAsRegion](https://docs.oracle.com/en/database/oracle/oracle-database/21/jajdb/oracle/jdbc/OracleConnection.html#CONNECTION_PROPERTY_TIMEZONE_AS_REGION)
- Setting this option to "false" may resolve "ORA-01882: timezone region not
found". The ORA-01882 error happens when Oracle Database doesn't recognize
the name returned by `java.util.TimeZone.getDefault().getId()`.

### Thread Safety and Parallel Execution
Oracle R2DBC's `ConnectionFactory` and `ConnectionFactoryProvider` are the only
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/oracle/r2dbc/OracleR2dbcOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -259,4 +259,11 @@ private OracleR2dbcOptions() {}
public static final Option<CharSequence> VSESSION_PROCESS =
Option.valueOf(OracleConnection.CONNECTION_PROPERTY_THIN_VSESSION_PROCESS);

/**
* Configures the Oracle JDBC Connection used by Oracle R2DBC as specified by:
* {@link OracleConnection#CONNECTION_PROPERTY_TIMEZONE_AS_REGION}
*/
public static final Option<CharSequence> TIMEZONE_AS_REGION =
Option.valueOf(OracleConnection.CONNECTION_PROPERTY_TIMEZONE_AS_REGION);

}
19 changes: 10 additions & 9 deletions src/main/java/oracle/r2dbc/impl/OracleReactiveJdbcAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,13 @@ final class OracleReactiveJdbcAdapter implements ReactiveJdbcAdapter {
OracleR2dbcOptions.VSESSION_TERMINAL,
OracleR2dbcOptions.VSESSION_PROCESS,
OracleR2dbcOptions.VSESSION_PROGRAM,
OracleR2dbcOptions.VSESSION_MACHINE
OracleR2dbcOptions.VSESSION_MACHINE,

// Allow JDBC to configure the session timezone as an offset of UTC
// (ie: +09:00), rather than a name (ie: Etc/UTC). This avoids
// "ORA-01882: timezone region not found" when the name of the JVM's
// default timezone is not recognized by Oracle Database.
OracleR2dbcOptions.TIMEZONE_AS_REGION
);

/** Guards access to a JDBC {@code Connection} created by this adapter */
Expand Down Expand Up @@ -401,17 +407,12 @@ public AsyncLock getLock() {
* </li><li>
* {@linkplain OracleConnection#CONNECTION_PROPERTY_THIN_VSESSION_MACHINE
* v$session.machine}
* </li><li>
* {@linkplain OracleConnection#CONNECTION_PROPERTY_TIMEZONE_AS_REGION
* oracle.jdbc.timezoneAsRegion}
* </li>
* </ul>
*
* @implNote The returned {@code DataSource} is configured to create
* connections that encode character bind values using the National
* Character Set of an Oracle Database. In 21c, the National Character Set
* must be either UTF-8 or UTF-16; This ensures that unicode bind data is
* properly encoded by Oracle JDBC. If the data source is not configured
* this way, the Oracle JDBC Driver uses the default character set of the
* database, which may not support Unicode characters.
*
* @throws IllegalArgumentException If the {@code oracleNetDescriptor}
* {@code Option} is provided with any other options that might have
* conflicting values, such as {@link ConnectionFactoryOptions#HOST}.
Expand Down
49 changes: 0 additions & 49 deletions src/oracle-r2dbc-master.iml

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import oracle.jdbc.OracleConnection;
import oracle.jdbc.datasource.OracleDataSource;
import oracle.r2dbc.OracleR2dbcOptions;
import oracle.r2dbc.test.DatabaseConfig;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
Expand All @@ -43,8 +42,10 @@
import java.nio.file.StandardOpenOption;
import java.sql.SQLException;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.Properties;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
Expand Down Expand Up @@ -528,6 +529,53 @@ public void testVSessionOptions() {
}
}

/**
* Verifies the {@link OracleR2dbcOptions#TIMEZONE_AS_REGION} option
*/
@Test
public void testTimezoneAsRegion() {
// Set the timezone to that of Warsaw. When JDBC opens a connection, it will
// read the Warsaw timezone from TimeZone.getDefault().
TimeZone warsawTimeZone = TimeZone.getTimeZone("Europe/Warsaw");
TimeZone timeZoneRestored = TimeZone.getDefault();
TimeZone.setDefault(warsawTimeZone);
try {

// Configure the JDBC connection property with a URL parameter. This has
// JDBC express the session timezone as an offset of UTC (+02:00), rather
// than a name (Europe/Warsaw).
Connection connection = awaitOne(ConnectionFactories.get(
ConnectionFactoryOptions.parse(format(
"r2dbc:oracle://%s:%d/%s?oracle.jdbc.timezoneAsRegion=false",
host(), port(), serviceName()))
.mutate()
.option(USER, user())
.option(PASSWORD, password())
.build())
.create());
try {

// Query the session timezone, and expect it to be expressed as an
// offset, rather than a name.
assertEquals(
ZonedDateTime.now(warsawTimeZone.toZoneId())
.getOffset()
.toString(),
awaitOne(awaitOne(connection.createStatement(
"SELECT sessionTimeZone FROM sys.dual")
.execute())
.map(row ->
row.get(0, String.class))));
}
finally {
tryAwaitNone(connection.close());
}
}
finally {
TimeZone.setDefault(timeZoneRestored);
}
}

/**
* Verifies that an attempt to connect with a {@code listeningChannel}
* results in an {@link R2dbcTimeoutException}.
Expand Down
31 changes: 28 additions & 3 deletions src/test/java/oracle/r2dbc/impl/TypeMappingTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import io.r2dbc.spi.Blob;
import io.r2dbc.spi.Clob;
import io.r2dbc.spi.Connection;
import io.r2dbc.spi.Parameters;
import io.r2dbc.spi.Row;
import io.r2dbc.spi.Statement;
import oracle.sql.json.OracleJsonFactory;
Expand All @@ -50,6 +51,9 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static io.r2dbc.spi.R2dbcType.NCHAR;
import static io.r2dbc.spi.R2dbcType.NCLOB;
import static io.r2dbc.spi.R2dbcType.NVARCHAR;
import static java.util.Arrays.asList;
import static oracle.r2dbc.test.DatabaseConfig.connectTimeout;
import static oracle.r2dbc.test.DatabaseConfig.databaseVersion;
Expand Down Expand Up @@ -95,6 +99,16 @@ public class TypeMappingTest {
* <a href="https://r2dbc.io/spec/0.8.3.RELEASE/spec/html/#datatypes.mapping">
* Table 4 of Section 12 of the R2DBC 0.8.3 Specification.
* </a>
* </p><p>
* This test method makes use of {@link io.r2dbc.spi.R2dbcType#NCHAR} and
* {@link io.r2dbc.spi.R2dbcType#NVARCHAR} when binding Strings that contain
* non-ascii characters. By default, a String bind is mapped to the VARCHAR
* SQL type. This default mapping has the driver encode the value using the
* database character set. The database character set may not support
* non-ascii characters. Binding Strings with the NCHAR/NVARCHAR type
* configures the driver to encode the string using the national character set
* of the database. The national character set is either UTF16 or UTF8, and so
* it must support non-ascii characters.
* </p>
*/
@Test
Expand All @@ -112,11 +126,18 @@ public void testCharacterTypeMappings() {

// Expect NCHAR and String to map
verifyTypeMapping(connection,
String.format("%100s", "你好, Oracle"), "NCHAR(100)");
Parameters.in(NCHAR, String.format("%100s", "你好, Oracle")),
"NCHAR(100)",
(expected, actual) ->
assertEquals(expected.getValue(), actual));

// Expect NVARCHAR and String to map. The Oracle type named "NVARCHAR2" is
// equivalent to the standard type named "NVARCHAR"
verifyTypeMapping(connection, "नमस्कार, Oracle", "NVARCHAR2(100)");
verifyTypeMapping(connection,
Parameters.in(NVARCHAR, "नमस्कार, Oracle"),
"NVARCHAR2(100)",
(expected, actual) ->
assertEquals(expected.getValue(), actual));

// Expect CLOB and String to map
verifyTypeMapping(connection, "Hola, Oracle", "CLOB");
Expand All @@ -130,7 +151,11 @@ public void testCharacterTypeMappings() {

// Expect NCLOB and String to map for bind values, but not for row values.
// For row values, expect Oracle CLOB to be mapped to io.r2dbc.spi.Clob
verifyTypeMapping(connection, "こんにちは, Oracle", "NCLOB");
verifyTypeMapping(connection,
Parameters.in(NVARCHAR, "こんにちは, Oracle"),
"NCLOB",
(expected, actual) ->
assertEquals(expected.getValue(), actual));

// Expect NCLOB and io.r2dbc.spi.Clob to map
verifyTypeMapping(connection,
Expand Down