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

snuyanzin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/flink.git


The following commit(s) were added to refs/heads/master by this push:
     new 097f914d6f2 [FLINK-40207][table-runtime] Modernize 
BinaryStringDataTest with JUnit 5
097f914d6f2 is described below

commit 097f914d6f2c0e71da6d6c50d16681eabdf8e2ce
Author: Ramin Gharib <[email protected]>
AuthorDate: Mon Aug 31 21:00:16 2026 +0200

    [FLINK-40207][table-runtime] Modernize BinaryStringDataTest with JUnit 5
---
 .../flink/table/data/BinaryStringDataTest.java     | 1368 +++++++++++---------
 1 file changed, 763 insertions(+), 605 deletions(-)

diff --git 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/data/BinaryStringDataTest.java
 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/data/BinaryStringDataTest.java
index bfdba11789b..9f8c1db8629 100644
--- 
a/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/data/BinaryStringDataTest.java
+++ 
b/flink-table/flink-table-runtime/src/test/java/org/apache/flink/table/data/BinaryStringDataTest.java
@@ -24,18 +24,23 @@ import org.apache.flink.table.data.binary.BinaryStringData;
 import org.apache.flink.table.data.writer.BinaryRowWriter;
 import org.apache.flink.table.runtime.operators.sort.SortUtil;
 import org.apache.flink.table.runtime.util.StringUtf8Utils;
-import 
org.apache.flink.testutils.junit.extensions.parameterized.ParameterizedTestExtension;
-import org.apache.flink.testutils.junit.extensions.parameterized.Parameters;
 
-import org.junit.jupiter.api.TestTemplate;
-import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
 
-import java.io.UnsupportedEncodingException;
 import java.math.BigDecimal;
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
+import java.util.stream.Stream;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.apache.flink.table.data.binary.BinaryStringData.blankString;
@@ -59,49 +64,30 @@ import static 
org.apache.flink.table.data.binary.BinaryStringDataUtil.trimLeft;
 import static 
org.apache.flink.table.data.binary.BinaryStringDataUtil.trimRight;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.junit.jupiter.params.provider.Arguments.arguments;
 
 /**
  * Test of {@link BinaryStringData}.
  *
- * <p>Caution that you must construct a string by {@link #fromString} to cover 
all the test cases.
+ * <p>Tests that depend on the backing memory layout are parameterized over 
{@link Mode} and must
+ * build their strings through {@link #fromString(Mode, String)} to cover all 
layouts. Tests that
+ * are layout-independent are plain {@link Test}s.
  */
