Return null for non-participating regex capture groups in strings::extract - #23123
Conversation
A capture group that does not participate in the overall match (e.g. an unmatched optional group) left its range uninitialized but was returned as a valid empty match, so strings::extract produced an empty string where pandas produces a null.
|
/okay to test a17fa3a |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughUpdates regex extraction so non-participating capture groups return ChangesNon-participating group fix
Estimated code review effort: 2 (Simple) | ~12 minutes Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
cpp/src/strings/regex/regex.inl (1)
240-253: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDoxygen doesn't mention the new nullopt-for-non-participating-group behavior.
The
@returndoc still just says>0 if match found; consider a brief note that non-overall groups that did not participate in the match now returncuda::std::nullopteven when the overall match succeeded.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@cpp/src/strings/regex/regex.inl` around lines 240 - 253, The Doxygen for the regex evaluation function does not describe the new non-participating-group return behavior. Update the comment on the main regex execution API (the function documented with dstr, jnk, begin, end, and group_id) so the `@return` section still notes successful matches but also explicitly mentions that non-overall groups which do not participate now return cuda::std::nullopt.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@cpp/src/strings/regex/regex.inl`:
- Around line 240-253: The Doxygen for the regex evaluation function does not
describe the new non-participating-group return behavior. Update the comment on
the main regex execution API (the function documented with dstr, jnk, begin,
end, and group_id) so the `@return` section still notes successful matches but
also explicitly mentions that non-overall groups which do not participate now
return cuda::std::nullopt.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 54bae93e-5ce0-4976-ae99-1fee2502ec94
📒 Files selected for processing (3)
cpp/src/strings/regex/regex.inlcpp/tests/strings/extract_tests.cpppython/cudf/cudf/tests/series/accessors/test_str.py
davidwendt
left a comment
There was a problem hiding this comment.
The seems like it should be a breaking change since it is changing the expected behavior.
|
/okay to test c2e326c |
|
/okay to test 359747e |
Co-authored-by: David Wendt <45795991+davidwendt@users.noreply.github.com>
davidwendt
left a comment
There was a problem hiding this comment.
Approving C++ changes.
|
/okay to test 4eafc94 |
|
/okay to test 5870411 |
|
/merge |
…15280) **JaCoCo sql-plugin line coverage: +25 lines** (8 interval-parsing lines covered by `CsvScanForIntervalSuite`, shim 351; 17 `regexp_extract_all` lines covered by `test_regexp_extract_all_idx_positive`, shim 401) Closes #15275. Closes #15281. ## Root cause [rapidsai/cudf#23123](rapidsai/cudf#23123) changed regex extraction so optional capture groups that do not participate in a match return null instead of an empty string. Two cudf-spark paths relied on the previous representation: - day-time interval parsing treated optional sign and fractional-seconds captures as non-null values; - `regexp_extract_all` removed null list elements, which lost Spark's required empty-string placeholders for unmatched captures and could change list cardinality. Because both failures come from the same cuDF behavior change and independently break premerge, this PR contains the two focused compatibility fixes so the complete Blossom matrix can validate and merge them together. ## Fix Due to the nature of the premerge CI, I have to merge 2 different fixes(though they are caused by the same upstream change) in 1 PR: ### Day-time intervals - Normalize missing sign comparisons to `false` before combining the two signs with XOR. - Normalize a missing fractional-seconds capture to numeric zero before decimal conversion. ### `regexp_extract_all` - Preserve one output list element per regex match. - Convert an unmatched requested capture from null to Spark's empty string. - Preserve empty lists for non-null inputs with no matches and null lists for null inputs. - Restore the input row count when cuDF returns a zero-row list column for an all-no-match batch. The longer-term proposal to move Spark-specific regex result normalization behind the JNI boundary is tracked in [NVIDIA/cudf-spark-jni#4821](NVIDIA/cudf-spark-jni#4821). This PR keeps the current ownership boundaries and fixes the two affected consumers without adding a temporary compatibility switch. ## Validation - Scala 2.12 / Spark 3.5.1 `CsvScanForIntervalSuite`: `Tests: succeeded 6, failed 0, canceled 1, ignored 0, pending 0`; reactor `BUILD SUCCESS`. - Scala 2.13 / Spark 4.0.1 production and integration-test build: reactor `BUILD SUCCESS`. - Spark 4.0.1 `regexp_test.py::test_regexp_extract_all_idx_positive`: `3 passed, 39627 deselected`. - The same regex IT with forced OOM injection: `3 passed, 39627 deselected`. - JaCoCo fix-line intersection: 8 of 16 added interval lines plus 17 of 30 added regex lines covered, for 25 unique added production lines. - `scripts/check-shim-coverage.sh`: passed. ## Performance impact The interval change adds null normalization only in the specialized string-to-day-time-interval path. The regex change removes the previous max-list-width expansion and reconstructs the existing variable-length list column in place; the focused microbenchmark measured 0.538 s before versus 0.464 s after (about 13.7% faster). No new regex match pass is introduced. Documentation - [ ] Updated for new or modified user-facing features or behaviors - [x] No user-facing change Testing - [ ] Added or modified tests to cover new code paths - [x] Covered by existing tests (`CsvScanForIntervalSuite` and `regexp_test.py::test_regexp_extract_all_idx_positive`, including forced OOM injection.) - [ ] Not required Performance - [x] Tests ran and results are added in the PR description - [ ] Issue filed with a link in the PR description - [ ] Not required --------- Signed-off-by: Allen Xu <allxu@nvidia.com>
Description
Fixes the
tests/strings/test_extract.pycapture-group failures undercudf.pandas(e.g.test_extract_dataframe_capture_groups_index, 135 parametrizations): a capture group that did not participate in the overall match (such as an unmatched optional group,(\d)?against"C") left its range uninitialized butregexecreturned it as a valid match, sostrings::extractproduced an empty string where pandas produces null. The final return now requires a valid range for sub-groups; a group that participates with an empty match ((\d)*) still returns"". Adds a libcudf gtest and a cudf Python test. The remainingstring=objectfailures in that file are an unrelated, inherent object-dtypeNone-vs-NaNrepresentation difference and stay in the xfail list.Checklist