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 30b5f50125d [FLINK-40350][table-runtime] `LPAD`/`RPAD` split 
supplementary-plane characters into unpaired surrogates
30b5f50125d is described below

commit 30b5f50125dc395f82baf61774cad8541a5b33d9
Author: Sepuri Sai Krishna <[email protected]>
AuthorDate: Fri Sep 4 21:49:32 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 f92847ee3a6..7179f157c1f 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
@@ -824,6 +824,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
@@ -846,6 +854,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 7dd55b73a47..c191559f2c0 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;
@@ -310,27 +311,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);
     }
 
@@ -345,31 +345,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