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);