This is an automated email from the ASF dual-hosted git repository.
morningman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new a290247000d [fix](jdbc) Set the Oracle driver's session time zone only
for Oracle catalogs (#69121)
a290247000d is described below
commit a290247000d76fd39546a7e6fcb79d61a441dea6
Author: Mingyu Chen (Rayner) <[email protected]>
AuthorDate: Mon Oct 19 07:36:04 2026 +0100
[fix](jdbc) Set the Oracle driver's session time zone only for Oracle
catalogs (#69121)
### What problem does this PR solve?
Issue Number: None
Related PR: #68786
Problem Summary:
**In short.** Since #68786, every scan of a JDBC catalog that connects
to OceanBase in Oracle mode fails with `Failed to initialize Oracle
session time zone`, whatever columns it reads. The Oracle type handler,
which such a catalog shares with Oracle catalogs, sets the session time
zone through Oracle's JDBC driver, and an OceanBase catalog has
OceanBase's driver instead. This PR makes only Oracle catalogs do that.
**Background.** BE's JDBC scanner picks a type handler by the catalog's
table type (`JdbcTypeHandlerFactory`): `ORACLE` and `OCEANBASE_ORACLE`,
which FE reports for an OceanBase database whose
`ob_compatibility_mode()` is ORACLE, both get `OracleTypeHandler`.
#68786 made its `initializeStatement` set the session time zone of every
borrowed connection to UTC through
`oracle.jdbc.OracleConnection#setSessionTimeZone`, loaded reflectively
from the class loader of the driver's connection, so that Oracle's
driver reads TIMESTAMP WITH LOCAL TIME ZONE as an instant: `ALTER
SESSION` alone leaves the driver's own zone unset. The review bot raised
this for OceanBase on #68786 (thread 4237173029); the thread was still
open when it merged.
**The problem, and what it cost.** An Oracle-mode OceanBase catalog
connects through OceanBase's driver, and the class loader of a catalog's
driver only sees that driver.
`Class.forName("oracle.jdbc.OracleConnection", ...)` therefore throws
ClassNotFoundException, and `initializeStatement` turns it into
`SQLException: Failed to initialize Oracle session time zone` before the
query is prepared. Every SELECT on such a catalog failed, including ones
that read no timestamp column. Writes were not affected, since they do
not go through `initializeStatement`.
**How this PR fixes it.** `OracleTypeHandler` takes the table type, as
`MySQLTypeHandler` does for `MYSQL` and `OCEANBASE`, and sets the Oracle
session time zone only for `ORACLE`. An Oracle-mode OceanBase catalog
prepares its statements as it did before #68786, and reads TIMESTAMP
WITH LOCAL TIME ZONE as OceanBase's driver returns it. The no-argument
constructor keeps the Oracle behavior.
**Results.**
- **FE UT** (`be-java-extensions/jdbc-scanner`).
`OracleSessionTimeZoneTest.oceanBaseOracleModeScansWithoutTheOracleDriver`
prepares a statement through the factory's `OCEANBASE_ORACLE` handler on
a connection defined by the platform class loader, which cannot see
`oracle.jdbc` either; before this change it failed with `Failed to
initialize Oracle session time zone`, and setting the Oracle session
time zone for `OCEANBASE_ORACLE` too turns it red again.
`oracleCatalogSetsTheSessionTimeZone` checks that the factory's `ORACLE`
handler still sets it. All 57 tests of jdbc-scanner pass (18 skipped:
the two integration test classes need their databases).
- **End to end.** Not tested: OceanBase's Oracle mode is only in its
enterprise edition, and the regression environment runs OceanBase CE in
MySQL mode.
branch-4.1 and branch-4.2 carry the same session time zone setup in
`OracleJdbcExecutor` since #68873, and also give it to
`OCEANBASE_ORACLE`.
---
.../apache/doris/jdbc/JdbcTypeHandlerFactory.java | 2 +-
.../org/apache/doris/jdbc/OracleTypeHandler.java | 28 +++++++++------
.../doris/jdbc/OracleSessionTimeZoneTest.java | 40 ++++++++++++++++++++++
3 files changed, 59 insertions(+), 11 deletions(-)
diff --git
a/fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/JdbcTypeHandlerFactory.java
b/fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/JdbcTypeHandlerFactory.java
index 549a4215d47..b2756757ec4 100644
---
a/fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/JdbcTypeHandlerFactory.java
+++
b/fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/JdbcTypeHandlerFactory.java
@@ -42,7 +42,7 @@ public class JdbcTypeHandlerFactory {
return new MySQLTypeHandler(tableType);
case "ORACLE":
case "OCEANBASE_ORACLE":
- return new OracleTypeHandler(sensitiveValues);
+ return new OracleTypeHandler(tableType, sensitiveValues);
case "POSTGRESQL":
return new PostgreSQLTypeHandler();
case "CLICKHOUSE":
diff --git
a/fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/OracleTypeHandler.java
b/fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/OracleTypeHandler.java
index 28f326d46fb..bd70b5756c7 100644
---
a/fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/OracleTypeHandler.java
+++
b/fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/OracleTypeHandler.java
@@ -60,11 +60,16 @@ public class OracleTypeHandler extends DefaultTypeHandler {
private final String[] sensitiveValues;
+ // Whether the catalog connects through Oracle's driver. An Oracle-mode
OceanBase catalog shares this handler but
+ // connects through OceanBase's driver, which has no oracle.jdbc classes.
+ private final boolean oracleDriver;
+
public OracleTypeHandler() {
- this(new String[0]);
+ this("ORACLE");
}
- public OracleTypeHandler(String... sensitiveValues) {
+ public OracleTypeHandler(String tableType, String... sensitiveValues) {
+ this.oracleDriver = "ORACLE".equalsIgnoreCase(tableType);
this.sensitiveValues = sensitiveValues.clone();
}
@@ -123,14 +128,17 @@ public class OracleTypeHandler extends DefaultTypeHandler
{
int fetchSize) throws
SQLException {
// Detect driver version when creating the statement (first time we
have access to connection)
detectDriverVersion(conn);
- // ALTER SESSION alone leaves the driver's TSLTZ zone unset.
Initialize every borrowed connection.
- try {
- Connection physical = conn.unwrap(Connection.class);
- Class<?> oracleConnection =
Class.forName("oracle.jdbc.OracleConnection", true,
- physical.getClass().getClassLoader());
- oracleConnection.getMethod("setSessionTimeZone",
String.class).invoke(conn.unwrap(oracleConnection), "UTC");
- } catch (ReflectiveOperationException e) {
- throw new SQLException("Failed to initialize Oracle session time
zone", e);
+ // ALTER SESSION alone leaves the Oracle driver's TSLTZ zone unset.
Initialize every borrowed connection.
+ if (oracleDriver) {
+ try {
+ Connection physical = conn.unwrap(Connection.class);
+ Class<?> oracleConnection =
Class.forName("oracle.jdbc.OracleConnection", true,
+ physical.getClass().getClassLoader());
+ oracleConnection.getMethod("setSessionTimeZone", String.class)
+ .invoke(conn.unwrap(oracleConnection), "UTC");
+ } catch (ReflectiveOperationException e) {
+ throw new SQLException("Failed to initialize Oracle session
time zone", e);
+ }
}
PreparedStatement stmt = conn.prepareStatement(sql,
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
diff --git
a/fe/be-java-extensions/jdbc-scanner/src/test/java/org/apache/doris/jdbc/OracleSessionTimeZoneTest.java
b/fe/be-java-extensions/jdbc-scanner/src/test/java/org/apache/doris/jdbc/OracleSessionTimeZoneTest.java
index 6efbc7948aa..1dd3c1b3221 100644
---
a/fe/be-java-extensions/jdbc-scanner/src/test/java/org/apache/doris/jdbc/OracleSessionTimeZoneTest.java
+++
b/fe/be-java-extensions/jdbc-scanner/src/test/java/org/apache/doris/jdbc/OracleSessionTimeZoneTest.java
@@ -24,6 +24,7 @@ import org.junit.jupiter.api.Test;
import org.mockito.InOrder;
import org.mockito.Mockito;
+import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
@@ -68,4 +69,43 @@ class OracleSessionTimeZoneTest {
Mockito.verify(pooled, Mockito.never()).prepareStatement(
Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt());
}
+
+ @Test
+ void oracleCatalogSetsTheSessionTimeZone() throws Exception {
+ Connection pooled = Mockito.mock(Connection.class);
+ OracleConnection physical = Mockito.mock(OracleConnection.class);
+ DatabaseMetaData metadata = Mockito.mock(DatabaseMetaData.class);
+ Mockito.when(metadata.getDriverVersion()).thenReturn("23.26.2");
+ Mockito.when(pooled.getMetaData()).thenReturn(metadata);
+ Mockito.when(pooled.unwrap(Connection.class)).thenReturn(physical);
+
Mockito.when(pooled.unwrap(OracleConnection.class)).thenReturn(physical);
+ Mockito.when(pooled.prepareStatement(Mockito.anyString(),
Mockito.anyInt(), Mockito.anyInt()))
+ .thenReturn(Mockito.mock(PreparedStatement.class));
+ JdbcTypeHandlerFactory.create("ORACLE").initializeStatement(pooled,
"SELECT ts FROM t", 100);
+ Mockito.verify(physical).setSessionTimeZone("UTC");
+ }
+
+ @Test
+ void oceanBaseOracleModeScansWithoutTheOracleDriver() throws Exception {
+ // An Oracle-mode OceanBase catalog shares this handler but connects
through OceanBase's driver, whose class
+ // loader has no oracle.jdbc classes, so its scans must not need them.
The connection below is defined by
+ // the platform class loader, which cannot see the Oracle driver on
the test class path either.
+ // MUTATION: setting the Oracle session time zone for OCEANBASE_ORACLE
too -> red.
+ Connection physical = (Connection)
Proxy.newProxyInstance(ClassLoader.getPlatformClassLoader(),
+ new Class<?>[] {Connection.class}, (proxy, method, args) -> {
+ throw new UnsupportedOperationException(method.getName());
+ });
+ Connection pooled = Mockito.mock(Connection.class);
+ DatabaseMetaData metadata = Mockito.mock(DatabaseMetaData.class);
+ PreparedStatement statement = Mockito.mock(PreparedStatement.class);
+ Mockito.when(metadata.getDriverVersion()).thenReturn("2.4.9");
+ Mockito.when(pooled.getMetaData()).thenReturn(metadata);
+ Mockito.when(pooled.unwrap(Connection.class)).thenReturn(physical);
+ Mockito.when(pooled.prepareStatement(Mockito.anyString(),
Mockito.anyInt(), Mockito.anyInt()))
+ .thenReturn(statement);
+
+ Assertions.assertSame(statement,
JdbcTypeHandlerFactory.create("OCEANBASE_ORACLE")
+ .initializeStatement(pooled, "SELECT 1 FROM dual", 100));
+ Mockito.verify(statement).setFetchSize(100);
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]