-@ExtendWith(ParameterizedTestExtension.class)
 class BinaryStringDataTest {
 
-    private BinaryStringData empty = fromString("");
-
-    private final Mode mode;
-
-    public BinaryStringDataTest(Mode mode) {
-        this.mode = mode;
-    }
-
-    @Parameters(name = "mode-{0}")
-    private static List<Mode> getVarSeg() {
-        return Arrays.asList(Mode.ONE_SEG, Mode.MULTI_SEGS, Mode.STRING, 
Mode.RANDOM);
-    }
-
-    private enum Mode {
+    /** The backing memory layout that {@link #fromString(Mode, String)} 
produces. */
+    enum Mode {
         ONE_SEG,
         MULTI_SEGS,
         STRING,
         RANDOM
     }
 
-    private BinaryStringData fromString(String str) {
+    private static BinaryStringData fromString(Mode mode, String str) {
         BinaryStringData string = BinaryStringData.fromString(str);
 
-        Mode mode = this.mode;
-
         if (mode == Mode.RANDOM) {
-            int rnd = new Random().nextInt(3);
-            if (rnd == 0) {
-                mode = Mode.ONE_SEG;
-            } else if (rnd == 1) {
-                mode = Mode.MULTI_SEGS;
-            } else if (rnd == 2) {
-                mode = Mode.STRING;
-            }
+            mode = new Mode[] {Mode.ONE_SEG, Mode.MULTI_SEGS, Mode.STRING}[new 
Random().nextInt(3)];
         }
 
         if (mode == Mode.STRING) {
@@ -130,19 +116,104 @@ class BinaryStringDataTest {
         }
     }
 
-    private void checkBasic(String str, int len) {
-        BinaryStringData s1 = fromString(str);
+    /** Cases for the string-to-decimal conversion, shared by the string and 
binary-row tests. */
+    private static final class DecimalCase {
+        private final String str;
+        private final int precision;
+        private final int scale;
+
+        private DecimalCase(String str, int precision, int scale) {
+            this.str = str;
+            this.precision = precision;
+            this.scale = scale;
+        }
+    }
+
+    private static final List<DecimalCase> DECIMAL_CASES =
+            List.of(
+                    new DecimalCase("12.345", 5, 3),
+                    new DecimalCase("-12.345", 5, 3),
+                    new DecimalCase("+12345", 5, 0),
+                    new DecimalCase("-12345", 5, 0),
+                    new DecimalCase("12345.", 5, 0),
+                    new DecimalCase("-12345.", 5, 0),
+                    new DecimalCase(".12345", 5, 5),
+                    new DecimalCase("-.12345", 5, 5),
+                    new DecimalCase("+12.345E3", 5, 0),
+                    new DecimalCase("-12.345e3", 5, 0),
+                    new DecimalCase("12.345e-3", 6, 6),
+                    new DecimalCase("-12.345E-3", 6, 6),
+                    new DecimalCase("12345E3", 8, 0),
+                    new DecimalCase("-12345e3", 8, 0),
+                    new DecimalCase("12345e-3", 5, 3),
+                    new DecimalCase("-12345E-3", 5, 3),
+                    new DecimalCase("+.12345E3", 5, 2),
+                    new DecimalCase("-.12345e3", 5, 2),
+                    new DecimalCase(".12345e-3", 8, 8),
+                    new DecimalCase("-.12345E-3", 8, 8),
+                    new DecimalCase("1234512345.1234", 18, 8),
+                    new DecimalCase("-1234512345.1234", 18, 8),
+                    new DecimalCase("1234512345.1234", 12, 2),
+                    new DecimalCase("-1234512345.1234", 12, 2),
+                    new DecimalCase("1234512345.1299", 12, 2),
+                    new DecimalCase("-1234512345.1299", 12, 2),
+                    new DecimalCase("999999999999999999", 18, 0),
+                    new DecimalCase("1234512345.1234512345", 20, 10),
+                    new DecimalCase("-1234512345.1234512345", 20, 10),
+                    new DecimalCase("1234512345.1234512345", 15, 5),
+                    new DecimalCase("-1234512345.1234512345", 15, 5),
+                    new DecimalCase("12345123451234512345E-10", 20, 10),
+                    new DecimalCase("-12345123451234512345E-10", 20, 10),
+                    new DecimalCase("12345123451234512345E-10", 15, 5),
+                    new DecimalCase("-12345123451234512345E-10", 15, 5),
+                    new DecimalCase("999999999999999999999", 21, 0),
+                    new DecimalCase("-999999999999999999999", 21, 0),
+                    new 
DecimalCase("0.00000000000000000000123456789123456789", 38, 38),
+                    new 
DecimalCase("-0.00000000000000000000123456789123456789", 38, 38),
+                    new 
DecimalCase("0.00000000000000000000123456789123456789", 29, 29),
+                    new 
DecimalCase("-0.00000000000000000000123456789123456789", 29, 29),
+                    new DecimalCase("123456789123E-27", 18, 18),
+                    new DecimalCase("-123456789123E-27", 18, 18),
+                    new DecimalCase("123456789999E-27", 18, 18),
+                    new DecimalCase("-123456789999E-27", 18, 18),
+                    new DecimalCase("123456789123456789E-36", 18, 18),
+                    new DecimalCase("-123456789123456789E-36", 18, 18),
+                    new DecimalCase("123456789999999999E-36", 18, 18),
+                    new DecimalCase("-123456789999999999E-36", 18, 18));
+
+    private static Stream<Arguments> decimalCases() {
+        return Arrays.stream(Mode.values())
+                .flatMap(
+                        mode ->
+                                DECIMAL_CASES.stream()
+                                        .map(c -> arguments(mode, c.str, 
c.precision, c.scale)));
+    }
+
+    /** Pairs every {@link Mode} with each of the given input strings. */
+    private static Stream<Arguments> withModes(String... inputs) {
+        return Arrays.stream(Mode.values())
+                .flatMap(mode -> Arrays.stream(inputs).map(input -> 
arguments(mode, input)));
+    }
+
+    /** Decodes UTF-8 bytes through one of the {@link 
StringUtf8Utils#decodeUTF8} entry points. */
+    @FunctionalInterface
+    private interface Utf8Decoder {
+        String decode(byte[] bytes);
+    }
+
+    private static void checkBasic(Mode mode, String str, int len) {
+        BinaryStringData s1 = fromString(mode, str);
         BinaryStringData s2 = fromBytes(str.getBytes(StandardCharsets.UTF_8));
-        assertThat(len).isEqualTo(s1.numChars());
-        assertThat(len).isEqualTo(s2.numChars());
+        assertThat(s1.numChars()).isEqualTo(len);
+        assertThat(s2.numChars()).isEqualTo(len);
 
-        assertThat(str).isEqualTo(s1.toString());
-        assertThat(str).isEqualTo(s2.toString());
+        assertThat(s1).hasToString(str);
+        assertThat(s2).hasToString(str);
         assertThat(s2).isEqualTo(s1);
 
         assertThat(s2.hashCode()).isEqualTo(s1.hashCode());
 
-        assertThat(s1.compareTo(s2)).isEqualTo(0);
+        assertThat(s1.compareTo(s2)).isZero();
 
         assertThat(s1.contains(s2)).isTrue();
         assertThat(s2.contains(s1)).isTrue();
@@ -150,617 +221,704 @@ class BinaryStringDataTest {
         assertThat(s1.endsWith(s1)).isTrue();
     }
 
-    @TestTemplate
-    void basicTest() {
-        checkBasic("", 0);
-        checkBasic(",", 1);
-        checkBasic("hello", 5);
-        checkBasic("hello world", 11);
-        checkBasic("Flink中文社区", 9);
-        checkBasic("中 文 社 区", 7);
-
-        checkBasic("¡", 1); // 2 bytes char
-        checkBasic("ку", 2); // 2 * 2 bytes chars
-        checkBasic("︽﹋%", 3); // 3 * 3 bytes chars
-        checkBasic("\uD83E\uDD19", 1); // 4 bytes char
-    }
+    @Nested
+    @DisplayName("Basics")
+    class Basics {
+
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void basicTest(Mode mode) {
+            checkBasic(mode, "", 0);
+            checkBasic(mode, ",", 1);
+            checkBasic(mode, "hello", 5);
+            checkBasic(mode, "hello world", 11);
+            checkBasic(mode, "Flink中文社区", 9);
+            checkBasic(mode, "中 文 社 区", 7);
+
+            checkBasic(mode, "¡", 1); // 2 bytes char
+            checkBasic(mode, "ку", 2); // 2 * 2 bytes chars
+            checkBasic(mode, "︽﹋%", 3); // 3 * 3 bytes chars
+            checkBasic(mode, "🤙", 1); // 4 bytes char
+        }
 
-    @TestTemplate
-    void emptyStringTest() {
-        assertThat(fromString("")).isEqualTo(empty);
-        assertThat(fromBytes(new byte[0])).isEqualTo(empty);
-        assertThat(empty.numChars()).isEqualTo(0);
-        assertThat(empty.getSizeInBytes()).isEqualTo(0);
-    }
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void emptyStringTest(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(fromString(mode, "")).isEqualTo(empty);
+            assertThat(fromBytes(new byte[0])).isEqualTo(empty);
+            assertThat(empty.numChars()).isZero();
+            assertThat(empty.getSizeInBytes()).isZero();
+        }
 
-    @TestTemplate
-    void compareTo() {
-        assertThat(fromString("   ").compareTo(blankString(3))).isEqualTo(0);
-        assertThat(fromString("").compareTo(fromString("a"))).isLessThan(0);
-        
assertThat(fromString("abc").compareTo(fromString("ABC"))).isGreaterThan(0);
-        
assertThat(fromString("abc0").compareTo(fromString("abc"))).isGreaterThan(0);
-        
assertThat(fromString("abcabcabc").compareTo(fromString("abcabcabc"))).isEqualTo(0);
-        
assertThat(fromString("aBcabcabc").compareTo(fromString("Abcabcabc"))).isGreaterThan(0);
-        
assertThat(fromString("Abcabcabc").compareTo(fromString("abcabcabC"))).isLessThan(0);
-        
assertThat(fromString("abcabcabc").compareTo(fromString("abcabcabC"))).isGreaterThan(0);
-
-        
assertThat(fromString("abc").compareTo(fromString("世界"))).isLessThan(0);
-        
assertThat(fromString("你好").compareTo(fromString("世界"))).isGreaterThan(0);
-        
assertThat(fromString("你好123").compareTo(fromString("你好122"))).isGreaterThan(0);
-
-        MemorySegment segment1 = 
MemorySegmentFactory.allocateUnpooledSegment(1024);
-        MemorySegment segment2 = 
MemorySegmentFactory.allocateUnpooledSegment(1024);
-        SortUtil.putStringNormalizedKey(fromString("abcabcabc"), segment1, 0, 
9);
-        SortUtil.putStringNormalizedKey(fromString("abcabcabC"), segment2, 0, 
9);
-        assertThat(segment1.compare(segment2, 0, 0, 9)).isGreaterThan(0);
-        SortUtil.putStringNormalizedKey(fromString("abcab"), segment1, 0, 9);
-        assertThat(segment1.compare(segment2, 0, 0, 9)).isLessThan(0);
-    }
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void testIsEmpty(Mode mode) {
+            assertThat(isEmpty(fromString(mode, ""))).isTrue();
+            assertThat(isEmpty(BinaryStringData.fromBytes(new byte[] 
{}))).isTrue();
+            assertThat(isEmpty(fromString(mode, "hello"))).isFalse();
+            
assertThat(isEmpty(BinaryStringData.fromBytes("hello".getBytes()))).isFalse();
+            assertThat(isEmpty(fromString(mode, "中文"))).isFalse();
+            
assertThat(isEmpty(BinaryStringData.fromBytes("中文".getBytes()))).isFalse();
+            assertThat(isEmpty(new BinaryStringData())).isTrue();
+        }
 
-    @TestTemplate
-    void testMultiSegments() {
-
-        // prepare
-        MemorySegment[] segments1 = new MemorySegment[2];
-        segments1[0] = MemorySegmentFactory.wrap(new byte[10]);
-        segments1[1] = MemorySegmentFactory.wrap(new byte[10]);
-        segments1[0].put(5, "abcde".getBytes(UTF_8), 0, 5);
-        segments1[1].put(0, "aaaaa".getBytes(UTF_8), 0, 5);
-
-        MemorySegment[] segments2 = new MemorySegment[2];
-        segments2[0] = MemorySegmentFactory.wrap(new byte[5]);
-        segments2[1] = MemorySegmentFactory.wrap(new byte[5]);
-        segments2[0].put(0, "abcde".getBytes(UTF_8), 0, 5);
-        segments2[1].put(0, "b".getBytes(UTF_8), 0, 1);
-
-        // test go ahead both
-        BinaryStringData binaryString1 = 
BinaryStringData.fromAddress(segments1, 5, 10);
-        BinaryStringData binaryString2 = 
BinaryStringData.fromAddress(segments2, 0, 6);
-        assertThat(binaryString1.toString()).isEqualTo("abcdeaaaaa");
-        assertThat(binaryString2.toString()).isEqualTo("abcdeb");
-        assertThat(binaryString1.compareTo(binaryString2)).isEqualTo(-1);
-
-        // test needCompare == len
-        binaryString1 = BinaryStringData.fromAddress(segments1, 5, 5);
-        binaryString2 = BinaryStringData.fromAddress(segments2, 0, 5);
-        assertThat(binaryString1.toString()).isEqualTo("abcde");
-        assertThat(binaryString2.toString()).isEqualTo("abcde");
-        assertThat(binaryString1.compareTo(binaryString2)).isEqualTo(0);
-
-        // test find the first segment of this string
-        binaryString1 = BinaryStringData.fromAddress(segments1, 10, 5);
-        binaryString2 = BinaryStringData.fromAddress(segments2, 0, 5);
-        assertThat(binaryString1.toString()).isEqualTo("aaaaa");
-        assertThat(binaryString2.toString()).isEqualTo("abcde");
-        assertThat(binaryString1.compareTo(binaryString2)).isEqualTo(-1);
-        assertThat(binaryString2.compareTo(binaryString1)).isEqualTo(1);
-
-        // test go ahead single
-        segments2 = new MemorySegment[] {MemorySegmentFactory.wrap(new 
byte[10])};
-        segments2[0].put(4, "abcdeb".getBytes(UTF_8), 0, 6);
-        binaryString1 = BinaryStringData.fromAddress(segments1, 5, 10);
-        binaryString2 = BinaryStringData.fromAddress(segments2, 4, 6);
-        assertThat(binaryString1.toString()).isEqualTo("abcdeaaaaa");
-        assertThat(binaryString2.toString()).isEqualTo("abcdeb");
-        assertThat(binaryString1.compareTo(binaryString2)).isEqualTo(-1);
-        assertThat(binaryString2.compareTo(binaryString1)).isEqualTo(1);
-    }
+        @Test
+        void testLazy() {
+            String javaStr = "haha";
+            BinaryStringData str = BinaryStringData.fromString(javaStr);
+            str.ensureMaterialized();
 
-    @TestTemplate
-    void concatTest() {
-        assertThat(concat()).isEqualTo(empty);
-        assertThat(concat((BinaryStringData) null)).isNull();
-        assertThat(concat(empty)).isEqualTo(empty);
-        assertThat(concat(fromString("ab"))).isEqualTo(fromString("ab"));
-        assertThat(concat(fromString("a"), 
fromString("b"))).isEqualTo(fromString("ab"));
-        assertThat(concat(fromString("a"), fromString("b"), fromString("c")))
-                .isEqualTo(fromString("abc"));
-        assertThat(concat(fromString("a"), null, fromString("c"))).isNull();
-        assertThat(concat(fromString("a"), null, null)).isNull();
-        assertThat(concat(null, null, null)).isNull();
-        assertThat(concat(fromString("数据"), 
fromString("砖头"))).isEqualTo(fromString("数据砖头"));
+            // check reference same.
+            assertThat(str.toString()).isSameAs(javaStr);
+        }
     }
 
-    @TestTemplate
-    void concatWsTest() {
-        // Returns empty if the separator is null
-        assertThat(concatWs(null, (BinaryStringData) null)).isNull();
-        assertThat(concatWs(null, fromString("a"))).isNull();
-
-        // If separator is null, concatWs should skip all null inputs and 
never return null.
-        BinaryStringData sep = fromString("哈哈");
-        assertThat(concatWs(sep, empty)).isEqualTo(empty);
-        assertThat(concatWs(sep, 
fromString("ab"))).isEqualTo(fromString("ab"));
-        assertThat(concatWs(sep, fromString("a"), 
fromString("b"))).isEqualTo(fromString("a哈哈b"));
-        assertThat(concatWs(sep, fromString("a"), fromString("b"), 
fromString("c")))
-                .isEqualTo(fromString("a哈哈b哈哈c"));
-        assertThat(concatWs(sep, fromString("a"), null, fromString("c")))
-                .isEqualTo(fromString("a哈哈c"));
-        assertThat(concatWs(sep, fromString("a"), null, 
null)).isEqualTo(fromString("a"));
-        assertThat(concatWs(sep, null, null, null)).isEqualTo(empty);
-        assertThat(concatWs(sep, fromString("数据"), fromString("砖头")))
-                .isEqualTo(fromString("数据哈哈砖头"));
-    }
+    @Nested
+    @DisplayName("Comparison")
+    class Comparison {
+
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void compareTo(Mode mode) {
+            assertThat(fromString(mode, "   
").compareTo(blankString(3))).isZero();
+            assertThat(fromString(mode, "").compareTo(fromString(mode, 
"a"))).isLessThan(0);
+            assertThat(fromString(mode, "abc").compareTo(fromString(mode, 
"ABC"))).isGreaterThan(0);
+            assertThat(fromString(mode, "abc0").compareTo(fromString(mode, 
"abc")))
+                    .isGreaterThan(0);
+            assertThat(fromString(mode, 
"abcabcabc").compareTo(fromString(mode, "abcabcabc")))
+                    .isZero();
+            assertThat(fromString(mode, 
"aBcabcabc").compareTo(fromString(mode, "Abcabcabc")))
+                    .isGreaterThan(0);
+            assertThat(fromString(mode, 
"Abcabcabc").compareTo(fromString(mode, "abcabcabC")))
+                    .isLessThan(0);
+            assertThat(fromString(mode, 
"abcabcabc").compareTo(fromString(mode, "abcabcabC")))
+                    .isGreaterThan(0);
+
+            assertThat(fromString(mode, "abc").compareTo(fromString(mode, 
"世界"))).isLessThan(0);
+            assertThat(fromString(mode, "你好").compareTo(fromString(mode, 
"世界"))).isGreaterThan(0);
+            assertThat(fromString(mode, "你好123").compareTo(fromString(mode, 
"你好122")))
+                    .isGreaterThan(0);
+
+            MemorySegment segment1 = 
MemorySegmentFactory.allocateUnpooledSegment(1024);
+            MemorySegment segment2 = 
MemorySegmentFactory.allocateUnpooledSegment(1024);
+            SortUtil.putStringNormalizedKey(fromString(mode, "abcabcabc"), 
segment1, 0, 9);
+            SortUtil.putStringNormalizedKey(fromString(mode, "abcabcabC"), 
segment2, 0, 9);
+            assertThat(segment1.compare(segment2, 0, 0, 9)).isGreaterThan(0);
+            SortUtil.putStringNormalizedKey(fromString(mode, "abcab"), 
segment1, 0, 9);
+            assertThat(segment1.compare(segment2, 0, 0, 9)).isLessThan(0);
+        }
 
-    @TestTemplate
-    void contains() {
-        assertThat(empty.contains(empty)).isTrue();
-        assertThat(fromString("hello").contains(fromString("ello"))).isTrue();
-        
assertThat(fromString("hello").contains(fromString("vello"))).isFalse();
-        
assertThat(fromString("hello").contains(fromString("hellooo"))).isFalse();
-        assertThat(fromString("大千世界").contains(fromString("千世界"))).isTrue();
-        assertThat(fromString("大千世界").contains(fromString("世千"))).isFalse();
-        assertThat(fromString("大千世界").contains(fromString("大千世界好"))).isFalse();
-    }
+        @Test
+        void testMultiSegments() {
+
+            // prepare
+            MemorySegment[] segments1 = new MemorySegment[2];
+            segments1[0] = MemorySegmentFactory.wrap(new byte[10]);
+            segments1[1] = MemorySegmentFactory.wrap(new byte[10]);
+            segments1[0].put(5, "abcde".getBytes(UTF_8), 0, 5);
+            segments1[1].put(0, "aaaaa".getBytes(UTF_8), 0, 5);
+
+            MemorySegment[] segments2 = new MemorySegment[2];
+            segments2[0] = MemorySegmentFactory.wrap(new byte[5]);
+            segments2[1] = MemorySegmentFactory.wrap(new byte[5]);
+            segments2[0].put(0, "abcde".getBytes(UTF_8), 0, 5);
+            segments2[1].put(0, "b".getBytes(UTF_8), 0, 1);
+
+            // test go ahead both
+            BinaryStringData binaryString1 = 
BinaryStringData.fromAddress(segments1, 5, 10);
+            BinaryStringData binaryString2 = 
BinaryStringData.fromAddress(segments2, 0, 6);
+            assertThat(binaryString1).hasToString("abcdeaaaaa");
+            assertThat(binaryString2).hasToString("abcdeb");
+            assertThat(binaryString1.compareTo(binaryString2)).isEqualTo(-1);
+
+            // test needCompare == len
+            binaryString1 = BinaryStringData.fromAddress(segments1, 5, 5);
+            binaryString2 = BinaryStringData.fromAddress(segments2, 0, 5);
+            assertThat(binaryString1).hasToString("abcde");
+            assertThat(binaryString2).hasToString("abcde");
+            assertThat(binaryString1.compareTo(binaryString2)).isZero();
+
+            // test find the first segment of this string
+            binaryString1 = BinaryStringData.fromAddress(segments1, 10, 5);
+            binaryString2 = BinaryStringData.fromAddress(segments2, 0, 5);
+            assertThat(binaryString1).hasToString("aaaaa");
+            assertThat(binaryString2).hasToString("abcde");
+            assertThat(binaryString1.compareTo(binaryString2)).isEqualTo(-1);
+            assertThat(binaryString2.compareTo(binaryString1)).isEqualTo(1);
+
+            // test go ahead single
+            segments2 = new MemorySegment[] {MemorySegmentFactory.wrap(new 
byte[10])};
+            segments2[0].put(4, "abcdeb".getBytes(UTF_8), 0, 6);
+            binaryString1 = BinaryStringData.fromAddress(segments1, 5, 10);
+            binaryString2 = BinaryStringData.fromAddress(segments2, 4, 6);
+            assertThat(binaryString1).hasToString("abcdeaaaaa");
+            assertThat(binaryString2).hasToString("abcdeb");
+            assertThat(binaryString1.compareTo(binaryString2)).isEqualTo(-1);
+            assertThat(binaryString2.compareTo(binaryString1)).isEqualTo(1);
+        }
 
-    @TestTemplate
-    void startsWith() {
-        assertThat(empty.startsWith(empty)).isTrue();
-        
assertThat(fromString("hello").startsWith(fromString("hell"))).isTrue();
-        
assertThat(fromString("hello").startsWith(fromString("ell"))).isFalse();
-        
assertThat(fromString("hello").startsWith(fromString("hellooo"))).isFalse();
-        assertThat(fromString("数据砖头").startsWith(fromString("数据"))).isTrue();
-        assertThat(fromString("大千世界").startsWith(fromString("千"))).isFalse();
-        
assertThat(fromString("大千世界").startsWith(fromString("大千世界好"))).isFalse();
-    }
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void testEmptyString(Mode mode) {
+            BinaryStringData str2 = fromString(mode, "hahahahah");
+            BinaryStringData str3;
+            MemorySegment[] segments = new MemorySegment[2];
+            segments[0] = MemorySegmentFactory.wrap(new byte[10]);
+            segments[1] = MemorySegmentFactory.wrap(new byte[10]);
+            str3 = BinaryStringData.fromAddress(segments, 15, 0);
 
-    @TestTemplate
-    void endsWith() {
-        assertThat(empty.endsWith(empty)).isTrue();
-        assertThat(fromString("hello").endsWith(fromString("ello"))).isTrue();
-        
assertThat(fromString("hello").endsWith(fromString("ellov"))).isFalse();
-        
assertThat(fromString("hello").endsWith(fromString("hhhello"))).isFalse();
-        assertThat(fromString("大千世界").endsWith(fromString("世界"))).isTrue();
-        assertThat(fromString("大千世界").endsWith(fromString("世"))).isFalse();
-        
assertThat(fromString("数据砖头").endsWith(fromString("我的数据砖头"))).isFalse();
-    }
+            
assertThat(BinaryStringData.EMPTY_UTF8.compareTo(str2)).isLessThan(0);
+            
assertThat(str2.compareTo(BinaryStringData.EMPTY_UTF8)).isGreaterThan(0);
 
-    @TestTemplate
-    void substring() {
-        assertThat(fromString("hello").substring(0, 0)).isEqualTo(empty);
-        assertThat(fromString("hello").substring(1, 
3)).isEqualTo(fromString("el"));
-        assertThat(fromString("数据砖头").substring(0, 
1)).isEqualTo(fromString("数"));
-        assertThat(fromString("数据砖头").substring(1, 
3)).isEqualTo(fromString("据砖"));
-        assertThat(fromString("数据砖头").substring(3, 
5)).isEqualTo(fromString("头"));
-        assertThat(fromString("ߵ梷").substring(0, 
2)).isEqualTo(fromString("ߵ梷"));
-    }
+            assertThat(BinaryStringData.EMPTY_UTF8.compareTo(str3)).isZero();
+            assertThat(str3.compareTo(BinaryStringData.EMPTY_UTF8)).isZero();
 
-    @TestTemplate
-    void trims() {
-        assertThat(fromString("1").trim()).isEqualTo(fromString("1"));
-
-        assertThat(fromString("  hello 
").trim()).isEqualTo(fromString("hello"));
-        assertThat(trimLeft(fromString("  hello 
"))).isEqualTo(fromString("hello "));
-        assertThat(trimRight(fromString("  hello "))).isEqualTo(fromString("  
hello"));
-
-        assertThat(trim(fromString("  hello "), false, false, fromString(" ")))
-                .isEqualTo(fromString("  hello "));
-        assertThat(trim(fromString("  hello "), true, true, fromString(" ")))
-                .isEqualTo(fromString("hello"));
-        assertThat(trim(fromString("  hello "), true, false, fromString(" ")))
-                .isEqualTo(fromString("hello "));
-        assertThat(trim(fromString("  hello "), false, true, fromString(" ")))
-                .isEqualTo(fromString("  hello"));
-        assertThat(trim(fromString("xxxhellox"), true, true, fromString("x")))
-                .isEqualTo(fromString("hello"));
-
-        assertThat(trim(fromString("xxxhellox"), 
fromString("xoh"))).isEqualTo(fromString("ell"));
-
-        assertThat(trimLeft(fromString("xxxhellox"), fromString("xoh")))
-                .isEqualTo(fromString("ellox"));
-
-        assertThat(trimRight(fromString("xxxhellox"), fromString("xoh")))
-                .isEqualTo(fromString("xxxhell"));
-
-        assertThat(empty.trim()).isEqualTo(empty);
-        assertThat(fromString("  ").trim()).isEqualTo(empty);
-        assertThat(trimLeft(fromString("  "))).isEqualTo(empty);
-        assertThat(trimRight(fromString("  "))).isEqualTo(empty);
-
-        assertThat(fromString("  数据砖头 ").trim()).isEqualTo(fromString("数据砖头"));
-        assertThat(trimLeft(fromString("  数据砖头 "))).isEqualTo(fromString("数据砖头 
"));
-        assertThat(trimRight(fromString("  数据砖头 "))).isEqualTo(fromString("  
数据砖头"));
-
-        assertThat(fromString("数据砖头").trim()).isEqualTo(fromString("数据砖头"));
-        assertThat(trimLeft(fromString("数据砖头"))).isEqualTo(fromString("数据砖头"));
-        
assertThat(trimRight(fromString("数据砖头"))).isEqualTo(fromString("数据砖头"));
-
-        assertThat(trim(fromString("年年岁岁, 岁岁年年"), fromString("年岁 
"))).isEqualTo(fromString(","));
-        assertThat(trimLeft(fromString("年年岁岁, 岁岁年年"), fromString("年岁 ")))
-                .isEqualTo(fromString(", 岁岁年年"));
-        assertThat(trimRight(fromString("年年岁岁, 岁岁年年"), fromString("年岁 ")))
-                .isEqualTo(fromString("年年岁岁,"));
-
-        char[] charsLessThan0x20 = new char[10];
-        Arrays.fill(charsLessThan0x20, (char) (' ' - 1));
-        String stringStartingWithSpace =
-                new String(charsLessThan0x20) + "hello" + new 
String(charsLessThan0x20);
-        assertThat(fromString(stringStartingWithSpace).trim())
-                .isEqualTo(fromString(stringStartingWithSpace));
-        assertThat(trimLeft(fromString(stringStartingWithSpace)))
-                .isEqualTo(fromString(stringStartingWithSpace));
-        assertThat(trimRight(fromString(stringStartingWithSpace)))
-                .isEqualTo(fromString(stringStartingWithSpace));
-    }
+            assertThat(str2).isNotEqualTo(BinaryStringData.EMPTY_UTF8);
+            assertThat(BinaryStringData.EMPTY_UTF8).isNotEqualTo(str2);
 
-    @TestTemplate
-    void testSqlSubstring() {
-        assertThat(substringSQL(fromString("hello"), 
2)).isEqualTo(fromString("ello"));
-        assertThat(substringSQL(fromString("hello"), 2, 
3)).isEqualTo(fromString("ell"));
-        assertThat(substringSQL(empty, 2, 3)).isEqualTo(empty);
-        assertThat(substringSQL(fromString("hello"), 0, -1)).isNull();
-        assertThat(substringSQL(fromString("hello"), 10)).isEqualTo(empty);
-        assertThat(substringSQL(fromString("hello"), 0, 
3)).isEqualTo(fromString("hel"));
-        assertThat(substringSQL(fromString("hello"), -2, 
3)).isEqualTo(fromString("lo"));
-        assertThat(substringSQL(fromString("hello"), -100, 
3)).isEqualTo(empty);
+            assertThat(str3).isEqualTo(BinaryStringData.EMPTY_UTF8);
+            assertThat(BinaryStringData.EMPTY_UTF8).isEqualTo(str3);
+        }
     }
 
-    @TestTemplate
-    void reverseTest() {
-        
assertThat(reverse(fromString("hello"))).isEqualTo(fromString("olleh"));
-        assertThat(reverse(fromString("中国"))).isEqualTo(fromString("国中"));
-        assertThat(reverse(fromString("hello, 中国"))).isEqualTo(fromString("国中 
,olleh"));
-        assertThat(reverse(empty)).isEqualTo(empty);
-    }
+    @Nested
+    @DisplayName("Search")
+    class Search {
+
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void contains(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(empty.contains(empty)).isTrue();
+            assertThat(fromString(mode, "hello").contains(fromString(mode, 
"ello"))).isTrue();
+            assertThat(fromString(mode, "hello").contains(fromString(mode, 
"vello"))).isFalse();
+            assertThat(fromString(mode, "hello").contains(fromString(mode, 
"hellooo"))).isFalse();
+            assertThat(fromString(mode, "大千世界").contains(fromString(mode, 
"千世界"))).isTrue();
+            assertThat(fromString(mode, "大千世界").contains(fromString(mode, 
"世千"))).isFalse();
+            assertThat(fromString(mode, "大千世界").contains(fromString(mode, 
"大千世界好"))).isFalse();
+        }
 
-    @TestTemplate
-    void indexOf() {
-        assertThat(empty.indexOf(empty, 0)).isEqualTo(0);
-        assertThat(empty.indexOf(fromString("l"), 0)).isEqualTo(-1);
-        assertThat(fromString("hello").indexOf(empty, 0)).isEqualTo(0);
-        assertThat(fromString("hello").indexOf(fromString("l"), 
0)).isEqualTo(2);
-        assertThat(fromString("hello").indexOf(fromString("l"), 
3)).isEqualTo(3);
-        assertThat(fromString("hello").indexOf(fromString("a"), 
0)).isEqualTo(-1);
-        assertThat(fromString("hello").indexOf(fromString("ll"), 
0)).isEqualTo(2);
-        assertThat(fromString("hello").indexOf(fromString("ll"), 
4)).isEqualTo(-1);
-        assertThat(fromString("数据砖头").indexOf(fromString("据砖"), 
0)).isEqualTo(1);
-        assertThat(fromString("数据砖头").indexOf(fromString("数"), 
3)).isEqualTo(-1);
-        assertThat(fromString("数据砖头").indexOf(fromString("数"), 
0)).isEqualTo(0);
-        assertThat(fromString("数据砖头").indexOf(fromString("头"), 
0)).isEqualTo(3);
-    }
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void startsWith(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(empty.startsWith(empty)).isTrue();
+            assertThat(fromString(mode, "hello").startsWith(fromString(mode, 
"hell"))).isTrue();
+            assertThat(fromString(mode, "hello").startsWith(fromString(mode, 
"ell"))).isFalse();
+            assertThat(fromString(mode, "hello").startsWith(fromString(mode, 
"hellooo"))).isFalse();
+            assertThat(fromString(mode, "数据砖头").startsWith(fromString(mode, 
"数据"))).isTrue();
+            assertThat(fromString(mode, "大千世界").startsWith(fromString(mode, 
"千"))).isFalse();
+            assertThat(fromString(mode, "大千世界").startsWith(fromString(mode, 
"大千世界好"))).isFalse();
+        }
 
-    @TestTemplate
-    void testToNumeric() {
-        // Test to integer.
-        assertThat(toByte(fromString("123"))).isEqualTo(Byte.parseByte("123"));
-        
assertThat(toByte(fromString("+123"))).isEqualTo(Byte.parseByte("123"));
-        
assertThat(toByte(fromString("-123"))).isEqualTo(Byte.parseByte("-123"));
-
-        
assertThat(toShort(fromString("123"))).isEqualTo(Short.parseShort("123"));
-        
assertThat(toShort(fromString("+123"))).isEqualTo(Short.parseShort("123"));
-        
assertThat(toShort(fromString("-123"))).isEqualTo(Short.parseShort("-123"));
-
-        
assertThat(toInt(fromString("123"))).isEqualTo(Integer.parseInt("123"));
-        
assertThat(toInt(fromString("+123"))).isEqualTo(Integer.parseInt("123"));
-        
assertThat(toInt(fromString("-123"))).isEqualTo(Integer.parseInt("-123"));
-
-        
assertThat(toLong(fromString("1234567890"))).isEqualTo(Long.parseLong("1234567890"));
-        
assertThat(toLong(fromString("+1234567890"))).isEqualTo(Long.parseLong("+1234567890"));
-        
assertThat(toLong(fromString("-1234567890"))).isEqualTo(Long.parseLong("-1234567890"));
-
-        // Test decimal string to integer.
-        
assertThat(toInt(fromString("123.456789"))).isEqualTo(Integer.parseInt("123"));
-        
assertThat(toLong(fromString("123.456789"))).isEqualTo(Long.parseLong("123"));
-
-        // Test negative cases.
-        assertThatThrownBy(() -> toInt(fromString("1a3.456789")))
-                .isInstanceOf(NumberFormatException.class);
-        assertThatThrownBy(() -> toInt(fromString("123.a56789")))
-                .isInstanceOf(NumberFormatException.class);
-
-        // Approximate numerics accept case-insensitive and abbreviated 
special values.
-        for (String infinity : new String[] {"Infinity", "infinity", "INF", 
"inf", "+inf"}) {
-            
assertThat(toFloat(fromString(infinity))).isEqualTo(Float.POSITIVE_INFINITY);
-            
assertThat(toDouble(fromString(infinity))).isEqualTo(Double.POSITIVE_INFINITY);
-        }
-        for (String negInfinity : new String[] {"-Infinity", "-infinity", 
"-INF", "  -inf  "}) {
-            
assertThat(toFloat(fromString(negInfinity))).isEqualTo(Float.NEGATIVE_INFINITY);
-            
assertThat(toDouble(fromString(negInfinity))).isEqualTo(Double.NEGATIVE_INFINITY);
-        }
-        for (String nan : new String[] {"NaN", "nan", "NAN"}) {
-            assertThat(toFloat(fromString(nan))).isNaN();
-            assertThat(toDouble(fromString(nan))).isNaN();
-        }
-        // Ordinary numbers are unaffected and signed NaN stays invalid.
-        assertThat(toDouble(fromString("1.5"))).isEqualTo(1.5);
-        assertThatThrownBy(() -> toFloat(fromString("-nan")))
-                .isInstanceOf(NumberFormatException.class);
-        // Only the trimmed token matches: surrounding whitespace is allowed, 
interior is not.
-        assertThat(toDouble(fromString("  -infinity  
"))).isEqualTo(Double.NEGATIVE_INFINITY);
-        assertThatThrownBy(() -> toFloat(fromString("-   Infinity")))
-                .isInstanceOf(NumberFormatException.class);
-
-        // Test composite in BinaryRowData.
-        BinaryRowData row = new BinaryRowData(20);
-        BinaryRowWriter writer = new BinaryRowWriter(row);
-        writer.writeString(0, BinaryStringData.fromString("1"));
-        writer.writeString(1, BinaryStringData.fromString("123"));
-        writer.writeString(2, BinaryStringData.fromString("12345"));
-        writer.writeString(3, BinaryStringData.fromString("123456789"));
-        writer.complete();
-
-        assertThat(toByte(((BinaryStringData) 
row.getString(0)))).isEqualTo(Byte.parseByte("1"));
-        assertThat(toShort(((BinaryStringData) row.getString(1))))
-                .isEqualTo(Short.parseShort("123"));
-        assertThat(toInt(((BinaryStringData) row.getString(2))))
-                .isEqualTo(Integer.parseInt("12345"));
-        assertThat(toLong(((BinaryStringData) row.getString(3))))
-                .isEqualTo(Long.parseLong("123456789"));
-    }
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void endsWith(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(empty.endsWith(empty)).isTrue();
+            assertThat(fromString(mode, "hello").endsWith(fromString(mode, 
"ello"))).isTrue();
+            assertThat(fromString(mode, "hello").endsWith(fromString(mode, 
"ellov"))).isFalse();
+            assertThat(fromString(mode, "hello").endsWith(fromString(mode, 
"hhhello"))).isFalse();
+            assertThat(fromString(mode, "大千世界").endsWith(fromString(mode, 
"世界"))).isTrue();
+            assertThat(fromString(mode, "大千世界").endsWith(fromString(mode, 
"世"))).isFalse();
+            assertThat(fromString(mode, "数据砖头").endsWith(fromString(mode, 
"我的数据砖头"))).isFalse();
+        }
 
-    @TestTemplate
-    void testToUpperLowerCase() {
-        
assertThat(fromString("我是中国人").toLowerCase()).isEqualTo(fromString("我是中国人"));
-        
assertThat(fromString("我是中国人").toUpperCase()).isEqualTo(fromString("我是中国人"));
-
-        
assertThat(fromString("aBcDeFg").toLowerCase()).isEqualTo(fromString("abcdefg"));
-        
assertThat(fromString("aBcDeFg").toUpperCase()).isEqualTo(fromString("ABCDEFG"));
-
-        
assertThat(fromString("!@#$%^*").toLowerCase()).isEqualTo(fromString("!@#$%^*"));
-        
assertThat(fromString("!@#$%^*").toLowerCase()).isEqualTo(fromString("!@#$%^*"));
-        // Test composite in BinaryRowData.
-        BinaryRowData row = new BinaryRowData(20);
-        BinaryRowWriter writer = new BinaryRowWriter(row);
-        writer.writeString(0, BinaryStringData.fromString("a"));
-        writer.writeString(1, BinaryStringData.fromString("我是中国人"));
-        writer.writeString(3, BinaryStringData.fromString("aBcDeFg"));
-        writer.writeString(5, BinaryStringData.fromString("!@#$%^*"));
-        writer.complete();
-
-        assertThat(((BinaryStringData) 
row.getString(0)).toUpperCase()).isEqualTo(fromString("A"));
-        assertThat(((BinaryStringData) row.getString(1)).toUpperCase())
-                .isEqualTo(fromString("我是中国人"));
-        assertThat(((BinaryStringData) row.getString(1)).toLowerCase())
-                .isEqualTo(fromString("我是中国人"));
-        assertThat(((BinaryStringData) row.getString(3)).toUpperCase())
-                .isEqualTo(fromString("ABCDEFG"));
-        assertThat(((BinaryStringData) row.getString(3)).toLowerCase())
-                .isEqualTo(fromString("abcdefg"));
-        assertThat(((BinaryStringData) row.getString(5)).toUpperCase())
-                .isEqualTo(fromString("!@#$%^*"));
-        assertThat(((BinaryStringData) row.getString(5)).toLowerCase())
-                .isEqualTo(fromString("!@#$%^*"));
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void indexOf(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(empty.indexOf(empty, 0)).isZero();
+            assertThat(empty.indexOf(fromString(mode, "l"), 0)).isEqualTo(-1);
+            assertThat(fromString(mode, "hello").indexOf(empty, 0)).isZero();
+            assertThat(fromString(mode, "hello").indexOf(fromString(mode, 
"l"), 0)).isEqualTo(2);
+            assertThat(fromString(mode, "hello").indexOf(fromString(mode, 
"l"), 3)).isEqualTo(3);
+            assertThat(fromString(mode, "hello").indexOf(fromString(mode, 
"a"), 0)).isEqualTo(-1);
+            assertThat(fromString(mode, "hello").indexOf(fromString(mode, 
"ll"), 0)).isEqualTo(2);
+            assertThat(fromString(mode, "hello").indexOf(fromString(mode, 
"ll"), 4)).isEqualTo(-1);
+            assertThat(fromString(mode, "数据砖头").indexOf(fromString(mode, 
"据砖"), 0)).isEqualTo(1);
+            assertThat(fromString(mode, "数据砖头").indexOf(fromString(mode, "数"), 
3)).isEqualTo(-1);
+            assertThat(fromString(mode, "数据砖头").indexOf(fromString(mode, "数"), 
0)).isZero();
+            assertThat(fromString(mode, "数据砖头").indexOf(fromString(mode, "头"), 
0)).isEqualTo(3);
+        }
     }
 
-    @TestTemplate
-    void testToDecimal() {
-        class DecimalTestData {
-            private String str;
-            private int precision, scale;
+    @Nested
+    @DisplayName("Manipulation")
+    class Manipulation {
+
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void concatTest(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(concat()).isEqualTo(empty);
+            assertThat(concat((BinaryStringData) null)).isNull();
+            assertThat(concat(empty)).isEqualTo(empty);
+            assertThat(concat(fromString(mode, 
"ab"))).isEqualTo(fromString(mode, "ab"));
+            assertThat(concat(fromString(mode, "a"), fromString(mode, "b")))
+                    .isEqualTo(fromString(mode, "ab"));
+            assertThat(concat(fromString(mode, "a"), fromString(mode, "b"), 
fromString(mode, "c")))
+                    .isEqualTo(fromString(mode, "abc"));
+            assertThat(concat(fromString(mode, "a"), null, fromString(mode, 
"c"))).isNull();
+            assertThat(concat(fromString(mode, "a"), null, null)).isNull();
+            assertThat(concat(null, null, null)).isNull();
+            assertThat(concat(fromString(mode, "数据"), fromString(mode, "砖头")))
+                    .isEqualTo(fromString(mode, "数据砖头"));
+        }
 
-            private DecimalTestData(String str, int precision, int scale) {
-                this.str = str;
-                this.precision = precision;
-                this.scale = scale;
-            }
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void concatWsTest(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            // Returns empty if the separator is null
+            assertThat(concatWs(null, (BinaryStringData) null)).isNull();
+            assertThat(concatWs(null, fromString(mode, "a"))).isNull();
+
+            // If separator is null, concatWs should skip all null inputs and 
never return null.
+            BinaryStringData sep = fromString(mode, "哈哈");
+            assertThat(concatWs(sep, empty)).isEqualTo(empty);
+            assertThat(concatWs(sep, fromString(mode, 
"ab"))).isEqualTo(fromString(mode, "ab"));
+            assertThat(concatWs(sep, fromString(mode, "a"), fromString(mode, 
"b")))
+                    .isEqualTo(fromString(mode, "a哈哈b"));
+            assertThat(
+                            concatWs(
+                                    sep,
+                                    fromString(mode, "a"),
+                                    fromString(mode, "b"),
+                                    fromString(mode, "c")))
+                    .isEqualTo(fromString(mode, "a哈哈b哈哈c"));
+            assertThat(concatWs(sep, fromString(mode, "a"), null, 
fromString(mode, "c")))
+                    .isEqualTo(fromString(mode, "a哈哈c"));
+            assertThat(concatWs(sep, fromString(mode, "a"), null, null))
+                    .isEqualTo(fromString(mode, "a"));
+            assertThat(concatWs(sep, null, null, null)).isEqualTo(empty);
+            assertThat(concatWs(sep, fromString(mode, "数据"), fromString(mode, 
"砖头")))
+                    .isEqualTo(fromString(mode, "数据哈哈砖头"));
         }
 
-        DecimalTestData[] data = {
-            new DecimalTestData("12.345", 5, 3),
-            new DecimalTestData("-12.345", 5, 3),
-            new DecimalTestData("+12345", 5, 0),
-            new DecimalTestData("-12345", 5, 0),
-            new DecimalTestData("12345.", 5, 0),
-            new DecimalTestData("-12345.", 5, 0),
-            new DecimalTestData(".12345", 5, 5),
-            new DecimalTestData("-.12345", 5, 5),
-            new DecimalTestData("+12.345E3", 5, 0),
-            new DecimalTestData("-12.345e3", 5, 0),
-            new DecimalTestData("12.345e-3", 6, 6),
-            new DecimalTestData("-12.345E-3", 6, 6),
-            new DecimalTestData("12345E3", 8, 0),
-            new DecimalTestData("-12345e3", 8, 0),
-            new DecimalTestData("12345e-3", 5, 3),
-            new DecimalTestData("-12345E-3", 5, 3),
-            new DecimalTestData("+.12345E3", 5, 2),
-            new DecimalTestData("-.12345e3", 5, 2),
-            new DecimalTestData(".12345e-3", 8, 8),
-            new DecimalTestData("-.12345E-3", 8, 8),
-            new DecimalTestData("1234512345.1234", 18, 8),
-            new DecimalTestData("-1234512345.1234", 18, 8),
-            new DecimalTestData("1234512345.1234", 12, 2),
-            new DecimalTestData("-1234512345.1234", 12, 2),
-            new DecimalTestData("1234512345.1299", 12, 2),
-            new DecimalTestData("-1234512345.1299", 12, 2),
-            new DecimalTestData("999999999999999999", 18, 0),
-            new DecimalTestData("1234512345.1234512345", 20, 10),
-            new DecimalTestData("-1234512345.1234512345", 20, 10),
-            new DecimalTestData("1234512345.1234512345", 15, 5),
-            new DecimalTestData("-1234512345.1234512345", 15, 5),
-            new DecimalTestData("12345123451234512345E-10", 20, 10),
-            new DecimalTestData("-12345123451234512345E-10", 20, 10),
-            new DecimalTestData("12345123451234512345E-10", 15, 5),
-            new DecimalTestData("-12345123451234512345E-10", 15, 5),
-            new DecimalTestData("999999999999999999999", 21, 0),
-            new DecimalTestData("-999999999999999999999", 21, 0),
-            new DecimalTestData("0.00000000000000000000123456789123456789", 
38, 38),
-            new DecimalTestData("-0.00000000000000000000123456789123456789", 
38, 38),
-            new DecimalTestData("0.00000000000000000000123456789123456789", 
29, 29),
-            new DecimalTestData("-0.00000000000000000000123456789123456789", 
29, 29),
-            new DecimalTestData("123456789123E-27", 18, 18),
-            new DecimalTestData("-123456789123E-27", 18, 18),
-            new DecimalTestData("123456789999E-27", 18, 18),
-            new DecimalTestData("-123456789999E-27", 18, 18),
-            new DecimalTestData("123456789123456789E-36", 18, 18),
-            new DecimalTestData("-123456789123456789E-36", 18, 18),
-            new DecimalTestData("123456789999999999E-36", 18, 18),
-            new DecimalTestData("-123456789999999999E-36", 18, 18)
-        };
-
-        for (DecimalTestData d : data) {
-            assertThat(toDecimal(fromString(d.str), d.precision, d.scale))
-                    .isEqualTo(
-                            DecimalData.fromBigDecimal(
-                                    new BigDecimal(d.str), d.precision, 
d.scale));
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void substring(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(fromString(mode, "hello").substring(0, 
0)).isEqualTo(empty);
+            assertThat(fromString(mode, "hello").substring(1, 
3)).isEqualTo(fromString(mode, "el"));
+            assertThat(fromString(mode, "数据砖头").substring(0, 
1)).isEqualTo(fromString(mode, "数"));
+            assertThat(fromString(mode, "数据砖头").substring(1, 
3)).isEqualTo(fromString(mode, "据砖"));
+            assertThat(fromString(mode, "数据砖头").substring(3, 
5)).isEqualTo(fromString(mode, "头"));
+            assertThat(fromString(mode, "ߵ梷").substring(0, 
2)).isEqualTo(fromString(mode, "ߵ梷"));
+        }
+
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void trims(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(fromString(mode, 
"1").trim()).isEqualTo(fromString(mode, "1"));
+
+            assertThat(fromString(mode, "  hello 
").trim()).isEqualTo(fromString(mode, "hello"));
+            assertThat(trimLeft(fromString(mode, "  hello ")))
+                    .isEqualTo(fromString(mode, "hello "));
+            assertThat(trimRight(fromString(mode, "  hello ")))
+                    .isEqualTo(fromString(mode, "  hello"));
+
+            assertThat(trim(fromString(mode, "  hello "), false, false, 
fromString(mode, " ")))
+                    .isEqualTo(fromString(mode, "  hello "));
+            assertThat(trim(fromString(mode, "  hello "), true, true, 
fromString(mode, " ")))
+                    .isEqualTo(fromString(mode, "hello"));
+            assertThat(trim(fromString(mode, "  hello "), true, false, 
fromString(mode, " ")))
+                    .isEqualTo(fromString(mode, "hello "));
+            assertThat(trim(fromString(mode, "  hello "), false, true, 
fromString(mode, " ")))
+                    .isEqualTo(fromString(mode, "  hello"));
+            assertThat(trim(fromString(mode, "xxxhellox"), true, true, 
fromString(mode, "x")))
+                    .isEqualTo(fromString(mode, "hello"));
+
+            assertThat(trim(fromString(mode, "xxxhellox"), fromString(mode, 
"xoh")))
+                    .isEqualTo(fromString(mode, "ell"));
+
+            assertThat(trimLeft(fromString(mode, "xxxhellox"), 
fromString(mode, "xoh")))
+                    .isEqualTo(fromString(mode, "ellox"));
+
+            assertThat(trimRight(fromString(mode, "xxxhellox"), 
fromString(mode, "xoh")))
+                    .isEqualTo(fromString(mode, "xxxhell"));
+
+            assertThat(empty.trim()).isEqualTo(empty);
+            assertThat(fromString(mode, "  ").trim()).isEqualTo(empty);
+            assertThat(trimLeft(fromString(mode, "  "))).isEqualTo(empty);
+            assertThat(trimRight(fromString(mode, "  "))).isEqualTo(empty);
+
+            assertThat(fromString(mode, "  数据砖头 
").trim()).isEqualTo(fromString(mode, "数据砖头"));
+            assertThat(trimLeft(fromString(mode, "  数据砖头 
"))).isEqualTo(fromString(mode, "数据砖头 "));
+            assertThat(trimRight(fromString(mode, "  数据砖头 ")))
+                    .isEqualTo(fromString(mode, "  数据砖头"));
+
+            assertThat(fromString(mode, 
"数据砖头").trim()).isEqualTo(fromString(mode, "数据砖头"));
+            assertThat(trimLeft(fromString(mode, 
"数据砖头"))).isEqualTo(fromString(mode, "数据砖头"));
+            assertThat(trimRight(fromString(mode, 
"数据砖头"))).isEqualTo(fromString(mode, "数据砖头"));
+
+            assertThat(trim(fromString(mode, "年年岁岁, 岁岁年年"), fromString(mode, 
"年岁 ")))
+                    .isEqualTo(fromString(mode, ","));
+            assertThat(trimLeft(fromString(mode, "年年岁岁, 岁岁年年"), 
fromString(mode, "年岁 ")))
+                    .isEqualTo(fromString(mode, ", 岁岁年年"));
+            assertThat(trimRight(fromString(mode, "年年岁岁, 岁岁年年"), 
fromString(mode, "年岁 ")))
+                    .isEqualTo(fromString(mode, "年年岁岁,"));
+
+            char[] charsLessThan0x20 = new char[10];
+            Arrays.fill(charsLessThan0x20, (char) (' ' - 1));
+            String stringStartingWithSpace =
+                    new String(charsLessThan0x20) + "hello" + new 
String(charsLessThan0x20);
+            assertThat(fromString(mode, stringStartingWithSpace).trim())
+                    .isEqualTo(fromString(mode, stringStartingWithSpace));
+            assertThat(trimLeft(fromString(mode, stringStartingWithSpace)))
+                    .isEqualTo(fromString(mode, stringStartingWithSpace));
+            assertThat(trimRight(fromString(mode, stringStartingWithSpace)))
+                    .isEqualTo(fromString(mode, stringStartingWithSpace));
+        }
+
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void testSqlSubstring(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(substringSQL(fromString(mode, "hello"), 2))
+                    .isEqualTo(fromString(mode, "ello"));
+            assertThat(substringSQL(fromString(mode, "hello"), 2, 3))
+                    .isEqualTo(fromString(mode, "ell"));
+            assertThat(substringSQL(empty, 2, 3)).isEqualTo(empty);
+            assertThat(substringSQL(fromString(mode, "hello"), 0, 
-1)).isNull();
+            assertThat(substringSQL(fromString(mode, "hello"), 
10)).isEqualTo(empty);
+            assertThat(substringSQL(fromString(mode, "hello"), 0, 3))
+                    .isEqualTo(fromString(mode, "hel"));
+            assertThat(substringSQL(fromString(mode, "hello"), -2, 3))
+                    .isEqualTo(fromString(mode, "lo"));
+            assertThat(substringSQL(fromString(mode, "hello"), -100, 
3)).isEqualTo(empty);
         }
 
-        BinaryRowData row = new BinaryRowData(data.length);
-        BinaryRowWriter writer = new BinaryRowWriter(row);
-        for (int i = 0; i < data.length; i++) {
-            writer.writeString(i, BinaryStringData.fromString(data[i].str));
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void reverseTest(Mode mode) {
+            BinaryStringData empty = fromString(mode, "");
+            assertThat(reverse(fromString(mode, 
"hello"))).isEqualTo(fromString(mode, "olleh"));
+            assertThat(reverse(fromString(mode, 
"中国"))).isEqualTo(fromString(mode, "国中"));
+            assertThat(reverse(fromString(mode, "hello, 中国")))
+                    .isEqualTo(fromString(mode, "国中 ,olleh"));
+            assertThat(reverse(empty)).isEqualTo(empty);
         }
-        writer.complete();
-        for (int i = 0; i < data.length; i++) {
-            DecimalTestData d = data[i];
-            assertThat(toDecimal((BinaryStringData) row.getString(i), 
d.precision, d.scale))
+
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void testSplit(Mode mode) {
+            assertThat(
+                            splitByWholeSeparatorPreserveAllTokens(
+                                    fromString(mode, ""), fromString(mode, 
"")))
+                    .isEqualTo(EMPTY_STRING_ARRAY);
+            assertThat(splitByWholeSeparatorPreserveAllTokens(fromString(mode, 
"ab de fg"), null))
+                    .isEqualTo(
+                            new BinaryStringData[] {
+                                fromString(mode, "ab"),
+                                fromString(mode, "de"),
+                                fromString(mode, "fg")
+                            });
+            assertThat(splitByWholeSeparatorPreserveAllTokens(fromString(mode, 
"ab   de fg"), null))
                     .isEqualTo(
-                            DecimalData.fromBigDecimal(
-                                    new BigDecimal(d.str), d.precision, 
d.scale));
+                            new BinaryStringData[] {
+                                fromString(mode, "ab"),
+                                fromString(mode, ""),
+                                fromString(mode, ""),
+                                fromString(mode, "de"),
+                                fromString(mode, "fg")
+                            });
+            assertThat(
+                            splitByWholeSeparatorPreserveAllTokens(
+                                    fromString(mode, "ab:cd:ef"), 
fromString(mode, ":")))
+                    .isEqualTo(
+                            new BinaryStringData[] {
+                                fromString(mode, "ab"),
+                                fromString(mode, "cd"),
+                                fromString(mode, "ef")
+                            });
+            assertThat(
+                            splitByWholeSeparatorPreserveAllTokens(
+                                    fromString(mode, "ab-!-cd-!-ef"), 
fromString(mode, "-!-")))
+                    .isEqualTo(
+                            new BinaryStringData[] {
+                                fromString(mode, "ab"),
+                                fromString(mode, "cd"),
+                                fromString(mode, "ef")
+                            });
         }
     }
 
-    @TestTemplate
-    void testEmptyString() {
-        BinaryStringData str2 = fromString("hahahahah");
-        BinaryStringData str3;
-        {
-            MemorySegment[] segments = new MemorySegment[2];
-            segments[0] = MemorySegmentFactory.wrap(new byte[10]);
-            segments[1] = MemorySegmentFactory.wrap(new byte[10]);
-            str3 = BinaryStringData.fromAddress(segments, 15, 0);
+    @Nested
+    @DisplayName("Conversion")
+    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+    class Conversion {
+
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void toIntegralTypes(Mode mode) {
+            // Test to integer.
+            assertThat(toByte(fromString(mode, 
"123"))).isEqualTo(Byte.parseByte("123"));
+            assertThat(toByte(fromString(mode, 
"+123"))).isEqualTo(Byte.parseByte("123"));
+            assertThat(toByte(fromString(mode, 
"-123"))).isEqualTo(Byte.parseByte("-123"));
+
+            assertThat(toShort(fromString(mode, 
"123"))).isEqualTo(Short.parseShort("123"));
+            assertThat(toShort(fromString(mode, 
"+123"))).isEqualTo(Short.parseShort("123"));
+            assertThat(toShort(fromString(mode, 
"-123"))).isEqualTo(Short.parseShort("-123"));
+
+            assertThat(toInt(fromString(mode, 
"123"))).isEqualTo(Integer.parseInt("123"));
+            assertThat(toInt(fromString(mode, 
"+123"))).isEqualTo(Integer.parseInt("123"));
+            assertThat(toInt(fromString(mode, 
"-123"))).isEqualTo(Integer.parseInt("-123"));
+
+            assertThat(toLong(fromString(mode, "1234567890")))
+                    .isEqualTo(Long.parseLong("1234567890"));
+            assertThat(toLong(fromString(mode, "+1234567890")))
+                    .isEqualTo(Long.parseLong("+1234567890"));
+            assertThat(toLong(fromString(mode, "-1234567890")))
+                    .isEqualTo(Long.parseLong("-1234567890"));
+
+            // Test decimal string to integer.
+            assertThat(toInt(fromString(mode, 
"123.456789"))).isEqualTo(Integer.parseInt("123"));
+            assertThat(toLong(fromString(mode, 
"123.456789"))).isEqualTo(Long.parseLong("123"));
+
+            // Test negative cases.
+            assertThatThrownBy(() -> toInt(fromString(mode, "1a3.456789")))
+                    .isInstanceOf(NumberFormatException.class);
+            assertThatThrownBy(() -> toInt(fromString(mode, "123.a56789")))
+                    .isInstanceOf(NumberFormatException.class);
         }
 
-        assertThat(BinaryStringData.EMPTY_UTF8.compareTo(str2)).isLessThan(0);
-        
assertThat(str2.compareTo(BinaryStringData.EMPTY_UTF8)).isGreaterThan(0);
+        @ParameterizedTest(name = "{0}: {1}")
+        @MethodSource("positiveInfinityCases")
+        void castsPositiveInfinity(Mode mode, String input) {
+            assertThat(toFloat(fromString(mode, 
input))).isEqualTo(Float.POSITIVE_INFINITY);
+            assertThat(toDouble(fromString(mode, 
input))).isEqualTo(Double.POSITIVE_INFINITY);
+        }
 
-        assertThat(BinaryStringData.EMPTY_UTF8.compareTo(str3)).isEqualTo(0);
-        assertThat(str3.compareTo(BinaryStringData.EMPTY_UTF8)).isEqualTo(0);
+        @ParameterizedTest(name = "{0}: {1}")
+        @MethodSource("negativeInfinityCases")
+        void castsNegativeInfinity(Mode mode, String input) {
+            assertThat(toFloat(fromString(mode, 
input))).isEqualTo(Float.NEGATIVE_INFINITY);
+            assertThat(toDouble(fromString(mode, 
input))).isEqualTo(Double.NEGATIVE_INFINITY);
+        }
 
-        assertThat(str2).isNotEqualTo(BinaryStringData.EMPTY_UTF8);
-        assertThat(BinaryStringData.EMPTY_UTF8).isNotEqualTo(str2);
+        @ParameterizedTest(name = "{0}: {1}")
+        @MethodSource("nanCases")
+        void castsNaN(Mode mode, String input) {
+            assertThat(toFloat(fromString(mode, input))).isNaN();
+            assertThat(toDouble(fromString(mode, input))).isNaN();
+        }
 
-        assertThat(str3).isEqualTo(BinaryStringData.EMPTY_UTF8);
-        assertThat(BinaryStringData.EMPTY_UTF8).isEqualTo(str3);
-    }
+        @ParameterizedTest(name = "{0}: {1}")
+        @MethodSource("invalidApproximateCases")
+        void rejectsInvalidApproximate(Mode mode, String input) {
+            assertThatThrownBy(() -> toFloat(fromString(mode, input)))
+                    .isInstanceOf(NumberFormatException.class);
+            assertThatThrownBy(() -> toDouble(fromString(mode, input)))
+                    .isInstanceOf(NumberFormatException.class);
+        }
 
-    @TestTemplate
-    void testEncodeWithIllegalCharacter() throws UnsupportedEncodingException {
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void parsesOrdinaryDouble(Mode mode) {
+            assertThat(toDouble(fromString(mode, "1.5"))).isEqualTo(1.5);
+        }
 
-        // Tis char array has some illegal character, such as 55357
-        // the jdk ignores theses character and cast them to '?'
-        // which StringUtf8Utils'encodeUTF8 should follow
-        char[] chars =
-                new char[] {
-                    20122, 40635, 124, 38271, 34966, 124, 36830, 34915, 35033, 
124, 55357, 124,
-                    56407
-                };
+        @Test
+        void toNumericFromBinaryRow() {
+            BinaryRowData row = new BinaryRowData(20);
+            BinaryRowWriter writer = new BinaryRowWriter(row);
+            writer.writeString(0, BinaryStringData.fromString("1"));
+            writer.writeString(1, BinaryStringData.fromString("123"));
+            writer.writeString(2, BinaryStringData.fromString("12345"));
+            writer.writeString(3, BinaryStringData.fromString("123456789"));
+            writer.complete();
+
+            assertThat(toByte(((BinaryStringData) row.getString(0))))
+                    .isEqualTo(Byte.parseByte("1"));
+            assertThat(toShort(((BinaryStringData) row.getString(1))))
+                    .isEqualTo(Short.parseShort("123"));
+            assertThat(toInt(((BinaryStringData) row.getString(2))))
+                    .isEqualTo(Integer.parseInt("12345"));
+            assertThat(toLong(((BinaryStringData) row.getString(3))))
+                    .isEqualTo(Long.parseLong("123456789"));
+        }
 
-        String str = new String(chars);
+        @ParameterizedTest(name = "{0}: {1} -> DECIMAL({2},{3})")
+        
@MethodSource("org.apache.flink.table.data.BinaryStringDataTest#decimalCases")
+        void toDecimalFromString(Mode mode, String str, int precision, int 
scale) {
+            assertThat(toDecimal(fromString(mode, str), precision, scale))
+                    .isEqualTo(DecimalData.fromBigDecimal(new BigDecimal(str), 
precision, scale));
+        }
 
-        
assertThat(StringUtf8Utils.encodeUTF8(str)).isEqualTo(str.getBytes("UTF-8"));
-    }
+        @ParameterizedTest(name = "{0} -> DECIMAL({1},{2})")
+        @MethodSource("decimalRowCases")
+        void toDecimalFromBinaryRow(String str, int precision, int scale) {
+            BinaryRowData row = new BinaryRowData(1);
+            BinaryRowWriter writer = new BinaryRowWriter(row);
+            writer.writeString(0, BinaryStringData.fromString(str));
+            writer.complete();
+            assertThat(toDecimal((BinaryStringData) row.getString(0), 
precision, scale))
+                    .isEqualTo(DecimalData.fromBigDecimal(new BigDecimal(str), 
precision, scale));
+        }
 
-    @TestTemplate
-    void testDecodeWithIllegalUtf8Bytes() throws UnsupportedEncodingException {
-
-        // illegal utf-8 bytes
-        byte[] bytes =
-                new byte[] {
-                    (byte) 20122,
-                    (byte) 40635,
-                    124,
-                    (byte) 38271,
-                    (byte) 34966,
-                    124,
-                    (byte) 36830,
-                    (byte) 34915,
-                    (byte) 35033,
-                    124,
-                    (byte) 55357,
-                    124,
-                    (byte) 56407
-                };
-
-        String str = new String(bytes, StandardCharsets.UTF_8);
-        assertThat(StringUtf8Utils.decodeUTF8(bytes, 0, 
bytes.length)).isEqualTo(str);
-        
assertThat(StringUtf8Utils.decodeUTF8(MemorySegmentFactory.wrap(bytes), 0, 
bytes.length))
-                .isEqualTo(str);
-
-        byte[] newBytes = new byte[bytes.length + 5];
-        System.arraycopy(bytes, 0, newBytes, 5, bytes.length);
-        
assertThat(StringUtf8Utils.decodeUTF8(MemorySegmentFactory.wrap(newBytes), 5, 
bytes.length))
-                .isEqualTo(str);
-    }
+        @ParameterizedTest(name = "{0}")
+        @EnumSource(Mode.class)
+        void testToUpperLowerCase(Mode mode) {
+            assertThat(fromString(mode, "我是中国人").toLowerCase())
+                    .isEqualTo(fromString(mode, "我是中国人"));
+            assertThat(fromString(mode, "我是中国人").toUpperCase())
+                    .isEqualTo(fromString(mode, "我是中国人"));
+
+            assertThat(fromString(mode, "aBcDeFg").toLowerCase())
+                    .isEqualTo(fromString(mode, "abcdefg"));
+            assertThat(fromString(mode, "aBcDeFg").toUpperCase())
+                    .isEqualTo(fromString(mode, "ABCDEFG"));
+
+            assertThat(fromString(mode, "!@#$%^*").toLowerCase())
+                    .isEqualTo(fromString(mode, "!@#$%^*"));
+            assertThat(fromString(mode, "!@#$%^*").toLowerCase())
+                    .isEqualTo(fromString(mode, "!@#$%^*"));
+            // Test composite in BinaryRowData.
+            BinaryRowData row = new BinaryRowData(20);
+            BinaryRowWriter writer = new BinaryRowWriter(row);
+            writer.writeString(0, BinaryStringData.fromString("a"));
+            writer.writeString(1, BinaryStringData.fromString("我是中国人"));
+            writer.writeString(3, BinaryStringData.fromString("aBcDeFg"));
+            writer.writeString(5, BinaryStringData.fromString("!@#$%^*"));
+            writer.complete();
+
+            assertThat(((BinaryStringData) row.getString(0)).toUpperCase())
+                    .isEqualTo(fromString(mode, "A"));
+            assertThat(((BinaryStringData) row.getString(1)).toUpperCase())
+                    .isEqualTo(fromString(mode, "我是中国人"));
+            assertThat(((BinaryStringData) row.getString(1)).toLowerCase())
+                    .isEqualTo(fromString(mode, "我是中国人"));
+            assertThat(((BinaryStringData) row.getString(3)).toUpperCase())
+                    .isEqualTo(fromString(mode, "ABCDEFG"));
+            assertThat(((BinaryStringData) row.getString(3)).toLowerCase())
+                    .isEqualTo(fromString(mode, "abcdefg"));
+            assertThat(((BinaryStringData) row.getString(5)).toUpperCase())
+                    .isEqualTo(fromString(mode, "!@#$%^*"));
+            assertThat(((BinaryStringData) row.getString(5)).toLowerCase())
+                    .isEqualTo(fromString(mode, "!@#$%^*"));
+        }
 
-    @TestTemplate
-    void skipWrongFirstByte() {
-        int[] wrongFirstBytes = {
-            0x80,
-            0x9F,
-            0xBF, // Skip Continuation bytes
-            0xC0,
-            0xC2, // 0xC0..0xC1 - disallowed in UTF-8
-            // 0xF5..0xFF - disallowed in UTF-8
-            0xF5,
-            0xF6,
-            0xF7,
-            0xF8,
-            0xF9,
-            0xFA,
-            0xFB,
-            0xFC,
-            0xFD,
-            0xFE,
-            0xFF
-        };
-        byte[] c = new byte[1];
-
-        for (int wrongFirstByte : wrongFirstBytes) {
-            c[0] = (byte) wrongFirstByte;
-            assertThat(1).isEqualTo(fromBytes(c).numChars());
+        private Stream<Arguments> decimalRowCases() {
+            return DECIMAL_CASES.stream().map(c -> arguments(c.str, 
c.precision, c.scale));
         }
-    }
 
-    @TestTemplate
-    void testSplit() {
-        assertThat(splitByWholeSeparatorPreserveAllTokens(fromString(""), 
fromString("")))
-                .isEqualTo(EMPTY_STRING_ARRAY);
-        assertThat(splitByWholeSeparatorPreserveAllTokens(fromString("ab de 
fg"), null))
-                .isEqualTo(
-                        new BinaryStringData[] {
-                            fromString("ab"), fromString("de"), 
fromString("fg")
-                        });
-        assertThat(splitByWholeSeparatorPreserveAllTokens(fromString("ab   de 
fg"), null))
-                .isEqualTo(
-                        new BinaryStringData[] {
-                            fromString("ab"),
-                            fromString(""),
-                            fromString(""),
-                            fromString("de"),
-                            fromString("fg")
-                        });
-        
assertThat(splitByWholeSeparatorPreserveAllTokens(fromString("ab:cd:ef"), 
fromString(":")))
-                .isEqualTo(
-                        new BinaryStringData[] {
-                            fromString("ab"), fromString("cd"), 
fromString("ef")
-                        });
-        assertThat(
-                        splitByWholeSeparatorPreserveAllTokens(
-                                fromString("ab-!-cd-!-ef"), fromString("-!-")))
-                .isEqualTo(
-                        new BinaryStringData[] {
-                            fromString("ab"), fromString("cd"), 
fromString("ef")
-                        });
-    }
+        private Stream<Arguments> positiveInfinityCases() {
+            return withModes("Infinity", "infinity", "INF", "inf", "+inf");
+        }
 
-    @TestTemplate
-    void testLazy() {
-        String javaStr = "haha";
-        BinaryStringData str = BinaryStringData.fromString(javaStr);
-        str.ensureMaterialized();
+        private Stream<Arguments> negativeInfinityCases() {
+            return withModes("-Infinity", "-infinity", "-INF", "  -inf  ", "  
-infinity  ");
+        }
 
-        // check reference same.
-        assertThat(javaStr).isSameAs(str.toString());
+        private Stream<Arguments> nanCases() {
+            return withModes("NaN", "nan", "NAN");
+        }
+
+        private Stream<Arguments> invalidApproximateCases() {
+            return withModes("-nan", "-   Infinity");
+        }
     }
 
-    @TestTemplate
-    void testIsEmpty() {
-        assertThat(isEmpty(fromString(""))).isEqualTo(true);
-        assertThat(isEmpty(BinaryStringData.fromBytes(new byte[] 
{}))).isEqualTo(true);
-        assertThat(isEmpty(fromString("hello"))).isEqualTo(false);
-        
assertThat(isEmpty(BinaryStringData.fromBytes("hello".getBytes()))).isEqualTo(false);
-        assertThat(isEmpty(fromString("中文"))).isEqualTo(false);
-        
assertThat(isEmpty(BinaryStringData.fromBytes("中文".getBytes()))).isEqualTo(false);
-        assertThat(isEmpty(new BinaryStringData())).isEqualTo(true);
+    @Nested
+    @DisplayName("Encoding")
+    @TestInstance(TestInstance.Lifecycle.PER_CLASS)
+    class Encoding {
+
+        @Test
+        void testEncodeWithIllegalCharacter() {
+
+            // This char array contains illegal characters, such as the lone 
surrogate 55357.
+            // The JDK replaces such characters with '?' when encoding to 
UTF-8, which
+            // StringUtf8Utils#encodeUTF8 should follow.
+            char[] chars =
+                    new char[] {
+                        20122, 40635, 124, 38271, 34966, 124, 36830, 34915, 
35033, 124, 55357, 124,
+                        56407
+                    };
+
+            String str = new String(chars);
+
+            
assertThat(StringUtf8Utils.encodeUTF8(str)).isEqualTo(str.getBytes(UTF_8));
+        }
+
+        @ParameterizedTest(name = "{0}")
+        @MethodSource("utf8Decoders")
+        void testDecodeWithIllegalUtf8Bytes(String name, Utf8Decoder decoder) {
+
+            // illegal utf-8 bytes
+            byte[] bytes =
+                    new byte[] {
+                        (byte) 20122,
+                        (byte) 40635,
+                        124,
+                        (byte) 38271,
+                        (byte) 34966,
+                        124,
+                        (byte) 36830,
+                        (byte) 34915,
+                        (byte) 35033,
+                        124,
+                        (byte) 55357,
+                        124,
+                        (byte) 56407
+                    };
+
+            String str = new String(bytes, StandardCharsets.UTF_8);
+            assertThat(decoder.decode(bytes)).isEqualTo(str);
+        }
+
+        @ParameterizedTest
+        @ValueSource(
+                ints = {
+                    0x80,
+                    0x9F,
+                    0xBF, // Skip continuation bytes
+                    0xC0,
+                    0xC2, // 0xC0..0xC1 - disallowed in UTF-8
+                    // 0xF5..0xFF - disallowed in UTF-8
+                    0xF5,
+                    0xF6,
+                    0xF7,
+                    0xF8,
+                    0xF9,
+                    0xFA,
+                    0xFB,
+                    0xFC,
+                    0xFD,
+                    0xFE,
+                    0xFF
+                })
+        void skipWrongFirstByte(int wrongFirstByte) {
+            assertThat(fromBytes(new byte[] {(byte) 
wrongFirstByte}).numChars()).isEqualTo(1);
+        }
+
+        private Stream<Arguments> utf8Decoders() {
+            return Stream.of(
+                    arguments(
+                            "byte[]",
+                            (Utf8Decoder)
+                                    bytes -> StringUtf8Utils.decodeUTF8(bytes, 
0, bytes.length)),
+                    arguments(
+                            "segment",
+                            (Utf8Decoder)
+                                    bytes ->
+                                            StringUtf8Utils.decodeUTF8(
+                                                    
MemorySegmentFactory.wrap(bytes),
+                                                    0,
+                                                    bytes.length)),
+                    arguments(
+                            "segment at offset",
+                            (Utf8Decoder)
+                                    bytes -> {
+                                        byte[] padded = new byte[bytes.length 
+ 5];
+                                        System.arraycopy(bytes, 0, padded, 5, 
bytes.length);
+                                        return StringUtf8Utils.decodeUTF8(
+                                                
MemorySegmentFactory.wrap(padded), 5, bytes.length);
+                                    }));
+        }
     }
 }

Reply via email to