This is an automated email from the ASF dual-hosted git repository.

davidzollo pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/seatunnel.git


The following commit(s) were added to refs/heads/dev by this push:
     new 2bc864c172 [Fix][Connector-V2][JDBC] Use Oracle-typed null binding for 
sparse null rows (#10768)
2bc864c172 is described below

commit 2bc864c172323309b980aceb9f43223386d75322
Author: corgy-w <[email protected]>
AuthorDate: Sun Jul 19 09:10:54 2026 +0800

    [Fix][Connector-V2][JDBC] Use Oracle-typed null binding for sparse null 
rows (#10768)
---
 .../converter/AbstractJdbcRowConverter.java        |   9 ++
 .../dialect/oracle/OracleJdbcRowConverter.java     |  91 +++++++++++++++
 .../dialect/oracle/OracleJdbcRowConverterTest.java | 122 +++++++++++++++++++++
 3 files changed, 222 insertions(+)

diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/converter/AbstractJdbcRowConverter.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/converter/AbstractJdbcRowConverter.java
index c2a3fb04ab..010daa8245 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/converter/AbstractJdbcRowConverter.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/converter/AbstractJdbcRowConverter.java
@@ -260,6 +260,15 @@ public abstract class AbstractJdbcRowConverter implements 
JdbcRowConverter {
         statement.setObject(statementIndex, null);
     }
 
+    protected void setNullToStatementByDataType(
+            PreparedStatement statement,
+            SeaTunnelDataType<?> seaTunnelDataType,
+            int statementIndex,
+            @Nullable String sourceType)
+            throws SQLException {
+        statement.setObject(statementIndex, null);
+    }
+
     protected void setValueToStatementByDataType(
             Object value,
             PreparedStatement statement,
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleJdbcRowConverter.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleJdbcRowConverter.java
index c0a3d88653..476aa886fc 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleJdbcRowConverter.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleJdbcRowConverter.java
@@ -28,10 +28,16 @@ import java.io.ByteArrayInputStream;
 import java.io.StringReader;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
+import java.sql.Types;
 
 import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_BLOB;
+import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_CHAR;
 import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_CLOB;
+import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_NCHAR;
 import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_NCLOB;
+import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_NVARCHAR2;
+import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_VARCHAR;
+import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_VARCHAR2;
 
 public class OracleJdbcRowConverter extends AbstractJdbcRowConverter {
 
@@ -40,6 +46,91 @@ public class OracleJdbcRowConverter extends 
AbstractJdbcRowConverter {
         return DatabaseIdentifier.ORACLE;
     }
 
+    @Override
+    protected void setNullToStatementByDataType(
+            PreparedStatement statement,
+            SeaTunnelDataType<?> seaTunnelDataType,
+            int statementIndex,
+            @Nullable String sourceType)
+            throws SQLException {
+        if (ORACLE_CLOB.equals(sourceType)) {
+            statement.setNull(statementIndex, Types.CLOB);
+            return;
+        }
+        if (ORACLE_NCLOB.equals(sourceType)) {
+            statement.setNull(statementIndex, Types.NCLOB);
+            return;
+        }
+        if (ORACLE_BLOB.equals(sourceType)) {
+            statement.setNull(statementIndex, Types.BLOB);
+            return;
+        }
+        if (ORACLE_CHAR.equals(sourceType)
+                || ORACLE_NCHAR.equals(sourceType)
+                || ORACLE_VARCHAR.equals(sourceType)
+                || ORACLE_VARCHAR2.equals(sourceType)
+                || ORACLE_NVARCHAR2.equals(sourceType)) {
+            statement.setNull(statementIndex, Types.VARCHAR);
+            return;
+        }
+
+        switch (seaTunnelDataType.getSqlType()) {
+            case STRING:
+                statement.setNull(statementIndex, Types.VARCHAR);
+                break;
+            case BOOLEAN:
+                statement.setNull(statementIndex, Types.INTEGER);
+                break;
+            case TINYINT:
+                statement.setNull(statementIndex, Types.TINYINT);
+                break;
+            case SMALLINT:
+                statement.setNull(statementIndex, Types.SMALLINT);
+                break;
+            case INT:
+                statement.setNull(statementIndex, Types.INTEGER);
+                break;
+            case BIGINT:
+                statement.setNull(statementIndex, Types.BIGINT);
+                break;
+            case FLOAT:
+                statement.setNull(statementIndex, Types.FLOAT);
+                break;
+            case DOUBLE:
+                statement.setNull(statementIndex, Types.DOUBLE);
+                break;
+            case DECIMAL:
+                statement.setNull(statementIndex, Types.DECIMAL);
+                break;
+            case DATE:
+                statement.setNull(statementIndex, Types.DATE);
+                break;
+            case TIME:
+                statement.setNull(statementIndex, Types.TIME);
+                break;
+            case TIMESTAMP:
+                statement.setNull(statementIndex, Types.TIMESTAMP);
+                break;
+            case TIMESTAMP_TZ:
+                statement.setNull(statementIndex, 
Types.TIMESTAMP_WITH_TIMEZONE);
+                break;
+            case BYTES:
+                statement.setNull(statementIndex, Types.BINARY);
+                break;
+            case NULL:
+                statement.setNull(statementIndex, Types.NULL);
+                break;
+            case ARRAY:
+                statement.setNull(statementIndex, Types.ARRAY);
+                break;
+            case MAP:
+            case ROW:
+            default:
+                super.setNullToStatementByDataType(
+                        statement, seaTunnelDataType, statementIndex, 
sourceType);
+        }
+    }
+
     @Override
     protected void setValueToStatementByDataType(
             Object value,
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleJdbcRowConverterTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleJdbcRowConverterTest.java
new file mode 100644
index 0000000000..00fec54cac
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/oracle/OracleJdbcRowConverterTest.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle;
+
+import org.apache.seatunnel.api.table.catalog.Column;
+import org.apache.seatunnel.api.table.catalog.PhysicalColumn;
+import org.apache.seatunnel.api.table.catalog.TableSchema;
+import org.apache.seatunnel.api.table.type.ArrayType;
+import org.apache.seatunnel.api.table.type.BasicType;
+import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
+import org.apache.seatunnel.api.table.type.SeaTunnelRow;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.converter.AbstractJdbcRowConverter;
+
+import org.junit.jupiter.api.Test;
+
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.Collections;
+
+import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_BLOB;
+import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_CLOB;
+import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_NCLOB;
+import static 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.oracle.OracleTypeConverter.ORACLE_VARCHAR2;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+
+public class OracleJdbcRowConverterTest {
+
+    private static final String FIELD_NAME = "payload";
+
+    private final OracleJdbcRowConverter oracleJdbcRowConverter = new 
OracleJdbcRowConverter();
+
+    @Test
+    public void testNullClobUsesTypedNullBinding() throws SQLException {
+        assertOracleNullBinding(BasicType.STRING_TYPE, ORACLE_CLOB, 
Types.CLOB);
+    }
+
+    @Test
+    public void testNullNclobUsesTypedNullBinding() throws SQLException {
+        assertOracleNullBinding(BasicType.STRING_TYPE, ORACLE_NCLOB, 
Types.NCLOB);
+    }
+
+    @Test
+    public void testNullBlobUsesTypedNullBinding() throws SQLException {
+        assertOracleNullBinding(ArrayType.BYTE_ARRAY_TYPE, ORACLE_BLOB, 
Types.BLOB);
+    }
+
+    @Test
+    public void testNullVarchar2UsesTypedNullBinding() throws SQLException {
+        assertOracleNullBinding(BasicType.STRING_TYPE, ORACLE_VARCHAR2, 
Types.VARCHAR);
+    }
+
+    @Test
+    public void testNonOracleConverterKeepsDefaultUntypedNullBinding() throws 
SQLException {
+        TableSchema tableSchema = createTableSchema(BasicType.STRING_TYPE, 
null);
+        TableSchema databaseTableSchema = 
createTableSchema(BasicType.STRING_TYPE, ORACLE_CLOB);
+        PreparedStatement statement = mock(PreparedStatement.class);
+
+        new DefaultJdbcRowConverter()
+                .toExternal(
+                        tableSchema,
+                        databaseTableSchema,
+                        new SeaTunnelRow(new Object[] {null}),
+                        statement);
+
+        verify(statement).setObject(1, null);
+        verify(statement, never()).setNull(1, Types.CLOB);
+        verifyNoMoreInteractions(statement);
+    }
+
+    private void assertOracleNullBinding(
+            SeaTunnelDataType<?> seaTunnelDataType, String sourceType, int 
expectedJdbcType)
+            throws SQLException {
+        TableSchema tableSchema = createTableSchema(seaTunnelDataType, null);
+        TableSchema databaseTableSchema = createTableSchema(seaTunnelDataType, 
sourceType);
+        PreparedStatement statement = mock(PreparedStatement.class);
+
+        oracleJdbcRowConverter.toExternal(
+                tableSchema, databaseTableSchema, new SeaTunnelRow(new 
Object[] {null}), statement);
+
+        verify(statement).setNull(1, expectedJdbcType);
+        verify(statement, never()).setObject(1, null);
+        verifyNoMoreInteractions(statement);
+    }
+
+    private TableSchema createTableSchema(
+            SeaTunnelDataType<?> seaTunnelDataType, String sourceType) {
+        PhysicalColumn.PhysicalColumnBuilder columnBuilder =
+                
PhysicalColumn.builder().name(FIELD_NAME).dataType(seaTunnelDataType);
+        if (sourceType != null) {
+            columnBuilder.sourceType(sourceType);
+        }
+        Column column = columnBuilder.build();
+        return 
TableSchema.builder().columns(Collections.singletonList(column)).build();
+    }
+
+    private static final class DefaultJdbcRowConverter extends 
AbstractJdbcRowConverter {
+
+        @Override
+        public String converterName() {
+            return "default-test-converter";
+        }
+    }
+}

Reply via email to