fix: support Dictionary arrays in regex_match_dyn (#23709)#23722
fix: support Dictionary arrays in regex_match_dyn (#23709)#23722Jithendra2608 wants to merge 7 commits into
Conversation
| } | ||
|
|
||
| /// Creates a $FUNC(left: ArrayRef, right: ScalarValue) that | ||
| /// downcasts left / right to the appropriate integral type and calls the kernel |
There was a problem hiding this comment.
Are these comments outdated? CAn we update instead of deleting?
There was a problem hiding this comment.
Pull request overview
This PR addresses #23709 by extending the regex array-array kernel dispatch (regex_match_dyn) to handle dictionary-encoded string arrays (e.g., Dictionary(Int32, Utf8)) so SIMILAR TO / regex operators don’t error when the value side is dictionary-encoded and the pattern is a non-scalar array.
Changes:
- Add a
DataType::Dictionarymatch arm inregex_match_dynthat unpacks dictionary values and reuses the existing string regex paths. - Add an sqllogictest regression case covering the reported failing query shape.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| datafusion/physical-expr/src/expressions/binary/kernels.rs | Adds dictionary handling to regex_match_dyn by unpacking dictionary arrays and re-dispatching into existing string regex kernels. |
| datafusion/sqllogictest/test_files/regexp/regexp_match.slt | Adds a regression test reproducing issue #23709 using arrow_cast(..., 'Dictionary(Int32, Utf8)') SIMILAR TO .... |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| DataType::Dictionary(_, _) => { | ||
| let dict = left.as_any_dictionary(); | ||
| let unpacked_left = arrow::compute::kernels::take::take( | ||
| dict.values().as_ref(), | ||
| dict.keys(), | ||
| None, | ||
| )?; | ||
| regex_match_dyn(&unpacked_left, right, not_match, flag) | ||
| } |
| NULL | ||
|
|
||
|
|
||
| # Test for Issue 23709: regex_match_dyn with Dictionary encoded patterns |
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #23722 +/- ##
==========================================
- Coverage 80.71% 80.71% -0.01%
==========================================
Files 1089 1089
Lines 368275 368294 +19
Branches 368275 368294 +19
==========================================
- Hits 297260 297256 -4
- Misses 53301 53318 +17
- Partials 17714 17720 +6 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
u70b3
left a comment
There was a problem hiding this comment.
A few line-specific notes from a deep dive — overall assessment in a separate comment below.
|
|
||
| use std::sync::Arc; | ||
|
|
||
| /// Downcasts $LEFT and $RIGHT to $ARRAY_TYPE and then calls $KERNEL($LEFT, $RIGHT) |
There was a problem hiding this comment.
These doc-comment removals (here and on the three macros below) look unrelated to the fix — and the double blank lines they leave behind are exactly what fails the cargo fmt CI job. Could you restore them?
| } | ||
| DataType::Dictionary(_, _) => { | ||
| let dict = left.as_any_dictionary(); | ||
| let unpacked_left = arrow::compute::kernels::take::take( |
There was a problem hiding this comment.
nit: this file imports its other arrow kernels at the top (use arrow::compute::kernels::...); mind adding take there too instead of using the fully-qualified path at the call site?
| dict.keys(), | ||
| None, | ||
| )?; | ||
| regex_match_dyn(&unpacked_left, right, not_match, flag) |
There was a problem hiding this comment.
One case worth knowing about: on this branch's base, a dictionary whose value width differs from the pattern — e.g. arrow_cast(s, 'Dictionary(Int32, Utf8View)') SIMILAR TO pat — unpacks fine here, but this recursive call then hits .expect("failed to downcast array") in regexp_is_match_flag! and panics (before this PR, the same query returned a clean "not supported" internal error, so on this base the PR turns an error into a panic for that combo). It's the same pre-existing panic class as #22886 for plain arrays, and #23704 on current main already replaced those .expect()s with exec_err! — so rebasing onto main turns this into a clean execution error. I verified that on a cherry-pick of this commit; more context in my summary comment.
| NULL | ||
|
|
||
|
|
||
| # Test for Issue 23709: regex_match_dyn with Dictionary encoded patterns |
There was a problem hiding this comment.
nit: it's the value side that is dictionary-encoded here; the pattern is a plain array. Maybe "Dictionary encoded value side" to avoid confusion?
| CREATE TABLE dict_regex_p AS SELECT * FROM (VALUES ('(auth|login)')) v(pat); | ||
|
|
||
| query B | ||
| SELECT arrow_cast(dict_regex_t.s, 'Dictionary(Int32, Utf8)') SIMILAR TO dict_regex_p.pat FROM dict_regex_t CROSS JOIN dict_regex_p; |
There was a problem hiding this comment.
Heads-up: after a rebase onto current main, this test passes without exercising the new kernel arm — #23704's analyzer coercion now inserts CAST(... AS Utf8) around the dictionary value side (EXPLAIN shows CAST(CAST(s@0 AS Dictionary(Int32, Utf8)) AS Utf8) ~ pat@1), so the query decodes before reaching regex_match_dyn. Definitely keep this as end-to-end regression coverage for the #23709 repro, but please also add a Rust unit test that calls regex_match_dyn with a DictionaryArray directly so the arm itself is covered — an example is in my summary comment.
| DROP TABLE dict_regex_t; | ||
|
|
||
| statement ok | ||
| DROP TABLE dict_regex_p; No newline at end of file |
There was a problem hiding this comment.
nit: missing trailing newline at end of file.
|
Thanks for the quick turnaround on this, and sorry it took me a while to get to the review. I went fairly deep: checked out the PR, probed it with extra sqllogictests on its base, cherry-picked it onto current Context shift: the #23709 repro is already fixed at the SQL level on mainThis PR branched off just before #23704 merged (current I verified the repro returns Two consequences:
#[test]
fn test_regex_match_dyn_dictionary() -> Result<()> {
let values = StringArray::from(vec![Some("user auth failed"), Some("anonymous")]);
let keys = Int32Array::from(vec![Some(0), Some(1), None, Some(0)]);
let left: ArrayRef = Arc::new(DictionaryArray::new(keys, Arc::new(values)));
let right: ArrayRef = Arc::new(StringArray::from(vec![
Some("(auth|login)"), Some("^anon"), Some("x"), Some("nope"),
]));
let result = regex_match_dyn(&left, &right, false, false)?;
assert_eq!(
result.as_any().downcast_ref::<BooleanArray>().unwrap(),
&BooleanArray::from(vec![Some(true), Some(true), None, Some(false)])
);
// negation must preserve nulls
let result = regex_match_dyn(&left, &right, true, false)?;
assert_eq!(
result.as_any().downcast_ref::<BooleanArray>().unwrap(),
&BooleanArray::from(vec![Some(false), Some(false), None, Some(true)])
);
Ok(())
}What I verified about the fix itselfProbed on the PR's base, where SIMILAR TO applies no coercion and the arm is genuinely exercised from SQL: multi-row array-array matching with per-row patterns,
Nits not covered inline
VerdictApproach is correct and the implementation is sound — concept ACK. Landing checklist: rebase onto main, add a direct unit test for the arm, and restore the deleted doc comments (which also fixes the failing |
2e4d3af to
3fd1b57
Compare
|
Confirmed — thanks for the quick turnaround! I pulled the new head (
One tiny leftover: the PR body still has stray CI on the new commit hasn't started yet (a maintainer needs to approve the run for first-time contributors). Once it's green, this looks ready to me — nice work! |
| use arrow::compute::kernels::boolean::not; | ||
| use arrow::compute::kernels::comparison::{regexp_is_match, regexp_is_match_scalar}; | ||
| use arrow::compute::kernels::take::take; | ||
| use arrow::datatypes::DataType; |
Which issue does this PR close?
This PR fixes the internal error thrown when SIMILAR TO is used with a dictionary-encoded value and a non-scalar array pattern, as described in #23709.
-->
What changes are included in this PR?
1.Added a DataType::Dictionary match arm to regex_match_dyn in kernels.rs that mirrors the scalar path.
2.Unpacks the dictionary using the take kernel and routes it back into the standard string matching paths.
3.Added an sqllogictest to regexp_match.slt to verify the fix against the failing query.
Are these changes tested?