1use crate::like::StringArrayType;
9
10use arrow_array::builder::{
11 BooleanBufferBuilder, GenericStringBuilder, ListBuilder, StringViewBuilder,
12};
13use arrow_array::cast::AsArray;
14use arrow_array::*;
15use arrow_buffer::{BooleanBuffer, NullBuffer};
16use arrow_data::ArrayDataBuilder;
17use arrow_schema::{ArrowError, DataType, Field};
18use regex::Regex;
19
20use std::collections::HashMap;
21use std::sync::Arc;
22
23pub fn regexp_is_match<'a, S1, S2, S3>(
57 array: &'a S1,
58 regex_array: &'a S2,
59 flags_array: Option<&'a S3>,
60) -> Result<BooleanArray, ArrowError>
61where
62 &'a S1: StringArrayType<'a>,
63 &'a S2: StringArrayType<'a>,
64 &'a S3: StringArrayType<'a>,
65{
66 if array.len() != regex_array.len() {
67 return Err(ArrowError::ComputeError(
68 "Cannot perform comparison operation on arrays of different length".to_string(),
69 ));
70 }
71
72 let nulls = NullBuffer::union(array.nulls(), regex_array.nulls());
73
74 let mut patterns: HashMap<String, Regex> = HashMap::new();
75 let mut result = BooleanBufferBuilder::new(array.len());
76
77 let complete_pattern = match flags_array {
78 Some(flags) => Box::new(
79 regex_array
80 .iter()
81 .zip(flags.iter())
82 .map(|(pattern, flags)| {
83 pattern.map(|pattern| match flags {
84 Some(flag) => format!("(?{flag}){pattern}"),
85 None => pattern.to_string(),
86 })
87 }),
88 ) as Box<dyn Iterator<Item = Option<String>>>,
89 None => Box::new(
90 regex_array
91 .iter()
92 .map(|pattern| pattern.map(|pattern| pattern.to_string())),
93 ),
94 };
95
96 array
97 .iter()
98 .zip(complete_pattern)
99 .map(|(value, pattern)| {
100 match (value, pattern) {
101 (Some(_), Some(pattern)) if pattern == *"" => {
104 result.append(true);
105 }
106 (Some(value), Some(pattern)) => {
107 let existing_pattern = patterns.get(&pattern);
108 let re = match existing_pattern {
109 Some(re) => re,
110 None => {
111 let re = Regex::new(pattern.as_str()).map_err(|e| {
112 ArrowError::ComputeError(format!(
113 "Regular expression did not compile: {e:?}"
114 ))
115 })?;
116 patterns.entry(pattern).or_insert(re)
117 }
118 };
119 result.append(re.is_match(value));
120 }
121 _ => result.append(false),
122 }
123 Ok(())
124 })
125 .collect::<Result<Vec<()>, ArrowError>>()?;
126
127 let data = unsafe {
128 ArrayDataBuilder::new(DataType::Boolean)
129 .len(array.len())
130 .buffers(vec![result.into()])
131 .nulls(nulls)
132 .build_unchecked()
133 };
134
135 Ok(BooleanArray::from(data))
136}
137
138pub fn regexp_is_match_scalar<'a, S>(
163 array: &'a S,
164 regex: &str,
165 flag: Option<&str>,
166) -> Result<BooleanArray, ArrowError>
167where
168 &'a S: StringArrayType<'a>,
169{
170 let mut result = BooleanBufferBuilder::new(array.len());
171
172 let pattern = match flag {
173 Some(flag) => format!("(?{flag}){regex}"),
174 None => regex.to_string(),
175 };
176
177 if pattern.is_empty() {
178 result.append_n(array.len(), true);
179 } else {
180 let re = Regex::new(pattern.as_str()).map_err(|e| {
181 ArrowError::ComputeError(format!("Regular expression did not compile: {e:?}"))
182 })?;
183 for i in 0..array.len() {
184 let value = array.value(i);
185 result.append(re.is_match(value));
186 }
187 }
188
189 let values = BooleanBuffer::from(result);
190 let nulls = array
191 .nulls()
192 .map(|n| n.inner().sliced())
193 .and_then(|b| NullBuffer::from_unsliced_buffer(b, array.len()));
194 Ok(BooleanArray::new(values, nulls))
195}
196
197macro_rules! process_regexp_array_match {
198 ($array:expr, $regex_array:expr, $flags_array:expr, $list_builder:expr) => {
199 let mut patterns: HashMap<String, Regex> = HashMap::new();
200
201 let complete_pattern = match $flags_array {
202 Some(flags) => Box::new($regex_array.iter().zip(flags.iter()).map(
203 |(pattern, flags)| {
204 pattern.map(|pattern| match flags {
205 Some(value) => format!("(?{value}){pattern}"),
206 None => pattern.to_string(),
207 })
208 },
209 )) as Box<dyn Iterator<Item = Option<String>>>,
210 None => Box::new(
211 $regex_array
212 .iter()
213 .map(|pattern| pattern.map(|pattern| pattern.to_string())),
214 ),
215 };
216
217 $array
218 .iter()
219 .zip(complete_pattern)
220 .map(|(value, pattern)| {
221 match (value, pattern) {
222 (Some(_), Some(pattern)) if pattern == *"" => {
225 $list_builder.values().append_value("");
226 $list_builder.append(true);
227 }
228 (Some(value), Some(pattern)) => {
229 let existing_pattern = patterns.get(&pattern);
230 let re = match existing_pattern {
231 Some(re) => re,
232 None => {
233 let re = Regex::new(pattern.as_str()).map_err(|e| {
234 ArrowError::ComputeError(format!(
235 "Regular expression did not compile: {e:?}"
236 ))
237 })?;
238 patterns.entry(pattern).or_insert(re)
239 }
240 };
241 match re.captures(value) {
242 Some(caps) => {
243 let mut iter = caps.iter();
244 if caps.len() > 1 {
245 iter.next();
246 }
247 for m in iter.flatten() {
248 $list_builder.values().append_value(m.as_str());
249 }
250
251 $list_builder.append(true);
252 }
253 None => $list_builder.append(false),
254 }
255 }
256 _ => $list_builder.append(false),
257 }
258 Ok(())
259 })
260 .collect::<Result<Vec<()>, ArrowError>>()?;
261 };
262}
263
264fn regexp_array_match<OffsetSize: OffsetSizeTrait>(
265 array: &GenericStringArray<OffsetSize>,
266 regex_array: &GenericStringArray<OffsetSize>,
267 flags_array: Option<&GenericStringArray<OffsetSize>>,
268) -> Result<ArrayRef, ArrowError> {
269 let builder: GenericStringBuilder<OffsetSize> = GenericStringBuilder::with_capacity(0, 0);
270 let mut list_builder = ListBuilder::new(builder);
271
272 process_regexp_array_match!(array, regex_array, flags_array, list_builder);
273
274 Ok(Arc::new(list_builder.finish()))
275}
276
277fn regexp_array_match_utf8view(
278 array: &StringViewArray,
279 regex_array: &StringViewArray,
280 flags_array: Option<&StringViewArray>,
281) -> Result<ArrayRef, ArrowError> {
282 let builder = StringViewBuilder::with_capacity(0);
283 let mut list_builder = ListBuilder::new(builder);
284
285 process_regexp_array_match!(array, regex_array, flags_array, list_builder);
286
287 Ok(Arc::new(list_builder.finish()))
288}
289
290fn get_scalar_pattern_flag<'a, OffsetSize: OffsetSizeTrait>(
291 regex_array: &'a dyn Array,
292 flag_array: Option<&'a dyn Array>,
293) -> (Option<&'a str>, Option<&'a str>) {
294 let regex = regex_array.as_string::<OffsetSize>();
295 let regex = regex.is_valid(0).then(|| regex.value(0));
296
297 if let Some(flag_array) = flag_array {
298 let flag = flag_array.as_string::<OffsetSize>();
299 (regex, flag.is_valid(0).then(|| flag.value(0)))
300 } else {
301 (regex, None)
302 }
303}
304
305fn get_scalar_pattern_flag_utf8view<'a>(
306 regex_array: &'a dyn Array,
307 flag_array: Option<&'a dyn Array>,
308) -> (Option<&'a str>, Option<&'a str>) {
309 let regex = regex_array.as_string_view();
310 let regex = regex.is_valid(0).then(|| regex.value(0));
311
312 if let Some(flag_array) = flag_array {
313 let flag = flag_array.as_string_view();
314 (regex, flag.is_valid(0).then(|| flag.value(0)))
315 } else {
316 (regex, None)
317 }
318}
319
320macro_rules! process_regexp_match {
321 ($array:expr, $regex:expr, $list_builder:expr) => {
322 let mut capture_locations = None;
324 $array
325 .iter()
326 .map(|value| {
327 match value {
328 Some(_) if $regex.as_str().is_empty() => {
331 $list_builder.values().append_value("");
332 $list_builder.append(true);
333 }
334 Some(value) => {
335 let locations =
336 capture_locations.get_or_insert_with(|| $regex.capture_locations());
337 match $regex.captures_read(locations, value) {
338 Some(_) => {
339 let mut groups = 0..locations.len();
340 if locations.len() > 1 {
341 groups.next();
343 }
344 let iter = groups.map(|group| locations.get(group));
345 for (start, end) in iter.flatten() {
346 $list_builder.values().append_value(&value[start..end]);
347 }
348 $list_builder.append(true);
349 }
350 None => $list_builder.append(false),
351 }
352 }
353 None => $list_builder.append(false),
354 }
355 Ok(())
356 })
357 .collect::<Result<Vec<()>, ArrowError>>()?
358 };
359}
360
361fn regexp_scalar_match<OffsetSize: OffsetSizeTrait>(
362 array: &GenericStringArray<OffsetSize>,
363 regex: &Regex,
364) -> Result<ArrayRef, ArrowError> {
365 let builder: GenericStringBuilder<OffsetSize> = GenericStringBuilder::with_capacity(0, 0);
366 let mut list_builder = ListBuilder::new(builder);
367
368 process_regexp_match!(array, regex, list_builder);
369
370 Ok(Arc::new(list_builder.finish()))
371}
372
373fn regexp_scalar_match_utf8view(
374 array: &StringViewArray,
375 regex: &Regex,
376) -> Result<ArrayRef, ArrowError> {
377 let builder = StringViewBuilder::with_capacity(0);
378 let mut list_builder = ListBuilder::new(builder);
379
380 process_regexp_match!(array, regex, list_builder);
381
382 Ok(Arc::new(list_builder.finish()))
383}
384
385pub fn regexp_match(
410 array: &dyn Array,
411 regex_array: &dyn Datum,
412 flags_array: Option<&dyn Datum>,
413) -> Result<ArrayRef, ArrowError> {
414 let (rhs, is_rhs_scalar) = regex_array.get();
415
416 if array.data_type() != rhs.data_type() {
417 return Err(ArrowError::ComputeError(
418 "regexp_match() requires both array and pattern to be either Utf8, Utf8View or LargeUtf8"
419 .to_string(),
420 ));
421 }
422
423 let (flags, is_flags_scalar) = match flags_array {
424 Some(flags) => {
425 let (flags, is_flags_scalar) = flags.get();
426 (Some(flags), Some(is_flags_scalar))
427 }
428 None => (None, None),
429 };
430
431 if is_flags_scalar.is_some_and(|is_flags_scalar| is_rhs_scalar != is_flags_scalar) {
432 return Err(ArrowError::ComputeError(
433 "regexp_match() requires both pattern and flags to be either scalar or array"
434 .to_string(),
435 ));
436 }
437
438 if flags.is_some_and(|flags| rhs.data_type() != flags.data_type()) {
439 return Err(ArrowError::ComputeError(
440 "regexp_match() requires both pattern and flags to be either Utf8, Utf8View or LargeUtf8"
441 .to_string(),
442 ));
443 }
444
445 if is_rhs_scalar {
446 let (regex, flag) = match rhs.data_type() {
448 DataType::Utf8View => get_scalar_pattern_flag_utf8view(rhs, flags),
449 DataType::Utf8 => get_scalar_pattern_flag::<i32>(rhs, flags),
450 DataType::LargeUtf8 => get_scalar_pattern_flag::<i64>(rhs, flags),
451 _ => {
452 return Err(ArrowError::ComputeError(
453 "regexp_match() requires pattern to be either Utf8, Utf8View or LargeUtf8"
454 .to_string(),
455 ));
456 }
457 };
458
459 let Some(regex) = regex else {
460 return Ok(new_null_array(
461 &DataType::List(Arc::new(Field::new_list_field(
462 array.data_type().clone(),
463 true,
464 ))),
465 array.len(),
466 ));
467 };
468
469 let pattern = if let Some(flag) = flag {
470 format!("(?{flag}){regex}")
471 } else {
472 regex.to_string()
473 };
474
475 let re = Regex::new(pattern.as_str()).map_err(|e| {
476 ArrowError::ComputeError(format!("Regular expression did not compile: {e:?}"))
477 })?;
478
479 match array.data_type() {
480 DataType::Utf8View => regexp_scalar_match_utf8view(array.as_string_view(), &re),
481 DataType::Utf8 => regexp_scalar_match(array.as_string::<i32>(), &re),
482 DataType::LargeUtf8 => regexp_scalar_match(array.as_string::<i64>(), &re),
483 _ => Err(ArrowError::ComputeError(
484 "regexp_match() requires array to be either Utf8, Utf8View or LargeUtf8"
485 .to_string(),
486 )),
487 }
488 } else {
489 match array.data_type() {
490 DataType::Utf8View => {
491 let regex_array = rhs.as_string_view();
492 let flags_array = flags.map(|flags| flags.as_string_view());
493 regexp_array_match_utf8view(array.as_string_view(), regex_array, flags_array)
494 }
495 DataType::Utf8 => {
496 let regex_array = rhs.as_string();
497 let flags_array = flags.map(|flags| flags.as_string());
498 regexp_array_match(array.as_string::<i32>(), regex_array, flags_array)
499 }
500 DataType::LargeUtf8 => {
501 let regex_array = rhs.as_string();
502 let flags_array = flags.map(|flags| flags.as_string());
503 regexp_array_match(array.as_string::<i64>(), regex_array, flags_array)
504 }
505 _ => Err(ArrowError::ComputeError(
506 "regexp_match() requires array to be either Utf8, Utf8View or LargeUtf8"
507 .to_string(),
508 )),
509 }
510 }
511}
512
513#[cfg(test)]
514mod tests {
515 use super::*;
516
517 #[test]
518 fn match_scalar_pattern_capture_groups() {
519 macro_rules! check {
520 ($array_type:ty, $builder_type:ty) => {{
521 let array = <$array_type>::from(vec![Some("aβ"), Some("β"), Some("none"), None]);
524 let cases: [(&str, [Option<&[&str]>; 4]); 4] = [
525 ("(a)?(β)", [Some(&["a", "β"]), Some(&["β"]), None, None]),
527 ("β", [Some(&["β"]), Some(&["β"]), None, None]),
529 ("()", [Some(&[""]), Some(&[""]), Some(&[""]), None]),
530 ("", [Some(&[""]), Some(&[""]), Some(&[""]), None]),
531 ];
532 for (pattern, rows) in cases {
533 let mut expected = ListBuilder::new(<$builder_type>::new());
534 for row in rows {
535 match row {
536 Some(values) => {
537 for value in values {
538 expected.values().append_value(value);
539 }
540 expected.append(true);
541 }
542 None => expected.append(false),
543 }
544 }
545 let expected = expected.finish();
546
547 let pattern_scalar = Scalar::new(<$array_type>::from(vec![pattern]));
548 let actual = regexp_match(&array, &pattern_scalar, None).unwrap();
549 assert_eq!(actual.as_list::<i32>(), &expected, "{pattern}");
550 }
551 }};
552 }
553 check!(StringArray, GenericStringBuilder<i32>);
554 check!(LargeStringArray, GenericStringBuilder<i64>);
555 check!(StringViewArray, StringViewBuilder);
556 }
557
558 macro_rules! test_match_single_group {
559 ($test_name:ident, $values:expr, $patterns:expr, $arr_type:ty, $builder_type:ty, $expected:expr) => {
560 #[test]
561 fn $test_name() {
562 let array: $arr_type = <$arr_type>::from($values);
563 let pattern: $arr_type = <$arr_type>::from($patterns);
564
565 let actual = regexp_match(&array, &pattern, None).unwrap();
566
567 let elem_builder: $builder_type = <$builder_type>::new();
568 let mut expected_builder = ListBuilder::new(elem_builder);
569
570 for val in $expected {
571 match val {
572 Some(v) => {
573 expected_builder.values().append_value(v);
574 expected_builder.append(true);
575 }
576 None => expected_builder.append(false),
577 }
578 }
579
580 let expected = expected_builder.finish();
581 let result = actual.as_any().downcast_ref::<ListArray>().unwrap();
582 assert_eq!(&expected, result);
583 }
584 };
585 }
586
587 test_match_single_group!(
588 match_single_group_string,
589 vec![
590 Some("abc-005-def"),
591 Some("X-7-5"),
592 Some("X545"),
593 None,
594 Some("foobarbequebaz"),
595 Some("foobarbequebaz"),
596 ],
597 vec![
598 r".*-(\d*)-.*",
599 r".*-(\d*)-.*",
600 r".*-(\d*)-.*",
601 r".*-(\d*)-.*",
602 r"(bar)(bequ1e)",
603 ""
604 ],
605 StringArray,
606 GenericStringBuilder<i32>,
607 [Some("005"), Some("7"), None, None, None, Some("")]
608 );
609 test_match_single_group!(
610 match_single_group_string_view,
611 vec![
612 Some("abc-005-def"),
613 Some("X-7-5"),
614 Some("X545"),
615 None,
616 Some("foobarbequebaz"),
617 Some("foobarbequebaz"),
618 ],
619 vec![
620 r".*-(\d*)-.*",
621 r".*-(\d*)-.*",
622 r".*-(\d*)-.*",
623 r".*-(\d*)-.*",
624 r"(bar)(bequ1e)",
625 ""
626 ],
627 StringViewArray,
628 StringViewBuilder,
629 [Some("005"), Some("7"), None, None, None, Some("")]
630 );
631
632 macro_rules! test_match_single_group_with_flags {
633 ($test_name:ident, $values:expr, $patterns:expr, $flags:expr, $array_type:ty, $builder_type:ty, $expected:expr) => {
634 #[test]
635 fn $test_name() {
636 let array: $array_type = <$array_type>::from($values);
637 let pattern: $array_type = <$array_type>::from($patterns);
638 let flags: $array_type = <$array_type>::from($flags);
639
640 let actual = regexp_match(&array, &pattern, Some(&flags)).unwrap();
641
642 let elem_builder: $builder_type = <$builder_type>::new();
643 let mut expected_builder = ListBuilder::new(elem_builder);
644
645 for val in $expected {
646 match val {
647 Some(v) => {
648 expected_builder.values().append_value(v);
649 expected_builder.append(true);
650 }
651 None => {
652 expected_builder.append(false);
653 }
654 }
655 }
656
657 let expected = expected_builder.finish();
658 let result = actual.as_any().downcast_ref::<ListArray>().unwrap();
659 assert_eq!(&expected, result);
660 }
661 };
662 }
663
664 test_match_single_group_with_flags!(
665 match_single_group_with_flags_string,
666 vec![Some("abc-005-def"), Some("X-7-5"), Some("X545"), None],
667 vec![r"x.*-(\d*)-.*"; 4],
668 vec!["i"; 4],
669 StringArray,
670 GenericStringBuilder<i32>,
671 [None, Some("7"), None, None]
672 );
673 test_match_single_group_with_flags!(
674 match_single_group_with_flags_stringview,
675 vec![Some("abc-005-def"), Some("X-7-5"), Some("X545"), None],
676 vec![r"x.*-(\d*)-.*"; 4],
677 vec!["i"; 4],
678 StringViewArray,
679 StringViewBuilder,
680 [None, Some("7"), None, None]
681 );
682
683 macro_rules! test_match_scalar_pattern {
684 ($test_name:ident, $values:expr, $pattern:expr, $flag:expr, $array_type:ty, $builder_type:ty, $expected:expr) => {
685 #[test]
686 fn $test_name() {
687 let array: $array_type = <$array_type>::from($values);
688
689 let pattern_scalar = Scalar::new(<$array_type>::from(vec![$pattern; 1]));
690 let flag_scalar = Scalar::new(<$array_type>::from(vec![$flag; 1]));
691
692 let actual = regexp_match(&array, &pattern_scalar, Some(&flag_scalar)).unwrap();
693
694 let elem_builder: $builder_type = <$builder_type>::new();
695 let mut expected_builder = ListBuilder::new(elem_builder);
696
697 for val in $expected {
698 match val {
699 Some(v) => {
700 expected_builder.values().append_value(v);
701 expected_builder.append(true);
702 }
703 None => expected_builder.append(false),
704 }
705 }
706
707 let expected = expected_builder.finish();
708 let result = actual.as_any().downcast_ref::<ListArray>().unwrap();
709 assert_eq!(&expected, result);
710 }
711 };
712 }
713
714 test_match_scalar_pattern!(
715 match_scalar_pattern_string_with_flags,
716 vec![
717 Some("abc-005-def"),
718 Some("x-7-5"),
719 Some("X-0-Y"),
720 Some("X545"),
721 None
722 ],
723 r"x.*-(\d*)-.*",
724 Some("i"),
725 StringArray,
726 GenericStringBuilder<i32>,
727 [None, Some("7"), Some("0"), None, None]
728 );
729 test_match_scalar_pattern!(
730 match_scalar_pattern_stringview_with_flags,
731 vec![
732 Some("abc-005-def"),
733 Some("x-7-5"),
734 Some("X-0-Y"),
735 Some("X545"),
736 None
737 ],
738 r"x.*-(\d*)-.*",
739 Some("i"),
740 StringViewArray,
741 StringViewBuilder,
742 [None, Some("7"), Some("0"), None, None]
743 );
744
745 test_match_scalar_pattern!(
746 match_scalar_pattern_string_no_flags,
747 vec![
748 Some("abc-005-def"),
749 Some("x-7-5"),
750 Some("X-0-Y"),
751 Some("X545"),
752 None
753 ],
754 r"x.*-(\d*)-.*",
755 None::<&str>,
756 StringArray,
757 GenericStringBuilder<i32>,
758 [None, Some("7"), None, None, None]
759 );
760 test_match_scalar_pattern!(
761 match_scalar_pattern_stringview_no_flags,
762 vec![
763 Some("abc-005-def"),
764 Some("x-7-5"),
765 Some("X-0-Y"),
766 Some("X545"),
767 None
768 ],
769 r"x.*-(\d*)-.*",
770 None::<&str>,
771 StringViewArray,
772 StringViewBuilder,
773 [None, Some("7"), None, None, None]
774 );
775
776 macro_rules! test_match_scalar_no_pattern {
777 ($test_name:ident, $values:expr, $array_type:ty, $pattern_type:expr, $builder_type:ty, $expected:expr) => {
778 #[test]
779 fn $test_name() {
780 let array: $array_type = <$array_type>::from($values);
781 let pattern = Scalar::new(new_null_array(&$pattern_type, 1));
782
783 let actual = regexp_match(&array, &pattern, None).unwrap();
784
785 let elem_builder: $builder_type = <$builder_type>::new();
786 let mut expected_builder = ListBuilder::new(elem_builder);
787
788 for val in $expected {
789 match val {
790 Some(v) => {
791 expected_builder.values().append_value(v);
792 expected_builder.append(true);
793 }
794 None => expected_builder.append(false),
795 }
796 }
797
798 let expected = expected_builder.finish();
799 let result = actual.as_any().downcast_ref::<ListArray>().unwrap();
800 assert_eq!(&expected, result);
801 }
802 };
803 }
804
805 test_match_scalar_no_pattern!(
806 match_scalar_no_pattern_string,
807 vec![Some("abc-005-def"), Some("X-7-5"), Some("X545"), None],
808 StringArray,
809 DataType::Utf8,
810 GenericStringBuilder<i32>,
811 [None::<&str>, None, None, None]
812 );
813 test_match_scalar_no_pattern!(
814 match_scalar_no_pattern_stringview,
815 vec![Some("abc-005-def"), Some("X-7-5"), Some("X545"), None],
816 StringViewArray,
817 DataType::Utf8View,
818 StringViewBuilder,
819 [None::<&str>, None, None, None]
820 );
821
822 macro_rules! test_match_single_group_not_skip {
823 ($test_name:ident, $values:expr, $pattern:expr, $array_type:ty, $builder_type:ty, $expected:expr) => {
824 #[test]
825 fn $test_name() {
826 let array: $array_type = <$array_type>::from($values);
827 let pattern: $array_type = <$array_type>::from(vec![$pattern]);
828
829 let actual = regexp_match(&array, &pattern, None).unwrap();
830
831 let elem_builder: $builder_type = <$builder_type>::new();
832 let mut expected_builder = ListBuilder::new(elem_builder);
833
834 for val in $expected {
835 match val {
836 Some(v) => {
837 expected_builder.values().append_value(v);
838 expected_builder.append(true);
839 }
840 None => expected_builder.append(false),
841 }
842 }
843
844 let expected = expected_builder.finish();
845 let result = actual.as_any().downcast_ref::<ListArray>().unwrap();
846 assert_eq!(&expected, result);
847 }
848 };
849 }
850
851 test_match_single_group_not_skip!(
852 match_single_group_not_skip_string,
853 vec![Some("foo"), Some("bar")],
854 r"foo",
855 StringArray,
856 GenericStringBuilder<i32>,
857 [Some("foo")]
858 );
859 test_match_single_group_not_skip!(
860 match_single_group_not_skip_stringview,
861 vec![Some("foo"), Some("bar")],
862 r"foo",
863 StringViewArray,
864 StringViewBuilder,
865 [Some("foo")]
866 );
867
868 macro_rules! test_flag_utf8 {
869 ($test_name:ident, $left:expr, $right:expr, $op:expr, $expected:expr) => {
870 #[test]
871 fn $test_name() {
872 let left = $left;
873 let right = $right;
874 let res = $op(&left, &right, None).unwrap();
875 let expected = $expected;
876 assert_eq!(expected.len(), res.len());
877 for i in 0..res.len() {
878 let v = res.value(i);
879 assert_eq!(v, expected[i]);
880 }
881 }
882 };
883 ($test_name:ident, $left:expr, $right:expr, $flag:expr, $op:expr, $expected:expr) => {
884 #[test]
885 fn $test_name() {
886 let left = $left;
887 let right = $right;
888 let flag = Some($flag);
889 let res = $op(&left, &right, flag.as_ref()).unwrap();
890 let expected = $expected;
891 assert_eq!(expected.len(), res.len());
892 for i in 0..res.len() {
893 let v = res.value(i);
894 assert_eq!(v, expected[i]);
895 }
896 }
897 };
898 }
899
900 macro_rules! test_flag_utf8_scalar {
901 ($test_name:ident, $left:expr, $right:expr, $op:expr, $expected:expr) => {
902 #[test]
903 fn $test_name() {
904 let left = $left;
905 let res = $op(&left, $right, None).unwrap();
906 let expected = $expected;
907 assert_eq!(expected.len(), res.len());
908 for i in 0..res.len() {
909 let v = res.value(i);
910 assert_eq!(
911 v,
912 expected[i],
913 "unexpected result when comparing {} at position {} to {} ",
914 left.value(i),
915 i,
916 $right
917 );
918 }
919 }
920 };
921 ($test_name:ident, $left:expr, $right:expr, $flag:expr, $op:expr, $expected:expr) => {
922 #[test]
923 fn $test_name() {
924 let left = $left;
925 let flag = Some($flag);
926 let res = $op(&left, $right, flag).unwrap();
927 let expected = $expected;
928 assert_eq!(expected.len(), res.len());
929 for i in 0..res.len() {
930 let v = res.value(i);
931 assert_eq!(
932 v,
933 expected[i],
934 "unexpected result when comparing {} at position {} to {} ",
935 left.value(i),
936 i,
937 $right
938 );
939 }
940 }
941 };
942 }
943
944 test_flag_utf8!(
945 test_array_regexp_is_match_utf8,
946 StringArray::from(vec!["arrow", "arrow", "arrow", "arrow", "arrow", "arrow"]),
947 StringArray::from(vec!["^ar", "^AR", "ow$", "OW$", "foo", ""]),
948 regexp_is_match::<StringArray, StringArray, StringArray>,
949 [true, false, true, false, false, true]
950 );
951 test_flag_utf8!(
952 test_array_regexp_is_match_utf8_insensitive,
953 StringArray::from(vec!["arrow", "arrow", "arrow", "arrow", "arrow", "arrow"]),
954 StringArray::from(vec!["^ar", "^AR", "ow$", "OW$", "foo", ""]),
955 StringArray::from(vec!["i"; 6]),
956 regexp_is_match,
957 [true, true, true, true, false, true]
958 );
959
960 test_flag_utf8_scalar!(
961 test_array_regexp_is_match_utf8_scalar,
962 StringArray::from(vec!["arrow", "ARROW", "parquet", "PARQUET"]),
963 "^ar",
964 regexp_is_match_scalar,
965 [true, false, false, false]
966 );
967 test_flag_utf8_scalar!(
968 test_array_regexp_is_match_utf8_scalar_empty,
969 StringArray::from(vec!["arrow", "ARROW", "parquet", "PARQUET"]),
970 "",
971 regexp_is_match_scalar,
972 [true, true, true, true]
973 );
974 test_flag_utf8_scalar!(
975 test_array_regexp_is_match_utf8_scalar_insensitive,
976 StringArray::from(vec!["arrow", "ARROW", "parquet", "PARQUET"]),
977 "^ar",
978 "i",
979 regexp_is_match_scalar,
980 [true, true, false, false]
981 );
982
983 test_flag_utf8!(
984 tes_array_regexp_is_match,
985 StringViewArray::from(vec!["arrow", "arrow", "arrow", "arrow", "arrow", "arrow"]),
986 StringViewArray::from(vec!["^ar", "^AR", "ow$", "OW$", "foo", ""]),
987 regexp_is_match::<StringViewArray, StringViewArray, StringViewArray>,
988 [true, false, true, false, false, true]
989 );
990 test_flag_utf8!(
991 test_array_regexp_is_match_2,
992 StringViewArray::from(vec!["arrow", "arrow", "arrow", "arrow", "arrow", "arrow"]),
993 StringArray::from(vec!["^ar", "^AR", "ow$", "OW$", "foo", ""]),
994 regexp_is_match::<StringViewArray, GenericStringArray<i32>, GenericStringArray<i32>>,
995 [true, false, true, false, false, true]
996 );
997 test_flag_utf8!(
998 test_array_regexp_is_match_insensitive,
999 StringViewArray::from(vec![
1000 "Official Rust implementation of Apache Arrow",
1001 "apache/arrow-rs",
1002 "apache/arrow-rs",
1003 "parquet",
1004 "parquet",
1005 "row",
1006 "row",
1007 ]),
1008 StringViewArray::from(vec![
1009 ".*rust implement.*",
1010 "^ap",
1011 "^AP",
1012 "et$",
1013 "ET$",
1014 "foo",
1015 ""
1016 ]),
1017 StringViewArray::from(vec!["i"; 7]),
1018 regexp_is_match::<StringViewArray, StringViewArray, StringViewArray>,
1019 [true, true, true, true, true, false, true]
1020 );
1021 test_flag_utf8!(
1022 test_array_regexp_is_match_insensitive_2,
1023 LargeStringArray::from(vec!["arrow", "arrow", "arrow", "arrow", "arrow", "arrow"]),
1024 StringViewArray::from(vec!["^ar", "^AR", "ow$", "OW$", "foo", ""]),
1025 StringArray::from(vec!["i"; 6]),
1026 regexp_is_match::<GenericStringArray<i64>, StringViewArray, GenericStringArray<i32>>,
1027 [true, true, true, true, false, true]
1028 );
1029
1030 test_flag_utf8_scalar!(
1031 test_array_regexp_is_match_scalar,
1032 StringViewArray::from(vec![
1033 "apache/arrow-rs",
1034 "APACHE/ARROW-RS",
1035 "parquet",
1036 "PARQUET",
1037 ]),
1038 "^ap",
1039 regexp_is_match_scalar::<StringViewArray>,
1040 [true, false, false, false]
1041 );
1042 test_flag_utf8_scalar!(
1043 test_array_regexp_is_match_scalar_empty,
1044 StringViewArray::from(vec![
1045 "apache/arrow-rs",
1046 "APACHE/ARROW-RS",
1047 "parquet",
1048 "PARQUET",
1049 ]),
1050 "",
1051 regexp_is_match_scalar::<StringViewArray>,
1052 [true, true, true, true]
1053 );
1054 test_flag_utf8_scalar!(
1055 test_array_regexp_is_match_scalar_insensitive,
1056 StringViewArray::from(vec![
1057 "apache/arrow-rs",
1058 "APACHE/ARROW-RS",
1059 "parquet",
1060 "PARQUET",
1061 ]),
1062 "^ap",
1063 "i",
1064 regexp_is_match_scalar::<StringViewArray>,
1065 [true, true, false, false]
1066 );
1067}