Skip to content

[FLINK-40578][table-runtime] OVERLAY discards the rest of the string and splits supplementary-plane characters - #29123

Open
SEPURI-SAI-KRISHNA wants to merge 1 commit into
apache:masterfrom
SEPURI-SAI-KRISHNA:overlay-code-points
Open

[FLINK-40578][table-runtime] OVERLAY discards the rest of the string and splits supplementary-plane characters#29123
SEPURI-SAI-KRISHNA wants to merge 1 commit into
apache:masterfrom
SEPURI-SAI-KRISHNA:overlay-code-points

Conversation

@SEPURI-SAI-KRISHNA

Copy link
Copy Markdown
Contributor

What is the purpose of the change

OVERLAY returns wrong results for several argument combinations. The first row needs no unusual data:

Expression Returns Expected
OVERLAY('abcdef' PLACING 'X' FROM 2 FOR 0) aX aXbcdef
OVERLAY('a😀b' PLACING 'X' FROM 2 FOR 1) aX + unpaired surrogate + b aXb
OVERLAY('abc' PLACING '😀' FROM 2) a😀 a😀c
OVERLAY('123456789' PLACING 'abc' FROM 2 FOR 2147483647) StringIndexOutOfBoundsException 1abc

In SqlFunctionUtils#overlay: a len > 0 guard drops the tail when the replaced length is zero,
offsets count UTF-16 code units rather than characters, and int len = (int) length wraps.

Brief change log

  • Count the start position and the replaced length in code points, using the walk
    BinaryStringData#substring already uses for SUBSTRING.
  • A zero length replaces nothing, so the tail survives.
  • length stays a long, so a large FOR neither wraps nor overflows the end offset.
  • Default the three-argument length to the replacement's code point count.

Left unchanged, because existing tests assert it: a start of zero or less, or past the end,
returns the input; a negative FOR leaves no tail.

Verifying this change

This change added tests and can be verified as follows:

  • 9 cases added to ScalarFunctionsTest#testOverlay, all failing without the fix.
  • ScalarFunctionsTest and SqlExpressionTest pass, including the 18 existing OVERLAY expectations.
  • Differentially checked against an independent implementation of the SQL:2016 formula over 60,192
    argument combinations covering the start and length boundaries: no differences.

Does this pull request potentially affect one of the following parts:

  • Dependencies (does it add or upgrade a dependency): no
  • The public API, i.e., is any changed class annotated with @Public(Evolving): no
  • The serializers: no
  • The runtime per-record code paths (performance sensitive): yes
  • Anything that affects deployment or recovery: no
  • The S3 file system connector: no

Replacing in a short string is faster (46 ns to 29 ns), one exactly-sized char[] instead of a
growing StringBuilder. Replacing near the tail of a 2000-character string is slower (940 ns to
2021 ns): converting a code point position to a char offset needs the same walk SUBSTRING does.

Documentation

  • Does this pull request introduce a new feature? no

Was generative AI tooling used to co-author this PR?
  • Yes (please specify the tool below)

Generated-by: Claude Code (Opus 5)

…g and splits supplementary-plane characters

Generated-by: Claude Code (Opus 5)
@flinkbot

flinkbot commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands The @flinkbot bot supports the following commands:
  • @flinkbot run azure re-run the last Azure build

("abcdef".overlay("X", 1, 0), "OVERLAY('abcdef' PLACING 'X' FROM 1 FOR 0)", "Xabcdef"),
("abcdef".overlay("X", 6, 0), "OVERLAY('abcdef' PLACING 'X' FROM 6 FOR 0)", "abcdeXf"),

// a supplementary-plane character is one character and is never split into half a pair

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

potential additional test cases

  • for malformed/unpaired surrogates
  • verifying behaviour on pure surrogate strings
    wdyt?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A SQL literal can't hold an unpaired surrogate, it becomes ? before execution: CHAR_LENGTH('a\ud83db') returns 3 and the value is a?b. Cases written that way would test 'a?b'.

The function handles them: a lone surrogate counts as one code point, overlay("a\ud83db", "X", 1, 1) gives X\ud83db. Only a direct unit test reaches that, and there's no SqlFunctionUtils test class today. Add one?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are addressing point 1. If you think it is worth adding a test - please add.
What are your thoughts on point 2?

@SEPURI-SAI-KRISHNA

Copy link
Copy Markdown
Contributor Author

@flinkbot run azure

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants