Skip to content

Commit 8deada7

Browse files
morningmanclaude
andcommitted
[fix](jdbc) Set the Oracle driver's session time zone only for Oracle catalogs
### What problem does this PR solve? Issue Number: None Related PR: #68786 Problem Summary: #68786 made `OracleTypeHandler.initializeStatement` set the session time zone of every borrowed connection to UTC through Oracle's driver (`oracle.jdbc.OracleConnection#setSessionTimeZone`, loaded reflectively from the driver's class loader), so that the driver reads TIMESTAMP WITH LOCAL TIME ZONE as an instant. `JdbcTypeHandlerFactory` gives the same handler to an Oracle-mode OceanBase catalog (`OCEANBASE_ORACLE`), which connects through OceanBase's driver. Its class loader has no `oracle.jdbc` classes, so every scan of such a catalog failed before its query was prepared, with "Failed to initialize Oracle session time zone" caused by a ClassNotFoundException, whatever columns it read. Writes were not affected. The handler now takes the table type, as `MySQLTypeHandler` does, and sets the Oracle session time zone only for an Oracle catalog. 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. ### Release note Fix scans of Oracle-mode OceanBase JDBC catalogs failing with "Failed to initialize Oracle session time zone". ### Check List (For Author) - Test: Unit Test (OracleSessionTimeZoneTest; all jdbc-scanner tests). No end-to-end test: OceanBase's Oracle mode is only in its enterprise edition, and the regression environment runs OceanBase CE in MySQL mode. - Behavior changed: No - Does this need documentation: No Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
1 parent f605375 commit 8deada7

3 files changed

Lines changed: 59 additions & 11 deletions

File tree

