datafusion_functions/regex/
regexpmatch.rs1use arrow::array::{Array, ArrayRef, AsArray, Datum};
20use arrow::compute::kernels::regexp;
21use arrow::datatypes::DataType;
22use arrow::datatypes::Field;
23use datafusion_common::Result;
24use datafusion_common::ScalarValue;
25use datafusion_common::exec_err;
26use datafusion_common::{arrow_datafusion_err, plan_err};
27use datafusion_expr::{ColumnarValue, Documentation, ScalarFunctionArgs, TypeSignature};
28use datafusion_expr::{ScalarUDFImpl, Signature, Volatility};
29use datafusion_macros::user_doc;
30use std::sync::Arc;
31
32#[user_doc(
33 doc_section(label = "Regular Expression Functions"),
34 description = "Returns the first [regular expression](https://docs.rs/regex/latest/regex/#syntax) matches in a string.",
35 syntax_example = "regexp_match(str, regexp[, flags])",
36 sql_example = r#"```sql
37 > select regexp_match('Köln', '[a-zA-Z]ö[a-zA-Z]{2}');
38 +---------------------------------------------------------+
39 | regexp_match(Utf8("Köln"),Utf8("[a-zA-Z]ö[a-zA-Z]{2}")) |
40 +---------------------------------------------------------+
41 | [Köln] |
42 +---------------------------------------------------------+
43 SELECT regexp_match('aBc', '(b|d)', 'i');
44 +---------------------------------------------------+
45 | regexp_match(Utf8("aBc"),Utf8("(b|d)"),Utf8("i")) |
46 +---------------------------------------------------+
47 | [B] |
48 +---------------------------------------------------+
49```
50Additional examples can be found [here](https://github.com/apache/datafusion/blob/main/datafusion-examples/examples/builtin_functions/regexp.rs)
51"#,
52 standard_argument(name = "str", prefix = "String"),
53 argument(
54 name = "regexp",
55 description = "Regular expression to match against.
56 Can be a constant, column, or function."
57 ),
58 argument(
59 name = "flags",
60 description = r#"Optional regular expression flags that control the behavior of the regular expression. Refer to the flags reference above for supported flags."#
61 )
62)]
63#[derive(Debug, PartialEq, Eq, Hash)]
64pub struct RegexpMatchFunc {
65 signature: Signature,
66}
67
68impl Default for RegexpMatchFunc {
69 fn default() -> Self {
70 Self::new()
71 }
72}
73
74impl RegexpMatchFunc {
75 pub fn new() -> Self {
76 use DataType::*;
77 Self {
78 signature: Signature::one_of(
79 vec![
80 TypeSignature::Exact(vec![Utf8View, Utf8View]),
84 TypeSignature::Exact(vec![Utf8, Utf8]),
85 TypeSignature::Exact(vec![LargeUtf8, LargeUtf8]),
86 TypeSignature::Exact(vec![Utf8View, Utf8View, Utf8View]),
87 TypeSignature::Exact(vec![Utf8, Utf8, Utf8]),
88 TypeSignature::Exact(vec![LargeUtf8, LargeUtf8, LargeUtf8]),
89 ],
90 Volatility::Immutable,
91 ),
92 }
93 }
94}
95
96impl ScalarUDFImpl for RegexpMatchFunc {
97 fn name(&self) -> &str {
98 "regexp_match"
99 }
100
101 fn signature(&self) -> &Signature {
102 &self.signature
103 }
104
105 fn return_type(&self, arg_types: &[DataType]) -> Result<DataType> {
106 Ok(match &arg_types[0] {
107 DataType::Null => DataType::Null,
108 other => DataType::List(Arc::new(Field::new_list_field(other.clone(), true))),
109 })
110 }
111
112 fn invoke_with_args(&self, args: ScalarFunctionArgs) -> Result<ColumnarValue> {
113 let args = &args.args;
114
115 if let Some(result) = regexp_match_scalar_pattern(args)? {
119 return Ok(ColumnarValue::Array(result));
120 }
121
122 let len = args
123 .iter()
124 .fold(Option::<usize>::None, |acc, arg| match arg {
125 ColumnarValue::Scalar(_) => acc,
126 ColumnarValue::Array(a) => Some(a.len()),
127 });
128
129 let is_scalar = len.is_none();
130 let inferred_length = len.unwrap_or(1);
131 let args = args
132 .iter()
133 .map(|arg| arg.to_array(inferred_length))
134 .collect::<Result<Vec<_>>>()?;
135
136 let result = regexp_match(&args);
137 if is_scalar {
138 let result = result.and_then(|arr| ScalarValue::try_from_array(&arr, 0));
140 result.map(ColumnarValue::Scalar)
141 } else {
142 result.map(ColumnarValue::Array)
143 }
144 }
145
146 fn documentation(&self) -> Option<&Documentation> {
147 self.doc()
148 }
149}
150
151fn regexp_match_scalar_pattern(args: &[ColumnarValue]) -> Result<Option<ArrayRef>> {
163 let (values, pattern, flags) = match args {
164 [values, pattern] => (values, pattern, None),
165 [values, pattern, flags] => (values, pattern, Some(flags)),
166 _ => return Ok(None),
167 };
168
169 let (ColumnarValue::Array(values), ColumnarValue::Scalar(pattern)) =
170 (values, pattern)
171 else {
172 return Ok(None);
173 };
174 let flags = match flags {
175 Some(ColumnarValue::Array(_)) => return Ok(None),
177 Some(ColumnarValue::Scalar(flags)) => Some(flags),
178 None => None,
179 };
180
181 let value_type = values.data_type();
184
185 if !matches!(pattern.try_as_str(), Some(Some(_)))
186 || &pattern.data_type() != value_type
187 || flags.is_some_and(|flags| {
188 flags.try_as_str() == Some(Some("g")) || &flags.data_type() != value_type
189 })
190 {
191 return Ok(None);
192 }
193
194 let pattern = pattern.to_scalar()?;
195 let flags = flags.map(ScalarValue::to_scalar).transpose()?;
196
197 regexp::regexp_match(
198 values,
199 &pattern,
200 flags.as_ref().map(|flags| flags as &dyn Datum),
201 )
202 .map(Some)
203 .map_err(|e| arrow_datafusion_err!(e))
204}
205
206pub fn regexp_match(args: &[ArrayRef]) -> Result<ArrayRef> {
207 match args.len() {
208 2 => regexp::regexp_match(&args[0], &args[1], None)
209 .map_err(|e| arrow_datafusion_err!(e)),
210 3 => {
211 match args[2].data_type() {
212 DataType::Utf8View => {
213 if args[2].as_string_view().iter().any(|s| s == Some("g")) {
214 return plan_err!(
215 "regexp_match() does not support the \"global\" option"
216 );
217 }
218 }
219 DataType::Utf8 => {
220 if args[2].as_string::<i32>().iter().any(|s| s == Some("g")) {
221 return plan_err!(
222 "regexp_match() does not support the \"global\" option"
223 );
224 }
225 }
226 DataType::LargeUtf8 => {
227 if args[2].as_string::<i64>().iter().any(|s| s == Some("g")) {
228 return plan_err!(
229 "regexp_match() does not support the \"global\" option"
230 );
231 }
232 }
233 e => {
234 return plan_err!(
235 "regexp_match was called with unexpected data type {e:?}"
236 );
237 }
238 }
239
240 regexp::regexp_match(&args[0], &args[1], Some(&args[2]))
241 .map_err(|e| arrow_datafusion_err!(e))
242 }
243 other => exec_err!(
244 "regexp_match was called with {other} arguments. It requires at least 2 and at most 3."
245 ),
246 }
247}
248#[cfg(test)]
249mod tests {
250 use crate::regex::regexpmatch::regexp_match;
251 use arrow::array::StringArray;
252 use arrow::array::{GenericStringBuilder, ListBuilder};
253 use std::sync::Arc;
254
255 #[test]
256 fn test_case_sensitive_regexp_match() {
257 let values = StringArray::from(vec!["abc"; 5]);
258 let patterns =
259 StringArray::from(vec!["^(a)", "^(A)", "(b|d)", "(B|D)", "^(b|c)"]);
260
261 let elem_builder: GenericStringBuilder<i32> = GenericStringBuilder::new();
262 let mut expected_builder = ListBuilder::new(elem_builder);
263 expected_builder.values().append_value("a");
264 expected_builder.append(true);
265 expected_builder.append(false);
266 expected_builder.values().append_value("b");
267 expected_builder.append(true);
268 expected_builder.append(false);
269 expected_builder.append(false);
270 let expected = expected_builder.finish();
271
272 let re = regexp_match(&[Arc::new(values), Arc::new(patterns)]).unwrap();
273
274 assert_eq!(re.as_ref(), &expected);
275 }
276
277 #[test]
278 fn test_case_insensitive_regexp_match() {
279 let values = StringArray::from(vec!["abc"; 5]);
280 let patterns =
281 StringArray::from(vec!["^(a)", "^(A)", "(b|d)", "(B|D)", "^(b|c)"]);
282 let flags = StringArray::from(vec!["i"; 5]);
283
284 let elem_builder: GenericStringBuilder<i32> = GenericStringBuilder::new();
285 let mut expected_builder = ListBuilder::new(elem_builder);
286 expected_builder.values().append_value("a");
287 expected_builder.append(true);
288 expected_builder.values().append_value("a");
289 expected_builder.append(true);
290 expected_builder.values().append_value("b");
291 expected_builder.append(true);
292 expected_builder.values().append_value("b");
293 expected_builder.append(true);
294 expected_builder.append(false);
295 let expected = expected_builder.finish();
296
297 let re = regexp_match(&[Arc::new(values), Arc::new(patterns), Arc::new(flags)])
298 .unwrap();
299
300 assert_eq!(re.as_ref(), &expected);
301 }
302
303 #[test]
304 fn test_unsupported_global_flag_regexp_match() {
305 let values = StringArray::from(vec!["abc"]);
306 let patterns = StringArray::from(vec!["^(a)"]);
307 let flags = StringArray::from(vec!["g"]);
308
309 let re_err =
310 regexp_match(&[Arc::new(values), Arc::new(patterns), Arc::new(flags)])
311 .expect_err("unsupported flag should have failed");
312
313 assert_eq!(
314 re_err.strip_backtrace(),
315 "Error during planning: regexp_match() does not support the \"global\" option"
316 );
317 }
318
319 #[test]
322 fn test_scalar_pattern_matches_array_pattern() {
323 use super::{RegexpMatchFunc, ScalarValue};
324 use arrow::array::{Array, ArrayRef};
325 use arrow::datatypes::{DataType, Field};
326 use datafusion_common::config::ConfigOptions;
327 use datafusion_expr::{ColumnarValue, ScalarFunctionArgs, ScalarUDFImpl};
328
329 let values = Arc::new(StringArray::from(vec![
330 Some("abc"),
331 Some("ABC"),
332 None,
333 Some(""),
334 Some("a-b-c"),
335 ])) as ArrayRef;
336
337 for pattern in ["([a-z])(b)?", "^(A)", "no-match", "", "[a-z]+"] {
338 for flags in [None, Some("i")] {
339 let mut scalar_args = vec![
340 ColumnarValue::Array(Arc::clone(&values)),
341 ColumnarValue::Scalar(ScalarValue::Utf8(Some(pattern.to_string()))),
342 ];
343 let mut array_args = vec![
344 Arc::clone(&values),
345 Arc::new(StringArray::from(vec![pattern; values.len()])) as ArrayRef,
346 ];
347 if let Some(flags) = flags {
348 scalar_args.push(ColumnarValue::Scalar(ScalarValue::Utf8(Some(
349 flags.to_string(),
350 ))));
351 array_args
352 .push(Arc::new(StringArray::from(vec![flags; values.len()]))
353 as ArrayRef);
354 }
355
356 let arg_fields = scalar_args
357 .iter()
358 .enumerate()
359 .map(|(idx, arg)| {
360 Field::new(format!("arg_{idx}"), arg.data_type(), true).into()
361 })
362 .collect();
363 let actual = RegexpMatchFunc::new()
364 .invoke_with_args(ScalarFunctionArgs {
365 args: scalar_args,
366 arg_fields,
367 number_rows: values.len(),
368 return_field: Field::new_list(
369 "f",
370 Field::new_list_field(DataType::Utf8, true),
371 true,
372 )
373 .into(),
374 config_options: Arc::new(ConfigOptions::default()),
375 })
376 .unwrap()
377 .to_array(values.len())
378 .unwrap();
379
380 let expected = regexp_match(&array_args).unwrap();
381 assert_eq!(&actual, &expected, "pattern={pattern:?} flags={flags:?}");
382 }
383 }
384 }
385}