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 edfc1c3447 [Improve][Connector-V2][JDBC] Add string split strategy 
(#10861)
edfc1c3447 is described below

commit edfc1c3447049537b667db0004c591018063575f
Author: Jast <[email protected]>
AuthorDate: Wed May 13 20:52:10 2026 +0800

    [Improve][Connector-V2][JDBC] Add string split strategy (#10861)
---
 docs/en/connectors/source/Jdbc.md                  |   1 +
 docs/zh/connectors/source/Jdbc.md                  |   5 +-
 .../seatunnel/jdbc/config/JdbcSourceConfig.java    |   5 +
 .../seatunnel/jdbc/config/JdbcSourceOptions.java   |   8 +
 .../jdbc/internal/dialect/JdbcDialect.java         |  18 +++
 .../jdbc/internal/dialect/mysql/MysqlDialect.java  | 119 +++++++++++++++
 .../jdbc/source/AsciiStringRangeSplitter.java      | 141 +++++++++++++++++
 .../seatunnel/jdbc/source/ChunkSplitter.java       |  45 ++++++
 .../jdbc/source/DynamicChunkSplitter.java          | 166 +++++++++++++++++++++
 .../seatunnel/jdbc/source/FixedChunkSplitter.java  | 140 ++++++++++++++++-
 .../seatunnel/jdbc/source/JdbcSourceFactory.java   |   1 +
 .../jdbc/source/StringRangeSplitDecision.java      |  40 +++++
 .../seatunnel/jdbc/source/StringSplitStrategy.java |  40 +++++
 .../jdbc/config/JdbcSourceConfigTest.java          |  57 +++++++
 .../internal/dialect/mysql/MysqlDialectTest.java   | 105 +++++++++++++
 .../jdbc/source/AsciiStringRangeSplitterTest.java  |  51 +++++++
 .../jdbc/source/FixedChunkSplitterTest.java        | 118 +++++++++++++++
 .../seatunnel/jdbc/JdbcMysqlSplitIT.java           |  45 ++++++
 18 files changed, 1095 insertions(+), 10 deletions(-)

diff --git a/docs/en/connectors/source/Jdbc.md 
b/docs/en/connectors/source/Jdbc.md
index 6f696f170c..88284fc874 100644
--- a/docs/en/connectors/source/Jdbc.md
+++ b/docs/en/connectors/source/Jdbc.md
@@ -74,6 +74,7 @@ supports query SQL and can achieve projection effect.
 | split.allow-sampling                       | Boolean | No        | true      
      | Whether to allow sampling-based sharding strategy. When set to false, 
the system will fall back to unevenly-sized chunk splitting (iterative query 
approach) regardless of the shard count.                                        
                                                                                
                                      |
 | common-options                             |         | No        | -         
      | Source plugin common parameters, please refer to [Source Common 
Options](../common-options/source-common-options.md) for details.               
                                                                                
                                                                                
                                                                                
                      [...]
 | split.string_split_mode                    | String  | No        | sample    
      | Supports different string splitting algorithms. By default, `sample` is 
used to determine the split by sampling the string value. You can switch to 
`charset_based` to enable charset-based string splitting algorithm. When set to 
`charset_based`, the algorithm assumes characters of partition_column are 
within ASCII range 32-126, which covers most character-based splitting 
scenarios.                       [...]
+| split.string-strategy                      | String  | No        | -         
      | Controls how String partition columns are split. Available values are 
`none`, `hash`, `range`, and `auto`. `range` and `auto` currently require MySQL 
binary collation and fixed-length printable ASCII key values. Other JDBC 
dialects reject `range` and `auto` until their range split support is 
explicitly validated. `auto` tries range splitting first and falls back to hash 
splitting when range splitting i [...]
 | split.string_split_mode_collate            | String  | No        | -         
      | Specifies the collation to use when string_split_mode is set to 
`charset_based` and the table has a special collation. If not specified, the 
database's default collation will be used.                                      
                                                                                
                                                                                
                         [...]
 
 ### Table Matching
diff --git a/docs/zh/connectors/source/Jdbc.md 
b/docs/zh/connectors/source/Jdbc.md
index b109dcbb2c..e8fdf2e48b 100644
--- a/docs/zh/connectors/source/Jdbc.md
+++ b/docs/zh/connectors/source/Jdbc.md
@@ -71,8 +71,9 @@ import ChangeLog from '../changelog/connector-jdbc.md';
 | split.even-distribution.factor.upper-bound | Double  | 否    | 100            
 | 不建议修改。<br/> 分片键分布因子的上界。该因子用于判断表数据是否均匀分布。如果计算出的分布因子小于或等于此上界(即 (MAX(id) - 
MIN(id) + 1) / 行数),则将优化为均匀分布的分片方式。否则,如果分布因子更大,则表将被视为非均匀分布,当预估的分片数超过 
`sample-sharding.threshold` 指定的值时,将使用基于采样的分片策略。默认值为 100.0。 |
 | split.sample-sharding.threshold            | Int     | 否    | 1000           
 | 此配置指定触发采样分片策略的预估分片数阈值。当分布因子超出 `split.even-distribution.factor.upper-bound` 和 
`split.even-distribution.factor.lower-bound` 指定的范围,且预估分片数(计算方式为近似行数 / 
分片大小)超过此阈值时,将使用采样分片策略。这有助于更高效地处理大数据集。默认值为 1000。                                 
                                                                                
|
 | split.inverse-sampling.rate                | Int     | 否    | 1000           
 | 采样分片策略中使用的采样率的倒数。例如,如果此值设置为 1000,则表示在采样过程中应用 1/1000 
的采样率。此选项提供了控制采样粒度的灵活性,从而影响最终的分片数量。在处理大数据集时尤为有用,此时较低的采样率可能更合适。默认值为 1000。         
                                                                                
                   |
-| split.allow-sampling               | Boolean | 否    | true            | 
是否启用基于采样的分片策略。当设置为 false 时,无论预估分片数是否超过阈值,系统都将回退到非均匀分片方式(迭代查询方式)。默认值为 true。      
                                                                                
                                                                                
                        |
+| split.allow-sampling                       | Boolean | 否    | true           
 | 是否启用基于采样的分片策略。当设置为 false 时,无论预估分片数是否超过阈值,系统都将回退到非均匀分片方式(迭代查询方式)。默认值为 true。   
                                                                                
                                                                                
                           |
 | split.string_split_mode                    | String  | 否    | sample         
 | 支持不同的字符串分割算法。默认使用 `sample`,通过采样字符串值来确定分割。可以切换为 `charset_based` 
启用基于字符集的字符串分割算法。设置为 `charset_based` 时,算法假定 partition_column 的字符在 ASCII 32-126 
范围内,覆盖了大多数基于字符的分割场景。                                                            
                                                                                
        |
+| split.string-strategy                      | String  | 否    | -              
 | 控制 String 分区列的分片方式,可选值为 `none`、`hash`、`range`、`auto`。`range` 和 `auto` 当前要求 
MySQL binary collation,且键值为固定长度的可打印 ASCII 字符串。其他 JDBC 方言在显式验证 range 分片支持前会拒绝 
`range` 和 `auto`。`auto` 会优先尝试 range 分片,range 不安全时回退为 hash 分片。未设置该参数时,SeaTunnel 
保持现有 `split.string_split_mode` 行为。                                              
                                                                                
                    [...]
 | split.string_split_mode_collate            | String  | 否    | -              
 | 当 string_split_mode 设置为 `charset_based` 
且表具有特殊排序规则时,指定要使用的排序规则。如果未指定,将使用数据库的默认排序规则。                                     
                                                                                
                                                                                
       |
 | common-options                             |         | 否    | -       | 
源插件通用参数,请参考 [源通用选项](../common-options/source-common-options.md) 详见。             
                                                                                
                                                                                
                                                                                
                                                                                
                   [...]
 
@@ -338,5 +339,3 @@ source {
 ## 变更日志
 
 <ChangeLog />
-
-
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceConfig.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceConfig.java
index 4f63baf207..093c66d4df 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceConfig.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceConfig.java
@@ -19,6 +19,7 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.config;
 
 import org.apache.seatunnel.api.configuration.ReadonlyConfig;
 import org.apache.seatunnel.connectors.seatunnel.jdbc.source.StringSplitMode;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.source.StringSplitStrategy;
 
 import lombok.Builder;
 import lombok.Data;
@@ -49,6 +50,8 @@ public class JdbcSourceConfig implements Serializable {
 
     private StringSplitMode stringSplitMode;
 
+    private StringSplitStrategy stringSplitStrategy;
+
     private String stringSplitModeCollate;
 
     public static JdbcSourceConfig of(ReadonlyConfig config) {
@@ -63,6 +66,8 @@ public class JdbcSourceConfig implements Serializable {
                         && 
config.getOptional(JdbcSourceOptions.PARTITION_COLUMN).isPresent();
         builder.useDynamicSplitter(!isOldVersion);
         
builder.stringSplitMode(config.get(JdbcSourceOptions.STRING_SPLIT_MODE));
+        config.getOptional(JdbcSourceOptions.STRING_SPLIT_STRATEGY)
+                .ifPresent(builder::stringSplitStrategy);
         
builder.stringSplitModeCollate(config.get(JdbcSourceOptions.STRING_SPLIT_MODE_COLLATE));
         builder.splitSize(config.get(JdbcSourceOptions.SPLIT_SIZE));
         builder.splitEvenDistributionFactorUpperBound(
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceOptions.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceOptions.java
index d2252e2997..b34a36b430 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceOptions.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceOptions.java
@@ -20,6 +20,7 @@ package org.apache.seatunnel.connectors.seatunnel.jdbc.config;
 import org.apache.seatunnel.api.configuration.Option;
 import org.apache.seatunnel.api.configuration.Options;
 import org.apache.seatunnel.connectors.seatunnel.jdbc.source.StringSplitMode;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.source.StringSplitStrategy;
 
 import java.util.List;
 
@@ -162,6 +163,13 @@ public class JdbcSourceOptions extends JdbcCommonOptions {
                     .withDescription(
                             "Supports different string splitting algorithms. 
By default, `sample` is used to determine the split by sampling the string 
value. You can switch to `charset_based` to enable charset-based string 
splitting algorithm. When set to `charset_based`, the algorithm assumes 
characters of partition_column are within ASCII range 32-126, which covers most 
character-based splitting scenarios.");
 
+    public static final Option<StringSplitStrategy> STRING_SPLIT_STRATEGY =
+            Options.key("split.string-strategy")
+                    .enumType(StringSplitStrategy.class)
+                    .noDefaultValue()
+                    .withDescription(
+                            "Controls how String partition columns are split. 
`range` uses range-based splitting, `hash` uses hash-based splitting, `none` 
disables String splitting, and `auto` prefers range splitting then falls back 
to hash when needed. `range` and `auto` currently require MySQL binary 
collation and fixed-length printable ASCII key values. When this option is not 
set, SeaTunnel keeps the existing split.string_split_mode behavior.");
+
     public static final Option<String> STRING_SPLIT_MODE_COLLATE =
             Options.key("split.string_split_mode_collate")
                     .stringType()
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialect.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialect.java
index 3f133606dc..17f9d5d49e 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialect.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/JdbcDialect.java
@@ -37,6 +37,7 @@ import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.connection.Simple
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.converter.JdbcRowConverter;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.dialectenum.FieldIdeEnum;
 import org.apache.seatunnel.connectors.seatunnel.jdbc.source.JdbcSourceTable;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.source.StringRangeSplitDecision;
 import org.apache.seatunnel.connectors.seatunnel.jdbc.utils.DefaultValueUtils;
 
 import org.slf4j.Logger;
@@ -335,6 +336,23 @@ public interface JdbcDialect extends Serializable {
         return SQLUtils.countForTable(connection, 
tableIdentifier(table.getTablePath()));
     }
 
+    /** Dialects must opt in after verifying collation and key-shape 
constraints. */
+    default StringRangeSplitDecision validateStringRangeSplit(
+            Connection connection, JdbcSourceTable table, String columnName, 
int sampleSize)
+            throws SQLException {
+        return StringRangeSplitDecision.unsafe(
+                "string range split is not validated for this JDBC dialect");
+    }
+
+    /** Returns whether this dialect has validated string range split support. 
*/
+    default boolean supportStringRangeSplit() {
+        return false;
+    }
+
+    default boolean supportHashSplitter() {
+        return true;
+    }
+
     /**
      * Performs a sampling operation on the specified column of a table in a 
JDBC-connected
      * database.
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java
index 2a6450b2cb..c63cbc4097 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialect.java
@@ -29,6 +29,7 @@ import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.JdbcDiale
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.SQLUtils;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.dialectenum.FieldIdeEnum;
 import org.apache.seatunnel.connectors.seatunnel.jdbc.source.JdbcSourceTable;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.source.StringRangeSplitDecision;
 
 import com.mysql.cj.MysqlType;
 import lombok.extern.slf4j.Slf4j;
@@ -42,6 +43,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Optional;
 import java.util.stream.Collectors;
@@ -238,6 +240,123 @@ public class MysqlDialect implements JdbcDialect {
         return SQLUtils.countForSubquery(connection, table.getQuery());
     }
 
+    @Override
+    public StringRangeSplitDecision validateStringRangeSplit(
+            Connection connection, JdbcSourceTable table, String columnName, 
int sampleSize)
+            throws SQLException {
+        if (table.getTablePath() == null
+                || 
TablePath.DEFAULT.getFullName().equals(table.getTablePath().getFullName())) {
+            return StringRangeSplitDecision.unsafe(
+                    "missing physical table path for MySQL string range split 
validation");
+        }
+
+        String collation = queryColumnCollation(connection, table, columnName);
+        if (StringUtils.isBlank(collation)) {
+            return StringRangeSplitDecision.unsafe(
+                    String.format(
+                            "column collation is unavailable for %s.%s",
+                            table.getTablePath(), columnName));
+        }
+        if (!collation.toLowerCase(Locale.ROOT).endsWith("_bin")) {
+            return StringRangeSplitDecision.unsafe(
+                    String.format("collation %s is not binary", collation));
+        }
+
+        List<String> samples = sampleStringValues(connection, table, 
columnName, sampleSize);
+        if (samples.isEmpty()) {
+            return StringRangeSplitDecision.unsafe("no non-null sample values 
found");
+        }
+        Integer sampleLength = null;
+        for (String sample : samples) {
+            if (!isPrintableAscii(sample)) {
+                return StringRangeSplitDecision.unsafe(
+                        String.format("sample value contains non-ASCII 
characters: [%s]", sample));
+            }
+            if (sampleLength == null) {
+                sampleLength = sample.length();
+            } else if (sample.length() != sampleLength) {
+                return StringRangeSplitDecision.unsafe(
+                        "sample values have variable lengths and cannot 
preserve string range order");
+            }
+        }
+        return StringRangeSplitDecision.safe(
+                String.format(
+                        "collation %s is binary and %s sampled values are 
fixed-length printable ASCII",
+                        collation, samples.size()));
+    }
+
+    @Override
+    public boolean supportStringRangeSplit() {
+        return true;
+    }
+
+    private String queryColumnCollation(
+            Connection connection, JdbcSourceTable table, String columnName) 
throws SQLException {
+        TablePath tablePath = table.getTablePath();
+        String sql =
+                "SELECT COLLATION_NAME "
+                        + "FROM information_schema.COLUMNS "
+                        + "WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ? AND 
COLUMN_NAME = ?";
+        try (PreparedStatement ps = connection.prepareStatement(sql)) {
+            ps.setString(1, tablePath.getDatabaseName());
+            ps.setString(2, tablePath.getTableName());
+            ps.setString(3, columnName);
+            try (ResultSet rs = ps.executeQuery()) {
+                if (rs.next()) {
+                    return rs.getString(1);
+                }
+                return null;
+            }
+        }
+    }
+
+    private List<String> sampleStringValues(
+            Connection connection, JdbcSourceTable table, String columnName, 
int sampleSize)
+            throws SQLException {
+        String quotedColumn = quoteIdentifier(columnName);
+        String sql;
+        if (StringUtils.isNotBlank(table.getQuery())) {
+            sql =
+                    String.format(
+                            "SELECT %s FROM (%s) tmp WHERE %s IS NOT NULL 
ORDER BY %s ASC LIMIT %s",
+                            quotedColumn, table.getQuery(), quotedColumn, 
quotedColumn, sampleSize);
+        } else {
+            sql =
+                    String.format(
+                            "SELECT %s FROM %s WHERE %s IS NOT NULL ORDER BY 
%s ASC LIMIT %s",
+                            quotedColumn,
+                            tableIdentifier(table.getTablePath()),
+                            quotedColumn,
+                            quotedColumn,
+                            sampleSize);
+        }
+        List<String> samples = new ArrayList<>(sampleSize);
+        try (Statement stmt =
+                connection.createStatement(
+                        ResultSet.TYPE_FORWARD_ONLY, 
ResultSet.CONCUR_READ_ONLY)) {
+            stmt.setFetchSize(Integer.MIN_VALUE);
+            try (ResultSet rs = stmt.executeQuery(sql)) {
+                while (rs.next()) {
+                    String value = rs.getString(1);
+                    if (value != null) {
+                        samples.add(value);
+                    }
+                }
+            }
+        }
+        return samples;
+    }
+
+    private boolean isPrintableAscii(String value) {
+        for (int i = 0; i < value.length(); i++) {
+            char ch = value.charAt(i);
+            if (ch < 32 || ch > 126) {
+                return false;
+            }
+        }
+        return true;
+    }
+
     @Override
     public boolean supportDefaultValue(BasicTypeDefine typeBasicTypeDefine) {
         MysqlType nativeType = (MysqlType) typeBasicTypeDefine.getNativeType();
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/AsciiStringRangeSplitter.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/AsciiStringRangeSplitter.java
new file mode 100644
index 0000000000..958844447a
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/AsciiStringRangeSplitter.java
@@ -0,0 +1,141 @@
+/*
+ * 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.source;
+
+import java.math.BigInteger;
+
+final class AsciiStringRangeSplitter {
+
+    private static final int FIRST_PRINTABLE_ASCII = 32;
+    private static final int LAST_PRINTABLE_ASCII = 126;
+    private static final int RADIX = LAST_PRINTABLE_ASCII - 
FIRST_PRINTABLE_ASCII + 1;
+
+    private AsciiStringRangeSplitter() {}
+
+    // This mapping is order-preserving only for fixed-length printable ASCII 
strings under a
+    // binary collation. Other shapes must be rejected and handled by hash or 
single splitting.
+    static String[] split(String left, String right, int expectSliceNumber) {
+        if (left == null || right == null) {
+            throw new IllegalArgumentException("String range boundary cannot 
be null");
+        }
+        validateRangeBoundary(left, "left");
+        validateRangeBoundary(right, "right");
+        if (left.length() != right.length()) {
+            throw new IllegalArgumentException(
+                    "String range split requires fixed-length ASCII 
boundaries");
+        }
+        if (left.equals(right)) {
+            return new String[] {left, right};
+        }
+        if (left.compareTo(right) > 0) {
+            throw new IllegalArgumentException(
+                    String.format(
+                            "String range left boundary [%s] must not be 
greater than right boundary [%s]",
+                            left, right));
+        }
+
+        BigInteger[] splitValues =
+                splitBigInteger(
+                        stringToBigInteger(left), stringToBigInteger(right), 
expectSliceNumber);
+        String[] result = new String[splitValues.length];
+        result[0] = left;
+        result[splitValues.length - 1] = right;
+        for (int i = 1; i < splitValues.length - 1; i++) {
+            result[i] = bigIntegerToString(splitValues[i], left.length());
+            validateRangeBoundary(result[i], "generated");
+        }
+        validateLexicalOrder(result);
+        return result;
+    }
+
+    private static BigInteger[] splitBigInteger(
+            BigInteger left, BigInteger right, int expectSliceNumber) {
+        if (expectSliceNumber < 1) {
+            throw new IllegalArgumentException("expectSliceNumber must be 
greater than 0");
+        }
+        BigInteger gap = right.subtract(left);
+        BigInteger step = gap.divide(BigInteger.valueOf(expectSliceNumber));
+        BigInteger remainder = 
gap.remainder(BigInteger.valueOf(expectSliceNumber));
+        if (step.compareTo(BigInteger.ZERO) == 0) {
+            expectSliceNumber = Math.max(remainder.intValue(), 1);
+        }
+
+        BigInteger[] result = new BigInteger[expectSliceNumber + 1];
+        result[0] = left;
+        result[expectSliceNumber] = right;
+
+        BigInteger upperBound = left;
+        for (int i = 1; i < expectSliceNumber; i++) {
+            upperBound = upperBound.add(step);
+            if (remainder.compareTo(BigInteger.valueOf(i)) >= 0) {
+                upperBound = upperBound.add(BigInteger.ONE);
+            }
+            result[i] = upperBound;
+        }
+        return result;
+    }
+
+    private static BigInteger stringToBigInteger(String value) {
+        BigInteger result = BigInteger.ZERO;
+        BigInteger radix = BigInteger.valueOf(RADIX);
+        for (int i = value.length() - 1, index = 0; i >= 0; i--, index++) {
+            result =
+                    result.add(
+                            BigInteger.valueOf(value.charAt(i) - 
FIRST_PRINTABLE_ASCII)
+                                    .multiply(radix.pow(index)));
+        }
+        return result;
+    }
+
+    private static String bigIntegerToString(BigInteger value, int length) {
+        char[] result = new char[length];
+        BigInteger current = value;
+        BigInteger radix = BigInteger.valueOf(RADIX);
+        for (int i = length - 1; i >= 0; i--) {
+            result[i] = (char) (current.mod(radix).intValue() + 
FIRST_PRINTABLE_ASCII);
+            current = current.divide(radix);
+        }
+        if (current.compareTo(BigInteger.ZERO) > 0) {
+            throw new IllegalArgumentException("Generated string boundary 
exceeds fixed length");
+        }
+        return new String(result);
+    }
+
+    private static void validateRangeBoundary(String value, String name) {
+        for (int i = 0; i < value.length(); i++) {
+            char ch = value.charAt(i);
+            if (ch < FIRST_PRINTABLE_ASCII || ch > LAST_PRINTABLE_ASCII) {
+                throw new IllegalArgumentException(
+                        String.format(
+                                "Only printable ASCII strings are supported 
for string range split, %s=[%s]",
+                                name, value));
+            }
+        }
+    }
+
+    private static void validateLexicalOrder(String[] values) {
+        for (int i = 1; i < values.length; i++) {
+            if (values[i - 1].compareTo(values[i]) >= 0) {
+                throw new IllegalArgumentException(
+                        String.format(
+                                "Generated string range boundary [%s] is not 
greater than previous boundary [%s]",
+                                values[i], values[i - 1]));
+            }
+        }
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/ChunkSplitter.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/ChunkSplitter.java
index 03d86a38c1..67750cd70b 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/ChunkSplitter.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/ChunkSplitter.java
@@ -178,6 +178,51 @@ public abstract class ChunkSplitter implements 
AutoCloseable, Serializable {
                 null);
     }
 
+    protected StringSplitStrategy resolveStringSplitStrategy(
+            JdbcSourceTable table, String splitKeyName) throws SQLException {
+        StringSplitStrategy requestedStrategy = 
config.getStringSplitStrategy();
+        if (requestedStrategy == null
+                || (requestedStrategy != StringSplitStrategy.RANGE
+                        && requestedStrategy != StringSplitStrategy.AUTO)) {
+            return requestedStrategy;
+        }
+
+        if (!jdbcDialect.supportStringRangeSplit()) {
+            throw new JdbcConnectorException(
+                    CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT,
+                    String.format(
+                            "String split strategy %s requires validated range 
split support, but dialect %s does not support range/auto string split 
strategy.",
+                            requestedStrategy, jdbcDialect.dialectName()));
+        }
+
+        StringRangeSplitDecision decision =
+                jdbcDialect.validateStringRangeSplit(
+                        getOrEstablishConnection(), table, splitKeyName, 256);
+        if (decision.isSafe()) {
+            return StringSplitStrategy.RANGE;
+        }
+        if (requestedStrategy == StringSplitStrategy.RANGE) {
+            throw new JdbcConnectorException(
+                    CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT,
+                    String.format(
+                            "String range split is unsafe for table %s, split 
column %s: %s",
+                            table.getTablePath(), splitKeyName, 
decision.getReason()));
+        }
+
+        StringSplitStrategy fallback =
+                jdbcDialect.supportHashSplitter()
+                        ? StringSplitStrategy.HASH
+                        : StringSplitStrategy.NONE;
+        log.warn(
+                "String range split is unsafe for table {}, split column {}. 
Requested strategy: {}, fallback strategy: {}, reason: {}",
+                table.getTablePath(),
+                splitKeyName,
+                requestedStrategy,
+                fallback,
+                decision.getReason());
+        return fallback;
+    }
+
     protected PreparedStatement createSingleSplitStatement(JdbcSourceSplit 
split)
             throws SQLException {
         String splitQuery = split.getSplitQuery();
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/DynamicChunkSplitter.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/DynamicChunkSplitter.java
index cc03b5d471..a6c10b8a5e 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/DynamicChunkSplitter.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/DynamicChunkSplitter.java
@@ -26,8 +26,11 @@ import org.apache.seatunnel.api.table.catalog.TablePath;
 import org.apache.seatunnel.api.table.catalog.TableSchema;
 import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
 import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
+import org.apache.seatunnel.api.table.type.SqlType;
 import org.apache.seatunnel.common.exception.CommonError;
+import org.apache.seatunnel.common.exception.CommonErrorCodeDeprecated;
 import org.apache.seatunnel.connectors.seatunnel.jdbc.config.JdbcSourceConfig;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorException;
 import org.apache.seatunnel.connectors.seatunnel.jdbc.utils.ObjectUtils;
 
 import lombok.Data;
@@ -79,6 +82,11 @@ public class DynamicChunkSplitter extends ChunkSplitter {
             JdbcSourceTable table, SeaTunnelRowType splitKey) throws Exception 
{
         String splitKeyName = splitKey.getFieldNames()[0];
         SeaTunnelDataType splitKeyType = splitKey.getFieldType(0);
+        if (SqlType.STRING.equals(splitKeyType.getSqlType())
+                && config.getStringSplitStrategy() != null) {
+            return createStringStrategySplits(table, splitKeyName, 
splitKeyType);
+        }
+
         List<ChunkRange> chunks = splitTableIntoChunks(table, splitKeyName, 
splitKeyType);
 
         List<JdbcSourceSplit> splits = new ArrayList<>();
@@ -100,12 +108,170 @@ public class DynamicChunkSplitter extends ChunkSplitter {
 
     private PreparedStatement createDynamicSplitStatement(JdbcSourceSplit 
split, TableSchema schema)
             throws SQLException {
+        if (isHashStringSplit(split)) {
+            return createStringColumnSplitStatement(split);
+        }
         String splitQuery = createDynamicSplitQuerySQL(split, schema);
         PreparedStatement statement = createPreparedStatement(splitQuery);
         prepareDynamicSplitStatement(statement, split);
         return statement;
     }
 
+    private boolean isHashStringSplit(JdbcSourceSplit split) {
+        return SqlType.STRING.equals(split.getSplitKeyType().getSqlType())
+                && split.getSplitStart() instanceof Integer
+                && split.getSplitEnd() == null;
+    }
+
+    private PreparedStatement createStringColumnSplitStatement(JdbcSourceSplit 
split)
+            throws SQLException {
+        PreparedStatement statement = 
createPreparedStatement(split.getSplitQuery());
+        statement.setInt(1, (Integer) split.getSplitStart());
+        return statement;
+    }
+
+    private Collection<JdbcSourceSplit> createStringStrategySplits(
+            JdbcSourceTable table, String splitKeyName, SeaTunnelDataType 
splitKeyType)
+            throws Exception {
+        StringSplitStrategy strategy = resolveStringSplitStrategy(table, 
splitKeyName);
+        switch (strategy) {
+            case NONE:
+                return Collections.singletonList(
+                        createSingleStringSplit(table, splitKeyName, 
splitKeyType));
+            case HASH:
+                if (jdbcDialect.supportHashSplitter()) {
+                    return createStringColumnSplits(
+                            table, splitKeyName, splitKeyType, 
config.getSplitSize());
+                }
+                return Collections.singletonList(
+                        createSingleStringSplit(table, splitKeyName, 
splitKeyType));
+            case RANGE:
+                if (config.getStringSplitStrategy() == 
StringSplitStrategy.AUTO) {
+                    try {
+                        return createStringRangeSplits(table, splitKeyName, 
splitKeyType);
+                    } catch (Exception e) {
+                        log.warn(
+                                "Range string split failed for table {}, 
fallback to hash split",
+                                table.getTablePath(),
+                                e);
+                        if (jdbcDialect.supportHashSplitter()) {
+                            return createStringColumnSplits(
+                                    table, splitKeyName, splitKeyType, 
config.getSplitSize());
+                        }
+                        return Collections.singletonList(
+                                createSingleStringSplit(table, splitKeyName, 
splitKeyType));
+                    }
+                }
+                return createStringRangeSplits(table, splitKeyName, 
splitKeyType);
+            default:
+                throw new JdbcConnectorException(
+                        CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT,
+                        "Unsupported string split strategy: " + 
config.getStringSplitStrategy());
+        }
+    }
+
+    private JdbcSourceSplit createSingleStringSplit(
+            JdbcSourceTable table, String splitKeyName, SeaTunnelDataType 
splitKeyType) {
+        return new JdbcSourceSplit(
+                table.getTablePath(),
+                createSplitId(table.getTablePath(), 0),
+                table.getQuery(),
+                splitKeyName,
+                splitKeyType,
+                null,
+                null);
+    }
+
+    @VisibleForTesting
+    Collection<JdbcSourceSplit> createStringColumnSplits(
+            JdbcSourceTable table,
+            String splitKeyName,
+            SeaTunnelDataType splitKeyType,
+            int chunkSize)
+            throws SQLException {
+        log.info("Use string hash chunks for table {}", table.getTablePath());
+        long approximateRowCnt = queryApproximateRowCnt(table);
+        int shardCount = Math.max((int) (approximateRowCnt / 
Math.max(chunkSize, 1)) + 1, 1);
+        List<JdbcSourceSplit> splits = new ArrayList<>(shardCount);
+        Column column =
+                table.getCatalogTable().getTableSchema().getColumns().stream()
+                        .filter(c -> c.getName().equals(splitKeyName))
+                        .findAny()
+                        .get();
+        for (int i = 0; i < shardCount; i++) {
+            String splitQuery;
+            if (StringUtils.isNotBlank(table.getQuery())) {
+                splitQuery =
+                        String.format(
+                                "SELECT * FROM (%s) st_jdbc_splitter WHERE %s 
= ?",
+                                applyUserWhereCondition(table.getQuery()),
+                                jdbcDialect.hashModForField(
+                                        column.getSourceType(), splitKeyName, 
shardCount));
+            } else if 
(StringUtils.isNotBlank(config.getWhereConditionClause())) {
+                String userQuery =
+                        String.format(
+                                "SELECT * FROM %s",
+                                
jdbcDialect.tableIdentifier(table.getTablePath()));
+                splitQuery =
+                        String.format(
+                                "SELECT * FROM (%s) st_jdbc_splitter WHERE %s 
= ?",
+                                applyUserWhereCondition(userQuery),
+                                jdbcDialect.hashModForField(
+                                        column.getSourceType(), splitKeyName, 
shardCount));
+            } else {
+                splitQuery =
+                        String.format(
+                                "SELECT * FROM %s WHERE %s = ?",
+                                
jdbcDialect.tableIdentifier(table.getTablePath()),
+                                jdbcDialect.hashModForField(
+                                        column.getSourceType(), splitKeyName, 
shardCount));
+            }
+
+            splits.add(
+                    new JdbcSourceSplit(
+                            table.getTablePath(),
+                            createSplitId(table.getTablePath(), i),
+                            splitQuery,
+                            splitKeyName,
+                            splitKeyType,
+                            i,
+                            null));
+        }
+        return splits;
+    }
+
+    private Collection<JdbcSourceSplit> createStringRangeSplits(
+            JdbcSourceTable table, String splitKeyName, SeaTunnelDataType 
splitKeyType)
+            throws SQLException {
+        Pair<Object, Object> splitColumnRange = queryMinMax(table, 
splitKeyName);
+        String min =
+                splitColumnRange.getLeft() == null ? null : 
splitColumnRange.getLeft().toString();
+        String max =
+                splitColumnRange.getRight() == null ? null : 
splitColumnRange.getRight().toString();
+        if (min == null || max == null || min.equals(max)) {
+            return Collections.singletonList(
+                    createSingleStringSplit(table, splitKeyName, 
splitKeyType));
+        }
+
+        long approximateRowCnt = queryApproximateRowCnt(table);
+        int shardCount =
+                Math.max((int) (approximateRowCnt / 
Math.max(config.getSplitSize(), 1)) + 1, 1);
+        String[] rangeResult = AsciiStringRangeSplitter.split(min, max, 
shardCount);
+        List<JdbcSourceSplit> splits = new ArrayList<>(rangeResult.length - 1);
+        for (int i = 0; i < rangeResult.length - 1; i++) {
+            splits.add(
+                    new JdbcSourceSplit(
+                            table.getTablePath(),
+                            createSplitId(table.getTablePath(), i),
+                            table.getQuery(),
+                            splitKeyName,
+                            splitKeyType,
+                            i == 0 ? null : rangeResult[i],
+                            i == rangeResult.length - 2 ? null : rangeResult[i 
+ 1]));
+        }
+        return splits;
+    }
+
     private List<ChunkRange> splitTableIntoChunks(
             JdbcSourceTable table, String splitColumnName, SeaTunnelDataType 
splitColumnType)
             throws Exception {
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitter.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitter.java
index 92a26490ee..309b6c82bd 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitter.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitter.java
@@ -76,11 +76,41 @@ public class FixedChunkSplitter extends ChunkSplitter {
             }
         }
         if (SqlType.STRING.equals(splitKeyType.getSqlType())) {
-            log.info("useNewStringSplitter is {}", 
useCharsetBasedStringSplitter);
-            if (useCharsetBasedStringSplitter) {
-                return getJdbcSourceStringSplits(table, splitKeyName, 
splitKeyType);
-            } else {
-                return createStringColumnSplits(table, splitKeyName, 
splitKeyType);
+            if (config.getStringSplitStrategy() == null) {
+                log.info("useNewStringSplitter is {}", 
useCharsetBasedStringSplitter);
+                if (useCharsetBasedStringSplitter) {
+                    return getJdbcSourceStringSplits(table, splitKeyName, 
splitKeyType);
+                } else {
+                    return createStringColumnSplits(table, splitKeyName, 
splitKeyType);
+                }
+            }
+            switch (resolveStringSplitStrategy(table, splitKeyName)) {
+                case NONE:
+                    return Collections.singletonList(createSingleSplit(table));
+                case HASH:
+                    return createStringColumnSplits(table, splitKeyName, 
splitKeyType);
+                case RANGE:
+                    if (config.getStringSplitStrategy() == 
StringSplitStrategy.AUTO) {
+                        try {
+                            return createStringRangeSplits(table, 
splitKeyName, splitKeyType);
+                        } catch (Exception e) {
+                            log.warn(
+                                    "Range string split failed for table {}, 
fallback to hash split",
+                                    table.getTablePath(),
+                                    e);
+                            return createStringColumnSplits(table, 
splitKeyName, splitKeyType);
+                        }
+                    }
+                    return createStringRangeSplits(table, splitKeyName, 
splitKeyType);
+                case AUTO:
+                    throw new JdbcConnectorException(
+                            CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT,
+                            "Unexpected string split strategy: " + 
config.getStringSplitStrategy());
+                default:
+                    throw new JdbcConnectorException(
+                            CommonErrorCodeDeprecated.ILLEGAL_ARGUMENT,
+                            "Unsupported string split strategy: "
+                                    + config.getStringSplitStrategy());
             }
         }
         return getJdbcSourceSplits(table, splitKeyName, splitKeyType);
@@ -194,17 +224,32 @@ public class FixedChunkSplitter extends ChunkSplitter {
     @Override
     protected PreparedStatement createSplitStatement(JdbcSourceSplit split, 
TableSchema schema)
             throws SQLException {
-        if (SqlType.STRING.equals(split.getSplitKeyType().getSqlType())
-                && !useCharsetBasedStringSplitter) {
+        if (isHashStringSplit(split)) {
             return createStringColumnSplitStatement(split);
         }
         if (split.getSplitStart() == null && split.getSplitEnd() == null) {
             return createSingleSplitStatement(split);
         }
+        if (isStringRangeSplit(split)) {
+            return createStringRangeSplitStatement(split);
+        }
 
         return createNumberColumnSplitStatement(split);
     }
 
+    private boolean isHashStringSplit(JdbcSourceSplit split) {
+        return SqlType.STRING.equals(split.getSplitKeyType().getSqlType())
+                && split.getSplitStart() instanceof Integer
+                && split.getSplitEnd() == null;
+    }
+
+    private boolean isStringRangeSplit(JdbcSourceSplit split) {
+        return config.getStringSplitStrategy() != null
+                && SqlType.STRING.equals(split.getSplitKeyType().getSqlType())
+                && (split.getSplitStart() instanceof String
+                        || split.getSplitEnd() instanceof String);
+    }
+
     private Collection<JdbcSourceSplit> createStringColumnSplits(
             JdbcSourceTable table, String splitKeyName, SeaTunnelDataType 
splitKeyType) {
         List<JdbcSourceSplit> splits = new 
ArrayList<>(table.getPartitionNumber());
@@ -264,6 +309,34 @@ public class FixedChunkSplitter extends ChunkSplitter {
         return splits;
     }
 
+    private Collection<JdbcSourceSplit> createStringRangeSplits(
+            JdbcSourceTable table, String splitKeyName, SeaTunnelDataType 
splitKeyType)
+            throws SQLException {
+        Pair<Object, Object> splitColumnRange = queryMinMax(table, 
splitKeyName);
+        String min =
+                splitColumnRange.getLeft() == null ? null : 
splitColumnRange.getLeft().toString();
+        String max =
+                splitColumnRange.getRight() == null ? null : 
splitColumnRange.getRight().toString();
+        if (min == null || max == null || min.equals(max)) {
+            return Collections.singletonList(createSingleSplit(table));
+        }
+
+        String[] rangeResult = AsciiStringRangeSplitter.split(min, max, 
table.getPartitionNumber());
+        List<JdbcSourceSplit> splits = new ArrayList<>(rangeResult.length - 1);
+        for (int i = 0; i < rangeResult.length - 1; i++) {
+            splits.add(
+                    new JdbcSourceSplit(
+                            table.getTablePath(),
+                            createSplitId(table.getTablePath(), i),
+                            table.getQuery(),
+                            splitKeyName,
+                            splitKeyType,
+                            i == 0 ? null : rangeResult[i],
+                            i == rangeResult.length - 2 ? null : rangeResult[i 
+ 1]));
+        }
+        return splits;
+    }
+
     private PreparedStatement createStringColumnSplitStatement(JdbcSourceSplit 
split)
             throws SQLException {
         PreparedStatement statement = 
createPreparedStatement(split.getSplitQuery());
@@ -271,6 +344,59 @@ public class FixedChunkSplitter extends ChunkSplitter {
         return statement;
     }
 
+    private PreparedStatement createStringRangeSplitStatement(JdbcSourceSplit 
split)
+            throws SQLException {
+        String splitKeyName = 
jdbcDialect.quoteIdentifier(split.getSplitKeyName());
+        boolean isFirstSplit = split.getSplitStart() == null;
+        boolean isLastSplit = split.getSplitEnd() == null;
+
+        String condition;
+        if (isFirstSplit) {
+            condition = String.format("%s <= ? AND NOT (%s = ?)", 
splitKeyName, splitKeyName);
+        } else if (isLastSplit) {
+            condition = String.format("%s >= ?", splitKeyName);
+        } else {
+            condition =
+                    String.format(
+                            "%s >= ? AND NOT (%s = ?) AND %s <= ?",
+                            splitKeyName, splitKeyName, splitKeyName);
+        }
+
+        String splitQuery;
+        if (StringUtils.isNotBlank(split.getSplitQuery())) {
+            splitQuery =
+                    String.format(
+                            "SELECT * FROM (%s) st_jdbc_splitter WHERE %s",
+                            applyUserWhereCondition(split.getSplitQuery()), 
condition);
+        } else if (StringUtils.isNotBlank(config.getWhereConditionClause())) {
+            String userQuery =
+                    String.format(
+                            "SELECT * FROM %s", 
jdbcDialect.tableIdentifier(split.getTablePath()));
+            splitQuery =
+                    String.format(
+                            "SELECT * FROM (%s) st_jdbc_splitter WHERE %s",
+                            applyUserWhereCondition(userQuery), condition);
+        } else {
+            splitQuery =
+                    String.format(
+                            "SELECT * FROM %s WHERE %s",
+                            jdbcDialect.tableIdentifier(split.getTablePath()), 
condition);
+        }
+
+        PreparedStatement statement = createPreparedStatement(splitQuery);
+        if (isFirstSplit) {
+            statement.setString(1, (String) split.getSplitEnd());
+            statement.setString(2, (String) split.getSplitEnd());
+        } else if (isLastSplit) {
+            statement.setString(1, (String) split.getSplitStart());
+        } else {
+            statement.setString(1, (String) split.getSplitStart());
+            statement.setString(2, (String) split.getSplitEnd());
+            statement.setString(3, (String) split.getSplitEnd());
+        }
+        return statement;
+    }
+
     private Collection<JdbcSourceSplit> createNumberColumnSplits(
             JdbcSourceTable table,
             String splitKeyName,
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/JdbcSourceFactory.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/JdbcSourceFactory.java
index c22d4555dc..de2da4aa41 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/JdbcSourceFactory.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/JdbcSourceFactory.java
@@ -74,6 +74,7 @@ public class JdbcSourceFactory implements TableSourceFactory {
                         JdbcSourceOptions.PARTITION_NUM,
                         JdbcSourceOptions.COMPATIBLE_MODE,
                         JdbcSourceOptions.STRING_SPLIT_MODE,
+                        JdbcSourceOptions.STRING_SPLIT_STRATEGY,
                         JdbcSourceOptions.STRING_SPLIT_MODE_COLLATE,
                         JdbcSourceOptions.PROPERTIES,
                         JdbcSourceOptions.QUERY,
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/StringRangeSplitDecision.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/StringRangeSplitDecision.java
new file mode 100644
index 0000000000..f3c60d43ea
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/StringRangeSplitDecision.java
@@ -0,0 +1,40 @@
+/*
+ * 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.source;
+
+import lombok.Getter;
+
+@Getter
+public class StringRangeSplitDecision {
+
+    private final boolean safe;
+    private final String reason;
+
+    private StringRangeSplitDecision(boolean safe, String reason) {
+        this.safe = safe;
+        this.reason = reason;
+    }
+
+    public static StringRangeSplitDecision safe(String reason) {
+        return new StringRangeSplitDecision(true, reason);
+    }
+
+    public static StringRangeSplitDecision unsafe(String reason) {
+        return new StringRangeSplitDecision(false, reason);
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/StringSplitStrategy.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/StringSplitStrategy.java
new file mode 100644
index 0000000000..07722320fb
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/main/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/StringSplitStrategy.java
@@ -0,0 +1,40 @@
+/*
+ * 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.source;
+
+public enum StringSplitStrategy {
+    AUTO("auto"),
+    RANGE("range"),
+    HASH("hash"),
+    NONE("none");
+
+    private final String strategy;
+
+    StringSplitStrategy(String strategy) {
+        this.strategy = strategy;
+    }
+
+    public String getStrategy() {
+        return strategy;
+    }
+
+    @Override
+    public String toString() {
+        return strategy;
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceConfigTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceConfigTest.java
new file mode 100644
index 0000000000..8dc074264a
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/config/JdbcSourceConfigTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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.config;
+
+import org.apache.seatunnel.api.configuration.ReadonlyConfig;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.source.StringSplitStrategy;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class JdbcSourceConfigTest {
+
+    @Test
+    public void testStringSplitStrategyIsUnsetByDefault() {
+        JdbcSourceConfig sourceConfig = 
JdbcSourceConfig.of(ReadonlyConfig.fromMap(baseConfig()));
+
+        assertNull(sourceConfig.getStringSplitStrategy());
+    }
+
+    @Test
+    public void testResolveExplicitStringSplitStrategy() {
+        Map<String, Object> configMap = baseConfig();
+        configMap.put("split.string-strategy", "range");
+
+        JdbcSourceConfig sourceConfig = 
JdbcSourceConfig.of(ReadonlyConfig.fromMap(configMap));
+
+        assertEquals(StringSplitStrategy.RANGE, 
sourceConfig.getStringSplitStrategy());
+    }
+
+    private Map<String, Object> baseConfig() {
+        Map<String, Object> configMap = new HashMap<>();
+        configMap.put("url", "jdbc:postgresql://localhost:5432/test");
+        configMap.put("driver", "org.postgresql.Driver");
+        configMap.put("table_path", "public.test");
+        return configMap;
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialectTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialectTest.java
index 51fe7d57b6..122cec9e28 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialectTest.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/internal/dialect/mysql/MysqlDialectTest.java
@@ -17,14 +17,24 @@
 
 package org.apache.seatunnel.connectors.seatunnel.jdbc.internal.dialect.mysql;
 
+import org.apache.seatunnel.api.table.catalog.TablePath;
+import org.apache.seatunnel.connectors.seatunnel.jdbc.source.JdbcSourceTable;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.source.StringRangeSplitDecision;
+
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
 import lombok.extern.slf4j.Slf4j;
 
 import java.security.MessageDigest;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.Statement;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.zip.CRC32;
@@ -32,6 +42,22 @@ import java.util.zip.CRC32;
 @Slf4j
 public class MysqlDialectTest {
 
+    @Test
+    public void testValidateStringRangeSplitAcceptsPrintableAsciiPunctuation() 
throws Exception {
+        MysqlDialect dialect = new MysqlDialect();
+        JdbcSourceTable table =
+                JdbcSourceTable.builder().tablePath(TablePath.of("test_db", 
"test_table")).build();
+
+        StringRangeSplitDecision decision =
+                dialect.validateStringRangeSplit(
+                        mockConnection("utf8mb4_bin", Arrays.asList("AA/01", 
"AB.CD", "ID~99")),
+                        table,
+                        "id",
+                        3);
+
+        Assertions.assertTrue(decision.isSafe());
+    }
+
     @Test
     public void testHashDistributionMD5vsCRC32WithSnowflakeIds() {
         int totalRecords = 1_100_000;
@@ -188,4 +214,83 @@ public class MysqlDialectTest {
 
         return Math.sqrt(sumSquaredDiff / partitions);
     }
+
+    private Connection mockConnection(String collation, List<String> samples) {
+        return (Connection)
+                java.lang.reflect.Proxy.newProxyInstance(
+                        Connection.class.getClassLoader(),
+                        new Class<?>[] {Connection.class},
+                        (proxy, method, args) -> {
+                            switch (method.getName()) {
+                                case "prepareStatement":
+                                    return mockPreparedStatement(collation);
+                                case "createStatement":
+                                    return mockStatement(samples);
+                                default:
+                                    return 
defaultValue(method.getReturnType());
+                            }
+                        });
+    }
+
+    private PreparedStatement mockPreparedStatement(String collation) {
+        return (PreparedStatement)
+                java.lang.reflect.Proxy.newProxyInstance(
+                        PreparedStatement.class.getClassLoader(),
+                        new Class<?>[] {PreparedStatement.class},
+                        (proxy, method, args) -> {
+                            if ("executeQuery".equals(method.getName())) {
+                                return mockResultSet(Arrays.asList(collation));
+                            }
+                            return defaultValue(method.getReturnType());
+                        });
+    }
+
+    private Statement mockStatement(List<String> samples) {
+        return (Statement)
+                java.lang.reflect.Proxy.newProxyInstance(
+                        Statement.class.getClassLoader(),
+                        new Class<?>[] {Statement.class},
+                        (proxy, method, args) -> {
+                            if ("executeQuery".equals(method.getName())) {
+                                return mockResultSet(samples);
+                            }
+                            return defaultValue(method.getReturnType());
+                        });
+    }
+
+    private ResultSet mockResultSet(List<String> values) {
+        Iterator<String> iterator = values.iterator();
+        String[] current = new String[1];
+        return (ResultSet)
+                java.lang.reflect.Proxy.newProxyInstance(
+                        ResultSet.class.getClassLoader(),
+                        new Class<?>[] {ResultSet.class},
+                        (proxy, method, args) -> {
+                            switch (method.getName()) {
+                                case "next":
+                                    if (iterator.hasNext()) {
+                                        current[0] = iterator.next();
+                                        return true;
+                                    }
+                                    return false;
+                                case "getString":
+                                    return current[0];
+                                default:
+                                    return 
defaultValue(method.getReturnType());
+                            }
+                        });
+    }
+
+    private Object defaultValue(Class<?> returnType) {
+        if (!returnType.isPrimitive()) {
+            return null;
+        }
+        if (boolean.class.equals(returnType)) {
+            return false;
+        }
+        if (void.class.equals(returnType)) {
+            return null;
+        }
+        return 0;
+    }
 }
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/AsciiStringRangeSplitterTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/AsciiStringRangeSplitterTest.java
new file mode 100644
index 0000000000..b8254ba108
--- /dev/null
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/AsciiStringRangeSplitterTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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.source;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class AsciiStringRangeSplitterTest {
+
+    @Test
+    public void testSplitFixedLengthPrintableAsciiRange() {
+        assertArrayEquals(
+                new String[] {"aa", "ff", "kk"}, 
AsciiStringRangeSplitter.split("aa", "kk", 2));
+    }
+
+    @Test
+    public void testSplitNumericSuffixKeepsGeneratedBoundariesPrintable() {
+        assertDoesNotThrow(() -> AsciiStringRangeSplitter.split("key00000", 
"key00099", 11));
+    }
+
+    @Test
+    public void testRejectVariableLengthRange() {
+        assertThrows(
+                IllegalArgumentException.class, () -> 
AsciiStringRangeSplitter.split("aa", "z", 2));
+    }
+
+    @Test
+    public void testRejectNonPrintableAsciiRange() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> AsciiStringRangeSplitter.split("aa", "中中", 2));
+    }
+}
diff --git 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitterTest.java
 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitterTest.java
index d36e8aaa1f..cdd4bb8e99 100644
--- 
a/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitterTest.java
+++ 
b/seatunnel-connectors-v2/connector-jdbc/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/source/FixedChunkSplitterTest.java
@@ -17,22 +17,103 @@
 
 package org.apache.seatunnel.connectors.seatunnel.jdbc.source;
 
+import org.apache.seatunnel.api.table.catalog.TablePath;
+import org.apache.seatunnel.api.table.catalog.TableSchema;
+import org.apache.seatunnel.api.table.type.BasicType;
+import org.apache.seatunnel.api.table.type.SeaTunnelDataType;
+import org.apache.seatunnel.api.table.type.SeaTunnelRowType;
 import 
org.apache.seatunnel.connectors.seatunnel.jdbc.config.JdbcConnectionConfig;
 import org.apache.seatunnel.connectors.seatunnel.jdbc.config.JdbcSourceConfig;
+import 
org.apache.seatunnel.connectors.seatunnel.jdbc.exception.JdbcConnectorException;
 
 import org.junit.jupiter.api.Test;
 
 import lombok.extern.slf4j.Slf4j;
 
+import java.lang.reflect.InvocationHandler;
 import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
 import java.math.BigDecimal;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.HashMap;
+import java.util.Map;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @Slf4j
 public class FixedChunkSplitterTest {
 
+    @Test
+    public void testCreateFirstStringRangeSplitStatement() throws SQLException 
{
+        CapturingFixedChunkSplitter splitter = new 
CapturingFixedChunkSplitter(mysqlConfig());
+        JdbcSourceSplit split =
+                new JdbcSourceSplit(
+                        TablePath.of("db", "tbl"),
+                        "split-0",
+                        null,
+                        "id",
+                        BasicType.STRING_TYPE,
+                        null,
+                        "mm");
+
+        splitter.generateSplitStatement(split, TableSchema.builder().build());
+
+        assertEquals("SELECT * FROM `db`.`tbl` WHERE `id` <= ? AND NOT (`id` = 
?)", splitter.sql);
+        assertEquals("mm", splitter.stringParameters.get(1));
+        assertEquals("mm", splitter.stringParameters.get(2));
+    }
+
+    @Test
+    public void testCreateLastStringRangeSplitStatement() throws SQLException {
+        CapturingFixedChunkSplitter splitter = new 
CapturingFixedChunkSplitter(mysqlConfig());
+        JdbcSourceSplit split =
+                new JdbcSourceSplit(
+                        TablePath.of("db", "tbl"),
+                        "split-1",
+                        null,
+                        "id",
+                        BasicType.STRING_TYPE,
+                        "mm",
+                        null);
+
+        splitter.generateSplitStatement(split, TableSchema.builder().build());
+
+        assertEquals("SELECT * FROM `db`.`tbl` WHERE `id` >= ?", splitter.sql);
+        assertEquals("mm", splitter.stringParameters.get(1));
+    }
+
+    @Test
+    public void testRejectAutoStringRangeSplitForNonMysqlDialect() {
+        JdbcSourceConfig config =
+                JdbcSourceConfig.builder()
+                        .jdbcConnectionConfig(
+                                JdbcConnectionConfig.builder()
+                                        
.url("jdbc:postgresql://localhost:5432/test")
+                                        .driverName("org.postgresql.Driver")
+                                        .build())
+                        .stringSplitStrategy(StringSplitStrategy.AUTO)
+                        .build();
+        CapturingFixedChunkSplitter splitter = new 
CapturingFixedChunkSplitter(config);
+        JdbcSourceTable table =
+                JdbcSourceTable.builder().tablePath(TablePath.of("public", 
"tbl")).build();
+
+        JdbcConnectorException exception =
+                assertThrows(
+                        JdbcConnectorException.class,
+                        () ->
+                                splitter.createSplits(
+                                        table,
+                                        new SeaTunnelRowType(
+                                                new String[] {"id"},
+                                                new SeaTunnelDataType[] 
{BasicType.STRING_TYPE})));
+
+        assertTrue(exception.getMessage().contains("does not support 
range/auto"));
+    }
+
     @Test
     public void testConvertFloat() throws Exception {
         JdbcSourceConfig config =
@@ -83,4 +164,41 @@ public class FixedChunkSplitterTest {
         BigDecimal oldPrecisionWay = BigDecimal.valueOf(precisionTestFloat);
         assertNotEquals(new BigDecimal("0.1"), oldPrecisionWay);
     }
+
+    private static JdbcSourceConfig mysqlConfig() {
+        return JdbcSourceConfig.builder()
+                .jdbcConnectionConfig(
+                        JdbcConnectionConfig.builder()
+                                .url("jdbc:mysql://localhost:3306/test")
+                                .driverName("com.mysql.cj.jdbc.Driver")
+                                .build())
+                .stringSplitStrategy(StringSplitStrategy.RANGE)
+                .build();
+    }
+
+    private static class CapturingFixedChunkSplitter extends 
FixedChunkSplitter {
+        private String sql;
+        private final Map<Integer, String> stringParameters = new HashMap<>();
+
+        private CapturingFixedChunkSplitter(JdbcSourceConfig config) {
+            super(config);
+        }
+
+        @Override
+        protected PreparedStatement createPreparedStatement(String sql) {
+            this.sql = sql;
+            InvocationHandler handler =
+                    (proxy, method, args) -> {
+                        if ("setString".equals(method.getName())) {
+                            stringParameters.put((Integer) args[0], (String) 
args[1]);
+                        }
+                        return null;
+                    };
+            return (PreparedStatement)
+                    Proxy.newProxyInstance(
+                            PreparedStatement.class.getClassLoader(),
+                            new Class<?>[] {PreparedStatement.class},
+                            handler);
+        }
+    }
 }
diff --git 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java
 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java
index 040a4563d9..8a43578b3d 100644
--- 
a/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java
+++ 
b/seatunnel-e2e/seatunnel-connector-v2-e2e/connector-jdbc-e2e/connector-jdbc-e2e-part-7/src/test/java/org/apache/seatunnel/connectors/seatunnel/jdbc/JdbcMysqlSplitIT.java
@@ -123,6 +123,7 @@ public class JdbcMysqlSplitIT extends TestSuiteBase 
implements TestResource {
                     + "    `c_mediumtext`           mediumtext,\n"
                     + "    `c_text`                 text,\n"
                     + "    `c_varchar`              varchar(255)          
DEFAULT NULL,\n"
+                    + "    `c_varchar_bin`          varchar(8) CHARACTER SET 
utf8mb4 COLLATE utf8mb4_bin DEFAULT NULL,\n"
                     + "    `c_json`                 json                  
DEFAULT NULL,\n"
                     + "    `c_longtext`             longtext,\n"
                     + "    `c_date`                 date                  
DEFAULT NULL,\n"
@@ -267,6 +268,7 @@ public class JdbcMysqlSplitIT extends TestSuiteBase 
implements TestResource {
                     "c_mediumtext",
                     "c_text",
                     "c_varchar",
+                    "c_varchar_bin",
                     "c_json",
                     "c_longtext",
                     "c_date",
@@ -328,6 +330,7 @@ public class JdbcMysqlSplitIT extends TestSuiteBase 
implements TestResource {
                                 String.format("f1_%s", i),
                                 String.format("f1_%s", i),
                                 String.format("f1_%s", i),
+                                String.format("key%05d", i),
                                 String.format("{\"aa\":\"bb_%s\"}", i),
                                 String.format("f1_%s", i),
                                 Date.valueOf(currentDate),
@@ -467,6 +470,22 @@ public class JdbcMysqlSplitIT extends TestSuiteBase 
implements TestResource {
         configMap.put("partition_column", "c_date");
         assertDateSplit(splitArray);
 
+        configMap.put("split.string-strategy", "none");
+        splitArray = getCheckedSplitArray(configMap, table, "c_varchar", 1);
+        assertStringSingleSplit(splitArray);
+
+        configMap.put("split.string-strategy", "hash");
+        splitArray = getCheckedSplitArray(configMap, table, "c_varchar", 11);
+        assertStringHashSplit(splitArray);
+
+        configMap.put("split.string-strategy", "range");
+        splitArray = getCheckedSplitArray(configMap, table, "c_varchar_bin", 
11);
+        assertStringRangeSplit(splitArray);
+
+        configMap.put("split.string-strategy", "auto");
+        splitArray = getCheckedSplitArray(configMap, table, "c_varchar_bin", 
11);
+        assertStringRangeSplit(splitArray);
+
         mySqlCatalog.close();
     }
 
@@ -535,6 +554,32 @@ public class JdbcMysqlSplitIT extends TestSuiteBase 
implements TestResource {
         }
     }
 
+    private void assertStringSingleSplit(JdbcSourceSplit[] splitArray) {
+        Assertions.assertEquals(1, splitArray.length);
+        Assertions.assertNull(splitArray[0].getSplitStart());
+        Assertions.assertNull(splitArray[0].getSplitEnd());
+    }
+
+    private void assertStringHashSplit(JdbcSourceSplit[] splitArray) {
+        Assertions.assertTrue(splitArray.length > 1);
+        for (JdbcSourceSplit split : splitArray) {
+            Assertions.assertTrue(split.getSplitStart() instanceof Integer);
+            Assertions.assertNull(split.getSplitEnd());
+        }
+    }
+
+    private void assertStringRangeSplit(JdbcSourceSplit[] splitArray) {
+        Assertions.assertTrue(splitArray.length > 1);
+        Assertions.assertNull(splitArray[0].getSplitStart());
+        for (int i = 0; i < splitArray.length; i++) {
+            if (i == splitArray.length - 1) {
+                Assertions.assertNull(splitArray[i].getSplitEnd());
+                continue;
+            }
+            Assertions.assertTrue(splitArray[i].getSplitEnd() instanceof 
String);
+        }
+    }
+
     @NotNull private DynamicChunkSplitter getDynamicChunkSplitter(Map<String, 
Object> configMap) {
         ReadonlyConfig readonlyConfig = ReadonlyConfig.fromMap(configMap);
         JdbcSourceConfig sourceConfig = JdbcSourceConfig.of(readonlyConfig);

Reply via email to