1use arrow::array::{Array, ArrayRef, AsArray, BooleanArray, GenericStringArray};
21use arrow::compute::kernels::regexp;
22use arrow::datatypes::DataType;
23use arrow::datatypes::DataType::{LargeUtf8, Utf8, Utf8View};
24use datafusion_common::types::logical_string;
25use datafusion_common::{
26 Result, ScalarValue, arrow_datafusion_err, exec_err, internal_err, plan_err,
27};
28use datafusion_expr::{
29 Coercion, ColumnarValue, Documentation, Expr, ScalarFunctionArgs, ScalarUDFImpl,
30 Signature, TypeSignature, TypeSignatureClass, Volatility, binary_expr, cast,
31};
32use datafusion_macros::user_doc;
33
34use datafusion_expr::simplify::{ExprSimplifyResult, SimplifyContext};
35use datafusion_expr_common::operator::Operator;
36use datafusion_expr_common::type_coercion::binary::BinaryTypeCoercer;
37use regex::Regex;
38use std::sync::Arc;
39
40#[user_doc(
41 doc_section(label = "Regular Expression Functions"),
42 description = "Returns true if a [regular expression](https://docs.rs/regex/latest/regex/#syntax) has at least one match in a string, false otherwise.",
43 syntax_example = "regexp_like(str, regexp[, flags])",
44 sql_example = r#"```sql
45select regexp_like('Köln', '[a-zA-Z]ö[a-zA-Z]{2}');
46+--------------------------------------------------------+
47| regexp_like(Utf8("Köln"),Utf8("[a-zA-Z]ö[a-zA-Z]{2}")) |
48+--------------------------------------------------------+
49| true |
50+--------------------------------------------------------+
51SELECT regexp_like('aBc', '(b|d)', 'i');
52+--------------------------------------------------+
53| regexp_like(Utf8("aBc"),Utf8("(b|d)"),Utf8("i")) |
54+--------------------------------------------------+
55| true |
56+--------------------------------------------------+
57```
58Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/regexp.rs)
59"#,
60 standard_argument(name = "str", prefix = "String"),
61 standard_argument(name = "regexp", prefix = "Regular"),
62 argument(
63 name = "flags",
64 description = r#"Optional regular expression flags that control the behavior of the regular expression. The following flags are supported:
65 - **i**: case-insensitive: letters match both upper and lower case
66 - **m**: multi-line mode: ^ and $ match begin/end of line
67 - **s**: allow . to match \n
68 - **R**: enables CRLF mode: when multi-line mode is enabled, \r\n is used
69 - **U**: swap the meaning of x* and x*?"#
70 )
71)]
72#[derive(Debug, PartialEq, Eq, Hash)]
73pub struct RegexpLikeFunc {
74 signature: Signature,
75}
76
77impl Default for RegexpLikeFunc {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl RegexpLikeFunc {
84 pub fn new() -> Self {
85 Self {
86 signature: Signature::one_of(
87 vec![
88 TypeSignature::Coercible(vec![
89 Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
90 Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
91 ]),
92 TypeSignature::Coercible(vec![
93 Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
94 Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
95 Coercion::new_exact(TypeSignatureClass::Native(logical_string())),
96 ]),
97 ],
98 Volatility::Immutable,
99 ),
100 }
101 }
102}
103
104impl ScalarUDFImpl for RegexpLikeFunc {
105 fn name(&self) -> &str {
106 "regexp_like"
107 }
108
109 fn signature(&self) -> &Signature {
110 &self.signature
111 }
112
113 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
114 use DataType::*;
115
116 Ok(match &arg_types[0] {
117 Null => Null,
118 _ => Boolean,
121 })
122 }
123
124 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
125 let args = &args.args;
126 match args.as_slice() {
127 [ColumnarValue::Scalar(value), ColumnarValue::Scalar(pattern)] => {
128 let value = scalar_string(value)?;
129 let pattern = scalar_string(pattern)?;
130 regexp_like_scalar(value, pattern, None)
131 }
132 [
133 ColumnarValue::Scalar(value),
134 ColumnarValue::Scalar(pattern),
135 ColumnarValue::Scalar(flags),
136 ] => {
137 let value = scalar_string(value)?;
138 let pattern = scalar_string(pattern)?;
139 let flags = scalar_string(flags)?;
140 regexp_like_scalar(value, pattern, flags)
141 }
142 [ColumnarValue::Array(values), ColumnarValue::Scalar(pattern)] => {
143 let pattern = scalar_string(pattern)?;
144 let array = regexp_like_array_scalar(values, pattern, None)?;
145 Ok(ColumnarValue::Array(array))
146 }
147 [
148 ColumnarValue::Array(values),
149 ColumnarValue::Scalar(pattern),
150 ColumnarValue::Scalar(flags),
151 ] => {
152 let flags = scalar_string(flags)?;
153 if flags.is_some_and(|flagz| flagz.contains('g')) {
154 plan_err!("regexp_like() does not support the \"global\" option")
155 } else {
156 let pattern = scalar_string(pattern)?;
157 let array = regexp_like_array_scalar(values, pattern, flags)?;
158 Ok(ColumnarValue::Array(array))
159 }
160 }
161 _ => {
162 let args = ColumnarValue::values_to_arrays(args)?;
163 regexp_like(&args).map(ColumnarValue::Array)
164 }
165 }
166 }
167
168 fn simplify(
169 &self,
170 mut args: Vec<Expr>,
171 info: &SimplifyContext,
172 ) -> Result<ExprSimplifyResult> {
173 let Some(op) = derive_operator(&args) else {
178 return Ok(ExprSimplifyResult::Original(args));
179 };
180
181 let string_type = info.get_data_type(&args[0])?;
182 let regexp_type = info.get_data_type(&args[1])?;
183 let binary_type_coercer = BinaryTypeCoercer::new(&string_type, &op, ®exp_type);
184 let Ok((coerced_string_type, coerced_regexp_type)) =
185 binary_type_coercer.get_input_types()
186 else {
187 return Ok(ExprSimplifyResult::Original(args));
188 };
189
190 let regexp = args.swap_remove(1);
192 let string = args.swap_remove(0);
193
194 Ok(ExprSimplifyResult::Simplified(binary_expr(
195 if string_type != coerced_string_type {
196 cast(string, coerced_string_type)
197 } else {
198 string
199 },
200 op,
201 if regexp_type != coerced_regexp_type {
202 cast(regexp, coerced_regexp_type)
203 } else {
204 regexp
205 },
206 )))
207 }
208
209 fn documentation(&self) -> Option<&Documentation> {
210 self.doc()
211 }
212}
213
214fn derive_operator(args: &[Expr]) -> Option<Operator> {
215 match args.len() {
216 3 => {
218 match &args[2] {
219 Expr::Literal(ScalarValue::Utf8(Some(flags)), _) => {
220 match flags.as_str() {
221 "i" => Some(Operator::RegexIMatch),
222 "" => Some(Operator::RegexMatch),
223 _ => None,
225 }
226 }
227 _ => None,
229 }
230 }
231 2 => Some(Operator::RegexMatch),
233 _ => None,
235 }
236}
237
238pub fn regexp_like(args: &[ArrayRef]) -> Result<ArrayRef> {
281 match args.len() {
282 2 => handle_regexp_like(&args[0], &args[1], None),
283 3 => {
284 let flags = match args[2].data_type() {
285 Utf8 => args[2].as_string::<i32>(),
286 LargeUtf8 => {
287 let large_string_array = args[2].as_string::<i64>();
288 let string_vec: Vec<Option<&str>> = (0..large_string_array.len())
289 .map(|i| {
290 if large_string_array.is_null(i) {
291 None
292 } else {
293 Some(large_string_array.value(i))
294 }
295 })
296 .collect();
297
298 &GenericStringArray::<i32>::from(string_vec)
299 }
300 _ => {
301 let string_view_array = args[2].as_string_view();
302 let string_vec: Vec<Option<String>> = (0..string_view_array.len())
303 .map(|i| {
304 if string_view_array.is_null(i) {
305 None
306 } else {
307 Some(string_view_array.value(i).to_string())
308 }
309 })
310 .collect();
311 &GenericStringArray::<i32>::from(string_vec)
312 }
313 };
314
315 if flags
316 .iter()
317 .any(|s| s.is_some_and(|flagz| flagz.contains('g')))
318 {
319 return plan_err!("regexp_like() does not support the \"global\" option");
320 }
321
322 handle_regexp_like(&args[0], &args[1], Some(flags))
323 }
324 other => exec_err!(
325 "`regexp_like` was called with {other} arguments. It requires at least 2 and at most 3."
326 ),
327 }
328}
329
330fn scalar_string(value: &ScalarValue) -> Result<Option<&str>> {
331 match value.try_as_str() {
332 Some(v) => Ok(v),
333 None => internal_err!(
334 "Unsupported data type {:?} for function `regexp_like`",
335 value.data_type()
336 ),
337 }
338}
339
340fn regexp_like_array_scalar(
341 values: &ArrayRef,
342 pattern: Option<&str>,
343 flags: Option<&str>,
344) -> Result<ArrayRef> {
345 use DataType::*;
346
347 let Some(pattern) = pattern else {
348 return Ok(Arc::new(BooleanArray::new_null(values.len())));
349 };
350 let array = match values.data_type() {
351 Utf8 => {
352 let array = values.as_string::<i32>();
353 regexp::regexp_is_match_scalar(array, pattern, flags)?
354 }
355 Utf8View => {
356 let array = values.as_string_view();
357 regexp::regexp_is_match_scalar(array, pattern, flags)?
358 }
359 LargeUtf8 => {
360 let array = values.as_string::<i64>();
361 regexp::regexp_is_match_scalar(array, pattern, flags)?
362 }
363 other => {
364 return internal_err!(
365 "Unsupported data type {other:?} for function `regexp_like`"
366 );
367 }
368 };
369
370 Ok(Arc::new(array))
371}
372
373fn regexp_like_scalar(
374 value: Option<&str>,
375 pattern: Option<&str>,
376 flags: Option<&str>,
377) -> Result<ColumnarValue> {
378 if flags.is_some_and(|flagz| flagz.contains('g')) {
379 return plan_err!("regexp_like() does not support the \"global\" option");
380 }
381
382 if value.is_none() || pattern.is_none() {
383 return Ok(ColumnarValue::Scalar(ScalarValue::Boolean(None)));
384 }
385
386 let value = value.unwrap();
387 let pattern = pattern.unwrap();
388 let pattern = match flags {
389 Some(flagz) => format!("(?{flagz}){pattern}"),
390 None => pattern.to_string(),
391 };
392
393 let result = if pattern.is_empty() {
394 true
395 } else {
396 let re = Regex::new(pattern.as_str()).map_err(|e| {
397 datafusion_common::DataFusionError::Execution(format!(
398 "Regular expression did not compile: {e:?}"
399 ))
400 })?;
401 re.is_match(value)
402 };
403
404 Ok(ColumnarValue::Scalar(ScalarValue::Boolean(Some(result))))
405}
406
407fn handle_regexp_like(
408 values: &ArrayRef,
409 patterns: &ArrayRef,
410 flags: Option<&GenericStringArray<i32>>,
411) -> Result<ArrayRef> {
412 let array = match (values.data_type(), patterns.data_type()) {
413 (Utf8View, Utf8) => {
414 let value = values.as_string_view();
415 let pattern = patterns.as_string::<i32>();
416
417 regexp::regexp_is_match(value, pattern, flags)
418 .map_err(|e| arrow_datafusion_err!(e))?
419 }
420 (Utf8View, Utf8View) => {
421 let value = values.as_string_view();
422 let pattern = patterns.as_string_view();
423
424 regexp::regexp_is_match(value, pattern, flags)
425 .map_err(|e| arrow_datafusion_err!(e))?
426 }
427 (Utf8View, LargeUtf8) => {
428 let value = values.as_string_view();
429 let pattern = patterns.as_string::<i64>();
430
431 regexp::regexp_is_match(value, pattern, flags)
432 .map_err(|e| arrow_datafusion_err!(e))?
433 }
434 (Utf8, Utf8) => {
435 let value = values.as_string::<i32>();
436 let pattern = patterns.as_string::<i32>();
437
438 regexp::regexp_is_match(value, pattern, flags)
439 .map_err(|e| arrow_datafusion_err!(e))?
440 }
441 (Utf8, Utf8View) => {
442 let value = values.as_string::<i32>();
443 let pattern = patterns.as_string_view();
444
445 regexp::regexp_is_match(value, pattern, flags)
446 .map_err(|e| arrow_datafusion_err!(e))?
447 }
448 (Utf8, LargeUtf8) => {
449 let value = values.as_string::<i32>();
450 let pattern = patterns.as_string::<i64>();
451
452 regexp::regexp_is_match(value, pattern, flags)
453 .map_err(|e| arrow_datafusion_err!(e))?
454 }
455 (LargeUtf8, Utf8) => {
456 let value = values.as_string::<i64>();
457 let pattern = patterns.as_string::<i32>();
458
459 regexp::regexp_is_match(value, pattern, flags)
460 .map_err(|e| arrow_datafusion_err!(e))?
461 }
462 (LargeUtf8, Utf8View) => {
463 let value = values.as_string::<i64>();
464 let pattern = patterns.as_string_view();
465
466 regexp::regexp_is_match(value, pattern, flags)
467 .map_err(|e| arrow_datafusion_err!(e))?
468 }
469 (LargeUtf8, LargeUtf8) => {
470 let value = values.as_string::<i64>();
471 let pattern = patterns.as_string::<i64>();
472
473 regexp::regexp_is_match(value, pattern, flags)
474 .map_err(|e| arrow_datafusion_err!(e))?
475 }
476 other => {
477 return internal_err!(
478 "Unsupported data type {other:?} for function `regexp_like`"
479 );
480 }
481 };
482
483 Ok(Arc::new(array) as ArrayRef)
484}
485
486#[cfg(test)]
487mod tests {
488 use std::sync::Arc;
489
490 use arrow::array::StringArray;
491 use arrow::array::{BooleanBuilder, StringViewArray};
492 use arrow::datatypes::{DataType, Field};
493 use datafusion_common::config::ConfigOptions;
494 use datafusion_common::{Result, ScalarValue};
495 use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl};
496
497 use crate::regex::regexplike::{RegexpLikeFunc, regexp_like};
498
499 fn invoke_regexp_like(args: Vec<ColumnarValue>) -> Result<ColumnarValue> {
500 let number_rows = args
501 .iter()
502 .find_map(|arg| match arg {
503 ColumnarValue::Array(array) => Some(array.len()),
504 _ => None,
505 })
506 .unwrap_or(1);
507 let arg_fields = args
508 .iter()
509 .enumerate()
510 .map(|(idx, arg)| {
511 Arc::new(Field::new(format!("arg_{idx}"), arg.data_type(), true))
512 })
513 .collect::<Vec<_>>();
514
515 RegexpLikeFunc::new().invoke_with_args(ScalarFunctionArgs {
516 args,
517 arg_fields,
518 number_rows,
519 return_field: Arc::new(Field::new("f", DataType::Boolean, true)),
520 config_options: Arc::new(ConfigOptions::default()),
521 })
522 }
523
524 #[test]
525 fn test_case_sensitive_regexp_like_utf8() {
526 let values = StringArray::from(vec!["abc"; 5]);
527
528 let patterns =
529 StringArray::from(vec!["^(a)", "^(A)", "(b|d)", "(B|D)", "^(b|c)"]);
530
531 let mut expected_builder: BooleanBuilder = BooleanBuilder::new();
532 expected_builder.append_value(true);
533 expected_builder.append_value(false);
534 expected_builder.append_value(true);
535 expected_builder.append_value(false);
536 expected_builder.append_value(false);
537 let expected = expected_builder.finish();
538
539 let re = regexp_like(&[Arc::new(values), Arc::new(patterns)]).unwrap();
540
541 assert_eq!(re.as_ref(), &expected);
542 }
543
544 #[test]
545 fn test_case_sensitive_regexp_like_utf8view() {
546 let values = StringViewArray::from(vec!["abc"; 5]);
547
548 let patterns =
549 StringArray::from(vec!["^(a)", "^(A)", "(b|d)", "(B|D)", "^(b|c)"]);
550
551 let mut expected_builder: BooleanBuilder = BooleanBuilder::new();
552 expected_builder.append_value(true);
553 expected_builder.append_value(false);
554 expected_builder.append_value(true);
555 expected_builder.append_value(false);
556 expected_builder.append_value(false);
557 let expected = expected_builder.finish();
558
559 let re = regexp_like(&[Arc::new(values), Arc::new(patterns)]).unwrap();
560
561 assert_eq!(re.as_ref(), &expected);
562 }
563
564 #[test]
565 fn test_case_insensitive_regexp_like_utf8() {
566 let values = StringArray::from(vec!["abc"; 5]);
567 let patterns =
568 StringArray::from(vec!["^(a)", "^(A)", "(b|d)", "(B|D)", "^(b|c)"]);
569 let flags = StringArray::from(vec!["i"; 5]);
570
571 let mut expected_builder: BooleanBuilder = BooleanBuilder::new();
572 expected_builder.append_value(true);
573 expected_builder.append_value(true);
574 expected_builder.append_value(true);
575 expected_builder.append_value(true);
576 expected_builder.append_value(false);
577 let expected = expected_builder.finish();
578
579 let re = regexp_like(&[Arc::new(values), Arc::new(patterns), Arc::new(flags)])
580 .unwrap();
581
582 assert_eq!(re.as_ref(), &expected);
583 }
584
585 #[test]
586 fn test_case_insensitive_regexp_like_utf8view() {
587 let values = StringViewArray::from(vec!["abc"; 5]);
588 let patterns =
589 StringViewArray::from(vec!["^(a)", "^(A)", "(b|d)", "(B|D)", "^(b|c)"]);
590 let flags = StringArray::from(vec!["i"; 5]);
591
592 let mut expected_builder: BooleanBuilder = BooleanBuilder::new();
593 expected_builder.append_value(true);
594 expected_builder.append_value(true);
595 expected_builder.append_value(true);
596 expected_builder.append_value(true);
597 expected_builder.append_value(false);
598 let expected = expected_builder.finish();
599
600 let re = regexp_like(&[Arc::new(values), Arc::new(patterns), Arc::new(flags)])
601 .unwrap();
602
603 assert_eq!(re.as_ref(), &expected);
604 }
605
606 #[test]
607 fn test_unsupported_global_flag_regexp_like() {
608 let values = StringArray::from(vec!["abc"]);
609 let patterns = StringArray::from(vec!["^(a)"]);
610 let flags = StringArray::from(vec!["g"]);
611
612 let re_err =
613 regexp_like(&[Arc::new(values), Arc::new(patterns), Arc::new(flags)])
614 .expect_err("unsupported flag should have failed");
615
616 assert_eq!(
617 re_err.strip_backtrace(),
618 "Error during planning: regexp_like() does not support the \"global\" option"
619 );
620 }
621
622 #[test]
623 fn test_regexp_like_scalar_invoke() {
624 let args = vec![
625 ColumnarValue::Scalar(ScalarValue::Utf8(Some("foobarbequebaz".to_string()))),
626 ColumnarValue::Scalar(ScalarValue::Utf8(Some("(bar)(beque)".to_string()))),
627 ];
628 let result = invoke_regexp_like(args).unwrap();
629 match result {
630 ColumnarValue::Scalar(ScalarValue::Boolean(Some(true))) => {}
631 other => panic!("Unexpected result {other:?}"),
632 }
633 }
634
635 #[test]
636 fn test_regexp_like_array_scalar_invoke() {
637 let values = Arc::new(StringArray::from(vec!["abc", "xyz"]));
638 let args = vec![
639 ColumnarValue::Array(values),
640 ColumnarValue::Scalar(ScalarValue::Utf8(Some("^(a)".to_string()))),
641 ];
642 let result = invoke_regexp_like(args).unwrap();
643 let mut expected_builder = BooleanBuilder::new();
644 expected_builder.append_value(true);
645 expected_builder.append_value(false);
646 let expected = expected_builder.finish();
647 match result {
648 ColumnarValue::Array(array) => {
649 assert_eq!(array.as_ref(), &expected);
650 }
651 other => panic!("Unexpected result {other:?}"),
652 }
653 }
654
655 #[test]
656 fn test_regexp_like_scalar_flags_with_global() {
657 let args = vec![
658 ColumnarValue::Scalar(ScalarValue::Utf8(Some("abc".to_string()))),
659 ColumnarValue::Scalar(ScalarValue::Utf8(Some("^(a)".to_string()))),
660 ColumnarValue::Scalar(ScalarValue::Utf8(Some("ig".to_string()))),
661 ];
662 let err = invoke_regexp_like(args).expect_err("global flag should be rejected");
663 assert_eq!(
664 err.strip_backtrace(),
665 "Error during planning: regexp_like() does not support the \"global\" option"
666 );
667 }
668
669 #[test]
670 fn test_regexp_like_array_scalar_flags_with_global() {
671 let values = Arc::new(StringArray::from(vec!["abc", "xyz"]));
672 let args = vec![
673 ColumnarValue::Array(values),
674 ColumnarValue::Scalar(ScalarValue::Utf8(Some("^(a)".to_string()))),
675 ColumnarValue::Scalar(ScalarValue::Utf8(Some("ig".to_string()))),
676 ];
677 let err = invoke_regexp_like(args).expect_err("global flag should be rejected");
678 assert_eq!(
679 err.strip_backtrace(),
680 "Error during planning: regexp_like() does not support the \"global\" option"
681 );
682 }
683}