1use memchr::memchr;
20
21use arrow::array::ArrayDataBuilder;
22use arrow::array::BufferBuilder;
23use arrow::array::GenericStringArray;
24use arrow::array::StringViewBuilder;
25use arrow::array::{Array, ArrayRef, OffsetSizeTrait};
26use arrow::array::{ArrayAccessor, StringViewArray};
27use arrow::array::{ArrayIter, AsArray, new_null_array};
28use arrow::datatypes::DataType;
29use datafusion_common::ScalarValue;
30use datafusion_common::cast::{
31 as_large_string_array, as_string_array, as_string_view_array,
32};
33use datafusion_common::exec_err;
34use datafusion_common::plan_err;
35use datafusion_common::{
36 DataFusionError, Result, cast::as_generic_string_array, internal_err,
37};
38use datafusion_expr::ColumnarValue;
39use datafusion_expr::TypeSignature;
40use datafusion_expr::function::Hint;
41use datafusion_expr::{
42 Documentation, ScalarFunctionArgs, ScalarUDFImpl, Signature, Volatility,
43};
44use datafusion_macros::user_doc;
45use regex::{CaptureLocations, Regex};
46use std::borrow::Cow;
47use std::collections::HashMap;
48use std::sync::{Arc, LazyLock};
49
50#[user_doc(
51 doc_section(label = "Regular Expression Functions"),
52 description = "Replaces substrings in a string that match a [regular expression](https://docs.rs/regex/latest/regex/#syntax).",
53 syntax_example = "regexp_replace(str, regexp, replacement[, flags])",
54 sql_example = r#"```sql
55> select regexp_replace('foobarbaz', 'b(..)', 'X\\1Y', 'g');
56+------------------------------------------------------------------------+
57| regexp_replace(Utf8("foobarbaz"),Utf8("b(..)"),Utf8("X\1Y"),Utf8("g")) |
58+------------------------------------------------------------------------+
59| fooXarYXazY |
60+------------------------------------------------------------------------+
61SELECT regexp_replace('aBc', '(b|d)', 'Ab\\1a', 'i');
62+-------------------------------------------------------------------+
63| regexp_replace(Utf8("aBc"),Utf8("(b|d)"),Utf8("Ab\1a"),Utf8("i")) |
64+-------------------------------------------------------------------+
65| aAbBac |
66+-------------------------------------------------------------------+
67```
68Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/regexp.rs)
69"#,
70 standard_argument(name = "str", prefix = "String"),
71 argument(
72 name = "regexp",
73 description = "Regular expression to match against.
74 Can be a constant, column, or function."
75 ),
76 argument(
77 name = "replacement",
78 description = "Replacement string expression to operate on. Can be a constant, column, or function, and any combination of operators."
79 ),
80 argument(
81 name = "flags",
82 description = r#"Optional regular expression flags that control the behavior of the regular expression. The following flags are supported:
83- **g**: (global) Search globally and don't return after the first match
84- **i**: case-insensitive: letters match both upper and lower case
85- **m**: multi-line mode: ^ and $ match begin/end of line
86- **s**: allow . to match \n
87- **R**: enables CRLF mode: when multi-line mode is enabled, \r\n is used
88- **U**: swap the meaning of x* and x*?"#
89 )
90)]
91#[derive(Debug, PartialEq, Eq, Hash)]
92pub struct RegexpReplaceFunc {
93 signature: Signature,
94}
95impl Default for RegexpReplaceFunc {
96 fn default() -> Self {
97 Self::new()
98 }
99}
100
101impl RegexpReplaceFunc {
102 pub fn new() -> Self {
103 use DataType::*;
104 use TypeSignature::*;
105 Self {
106 signature: Signature::one_of(
107 vec![
108 Uniform(3, vec![Utf8View, LargeUtf8, Utf8]),
109 Uniform(4, vec![Utf8View, LargeUtf8, Utf8]),
110 ],
111 Volatility::Immutable,
112 ),
113 }
114 }
115}
116
117impl ScalarUDFImpl for RegexpReplaceFunc {
118 fn name(&self) -> &str {
119 "regexp_replace"
120 }
121
122 fn signature(&self) -> &Signature {
123 &self.signature
124 }
125
126 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
127 use DataType::*;
128 Ok(match &arg_types[0] {
129 LargeUtf8 | LargeBinary => LargeUtf8,
130 Utf8 | Binary => Utf8,
131 Utf8View | BinaryView => Utf8View,
132 Null => Null,
133 Dictionary(_, t) => match **t {
134 LargeUtf8 | LargeBinary => LargeUtf8,
135 Utf8 | Binary => Utf8,
136 Null => Null,
137 _ => {
138 return plan_err!(
139 "the regexp_replace can only accept strings but got {:?}",
140 **t
141 );
142 }
143 },
144 other => {
145 return plan_err!(
146 "The regexp_replace function can only accept strings. Got {other}"
147 );
148 }
149 })
150 }
151
152 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
153 let args = &args.args;
154
155 let len = args
156 .iter()
157 .fold(Option::<usize>::None, |acc, arg| match arg {
158 ColumnarValue::Scalar(_) => acc,
159 ColumnarValue::Array(a) => Some(a.len()),
160 });
161
162 let is_scalar = len.is_none();
163 let result = regexp_replace_func(args);
164 if is_scalar {
165 let result = result.and_then(|arr| ScalarValue::try_from_array(&arr, 0));
167 result.map(ColumnarValue::Scalar)
168 } else {
169 result.map(ColumnarValue::Array)
170 }
171 }
172
173 fn documentation(&self) -> Option<&Documentation> {
174 self.doc()
175 }
176}
177
178fn regexp_replace_func(args: &[ColumnarValue]) -> Result<ArrayRef> {
179 match args[0].data_type() {
180 DataType::Utf8 => specialize_regexp_replace::<i32>(args),
181 DataType::LargeUtf8 => specialize_regexp_replace::<i64>(args),
182 DataType::Utf8View => specialize_regexp_replace::<i32>(args),
183 other => {
184 internal_err!("Unsupported data type {other:?} for function regexp_replace")
185 }
186 }
187}
188
189fn regex_replace_posix_groups(replacement: &str) -> String {
198 static CAPTURE_GROUPS_RE_LOCK: LazyLock<Regex> =
199 LazyLock::new(|| Regex::new(r"\\{1,2}(\d+)").unwrap());
200 CAPTURE_GROUPS_RE_LOCK
201 .replace_all(replacement, "$${$1}")
202 .into_owned()
203}
204
205struct ShortRegex {
206 short_re: Regex,
209 locs: CaptureLocations,
211}
212
213struct OptimizedRegex {
216 re: Regex,
218 short_re: Option<ShortRegex>,
220}
221
222impl OptimizedRegex {
223 fn new(re: Regex, limit: usize, pattern: &str, replacement: &str) -> Self {
228 let short_re = if limit == 1 {
229 try_build_short_extract_regex(pattern, replacement)
230 } else {
231 None
232 };
233
234 let short_re = short_re.map(|short_re| {
235 let locs = short_re.capture_locations();
236 ShortRegex { short_re, locs }
237 });
238
239 Self { re, short_re }
240 }
241
242 fn replacen<'a>(
245 &mut self,
246 val: &'a str,
247 limit: usize,
248 replacement: &str,
249 ) -> Cow<'a, str> {
250 let Some(ShortRegex { short_re, locs }) = self.short_re.as_mut() else {
252 return self.re.replacen(val, limit, replacement);
253 };
254
255 if short_re.captures_read(locs, val).is_none() {
258 return Cow::Borrowed(val);
259 };
260
261 let match_end = locs.get(0).unwrap().1;
263 if memchr(b'\n', &val.as_bytes()[match_end..]).is_some() {
264 return self.re.replacen(val, limit, replacement);
268 };
269 if let Some((start, end)) = locs.get(1) {
272 Cow::Borrowed(&val[start..end])
273 } else {
274 Cow::Borrowed("")
275 }
276 }
277}
278
279fn try_build_short_extract_regex(pattern: &str, replacement: &str) -> Option<Regex> {
286 if replacement != "${1}" || !pattern.starts_with('^') || !pattern.ends_with(".*$") {
287 return None;
288 }
289 let short = &pattern[..pattern.len() - 3];
290 let re = Regex::new(short).ok()?;
291 if re.captures_len() != 2 {
292 return None;
293 }
294 Some(re)
295}
296
297pub fn regexp_replace<'a, T: OffsetSizeTrait, U>(
338 string_array: U,
339 pattern_array: U,
340 replacement_array: U,
341 flags_array: Option<U>,
342) -> Result<ArrayRef>
343where
344 U: ArrayAccessor<Item = &'a str>,
345{
346 let mut patterns: HashMap<String, Regex> = HashMap::new();
351
352 let datatype = string_array.data_type().to_owned();
353
354 let string_array_iter = ArrayIter::new(string_array);
355 let pattern_array_iter = ArrayIter::new(pattern_array);
356 let replacement_array_iter = ArrayIter::new(replacement_array);
357
358 match flags_array {
359 None => {
360 let result_iter = string_array_iter
361 .zip(pattern_array_iter)
362 .zip(replacement_array_iter)
363 .map(|((string, pattern), replacement)| {
364 match (string, pattern, replacement) {
365 (Some(string), Some(pattern), Some(replacement)) => {
366 let replacement = regex_replace_posix_groups(replacement);
367 let re = match patterns.get(pattern) {
369 Some(re) => Ok(re),
370 None => match Regex::new(pattern) {
371 Ok(re) => {
372 patterns.insert(pattern.to_string(), re);
373 Ok(patterns.get(pattern).unwrap())
374 }
375 Err(err) => {
376 Err(DataFusionError::External(Box::new(err)))
377 }
378 },
379 };
380
381 Some(re.map(|re| re.replace(string, replacement.as_str())))
382 .transpose()
383 }
384 _ => Ok(None),
385 }
386 });
387
388 match datatype {
389 DataType::Utf8 | DataType::LargeUtf8 => {
390 let result =
391 result_iter.collect::<Result<GenericStringArray<T>>>()?;
392 Ok(Arc::new(result) as ArrayRef)
393 }
394 DataType::Utf8View => {
395 let result = result_iter.collect::<Result<StringViewArray>>()?;
396 Ok(Arc::new(result) as ArrayRef)
397 }
398 other => {
399 exec_err!(
400 "Unsupported data type {other:?} for function regex_replace"
401 )
402 }
403 }
404 }
405 Some(flags_array) => {
406 let flags_array_iter = ArrayIter::new(flags_array);
407
408 let result_iter = string_array_iter
409 .zip(pattern_array_iter)
410 .zip(replacement_array_iter)
411 .zip(flags_array_iter)
412 .map(|(((string, pattern), replacement), flags)| {
413 match (string, pattern, replacement, flags) {
414 (Some(string), Some(pattern), Some(replacement), Some(flags)) => {
415 let replacement = regex_replace_posix_groups(replacement);
416
417 let (pattern, replace_all) = if flags == "g" {
419 (pattern.to_string(), true)
420 } else if flags.contains('g') {
421 (
422 format!(
423 "(?{}){}",
424 flags.to_string().replace('g', ""),
425 pattern
426 ),
427 true,
428 )
429 } else {
430 (format!("(?{flags}){pattern}"), false)
431 };
432
433 let re = match patterns.get(&pattern) {
435 Some(re) => Ok(re),
436 None => match Regex::new(pattern.as_str()) {
437 Ok(re) => {
438 patterns.insert(pattern.clone(), re);
439 Ok(patterns.get(&pattern).unwrap())
440 }
441 Err(err) => {
442 Err(DataFusionError::External(Box::new(err)))
443 }
444 },
445 };
446
447 Some(re.map(|re| {
448 if replace_all {
449 re.replace_all(string, replacement.as_str())
450 } else {
451 re.replace(string, replacement.as_str())
452 }
453 }))
454 .transpose()
455 }
456 _ => Ok(None),
457 }
458 });
459
460 match datatype {
461 DataType::Utf8 | DataType::LargeUtf8 => {
462 let result =
463 result_iter.collect::<Result<GenericStringArray<T>>>()?;
464 Ok(Arc::new(result) as ArrayRef)
465 }
466 DataType::Utf8View => {
467 let result = result_iter.collect::<Result<StringViewArray>>()?;
468 Ok(Arc::new(result) as ArrayRef)
469 }
470 other => {
471 exec_err!(
472 "Unsupported data type {other:?} for function regex_replace"
473 )
474 }
475 }
476 }
477 }
478}
479
480macro_rules! fetch_string_arg {
485 ($ARG:expr, $NAME:expr, $ARRAY_SIZE:expr) => {{
486 let string_array_type = ($ARG).data_type();
487 match string_array_type {
488 dt if $ARG.len() == 0 || $ARG.is_null(0) => {
489 return Ok(new_null_array(dt, $ARRAY_SIZE));
494 }
495 DataType::Utf8 => {
496 let array = as_string_array($ARG)?;
497 array.value(0)
498 }
499 DataType::LargeUtf8 => {
500 let array = as_large_string_array($ARG)?;
501 array.value(0)
502 }
503 DataType::Utf8View => {
504 let array = as_string_view_array($ARG)?;
505 array.value(0)
506 }
507 _ => unreachable!(
508 "Invalid data type for regexp_replace: {}",
509 string_array_type
510 ),
511 }
512 }};
513}
514fn regexp_replace_static_pattern_replace<T: OffsetSizeTrait>(
521 args: &[ArrayRef],
522) -> Result<ArrayRef> {
523 let array_size = args[0].len();
524 let pattern = fetch_string_arg!(&args[1], "pattern", array_size);
525 let replacement = fetch_string_arg!(&args[2], "replacement", array_size);
526 let flags = match args.len() {
527 3 => None,
528 4 => Some(fetch_string_arg!(&args[3], "flags", array_size)),
529 other => {
530 return exec_err!(
531 "regexp_replace was called with {other} arguments. It requires at least 3 and at most 4."
532 );
533 }
534 };
535
536 let (pattern, limit) = match flags {
540 Some("g") => (pattern.to_string(), 0),
541 Some(flags) => (
542 format!("(?{}){}", flags.to_string().replace('g', ""), pattern),
543 !flags.contains('g') as usize,
544 ),
545 None => (pattern.to_string(), 1),
546 };
547
548 let re =
549 Regex::new(&pattern).map_err(|err| DataFusionError::External(Box::new(err)))?;
550
551 let replacement = regex_replace_posix_groups(replacement);
554
555 let mut opt_re = OptimizedRegex::new(re, limit, &pattern, &replacement);
556
557 let string_array_type = args[0].data_type();
558 match string_array_type {
559 DataType::Utf8 | DataType::LargeUtf8 => {
560 let string_array = as_generic_string_array::<T>(&args[0])?;
561
562 let mut vals = BufferBuilder::<u8>::new({
565 let offsets = string_array.value_offsets();
566 (offsets[string_array.len()] - offsets[0])
567 .to_usize()
568 .unwrap()
569 });
570 let mut new_offsets = BufferBuilder::<T>::new(string_array.len() + 1);
571 new_offsets.append(T::zero());
572
573 string_array.iter().for_each(|val| {
574 if let Some(val) = val {
575 let result = opt_re.replacen(val, limit, replacement.as_str());
576 vals.append_slice(result.as_bytes());
577 }
578 new_offsets.append(T::from_usize(vals.len()).unwrap());
579 });
580
581 let data = ArrayDataBuilder::new(GenericStringArray::<T>::DATA_TYPE)
582 .len(string_array.len())
583 .nulls(string_array.nulls().cloned())
584 .buffers(vec![new_offsets.finish(), vals.finish()])
585 .build()?;
586 let result_array = GenericStringArray::<T>::from(data);
587 Ok(Arc::new(result_array) as ArrayRef)
588 }
589 DataType::Utf8View => {
590 let string_view_array = as_string_view_array(&args[0])?;
591
592 let mut builder = StringViewBuilder::with_capacity(string_view_array.len());
593
594 for val in string_view_array.iter() {
595 if let Some(val) = val {
596 let result = opt_re.replacen(val, limit, replacement.as_str());
597 builder.append_value(result.as_ref());
598 } else {
599 builder.append_null();
600 }
601 }
602
603 let result = builder.finish();
604 Ok(Arc::new(result) as ArrayRef)
605 }
606 _ => unreachable!(
607 "Invalid data type for regexp_replace: {}",
608 string_array_type
609 ),
610 }
611}
612
613fn specialize_regexp_replace<T: OffsetSizeTrait>(
616 args: &[ColumnarValue],
617) -> Result<ArrayRef> {
618 let (is_source_scalar, is_pattern_scalar, is_replacement_scalar, is_flags_scalar) = (
623 matches!(args[0], ColumnarValue::Scalar(_)),
624 matches!(args[1], ColumnarValue::Scalar(_)),
625 matches!(args[2], ColumnarValue::Scalar(_)),
626 matches!(args.get(3), Some(ColumnarValue::Scalar(_)) | None),
629 );
630 let len = args
631 .iter()
632 .fold(Option::<usize>::None, |acc, arg| match arg {
633 ColumnarValue::Scalar(_) => acc,
634 ColumnarValue::Array(a) => Some(a.len()),
635 });
636 let inferred_length = len.unwrap_or(1);
637 match (
638 is_source_scalar,
639 is_pattern_scalar,
640 is_replacement_scalar,
641 is_flags_scalar,
642 ) {
643 (_, true, true, true) => {
657 let hints = [
658 Hint::Pad,
659 Hint::AcceptsSingular,
660 Hint::AcceptsSingular,
661 Hint::AcceptsSingular,
662 ];
663 let args = args
664 .iter()
665 .zip(hints.iter().chain(std::iter::repeat(&Hint::Pad)))
666 .map(|(arg, hint)| {
667 let expansion_len = match hint {
670 Hint::AcceptsSingular => 1,
671 Hint::Pad => inferred_length,
672 };
673 arg.to_array(expansion_len)
674 })
675 .collect::<Result<Vec<_>>>()?;
676 regexp_replace_static_pattern_replace::<T>(&args)
677 }
678
679 (_, _, _, _) => {
682 let args = args
683 .iter()
684 .map(|arg| arg.to_array(inferred_length))
685 .collect::<Result<Vec<_>>>()?;
686
687 match (
688 args[0].data_type(),
689 args[1].data_type(),
690 args[2].data_type(),
691 args.get(3).map(|a| a.data_type()),
692 ) {
693 (
694 DataType::Utf8,
695 DataType::Utf8,
696 DataType::Utf8,
697 Some(DataType::Utf8) | None,
698 ) => {
699 let string_array = args[0].as_string::<i32>();
700 let pattern_array = args[1].as_string::<i32>();
701 let replacement_array = args[2].as_string::<i32>();
702 let flags_array = args.get(3).map(|a| a.as_string::<i32>());
703 regexp_replace::<i32, _>(
704 string_array,
705 pattern_array,
706 replacement_array,
707 flags_array,
708 )
709 }
710 (
711 DataType::Utf8View,
712 DataType::Utf8View,
713 DataType::Utf8View,
714 Some(DataType::Utf8View) | None,
715 ) => {
716 let string_array = args[0].as_string_view();
717 let pattern_array = args[1].as_string_view();
718 let replacement_array = args[2].as_string_view();
719 let flags_array = args.get(3).map(|a| a.as_string_view());
720 regexp_replace::<i32, _>(
721 string_array,
722 pattern_array,
723 replacement_array,
724 flags_array,
725 )
726 }
727 (
728 DataType::LargeUtf8,
729 DataType::LargeUtf8,
730 DataType::LargeUtf8,
731 Some(DataType::LargeUtf8) | None,
732 ) => {
733 let string_array = args[0].as_string::<i64>();
734 let pattern_array = args[1].as_string::<i64>();
735 let replacement_array = args[2].as_string::<i64>();
736 let flags_array = args.get(3).map(|a| a.as_string::<i64>());
737 regexp_replace::<i64, _>(
738 string_array,
739 pattern_array,
740 replacement_array,
741 flags_array,
742 )
743 }
744 other => {
745 exec_err!(
746 "Unsupported data type {other:?} for function regex_replace"
747 )
748 }
749 }
750 }
751 }
752}
753#[cfg(test)]
754mod tests {
755 use arrow::array::*;
756
757 use super::*;
758
759 #[test]
760 fn test_regex_replace_posix_groups() {
761 assert_eq!(regex_replace_posix_groups(r"\1"), "${1}");
763 assert_eq!(regex_replace_posix_groups(r"\12"), "${12}");
764 assert_eq!(regex_replace_posix_groups(r"X\1Y"), "X${1}Y");
765 assert_eq!(regex_replace_posix_groups(r"\1\2"), "${1}${2}");
766
767 assert_eq!(regex_replace_posix_groups(r"\\1"), "${1}");
769 assert_eq!(regex_replace_posix_groups(r"X\\1Y"), "X${1}Y");
770 assert_eq!(regex_replace_posix_groups(r"\\1\\2"), "${1}${2}");
771
772 assert_eq!(regex_replace_posix_groups(r"\\\1"), r"\${1}");
774 assert_eq!(regex_replace_posix_groups(r"\\\\1"), r"\\${1}");
775 assert_eq!(regex_replace_posix_groups(r"\\\1\\\\2"), r"\${1}\\${2}");
776
777 assert_eq!(regex_replace_posix_groups(r"\"), r"\");
779 assert_eq!(regex_replace_posix_groups(r"foo\bar"), r"foo\bar");
780
781 assert_eq!(regex_replace_posix_groups(r"\n"), r"\n");
783 assert_eq!(regex_replace_posix_groups(r"\t"), r"\t");
784
785 assert_eq!(regex_replace_posix_groups(r"\0"), "${0}");
789 assert_eq!(
790 regex_replace_posix_groups(r"prefix\0suffix"),
791 "prefix${0}suffix"
792 );
793 }
794
795 macro_rules! static_pattern_regexp_replace {
796 ($name:ident, $T:ty, $O:ty) => {
797 #[test]
798 fn $name() {
799 let values = vec!["abc", "acd", "abcd1234567890123", "123456789012abc"];
800 let patterns = vec!["b"; 4];
801 let replacement = vec!["foo"; 4];
802 let expected =
803 vec!["afooc", "acd", "afoocd1234567890123", "123456789012afooc"];
804
805 let values = <$T>::from(values);
806 let patterns = <$T>::from(patterns);
807 let replacements = <$T>::from(replacement);
808 let expected = <$T>::from(expected);
809
810 let re = regexp_replace_static_pattern_replace::<$O>(&[
811 Arc::new(values),
812 Arc::new(patterns),
813 Arc::new(replacements),
814 ])
815 .unwrap();
816
817 assert_eq!(re.as_ref(), &expected);
818 }
819 };
820 }
821
822 static_pattern_regexp_replace!(string_array, StringArray, i32);
823 static_pattern_regexp_replace!(string_view_array, StringViewArray, i32);
824 static_pattern_regexp_replace!(large_string_array, LargeStringArray, i64);
825
826 macro_rules! static_pattern_regexp_replace_with_flags {
827 ($name:ident, $T:ty, $O: ty) => {
828 #[test]
829 fn $name() {
830 let values = vec![
831 "abc",
832 "aBc",
833 "acd",
834 "abcd1234567890123",
835 "aBcd1234567890123",
836 "123456789012abc",
837 "123456789012aBc",
838 ];
839 let expected = vec![
840 "afooc",
841 "afooc",
842 "acd",
843 "afoocd1234567890123",
844 "afoocd1234567890123",
845 "123456789012afooc",
846 "123456789012afooc",
847 ];
848
849 let values = <$T>::from(values);
850 let patterns = StringArray::from(vec!["b"; 7]);
851 let replacements = StringArray::from(vec!["foo"; 7]);
852 let flags = StringArray::from(vec!["i"; 5]);
853 let expected = <$T>::from(expected);
854
855 let re = regexp_replace_static_pattern_replace::<$O>(&[
856 Arc::new(values),
857 Arc::new(patterns),
858 Arc::new(replacements),
859 Arc::new(flags),
860 ])
861 .unwrap();
862
863 assert_eq!(re.as_ref(), &expected);
864 }
865 };
866 }
867
868 static_pattern_regexp_replace_with_flags!(string_array_with_flags, StringArray, i32);
869 static_pattern_regexp_replace_with_flags!(
870 string_view_array_with_flags,
871 StringViewArray,
872 i32
873 );
874 static_pattern_regexp_replace_with_flags!(
875 large_string_array_with_flags,
876 LargeStringArray,
877 i64
878 );
879
880 #[test]
881 fn test_static_pattern_regexp_replace_early_abort() {
882 let values = StringArray::from(vec!["abc"; 5]);
883 let patterns = StringArray::from(vec![None::<&str>; 5]);
884 let replacements = StringArray::from(vec!["foo"; 5]);
885 let expected = StringArray::from(vec![None::<&str>; 5]);
886
887 let re = regexp_replace_static_pattern_replace::<i32>(&[
888 Arc::new(values),
889 Arc::new(patterns),
890 Arc::new(replacements),
891 ])
892 .unwrap();
893
894 assert_eq!(re.as_ref(), &expected);
895 }
896
897 #[test]
898 fn test_static_pattern_regexp_replace_early_abort_when_empty() {
899 let values = StringArray::from(Vec::<Option<&str>>::new());
900 let patterns = StringArray::from(Vec::<Option<&str>>::new());
901 let replacements = StringArray::from(Vec::<Option<&str>>::new());
902 let expected = StringArray::from(Vec::<Option<&str>>::new());
903
904 let re = regexp_replace_static_pattern_replace::<i32>(&[
905 Arc::new(values),
906 Arc::new(patterns),
907 Arc::new(replacements),
908 ])
909 .unwrap();
910
911 assert_eq!(re.as_ref(), &expected);
912 }
913
914 #[test]
915 fn test_static_pattern_regexp_replace_early_abort_flags() {
916 let values = StringArray::from(vec!["abc"; 5]);
917 let patterns = StringArray::from(vec!["a"; 5]);
918 let replacements = StringArray::from(vec!["foo"; 5]);
919 let flags = StringArray::from(vec![None::<&str>; 5]);
920 let expected = StringArray::from(vec![None::<&str>; 5]);
921
922 let re = regexp_replace_static_pattern_replace::<i32>(&[
923 Arc::new(values),
924 Arc::new(patterns),
925 Arc::new(replacements),
926 Arc::new(flags),
927 ])
928 .unwrap();
929
930 assert_eq!(re.as_ref(), &expected);
931 }
932
933 #[test]
934 fn test_static_pattern_regexp_replace_pattern_error() {
935 let values = StringArray::from(vec!["abc"; 5]);
936 let patterns = StringArray::from(vec!["["; 5]);
939 let replacements = StringArray::from(vec!["foo"; 5]);
940
941 let re = regexp_replace_static_pattern_replace::<i32>(&[
942 Arc::new(values),
943 Arc::new(patterns),
944 Arc::new(replacements),
945 ]);
946 let pattern_err = re.expect_err("broken pattern should have failed");
947 assert_eq!(
948 pattern_err.strip_backtrace(),
949 "External error: regex parse error:\n [\n ^\nerror: unclosed character class"
950 );
951 }
952
953 #[test]
954 fn test_static_pattern_regexp_replace_with_null_buffers() {
955 let values = StringArray::from(vec![
956 Some("a"),
957 None,
958 Some("b"),
959 None,
960 Some("a"),
961 None,
962 None,
963 Some("c"),
964 ]);
965 let patterns = StringArray::from(vec!["a"; 1]);
966 let replacements = StringArray::from(vec!["foo"; 1]);
967 let expected = StringArray::from(vec![
968 Some("foo"),
969 None,
970 Some("b"),
971 None,
972 Some("foo"),
973 None,
974 None,
975 Some("c"),
976 ]);
977
978 let re = regexp_replace_static_pattern_replace::<i32>(&[
979 Arc::new(values),
980 Arc::new(patterns),
981 Arc::new(replacements),
982 ])
983 .unwrap();
984
985 assert_eq!(re.as_ref(), &expected);
986 assert_eq!(re.null_count(), 4);
987 }
988
989 #[test]
990 fn test_static_pattern_regexp_replace_with_sliced_null_buffer() {
991 let values = StringArray::from(vec![
992 Some("a"),
993 None,
994 Some("b"),
995 None,
996 Some("a"),
997 None,
998 None,
999 Some("c"),
1000 ]);
1001 let values = values.slice(2, 5);
1002 let patterns = StringArray::from(vec!["a"; 1]);
1003 let replacements = StringArray::from(vec!["foo"; 1]);
1004 let expected = StringArray::from(vec![Some("b"), None, Some("foo"), None, None]);
1005
1006 let re = regexp_replace_static_pattern_replace::<i32>(&[
1007 Arc::new(values),
1008 Arc::new(patterns),
1009 Arc::new(replacements),
1010 ])
1011 .unwrap();
1012 assert_eq!(re.as_ref(), &expected);
1013 assert_eq!(re.null_count(), 3);
1014 }
1015}