‎fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/JdbcTypeHandlerFactory.java‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public static JdbcTypeHandler create(String tableType, String... sensitiveValues
4242
return new MySQLTypeHandler(tableType);
4343
case "ORACLE":
4444
case "OCEANBASE_ORACLE":
45-
return new OracleTypeHandler(sensitiveValues);
45+
return new OracleTypeHandler(tableType, sensitiveValues);
4646
case "POSTGRESQL":
4747
return new PostgreSQLTypeHandler();
4848
case "CLICKHOUSE":

‎fe/be-java-extensions/jdbc-scanner/src/main/java/org/apache/doris/jdbc/OracleTypeHandler.java‎

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,16 @@ public class OracleTypeHandler extends DefaultTypeHandler {
6060

6161
private final String[] sensitiveValues;
6262

63+
// Whether the catalog connects through Oracle's driver. An Oracle-mode OceanBase catalog shares this handler but
64+
// connects through OceanBase's driver, which has no oracle.jdbc classes.
65+
private final boolean oracleDriver;
66+
6367
public OracleTypeHandler() {
64-
this(new String[0]);
68+
this("ORACLE");
6569
}
6670

67-
public OracleTypeHandler(String... sensitiveValues) {
71+
public OracleTypeHandler(String tableType, String... sensitiveValues) {
72+
this.oracleDriver = "ORACLE".equalsIgnoreCase(tableType);
6873
this.sensitiveValues = sensitiveValues.clone();
6974
}
7075

@@ -123,14 +128,17 @@ public PreparedStatement initializeStatement(Connection conn, String sql,
123128
int fetchSize) throws SQLException {
124129
// Detect driver version when creating the statement (first time we have access to connection)
125130
detectDriverVersion(conn);
126-
// ALTER SESSION alone leaves the driver's TSLTZ zone unset. Initialize every borrowed connection.
127-
try {
128-
Connection physical = conn.unwrap(Connection.class);
129-
Class<?> oracleConnection = Class.forName("oracle.jdbc.OracleConnection", true,
130-
physical.getClass().getClassLoader());
131-
oracleConnection.getMethod("setSessionTimeZone", String.class).invoke(conn.unwrap(oracleConnection), "UTC");
132-
} catch (ReflectiveOperationException e) {
133-
throw new SQLException("Failed to initialize Oracle session time zone", e);
131+
// ALTER SESSION alone leaves the Oracle driver's TSLTZ zone unset. Initialize every borrowed connection.
132+
if (oracleDriver) {
133+
try {
134+
Connection physical = conn.unwrap(Connection.class);
135+
Class<?> oracleConnection = Class.forName("oracle.jdbc.OracleConnection", true,
136+
physical.getClass().getClassLoader());
137+
oracleConnection.getMethod("setSessionTimeZone", String.class)
138+
.invoke(conn.unwrap(oracleConnection), "UTC");
139+
} catch (ReflectiveOperationException e) {
140+
throw new SQLException("Failed to initialize Oracle session time zone", e);
141+
}
134142
}
135143
PreparedStatement stmt = conn.prepareStatement(sql,
136144
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);

‎fe/be-java-extensions/jdbc-scanner/src/test/java/org/apache/doris/jdbc/OracleSessionTimeZoneTest.java‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.mockito.InOrder;
2525
import org.mockito.Mockito;
2626

27+
import java.lang.reflect.Proxy;
2728
import java.sql.Connection;
2829
import java.sql.DatabaseMetaData;
2930
import java.sql.PreparedStatement;
@@ -68,4 +69,43 @@ void failedSessionInitializationAbortsBeforeQuery() throws Exception {
6869
Mockito.verify(pooled, Mockito.never()).prepareStatement(
6970
Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt());
7071
}
72+
73+
@Test
74+
void oracleCatalogSetsTheSessionTimeZone() throws Exception {
75+
Connection pooled = Mockito.mock(Connection.class);
76+
OracleConnection physical = Mockito.mock(OracleConnection.class);
77+
DatabaseMetaData metadata = Mockito.mock(DatabaseMetaData.class);
78+
Mockito.when(metadata.getDriverVersion()).thenReturn("23.26.2");
79+
Mockito.when(pooled.getMetaData()).thenReturn(metadata);
80+
Mockito.when(pooled.unwrap(Connection.class)).thenReturn(physical);
81+
Mockito.when(pooled.unwrap(OracleConnection.class)).thenReturn(physical);
82+
Mockito.when(pooled.prepareStatement(Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt()))
83+
.thenReturn(Mockito.mock(PreparedStatement.class));
84+
JdbcTypeHandlerFactory.create("ORACLE").initializeStatement(pooled, "SELECT ts FROM t", 100);
85+
Mockito.verify(physical).setSessionTimeZone("UTC");
86+
}
87+
88+
@Test
89+
void oceanBaseOracleModeScansWithoutTheOracleDriver() throws Exception {
90+
// An Oracle-mode OceanBase catalog shares this handler but connects through OceanBase's driver, whose class
91+
// loader has no oracle.jdbc classes, so its scans must not need them. The connection below is defined by
92+
// the platform class loader, which cannot see the Oracle driver on the test class path either.
93+
// MUTATION: setting the Oracle session time zone for OCEANBASE_ORACLE too -> red.
94+
Connection physical = (Connection) Proxy.newProxyInstance(ClassLoader.getPlatformClassLoader(),
95+
new Class<?>[] {Connection.class}, (proxy, method, args) -> {
96+
throw new UnsupportedOperationException(method.getName());
97+
});
98+
Connection pooled = Mockito.mock(Connection.class);
99+
DatabaseMetaData metadata = Mockito.mock(DatabaseMetaData.class);
100+
PreparedStatement statement = Mockito.mock(PreparedStatement.class);
101+
Mockito.when(metadata.getDriverVersion()).thenReturn("2.4.9");
102+
Mockito.when(pooled.getMetaData()).thenReturn(metadata);
103+
Mockito.when(pooled.unwrap(Connection.class)).thenReturn(physical);
104+
Mockito.when(pooled.prepareStatement(Mockito.anyString(), Mockito.anyInt(), Mockito.anyInt()))
105+
.thenReturn(statement);
106+
107+
Assertions.assertSame(statement, JdbcTypeHandlerFactory.create("OCEANBASE_ORACLE")
108+
.initializeStatement(pooled, "SELECT 1 FROM dual", 100));
109+
Mockito.verify(statement).setFetchSize(100);
110+
}
71111
}

0 commit comments

Comments
 (0)