Skip to content

[VL] Translate Java regex patterns to RE2 with fallback for unsupported constructs - #13278

Open
KushShriv wants to merge 11 commits into
apache:mainfrom
KushShriv:regex-re2-translation
Open

KushShriv wants to merge 11 commits into
apache:mainfrom
KushShriv:regex-re2-translation

Conversation

@KushShriv

@KushShriv KushShriv commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor

Problem

Velox and Bolt execute regexes with RE2, which doesn't understand Java's Unicode escapes (e.g., \uXXXX). A pattern like \u4e00-\u9fa5 fails RE2 compilation, so the whole operator falls back to Spark (tracked in FallbackSuite). Supplementary characters written as surrogate pairs (\uD83D\uDE00) have the same problem.


Solution

Translate to RE2 syntax in Scala, and make the C++ validator a strict safeguard.

  • Scala translation: ExpressionConverter.withRe2PatternTranslation rewrites literal patterns before they are written into the Substrait plan, for RegExpReplace, RLike, RegExpExtract, RegExpExtractAll, RegExpInstr, and StringSplit. It uses Re2PatternUtils.translateJavaPatternToRe2, a single escape-aware scanner:
    • \uXXXX $\rightarrow$ \x{XXXX}; a high+low surrogate pair $\rightarrow$ one \x{cp}
    • Escape pairs are copied verbatim, so \\u0041 (escaped backslash + u0041) is left alone.
    • Returns None ($\rightarrow$ GlutenNotSupportException $\rightarrow$ fallback to Spark) for lookahead/lookbehind, backreferences (any backslash followed by 1–9, including inside a character class, where Java itself rejects it), and unpaired surrogates. Escaped occurrences (\(?=, \\1) and lookaround markers inside a character class ([(?=]) are not flagged.
    • Backreferences are rejected even in a character class on purpose. Java raises an error for [\1], so falling back lets Spark report it. Letting it through would be unsafe: RE2 reads [\12] as an octal escape and would run it natively where Spark throws.
    • Named groups (?<n>...) are allowed.
  • Backend gating: Translation only runs when SparkPlanExecApi.needsRe2PatternTranslation is true. It is overridden to true for Velox and Bolt (both RE2-based) and defaults to false, so ClickHouse is unaffected. Bolt has no C++ validator change and relies on the Scala-side rewrite alone.
  • C++ (Velox safeguard): validatePattern() compiles the pattern with RE2 exactly as given, with no translation. A raw \u that reaches the native side (for example, a plan not built by Gluten's converter, or a new regex function added without Scala wiring) is rejected at validation, so the operator falls back to Spark instead of failing at runtime with VeloxUserError: invalid escape sequence: \u.
    • An earlier revision validated a translated copy while the plan kept the raw literal, which accepted the pattern and then failed at execution. This revision fixes that.
    • translateJavaUnicodeToRe2 is kept as a public, tested helper but the validator no longer uses it. It is a lexical rewrite only: an unpaired surrogate escape is left as \uXXXX on purpose so RE2 rejects it, so its output is not guaranteed to compile.
  • Error messages: the pattern shown in the Scala fallback reason and in the native validator error (including RE2's own message, which repeats it) is quoted, has control characters escaped, and is truncated to about 100 characters, so long or multi-line patterns do not bloat fallback reasons and logs.

Changes

File Change
gluten-substrait/.../Re2PatternUtils.scala New translator, plus describePattern for error messages
gluten-substrait/.../ExpressionConverter.scala withRe2PatternTranslation; explicit RLike / RegExpExtract / RegExpExtractAll cases; wired into all 6 regex expressions
gluten-substrait/.../SparkPlanExecApi.scala needsRe2PatternTranslation hook (default false)
backends-velox/.../VeloxSparkPlanExecApi.scala, backends-bolt/.../BoltSparkPlanExecApi.scala Override to true
cpp/velox/utils/Common.{h,cc} translateJavaUnicodeToRe2 helper; validatePattern compiles and checks the pattern as given (rejects raw \u) and reports a quoted, truncated pattern on failure
cpp/velox/tests/CommonTest.cc, CMakeLists.txt 22 C++ tests; register common_test
gluten-substrait/.../Re2PatternUtilsSuite.scala 10 unit tests
backends-velox/.../FallbackSuite.scala \u4e00 join-filter test now asserts native execution; lookbehind fallback assertion tightened to the specific reason
backends-velox/.../VeloxStringFunctionsSuite.scala rlike tests: Unicode escape, surrogate pair, escaped backslash run natively; lookbehind falls back

Testing

Unit and integration tests (Spark 3.5, Velox):

Suite Result
common_test (C++) 21/21
Re2PatternUtilsSuite 8/8
VeloxStringFunctionsSuite 50/50
FallbackSuite 16 passed, 0 failed, 1 ignored (fallback final aggregate of collect_set, unrelated)

End-to-end comparison on a 6,000-row table, with results checked against vanilla Spark and an independent Java regex oracle. Ten Unicode escape query shapes were run: rlike, regexp_replace, regexp_extract, regexp_extract_all, regexp_instr, split, surrogate pairs, and escaped backslashes.

  • Main branch: the translated shapes fall back to Spark.
  • This branch: all of them execute natively in Velox. Results match Spark, with 15/15 checks passing.
  • Unsupported constructs (lookaround, backreferences, unpaired surrogates): still fall back on both.

Safeguard check: I ran the main Scala layer (no Phase 1) against this branch's native library to simulate a raw \u reaching the C++ side.

  • Before the validator change, 8 shapes were accepted by validatePattern and then failed with VeloxUserError ... invalid escape sequence: \u.
  • After the change, those shapes fall back to Spark with correct results and no VeloxUserError.

Style checks: scalastyle, checkstyle, and spotless pass for backends-velox and gluten-substrait.


Notes

  • No new config keys. Already RE2-compatible patterns are unchanged.
  • Lookaround/backreference patterns still fall back to Spark on Velox and Bolt.
  • Character class union/intersection ([a|b], [a&&[b]]) is unaffected: ensureRegexIsCompatible sees a string with the same bracket depth.
  • Not tested: Spark 4.x collated string literals. The Scala match is on StringType exactly, so such a pattern may skip translation. The C++ safeguard makes it fall back instead of failing at runtime.
  • Out of scope: str_to_map(s, '\\u0020', '\\u0031') runs natively and fails at runtime (entryDelimiter's size should be 1) on both main and this branch.

Was this patch authored or co-authored using generative AI tooling?
Generated-by: Claude claude-sonnet-5.5

KushShriv and others added 2 commits October 13, 2026 21:45
…ed constructs

Translate Java-style patterns to RE2 syntax for backends that use RE2
(Velox, Bolt), gated by SparkPlanExecApi.needsRe2PatternTranslation so
other backends (e.g. ClickHouse) are unaffected.

- Add Re2PatternUtils: an escape-aware scanner that rewrites \uXXXX to
  \x{XXXX}, combines surrogate pairs into one code point, and returns
  None (so the plan falls back to Spark) for lookaround, backreferences
  and unpaired surrogates.
- Apply it to RLike, RegExpExtract and RegExpExtractAll literal patterns
  in ExpressionConverter.
- Add the same escape-pair and surrogate handling to the C++ safeguard
  translateJavaUnicodeToRe2 in cpp/velox/utils/Common.cc.
- Add Re2PatternUtilsSuite, C++ CommonTest cases, and rlike cases in
  VeloxStringFunctionsSuite; tighten the FallbackSuite assertion.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Use Locale.ROOT for toUpperCase, wrap long/non-ASCII lines, and import
testImplicits for toDF in VeloxStringFunctionsSuite.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added CORE works for Gluten Core VELOX labels Oct 14, 2026
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

KushShriv and others added 2 commits October 13, 2026 23:32
Resolve FallbackSuite.scala conflict: keep main's ANSI-disabled wrapper around the
nested loop join test and our updated lookaround fallback-reason assertion.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ded to LocalRelation

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

3 similar comments
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

…ranslated copy

validatePattern translated \uXXXX to \x{XXXX} only for its own RE2 compile,
but the Substrait plan keeps the original literal and Velox compiles that
untranslated at execution time. A raw \uXXXX that reached the native side
(any path that bypasses withRe2PatternTranslation) was therefore approved by
the validator and then failed at runtime with "invalid escape sequence: \u"
instead of falling back to Spark.

Validate the pattern exactly as Velox will compile it. Patterns translated on
the Scala side (\x{XXXX}) still pass, so native coverage is unchanged; an
untranslated \uXXXX now falls back as it did on main.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

@KushShriv
KushShriv marked this pull request as ready for review October 15, 2026 08:07
@KushShriv

Copy link
Copy Markdown
Contributor Author

Hey @zhouyuan, could you please have a look at this PR, thank you.

@zhouyuan
zhouyuan requested review from philo-he and a balanced review from Copilot October 15, 2026 08:24
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

Copilot AI left a comment

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.

Warning

Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.

Copilot review overview

5 open findings
What changed in this PR

Adds Java→RE2 regex literal translation (mainly \uXXXX and surrogate pairs) for RE2-based backends to avoid unnecessary Spark fallbacks, while keeping a native-side RE2 compilation validator as a strict safeguard.

Changes:

  • Add Scala-side regex literal translation + rejection of unsupported constructs (lookaround/backrefs/unpaired surrogates) gated by a new backend hook.
  • Add Velox C++ helper for Java unicode escape translation (tested) and make validatePattern() compile the raw pattern as-is.
  • Extend test coverage across Scala unit tests, Velox C++ tests, and Velox integration/fallback suites.
File Description
gluten-substrait/​src/​test/​scala/​org/​apache/​gluten/​expression/​Re2PatternUtilsSuite.scala Scala unit tests for Java→RE2 translation and unsupported-construct detection
gluten-substrait/​src/​main/​scala/​org/​apache/​gluten/​expression/​Re2PatternUtils.scala New Java-regex scanner/translator to RE2-compatible syntax
gluten-substrait/​src/​main/​scala/​org/​apache/​gluten/​expression/​ExpressionConverter.scala Translate regex literal arguments before writing Substrait literals (RE2 backends only)
gluten-substrait/​src/​main/​scala/​org/​apache/​gluten/​backendsapi/​SparkPlanExecApi.scala Add needsRe2PatternTranslation hook (default false)
cpp/​velox/​utils/​Common.h Declare unicode-translation helper + clarify validator behavior
cpp/​velox/​utils/​Common.cc Implement unicode translation helper; validate raw pattern without translation
cpp/​velox/​tests/​CommonTest.cc Add C++ unit tests for translator + validator behavior
cpp/​velox/​tests/​CMakeLists.txt Register new common_test target
backends-velox/​src/​test/​scala/​org/​apache/​gluten/​execution/​VeloxStringFunctionsSuite.scala Add integration tests for unicode escapes/surrogate pairs and fallback on lookbehind
backends-velox/​src/​test/​scala/​org/​apache/​gluten/​execution/​FallbackSuite.scala Update fallback assertions + add regression ensuring unicode-escape rlike doesn’t fallback
backends-velox/​src/​main/​scala/​org/​apache/​gluten/​backendsapi/​velox/​VeloxSparkPlanExecApi.scala Enable translation for Velox backend
backends-bolt/​src/​main/​scala/​org/​apache/​gluten/​backendsapi/​bolt/​BoltSparkPlanExecApi.scala Enable translation for Bolt backend

🧠 Review effort: Lite


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

}
} else if (i + 1 < n) {
val next = pattern.charAt(i + 1)
if (next >= '1' && next <= '9') return None // backreference
Comment thread cpp/velox/utils/Common.h Outdated
Comment on lines +33 to +34
/// Converts Java \uXXXX Unicode escapes to RE2's \x{XXXX} syntax.
/// Returns the input unchanged if no \uXXXX sequences are found (idempotent).
Comment on lines +999 to +1001
.getOrElse(throw new GlutenNotSupportException(
s"Regex pattern '$patStr' uses constructs (lookahead/lookbehind/backreference) " +
s"or unpaired surrogates that RE2 cannot handle. Falling back to Spark."))
Comment on lines +83 to +84
val next = pattern.charAt(i + 1)
if (next >= '1' && next <= '9') return None // backreference
Comment thread cpp/velox/utils/Common.cc Outdated
// \uXXXX escapes are rewritten on the Scala side before the plan is built.
auto re2 = compilePattern(pattern);
if (!re2->ok()) {
error = "Pattern " + pattern + " compilation failed in RE2. Reason: " + re2->error();
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

- Document that translateJavaUnicodeToRe2 is lexical only: unpaired surrogate
  escapes are left as \uXXXX on purpose, so the output may be partly
  translated and is not guaranteed to compile in RE2.
- Document that every backslash followed by 1-9 is rejected as a
  backreference, including inside a character class where Java itself
  rejects it, and add tests for [\1] and [\12].
- Quote, escape control characters in, and truncate the pattern (and RE2's
  own error text) in the Scala fallback reason and the native validator error.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Run Gluten Clickhouse CI on x86

This branch has not been deployed

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

Labels

CORE works for Gluten Core VELOX

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants