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

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


The following commit(s) were added to refs/heads/release-2.3 by this push:
     new 252231a7474 [FLINK-40350][table-runtime] `LPAD`/`RPAD` split 
supplementary-plane characters into unpaired surrogates
252231a7474 is described below

commit 252231a7474b2b4b3d6c9f026f26796a0563e987
Author: Sepuri Sai Krishna <[email protected]>
AuthorDate: Mon Sep 7 15:06:25 2026 +0530

    [FLINK-40350][table-runtime] `LPAD`/`RPAD` split supplementary-plane 
characters into unpaired surrogates
---
 .../planner/expressions/ScalarFunctionsTest.scala  | 16 ++++
 .../table/runtime/functions/SqlFunctionUtils.java  | 99 ++++++++++++++--------
 2 files changed, 80 insertions(+), 35 deletions(-)

diff --git 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarFunctionsTest.scala
 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarFunctionsTest.scala
index bede804aa8b..7218c87f3d0 100644
--- 
a/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarFunctionsTest.scala
+++ 
b/flink-table/flink-table-planner/src/test/scala/org/apache/flink/table/planner/expressions/ScalarFunctionsTest.scala
@@ -865,6 +865,14 @@ class ScalarFunctionsTest extends ScalarTypesTestBase {
     testSqlApi("lpad('ab', 5, '')", "NULL")
 
     testAllApis("äää".lpad(13, "12345"), "lpad('äää',13,'12345')", 
"1234512345äää")
+
+    // a supplementary-plane character is one character and is never split 
into half a pair
+    testSqlApi("lpad('😀', 1, 'x')", "😀")
+    testSqlApi("lpad('😀😀', 1, 'x')", "😀")
+    testSqlApi("lpad('😀', 3, 'x')", "xx😀")
+    testSqlApi("lpad('a', 3, '😀')", "😀😀a")
+    testSqlApi("lpad('a', 4, '😀x')", "😀x😀a")
+    testSqlApi("lpad('', 2, '😀')", "😀😀")
   }
 
   @Test
@@ -887,6 +895,14 @@ class ScalarFunctionsTest extends ScalarTypesTestBase {
     testSqlApi("rpad('üö',1,'??')", "ü")
     testSqlApi("rpad('abcd', 5, '')", "NULL")
     testAllApis("äää".rpad(13, "12345"), "rpad('äää',13,'12345')", 
"äää1234512345")
+
+    // a supplementary-plane character is one character and is never split 
into half a pair
+    testSqlApi("rpad('😀', 1, 'x')", "😀")
+    testSqlApi("rpad('😀😀', 1, 'x')", "😀")
+    testSqlApi("rpad('😀', 3, 'x')", "😀xx")
+    testSqlApi("rpad('a', 4, '😀')", "a😀😀😀")
+    testSqlApi("rpad('a', 4, '😀x')", "a😀x😀")
+    testSqlApi("rpad('', 2, '😀')", "😀😀")
   }
 
   @Test
diff --git 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/SqlFunctionUtils.java
 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/SqlFunctionUtils.java
index 0fdd5213613..1853acef9ca 100644
--- 
a/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/SqlFunctionUtils.java
+++ 
b/flink-table/flink-table-runtime/src/main/java/org/apache/flink/table/runtime/functions/SqlFunctionUtils.java
@@ -42,6 +42,7 @@ import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.security.MessageDigest;
 import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
 import java.util.Base64;
 import java.util.HashMap;
 import java.util.Map;
@@ -311,27 +312,26 @@ public class SqlFunctionUtils {
             return "";
         }
 
-        char[] data = new char[len];
-        char[] baseChars = base.toCharArray();
-        char[] padChars = pad.toCharArray();
-
-        // the length of the padding needed
-        int pos = Math.max(len - base.length(), 0);
-
-        // copy the padding
-        for (int i = 0; i < pos; i += pad.length()) {
-            for (int j = 0; j < pad.length() && j < pos - i; j++) {
-                data[i + j] = padChars[j];
-            }
+        final int length = base.length();
+        int baseEnd = 0;
+        int baseCodePoints = 0;
+        while (baseCodePoints < len && baseEnd < length) {
+            baseEnd += Character.charCount(base.codePointAt(baseEnd));
+            baseCodePoints++;
         }
 
-        // copy the base
-        int i = 0;
-        while (pos + i < len && i < base.length()) {
-            data[pos + i] = baseChars[i];
-            i += 1;
+        final int padCount = len - baseCodePoints;
+        if (padCount == 0) {
+            // The base already holds len code points, so it only needs 
truncating.
+            return base.substring(0, baseEnd);
         }
 
+        final int padChars = padLength(pad, padCount);
+        final char[] data = new char[Math.addExact(padChars, baseEnd)];
+
+        writePad(data, 0, pad, padChars);
+        base.getChars(0, baseEnd, data, padChars);
+
         return new String(data);
     }
 
@@ -346,31 +346,60 @@ public class SqlFunctionUtils {
             return "";
         }
 
-        char[] data = new char[len];
-        char[] baseChars = base.toCharArray();
-        char[] padChars = pad.toCharArray();
-
-        int pos = 0;
-
-        // copy the base
-        while (pos < base.length() && pos < len) {
-            data[pos] = baseChars[pos];
-            pos += 1;
+        final int length = base.length();
+        int baseEnd = 0;
+        int baseCodePoints = 0;
+        while (baseCodePoints < len && baseEnd < length) {
+            baseEnd += Character.charCount(base.codePointAt(baseEnd));
+            baseCodePoints++;
         }
 
-        // copy the padding
-        while (pos < len) {
-            int i = 0;
-            while (i < pad.length() && i < len - pos) {
-                data[pos + i] = padChars[i];
-                i += 1;
-            }
-            pos += pad.length();
+        final int padCount = len - baseCodePoints;
+        if (padCount == 0) {
+            // The base already holds len code points, so it only needs 
truncating.
+            return base.substring(0, baseEnd);
         }
 
+        final int padChars = padLength(pad, padCount);
+        final char[] data = new char[Math.addExact(baseEnd, padChars)];
+
+        base.getChars(0, baseEnd, data, 0);
+        writePad(data, baseEnd, pad, padChars);
+
         return new String(data);
     }
 
+    /** Number of chars taken by count code points of pad repeated cyclically. 
*/
+    private static int padLength(String pad, int count) {
+        final int padLen = pad.length();
+        final int cycle = pad.codePointCount(0, padLen);
+        return Math.addExact(
+                Math.multiplyExact(count / cycle, padLen),
+                pad.offsetByCodePoints(0, count % cycle));
+    }
+
+    /** Writes chars characters into data at pos, repeating pad cyclically. */
+    private static void writePad(char[] data, int pos, String pad, int chars) {
+        final int padLen = pad.length();
+        if (padLen == 1) {
+            // A single char fills directly, which is faster than the doubling 
copy below.
+            Arrays.fill(data, pos, pos + chars, pad.charAt(0));
+            return;
+        }
+
+        // Arrays.fill can only repeat a single char, so a longer pad is 
replicated by copying:
+        // each pass doubles the region written, taking log2(chars) copies not 
one per cycle.
+        final int first = Math.min(padLen, chars);
+        pad.getChars(0, first, data, pos);
+
+        int written = first;
+        while (written < chars) {
+            final int next = Math.min(written, chars - written);
+            System.arraycopy(data, pos, data, pos + written, next);
+            written += next;
+        }
+    }
+
     /** Returns a string that repeats the base string n times. */
     public static String repeat(String str, int repeat) {
         return EncodingUtils.repeat(str, repeat);

Reply via email to