andygrove opened a new issue, #6627:
URL: https://github.com/apache/datafusion-comet/issues/6627
Part of #6615.
## What / Why
This issue moves the regex coverage in `CometRegExpJvmSuite`,
`CometRegexParitySuite` and the `rlike` tests in `CometExpressionSuite` to
Comet SQL tests under `sql-tests/expressions/string/`. Static analysis on
`main` at 3bc2faa934 found the following:
| Suite | Covered | Convert | Blocked | Split | Keep | Total |
|---|---|---|---|---|---|---|
| `CometRegExpJvmSuite` | 12 | 53 | 8 | 0 | 1 | 74 |
| `CometRegexSuite` | 0 | 0 | 0 | 0 | 14 | 14 |
| `CometRegexParitySuite` | 0 | 3 | 0 | 0 | 0 | 3 |
| `CometExpressionSuite` | 2 | 2 | 0 | 0 | 0 | 4 |
| **Total** | **14** | **58** | **8** | **0** | **15** | **95** |
Only 14 of the 95 are covered by a fixture today, and none of those depends
on `\d`, `\w` or a backreference. The reason is the first item below.
## Fix first: seven regex fixtures don't run the patterns they name
Spark's SQL string-literal unescaping (`unescapeSQLString`, with
`spark.sql.parser.escapedStringLiterals=false`) drops the backslash before any
character that is not a recognized escape. So `'\d'` becomes `d`, `'\w'`
becomes `w`, `'\1'` becomes `1` and `'\+'` becomes `+`.
These fixtures write single backslashes:
| Fixture | Lines |
| --- | --- |
| `regexp_extract.sql` | 28, 32, 36, 44, 48, 56 |
| `regexp_extract_all.sql` | 28, 32, 36, 44, 52 |
| `regexp_instr.sql` | 28, 32, 40, 48 |
| `regexp_replace_java.sql` | 27, 30, 34, 38, 42, 50 |
| `regexp_replace_rust.sql` | 29, 32, 36 |
| `rlike_java.sql` | 27, 37, 41, 49 |
| `rlike_auto_native.sql` | 38 |
Examples:
- `rlike_java.sql:27` runs `^d+$`.
- The "backreference (Java-only)" queries run `^(w)1$`, which contains no
backreference.
- `regexp_replace_java.sql:34` (`'(\d+)-(\d+)'`) matches no row, so its
`$2-$1` replacement is never applied.
- `rlike_auto_native.sql:38` (`'a\+b'`) is `a+b`, i.e. one or more `a`
followed by `b`, not the literal string.
The two `*_enabled.sql` regex fixtures already write `'\\d'` correctly. Use
`'\\d'` or a raw literal `r'\d'`; raw literals work on every supported Spark
version.
The Scala tests have the same bug. In `CometExpressionSuite`'s `rlike` test
(L1507), none of the classes and anchors (`\d \D \w \W \b \B \h \H \s \S \v \V
\A \Z \z`) ever reached a regex engine. In `rlike whitespace` (L1473), six of
the 13 patterns are literals that can never match. So the conversion has to fix
the patterns, not copy them.
## Work
Line numbers refer to `CometRegExpJvmSuite` unless marked; "parity" means
`CometRegexParitySuite`.
The full per-test `add` text is in the analysis; this is the condensed
version. Use `expect_dispatch(rlike)` / `expect_native(rlike)` wherever the
Scala test pins the engine. The explain-text checks `contains("JVM codegen
dispatcher: rlike")` map one-to-one onto these. `assertCodegenRan` is weaker:
it passes if anything in the block compiled a kernel.
**Existing fixtures to fix and extend**
- `rlike_java.sql`
- Covers L65-L155.
- Fix lines 27, 37, 41 and 49.
- Add `'\\d+'` and `'^(\\w)\\1$'` in a projection and in a filter,
`'foo(?=bar)'`, `'(?<=foo)bar'`, `'(?<digit>\\d)'`, `'^$'`, an all-NULL table,
and the backreference filter with GROUP BY.
- All of these patterns are outside the native subset, so pin them with
`expect_dispatch(rlike)`.
- `rlike_auto_native.sql`
- Covers L224 and L606 (4.0+).
- Line 28 becomes `expect_native(rlike)`. The file currently says routing
is asserted in the Scala suites this issue deletes.
- `rlike_rust.sql`
- Covers L501, plus `CometExpressionSuite` L1473 and L1507.
- Add `expect_native(rlike)` for `'\\d+'` under the opt-in.
- Add CR, LF and CRLF rows with `^` / `$` anchors.
- Add the curated class, qualifier and anchor set with doubled backslashes.
- `routing_regex_enabled.sql`
- Covers L488 and L943.
- Add `expect_dispatch(rlike)` for `'\\d+'`, and
`expect_dispatch(regexp_instr)`, which no fixture pins today.
- `routing_regex_disabled.sql`
- Covers L419 and L549.
- Add `expect_fallback(rlike:
spark.comet.exec.scalaUDF.codegen.enabled=false)` for the 64-branch pattern and
for `'\\d+'`.
- `regexp_extract.sql`
- Covers L682-L722.
- Fix the backslashes, add rows, and add all-NULL input.
- `regexp_extract_all.sql`
- Covers L730-L757.
- Fix the backslashes, and add `'([a-z])(\\d)'` with groups 1 and 2, and
`'foo(?=ba[rz])'`.
- `regexp_replace_java.sql`
- Covers L766-L797.
- Fix the backslashes.
- Add `'(\\w+) (\\w+)'` with `'$2 $1'`, `'foo(?=bar)'`, the empty pattern,
and all-NULL input.
- `regexp_instr.sql`
- Covers L852-L871.
- Fix the backslashes, and add groups 1 and 2 and a lookahead.
- `split_java.sql`
- Covers L879, L885 and L909.
- Add `'x,,y'`, `'\\d+'` and all-NULL input.
**New fixtures**
- `rlike_java_batches.sql`: L169, 5000 generated rows across several batches.
- `rlike_routing.sql`
- Covers the analyzer-budget tests: L237-L465 and L531.
- Write each pattern end to end as `expect_dispatch(rlike)` or
`expect_native(rlike)`.
- Patterns up to 16,388 characters must be spelled out in full; `repeat()`
would make the pattern non-literal and change the route.
- `rlike_native_parity.sql`
- Covers parity L129 and L136.
- 43 subjects × 30 corpus patterns, in a projection and in a filter, all
`expect_native(rlike)`.
- Write control characters and line terminators as `\uXXXX`.
- `rlike_native_batches.sql`: parity L163, with `spark.comet.batchSize=64`
and 5000 rows.
- `rlike_collation.sql` and `rlike_collation_opt_in.sql` (`MinSparkVersion:
4.0`)
- L620 and L648 use a collated subject with `expect_dispatch(rlike)`.
- RLike is not in Spark's collation-coercion list, so the pattern stays a
plain literal.
- `regexp_extract_batches.sql` (L917) and `regexp_combined.sql` (L929).
- `regexp_replace_errors.sql` and `regexp_replace_errors_row_filter.sql`
(4.0+)
- L803 and L828. These are blocked on #6617.
- `expect_error(INVALID_REGEXP_REPLACE)` can go in now as an interim check.
For the invalid-pattern tests (L144, L272, L579), check the `expect_error`
substring on each profile. Spark's codegen path throws a raw
`PatternSyntaxException` ("Unclosed character class") from generated init code,
while the interpreted path wraps it in `INVALID_PARAMETER_VALUE.PATTERN`.
**Blocked**
- **#6617 (error parity):** L272, L579, L803, L828.
- **#6618 (optimizer rules):**
- L531 needs `NullPropagation` excluded.
- L634 and L664 need a collated pattern literal, which only exists when
ConstantFolding runs.
- A mid-file `statement SET spark.sql.optimizer.excludedRules=...` may
work today; this was reasoned from the code but not run.
- **L440:** JDK 21 only, with a 303,103-character pattern. There is no
JDK-version directive. L419 covers the same route with a 64-branch pattern, and
`CometRegexSuite` L222 unit-tests the analyzer verdict, so this can stay in
Scala.
## Stays in Scala
- **All 14 `CometRegexSuite` tests.** They are unit tests of
`CometRegex.supportLevel`, the plan-time analyzer, with no Spark session.
Several of the patterns don't compile in Java at all.
- **L585.** It pins a Comet-only failure: the opt-in sends a lookahead to
the Rust engine, which rejects it, while Spark succeeds. `expect_error` needs
both engines to fail.
## Notes on the Scala tests
- **L144** ("invalid pattern falls back to Spark") never checks a fallback.
It is a weaker duplicate of L579.
- **L943** ("always run via the JVM dispatcher") never pins the dispatcher.
Its queries duplicate L682, L730 and L852.
- **Covered by other tests:** L606 is a subset of L224, and parity L129 is
implied by L136.
## Done when
- Every test in the table below is accounted for. Either a fixture covers it
(the same inputs, configs and assertion strength, or stronger), the fixture
that already covers it is cited, or it stays in Scala for the reason given
above.
- Each converted Scala test is deleted in the PR that adds its coverage. A
suite left empty is deleted and removed from the suite lists in
`.github/workflows/pr_build_linux.yml` and `pr_build_macos.yml`.
- The new and changed fixtures pass on every Spark profile. Apply
`run-all-spark-profiles`, because pull requests run only the default profile.
<details><summary>Every Scala test in scope, with its verdict and target
fixture</summary>
| Suite | Line | Test | Verdict | Target |
|---|---|---|---|---|
| `CometExpressionSuite` | 1367 | rlike simple case | covered |
string/rlike_rust.sql |
| `CometExpressionSuite` | 1462 | rlike with non-scalar pattern runs via
codegen dispatcher | covered | string/routing_regex_enabled.sql |
| `CometExpressionSuite` | 1473 | rlike whitespace | convert |
string/rlike_rust.sql |
| `CometExpressionSuite` | 1507 | rlike | convert | string/rlike_rust.sql |
| `CometRegExpJvmSuite` | 65 | rlike: projection produces Java regex
semantics with null handling | convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 72 | rlike: predicate filters rows using Java
regex semantics | convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 79 | rlike: backreference in projection (Java-only
construct) | convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 90 | rlike: backreference in predicate (Java-only
construct) | convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 101 | rlike: lookahead pattern (Java-only
construct) | convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 108 | rlike: lookbehind pattern (Java-only
construct) | convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 114 | rlike: embedded case-insensitive flag
(Java-only construct) | covered | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 120 | rlike: named groups (Java-only construct) |
convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 126 | rlike: empty pattern matches every non-null
row | covered | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 132 | rlike: empty subject string is handled
correctly | convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 138 | rlike: all-null subject column produces
all-null result | convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 144 | rlike: invalid pattern falls back to Spark |
convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 155 | rlike: combines with filter, projection, and
aggregate | convert | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 169 | rlike: many rows spanning multiple batches |
convert | string/rlike_java_batches.sql (new) |
| `CometRegExpJvmSuite` | 224 | rlike: safe literal pattern takes the native
path by default | convert | string/rlike_auto_native.sql |
| `CometRegExpJvmSuite` | 237 | rlike: Rust class set operations stay on the
dispatcher | convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 258 | rlike: leading-bracket class ranges stay on
the dispatcher | convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 272 | rlike: raw [ range endpoint stays on the
dispatcher and preserves Spark error | blocked: #6617 |
string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 287 | rlike: over-budget counted repetition stays
on the dispatcher | convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 329 | rlike: compile-budget boundary stays native
| convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 342 | rlike: exact-zero counted repetitions stay
native | convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 356 | rlike: aggregate expansion budget stays on
the dispatcher | convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 370 | rlike: nested quantified stars stay on the
dispatcher | convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 384 | rlike: capturing groups copied by counted
repetition stay on the dispatcher | convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 406 | rlike: uncounted capturing groups stay on
the dispatcher | convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 419 | rlike: uncounted capturing groups fall back
to Spark when the dispatcher is disabled | convert |
string/routing_regex_disabled.sql |
| `CometRegExpJvmSuite` | 440 | rlike: full uncounted-capture reproducer
falls back to Spark when JVM dispatcher is disabled | blocked: other |
string/routing_regex_disabled_large.sql (new) |
| `CometRegExpJvmSuite` | 465 | rlike: repeated complex character classes
stay on the dispatcher | convert | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 488 | rlike: unsafe literal pattern stays on the
JVM dispatcher by default | convert | string/routing_regex_enabled.sql |
| `CometRegExpJvmSuite` | 501 | rlike: unsafe but Rust-accepted pattern is
native after opt-in | convert | string/rlike_rust.sql |
| `CometRegExpJvmSuite` | 516 | rlike: non-literal pattern stays on the
dispatcher | covered | string/routing_regex_enabled.sql |
| `CometRegExpJvmSuite` | 531 | rlike: null literal pattern stays on the
dispatcher | blocked: #6618 | string/rlike_routing.sql (new) |
| `CometRegExpJvmSuite` | 549 | rlike: unsafe pattern falls back to Spark
when the dispatcher is disabled | convert | string/routing_regex_disabled.sql |
| `CometRegExpJvmSuite` | 559 | rlike: in-subset pattern stays native when
the dispatcher is disabled | covered | string/routing_regex_disabled.sql |
| `CometRegExpJvmSuite` | 579 | rlike: invalid pattern preserves Spark
exception type and message | blocked: #6617 | string/rlike_java.sql |
| `CometRegExpJvmSuite` | 585 | rlike: Java-only pattern with
allowIncompatible keeps existing opt-in behavior | keep | |
| `CometRegExpJvmSuite` | 606 | rlike: UTF8_BINARY safe literal is native on
Spark 4 | convert | string/rlike_auto_native.sql |
| `CometRegExpJvmSuite` | 620 | rlike: non-default collation on the subject
stays on the dispatcher | convert | string/rlike_collation.sql (new) |
| `CometRegExpJvmSuite` | 634 | rlike: non-default collation on the pattern
stays on the dispatcher | blocked: #6618 | string/rlike_collation.sql (new) |
| `CometRegExpJvmSuite` | 648 | rlike: collated subject stays on dispatcher
even with allowIncompatible | convert | string/rlike_collation_opt_in.sql (new)
|
| `CometRegExpJvmSuite` | 664 | rlike: collated pattern stays on dispatcher
even with allowIncompatible | blocked: #6618 |
string/rlike_collation_opt_in.sql (new) |
| `CometRegExpJvmSuite` | 682 | regexp_extract: basic group extraction |
convert | string/regexp_extract.sql |
| `CometRegExpJvmSuite` | 691 | regexp_extract: group 0 returns entire match
| convert | string/regexp_extract.sql |
| `CometRegExpJvmSuite` | 697 | regexp_extract: no match returns empty
string | covered | string/regexp_extract.sql |
| `CometRegExpJvmSuite` | 703 | regexp_extract: backreference pattern
(Java-only) | convert | string/regexp_extract.sql |
| `CometRegExpJvmSuite` | 709 | regexp_extract: lookahead pattern
(Java-only) | convert | string/regexp_extract.sql |
| `CometRegExpJvmSuite` | 715 | regexp_extract: embedded flags (Java-only) |
covered | string/regexp_extract.sql |
| `CometRegExpJvmSuite` | 722 | regexp_extract: all-null column | convert |
string/regexp_extract.sql |
| `CometRegExpJvmSuite` | 730 | regexp_extract_all: basic extraction of all
matches | convert | string/regexp_extract_all.sql |
| `CometRegExpJvmSuite` | 736 | regexp_extract_all: group 0 returns full
matches | covered | string/regexp_extract_all.sql |
| `CometRegExpJvmSuite` | 742 | regexp_extract_all: multiple groups |
convert | string/regexp_extract_all.sql |
| `CometRegExpJvmSuite` | 751 | regexp_extract_all: no matches returns empty
array | covered | string/regexp_extract_all.sql |
| `CometRegExpJvmSuite` | 757 | regexp_extract_all: lookahead pattern
(Java-only) | convert | string/regexp_extract_all.sql |
| `CometRegExpJvmSuite` | 766 | regexp_replace: basic replacement | convert
| string/regexp_replace_java.sql |
| `CometRegExpJvmSuite` | 772 | regexp_replace: backreference in pattern
(Java-only) | convert | string/regexp_replace_java.sql |
| `CometRegExpJvmSuite` | 778 | regexp_replace: backreference in replacement
| convert | string/regexp_replace_java.sql |
| `CometRegExpJvmSuite` | 785 | regexp_replace: lookahead pattern
(Java-only) | convert | string/regexp_replace_java.sql |
| `CometRegExpJvmSuite` | 791 | regexp_replace: empty pattern replaces
between characters | convert | string/regexp_replace_java.sql |
| `CometRegExpJvmSuite` | 797 | regexp_replace: all-null column | convert |
string/regexp_replace_java.sql |
| `CometRegExpJvmSuite` | 803 | regexp_replace: invalid group reference
surfaces the original Spark exception | blocked: #6617 |
string/regexp_replace_errors.sql (new) |
| `CometRegExpJvmSuite` | 828 | regexp_replace: invalid group reference in a
filter surfaces the original exception | blocked: #6617 |
string/regexp_replace_errors_row_filter.sql (new) |
| `CometRegExpJvmSuite` | 852 | regexp_instr: basic position finding |
convert | string/regexp_instr.sql |
| `CometRegExpJvmSuite` | 858 | regexp_instr: specific group position |
convert | string/regexp_instr.sql |
| `CometRegExpJvmSuite` | 865 | regexp_instr: no match returns 0 | covered |
string/regexp_instr.sql |
| `CometRegExpJvmSuite` | 871 | regexp_instr: lookahead (Java-only) |
convert | string/regexp_instr.sql |
| `CometRegExpJvmSuite` | 879 | split: basic regex split | convert |
string/split_java.sql |
| `CometRegExpJvmSuite` | 885 | split: regex pattern | convert |
string/split_java.sql |
| `CometRegExpJvmSuite` | 891 | split: with limit | covered |
string/split_java.sql |
| `CometRegExpJvmSuite` | 897 | split: limit -1 returns all | covered |
string/split_java.sql |
| `CometRegExpJvmSuite` | 903 | split: lookahead pattern (Java-only) |
covered | string/split_java.sql |
| `CometRegExpJvmSuite` | 909 | split: all-null column | convert |
string/split_java.sql |
| `CometRegExpJvmSuite` | 917 | regexp_extract: many rows spanning multiple
batches | convert | string/regexp_extract_batches.sql (new) |
| `CometRegExpJvmSuite` | 929 | all regexp expressions combined in one query
| convert | string/regexp_combined.sql (new) |
| `CometRegExpJvmSuite` | 943 | expressions with no native path always run
via the JVM dispatcher | convert | string/routing_regex_enabled.sql |
| `CometRegexParitySuite` | 129 | analyzer admits every corpus pattern |
convert | string/rlike_native_parity.sql (new) |
| `CometRegexParitySuite` | 136 | native rlike matches Spark for every
admitted pattern | convert | string/rlike_native_parity.sql (new) |
| `CometRegexParitySuite` | 163 | native rlike matches Spark across multiple
Arrow batches | convert | string/rlike_native_batches.sql (new) |
| `CometRegexSuite` | 38 | admits ASCII literals, classes, quantifiers,
groups, and alternation | keep | |
| `CometRegexSuite` | 69 | admits lexer-boundary patterns that a substring
search would misclassify | keep | |
| `CometRegexSuite` | 78 | rejects constructs that diverge from Java regex
or are unrecognized | keep | |
| `CometRegexSuite` | 110 | rejects scanner edge cases the whitelist must
not silently admit | keep | |
| `CometRegexSuite` | 131 | rejects Rust-only character-class set operations
| keep | |
| `CometRegexSuite` | 135 | rejects unescaped ] used as a character-class
atom or range endpoint | keep | |
| `CometRegexSuite` | 141 | rejects unescaped [ used as a character-class
range endpoint | keep | |
| `CometRegexSuite` | 146 | rejects counted or nested patterns that can
exceed the Rust compile budget | keep | |
| `CometRegexSuite` | 163 | rejects aggregate patterns that exceed the Rust
compile budget | keep | |
| `CometRegexSuite` | 179 | rejects nested quantified patterns that can
exceed the Rust compile budget | keep | |
| `CometRegexSuite` | 192 | rejects capturing groups duplicated by counted
repetition past the compile budget | keep | |
| `CometRegexSuite` | 208 | rejects repeated complex character classes that
can exceed the Rust compile budget | keep | |
| `CometRegexSuite` | 222 | rejects uncounted capturing groups that exceed
the compile budget | keep | |
| `CometRegexSuite` | 230 | capture cost propagates through uncounted
repetition and composition | keep | |
</details>
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]