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]

Reply via email to