From 1ba25824524fefd5c3c64f6a671b4525ce64f997 Mon Sep 17 00:00:00 2001 From: Neil Conway Date: Mon, 10 Aug 2026 20:39:57 -0400 Subject: [PATCH 1/2] fix: return NULL from regexp_count() for NULL arguments --- datafusion/functions/src/regex/regexpcount.rs | 318 ++++++++++++------ .../test_files/regexp/regexp_count.slt | 127 ++++--- 2 files changed, 288 insertions(+), 157 deletions(-) diff --git a/datafusion/functions/src/regex/regexpcount.rs b/datafusion/functions/src/regex/regexpcount.rs index 0ecedc7cbbb1e..8e8b4436e3ba2 100644 --- a/datafusion/functions/src/regex/regexpcount.rs +++ b/datafusion/functions/src/regex/regexpcount.rs @@ -262,48 +262,37 @@ fn regexp_count_inner<'a, S>( where S: StringArrayType<'a>, { - let (regex_scalar, is_regex_scalar) = if is_regex_scalar || regex_array.len() == 1 { - ( - (!regex_array.is_null(0)).then(|| regex_array.value(0)), - true, - ) + // Treat single-element arrays as scalars, broadcast to every row. An + // absent optional argument behaves like a scalar set to its default. + let is_regex_scalar = is_regex_scalar || regex_array.len() == 1; + let is_start_scalar = + start_array.is_none_or(|array| is_start_scalar || array.len() == 1); + let is_flags_scalar = + flags_array.is_none_or(|array| is_flags_scalar || array.len() == 1); + + // A NULL in any scalar argument produces a NULL result for every row + if (is_regex_scalar && regex_array.is_null(0)) + || (is_start_scalar && start_array.is_some_and(|array| array.is_null(0))) + || (is_flags_scalar && flags_array.is_some_and(|array| array.is_null(0))) + { + return Ok(Arc::new(Int64Array::new_null(values.len()))); + } + + let regex_scalar = is_regex_scalar.then(|| regex_array.value(0)); + // An absent `start` defaults to 1 + let start_scalar = + is_start_scalar.then(|| start_array.map_or(1, |array| array.value(0))); + // A `flags_scalar` of None means no flags were supplied + let flags_scalar = if is_flags_scalar { + flags_array.map(|array| array.value(0)) } else { - (None, false) + None }; - let (start_array, start_scalar, is_start_scalar) = - if let Some(start_array) = start_array { - if is_start_scalar || start_array.len() == 1 { - (None, Some(start_array.value(0)), true) - } else { - (Some(start_array), None, false) - } - } else { - (None, Some(1), true) - }; - - let (flags_array, flags_scalar, is_flags_scalar) = - if let Some(flags_array) = flags_array { - if is_flags_scalar || flags_array.len() == 1 { - (None, Some(flags_array.value(0)), true) - } else { - (Some(flags_array), None, false) - } - } else { - (None, None, true) - }; - let mut regex_cache = HashMap::new(); - match (is_regex_scalar, is_start_scalar, is_flags_scalar) { - (true, true, true) => { - let regex = match regex_scalar { - None => { - return Ok(Arc::new(Int64Array::from(vec![0; values.len()]))); - } - Some(regex) => regex, - }; - + match (regex_scalar, is_start_scalar, is_flags_scalar) { + (Some(regex), true, true) => { let pattern = compile_regex(regex, flags_scalar)?; Ok(Arc::new( @@ -313,14 +302,7 @@ where .collect::>()?, )) } - (true, true, false) => { - let regex = match regex_scalar { - None => { - return Ok(Arc::new(Int64Array::from(vec![0; values.len()]))); - } - Some(regex) => regex, - }; - + (Some(regex), true, false) => { let flags_array = flags_array.unwrap(); if values.len() != flags_array.len() { return Err(ArrowError::ComputeError(format!( @@ -335,21 +317,21 @@ where .iter() .zip(flags_array.iter()) .map(|(value, flags)| { - let pattern = - compile_and_cache_regex(regex, flags, &mut regex_cache)?; + let Some(flags) = flags else { + return Ok(None); + }; + + let pattern = compile_and_cache_regex( + regex, + Some(flags), + &mut regex_cache, + )?; count_matches(value, pattern, start_scalar) }) .collect::>()?, )) } - (true, false, true) => { - let regex = match regex_scalar { - None => { - return Ok(Arc::new(Int64Array::from(vec![0; values.len()]))); - } - Some(regex) => regex, - }; - + (Some(regex), false, true) => { let pattern = compile_regex(regex, flags_scalar)?; let start_array = start_array.unwrap(); @@ -362,14 +344,7 @@ where .collect::>()?, )) } - (true, false, false) => { - let regex = match regex_scalar { - None => { - return Ok(Arc::new(Int64Array::from(vec![0; values.len()]))); - } - Some(regex) => regex, - }; - + (Some(regex), false, false) => { let flags_array = flags_array.unwrap(); if values.len() != flags_array.len() { return Err(ArrowError::ComputeError(format!( @@ -386,15 +361,19 @@ where flags_array.iter() ) .map(|(value, start, flags)| { + let Some(flags) = flags else { + return Ok(None); + }; + let pattern = - compile_and_cache_regex(regex, flags, &mut regex_cache)?; + compile_and_cache_regex(regex, Some(flags), &mut regex_cache)?; count_matches(value, pattern, start) }) .collect::>()?, )) } - (false, true, true) => { + (None, true, true) => { if values.len() != regex_array.len() { return Err(ArrowError::ComputeError(format!( "regex_array must be the same length as values array; got {} and {}", @@ -408,9 +387,8 @@ where .iter() .zip(regex_array.iter()) .map(|(value, regex)| { - let regex = match regex { - None => return Ok(0), - Some(regex) => regex, + let Some(regex) = regex else { + return Ok(None); }; let pattern = compile_and_cache_regex( @@ -423,7 +401,7 @@ where .collect::>()?, )) } - (false, true, false) => { + (None, true, false) => { if values.len() != regex_array.len() { return Err(ArrowError::ComputeError(format!( "regex_array must be the same length as values array; got {} and {}", @@ -444,20 +422,22 @@ where Ok(Arc::new( izip!(values.iter(), regex_array.iter(), flags_array.iter()) .map(|(value, regex, flags)| { - let regex = match regex { - None => return Ok(0), - Some(regex) => regex, + let (Some(regex), Some(flags)) = (regex, flags) else { + return Ok(None); }; - let pattern = - compile_and_cache_regex(regex, flags, &mut regex_cache)?; + let pattern = compile_and_cache_regex( + regex, + Some(flags), + &mut regex_cache, + )?; count_matches(value, pattern, start_scalar) }) .collect::>()?, )) } - (false, false, true) => { + (None, false, true) => { if values.len() != regex_array.len() { return Err(ArrowError::ComputeError(format!( "regex_array must be the same length as values array; got {} and {}", @@ -478,9 +458,8 @@ where Ok(Arc::new( izip!(values.iter(), regex_array.iter(), start_array.iter()) .map(|(value, regex, start)| { - let regex = match regex { - None => return Ok(0), - Some(regex) => regex, + let Some(regex) = regex else { + return Ok(None); }; let pattern = compile_and_cache_regex( @@ -493,7 +472,7 @@ where .collect::>()?, )) } - (false, false, false) => { + (None, false, false) => { if values.len() != regex_array.len() { return Err(ArrowError::ComputeError(format!( "regex_array must be the same length as values array; got {} and {}", @@ -528,13 +507,12 @@ where flags_array.iter() ) .map(|(value, regex, start, flags)| { - let regex = match regex { - None => return Ok(0), - Some(regex) => regex, + let (Some(regex), Some(flags)) = (regex, flags) else { + return Ok(None); }; let pattern = - compile_and_cache_regex(regex, flags, &mut regex_cache)?; + compile_and_cache_regex(regex, Some(flags), &mut regex_cache)?; count_matches(value, pattern, start) }) .collect::>()?, @@ -547,28 +525,23 @@ fn count_matches( value: Option<&str>, pattern: &Regex, start: Option, -) -> Result { - let value = match value { - None => return Ok(0), - Some(value) => value, +) -> Result, ArrowError> { + // A NULL value or start position produces a NULL result. + let (Some(value), Some(start)) = (value, start) else { + return Ok(None); }; - if let Some(start) = start { - if start < 1 { - return Err(ArrowError::ComputeError( - "regexp_count() requires start to be 1 based".to_string(), - )); - } - - let Some(byte_offset) = start_to_byte_offset(value, start) else { - return Ok(0); - }; - let count = pattern.find_iter(&value[byte_offset..]).count(); - Ok(count as i64) - } else { - let count = pattern.find_iter(value).count(); - Ok(count as i64) + if start < 1 { + return Err(ArrowError::ComputeError( + "regexp_count() requires start to be 1 based".to_string(), + )); } + + let Some(byte_offset) = start_to_byte_offset(value, start) else { + return Ok(Some(0)); + }; + let count = pattern.find_iter(&value[byte_offset..]).count(); + Ok(Some(count as i64)) } #[cfg(test)] @@ -603,6 +576,24 @@ mod tests { test_case_sensitive_regexp_count_array_complex::(); test_case_regexp_count_cache_check::>(); + + test_regexp_count_null_scalars(); + + test_regexp_count_null_array_rows::>(); + test_regexp_count_null_array_rows::>(); + test_regexp_count_null_array_rows::(); + + test_regexp_count_null_start_array::>(); + test_regexp_count_null_start_array::>(); + test_regexp_count_null_start_array::(); + + test_regexp_count_null_flags_array::>(); + test_regexp_count_null_flags_array::>(); + test_regexp_count_null_flags_array::(); + + test_regexp_count_null_scalar_regex_array_values::>(); + test_regexp_count_null_scalar_regex_array_values::>(); + test_regexp_count_null_scalar_regex_array_values::(); } fn regexp_count_with_scalar_values(args: &[ScalarValue]) -> Result { @@ -966,6 +957,129 @@ mod tests { assert_eq!(re.as_ref(), &expected); } + fn test_regexp_count_null_scalars() { + // A NULL in any scalar argument produces a NULL result. + let cases: Vec> = vec![ + vec![ScalarValue::Utf8(None), ScalarValue::Utf8(None)], + vec![ + ScalarValue::Utf8(None), + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Int64(Some(1)), + ScalarValue::Utf8(Some("i".to_string())), + ], + vec![ + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Utf8(None), + ScalarValue::Int64(Some(1)), + ScalarValue::Utf8(Some("i".to_string())), + ], + vec![ + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Int64(None), + ScalarValue::Utf8(Some("i".to_string())), + ], + vec![ + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Int64(Some(1)), + ScalarValue::Utf8(None), + ], + ]; + + for args in cases { + let re = regexp_count_with_scalar_values(&args); + match re { + Ok(ColumnarValue::Scalar(ScalarValue::Int64(v))) => { + assert_eq!(v, None, "regexp_count null scalar test failed"); + } + _ => panic!("Unexpected result"), + } + } + } + + fn test_regexp_count_null_array_rows() + where + A: From>> + Array + 'static, + { + let values = A::from(vec![ + None, + Some("abc"), + Some("abc"), + Some("abc"), + Some("abc"), + ]); + let regex = A::from(vec![ + Some("abc"), + None, + Some("abc"), + Some("abc"), + Some("abc"), + ]); + let start = Int64Array::from(vec![Some(1), Some(1), None, Some(1), Some(1)]); + let flags = A::from(vec![Some("i"), Some("i"), Some("i"), None, Some("i")]); + + let expected = Int64Array::from(vec![None, None, None, None, Some(1)]); + + let re = regexp_count_func(&[ + Arc::new(values), + Arc::new(regex), + Arc::new(start), + Arc::new(flags), + ]) + .unwrap(); + assert_eq!(re.as_ref(), &expected); + } + + fn test_regexp_count_null_start_array() + where + A: From> + Array + 'static, + { + let values = A::from(vec!["abc", "abcb"]); + let regex = A::from(vec!["b"]); + let start = Int64Array::from(vec![Some(1), None]); + + let expected = Int64Array::from(vec![Some(1), None]); + + let re = regexp_count_func(&[Arc::new(values), Arc::new(regex), Arc::new(start)]) + .unwrap(); + assert_eq!(re.as_ref(), &expected); + } + + fn test_regexp_count_null_flags_array() + where + A: From> + From>> + Array + 'static, + { + let values: A = vec!["aB", "aB"].into(); + let regex: A = vec!["b"].into(); + let start = Int64Array::from(vec![1]); + let flags: A = vec![None, Some("i")].into(); + + let expected = Int64Array::from(vec![None, Some(1)]); + + let re = regexp_count_func(&[ + Arc::new(values), + Arc::new(regex), + Arc::new(start), + Arc::new(flags), + ]) + .unwrap(); + assert_eq!(re.as_ref(), &expected); + } + + fn test_regexp_count_null_scalar_regex_array_values() + where + A: From> + From>> + Array + 'static, + { + let values: A = vec!["abc", "abcabc"].into(); + let regex: A = vec![Option::<&str>::None].into(); + + let expected = Int64Array::from(vec![None::, None]); + + let re = regexp_count_func(&[Arc::new(values), Arc::new(regex)]).unwrap(); + assert_eq!(re.as_ref(), &expected); + } + fn test_case_regexp_count_cache_check() where A: From> + Array + 'static, diff --git a/datafusion/sqllogictest/test_files/regexp/regexp_count.slt b/datafusion/sqllogictest/test_files/regexp/regexp_count.slt index 1fd43eeb46aec..0b2b9e5e74559 100644 --- a/datafusion/sqllogictest/test_files/regexp/regexp_count.slt +++ b/datafusion/sqllogictest/test_files/regexp/regexp_count.slt @@ -106,7 +106,7 @@ SELECT regexp_count('123123123123', '123', 1, 'g'); query I SELECT regexp_count(str, '\w') from regexp_test_data; ---- -0 +NULL 3 3 3 @@ -122,7 +122,7 @@ SELECT regexp_count(str, '\w') from regexp_test_data; query I SELECT regexp_count(str, '\w{2}', start) from regexp_test_data; ---- -0 +NULL 1 1 1 @@ -138,7 +138,7 @@ SELECT regexp_count(str, '\w{2}', start) from regexp_test_data; query I SELECT regexp_count(str, 'ab', 1, 'i') from regexp_test_data; ---- -0 +NULL 1 1 1 @@ -155,7 +155,7 @@ SELECT regexp_count(str, 'ab', 1, 'i') from regexp_test_data; query I SELECT regexp_count(str, pattern) from regexp_test_data; ---- -0 +NULL 1 1 0 @@ -171,7 +171,7 @@ SELECT regexp_count(str, pattern) from regexp_test_data; query I SELECT regexp_count(str, pattern, start) from regexp_test_data; ---- -0 +NULL 1 1 0 @@ -187,35 +187,35 @@ SELECT regexp_count(str, pattern, start) from regexp_test_data; query I SELECT regexp_count(str, pattern, start, flags) from regexp_test_data; ---- -0 -1 -1 -1 -0 -0 -0 -0 -1 +NULL 1 1 1 +NULL +NULL +NULL +NULL +NULL +NULL +NULL +NULL # test type coercion query I SELECT regexp_count(arrow_cast(str, 'Utf8'), arrow_cast(pattern, 'LargeUtf8'), arrow_cast(start, 'Int32'), flags) from regexp_test_data; ---- -0 -1 -1 -1 -0 -0 -0 -0 -1 +NULL 1 1 1 +NULL +NULL +NULL +NULL +NULL +NULL +NULL +NULL # test string views @@ -226,7 +226,7 @@ SELECT arrow_cast(str, 'Utf8View') as str, arrow_cast(pattern, 'Utf8View') as pa query I SELECT regexp_count(str, '\w') from t_stringview; ---- -0 +NULL 3 3 3 @@ -242,7 +242,7 @@ SELECT regexp_count(str, '\w') from t_stringview; query I SELECT regexp_count(str, '\w{2}', start) from t_stringview; ---- -0 +NULL 1 1 1 @@ -258,7 +258,7 @@ SELECT regexp_count(str, '\w{2}', start) from t_stringview; query I SELECT regexp_count(str, 'ab', 1, 'i') from t_stringview; ---- -0 +NULL 1 1 1 @@ -275,7 +275,7 @@ SELECT regexp_count(str, 'ab', 1, 'i') from t_stringview; query I SELECT regexp_count(str, pattern) from t_stringview; ---- -0 +NULL 1 1 0 @@ -291,7 +291,7 @@ SELECT regexp_count(str, pattern) from t_stringview; query I SELECT regexp_count(str, pattern, start) from t_stringview; ---- -0 +NULL 1 1 0 @@ -307,57 +307,74 @@ SELECT regexp_count(str, pattern, start) from t_stringview; query I SELECT regexp_count(str, pattern, start, flags) from t_stringview; ---- -0 -1 -1 -1 -0 -0 -0 -0 -1 +NULL 1 1 1 +NULL +NULL +NULL +NULL +NULL +NULL +NULL +NULL # test type coercion query I SELECT regexp_count(arrow_cast(str, 'Utf8'), arrow_cast(pattern, 'LargeUtf8'), arrow_cast(start, 'Int32'), flags) from t_stringview; ---- -0 -1 -1 -1 -0 -0 -0 -0 -1 +NULL 1 1 1 +NULL +NULL +NULL +NULL +NULL +NULL +NULL +NULL -# NULL tests +# NULL tests: like PostgreSQL, a NULL in any argument produces a NULL result query I SELECT regexp_count(NULL, NULL); ---- -0 +NULL query I SELECT regexp_count(NULL, 'a'); ---- -0 +NULL query I SELECT regexp_count('a', NULL); ---- -0 +NULL query I SELECT regexp_count(NULL, NULL, NULL, NULL); ---- -0 +NULL + +query I +SELECT regexp_count('abc', 'b', NULL); +---- +NULL + +query I +SELECT regexp_count('abc', 'b', 1, NULL); +---- +NULL + +# NULL start position in one row of a column +query I +SELECT regexp_count(v, 'b', s) FROM (VALUES ('abc', 1), ('abcb', NULL)) AS t(v, s); +---- +1 +NULL statement ok CREATE TABLE empty_table (str varchar, pattern varchar, start int, flags varchar); @@ -372,10 +389,10 @@ INSERT INTO empty_table VALUES ('a', NULL, 1, 'i'), (NULL, 'a', 1, 'i'), (NULL, query I SELECT regexp_count(str, pattern, start, flags) from empty_table; ---- -0 -0 -0 -0 +NULL +NULL +NULL +NULL statement ok drop table t_stringview; From 7e535f1b41960ce691c21a382c4951424d987e6d Mon Sep 17 00:00:00 2001 From: Neil Conway Date: Mon, 10 Aug 2026 20:39:57 -0400 Subject: [PATCH 2/2] fix: return NULL from regexp_instr() for NULL optional arguments --- datafusion/functions/src/regex/regexpinstr.rs | 154 ++++++++++++++++-- .../test_files/regexp/regexp_instr.slt | 28 ++++ 2 files changed, 169 insertions(+), 13 deletions(-) diff --git a/datafusion/functions/src/regex/regexpinstr.rs b/datafusion/functions/src/regex/regexpinstr.rs index 30385672df1bd..96152297fbc87 100644 --- a/datafusion/functions/src/regex/regexpinstr.rs +++ b/datafusion/functions/src/regex/regexpinstr.rs @@ -18,6 +18,7 @@ use arrow::array::{ Array, ArrayRef, AsArray, Datum, Int64Array, Int64Builder, StringArrayType, }; +use arrow::buffer::NullBuffer; use arrow::datatypes::{DataType, Int64Type}; use arrow::datatypes::{ DataType::Int64, DataType::LargeUtf8, DataType::Utf8, DataType::Utf8View, @@ -291,27 +292,28 @@ where let mut regex_cache = RegexCache::default(); let mut result = Int64Builder::with_capacity(len); + // A NULL in any argument produces a NULL result + let nulls = NullBuffer::union_many([ + values.nulls(), + regex_array.nulls(), + start_array.and_then(|array| array.nulls()), + nth_array.and_then(|array| array.nulls()), + flags_array.and_then(|array| array.nulls()), + subexp_array.and_then(|array| array.nulls()), + ]); + for i in 0..len { - if regex_array.is_null(i) { + if nulls.as_ref().is_some_and(|nulls| nulls.is_null(i)) { result.append_null(); continue; } - let regex = regex_array.value(i); - if values.is_null(i) { - result.append_null(); - continue; - } let value = values.value(i); - - let flags = match flags_array { - Some(flags) if !flags.is_null(i) => Some(flags.value(i)), - _ => None, - }; + let regex = regex_array.value(i); + let flags = flags_array.map(|array| array.value(i)); let pattern = regex_cache.get_or_compile(regex, flags)?; - // The defaults apply when the optional argument was not supplied at - // all. A supplied but null slot reads through as its raw buffer value. + // The defaults apply when the optional argument was not supplied. let start = start_array.map_or(1, |array| array.value(i)); let nth = nth_array.map_or(1, |array| array.value(i)); let subexp = subexp_array.map_or(0, |array| array.value(i)); @@ -443,6 +445,12 @@ mod tests { test_case_sensitive_regexp_instr_zero_width_pattern::>(); test_case_sensitive_regexp_instr_zero_width_pattern::>(); test_case_sensitive_regexp_instr_zero_width_pattern::(); + + test_regexp_instr_null_scalar_args(); + + test_regexp_instr_null_array_rows::>(); + test_regexp_instr_null_array_rows::>(); + test_regexp_instr_null_array_rows::(); } fn regexp_instr_with_scalar_values(args: &[ScalarValue]) -> Result { @@ -764,6 +772,126 @@ mod tests { }); } + fn test_regexp_instr_null_scalar_args() { + // A NULL in any argument produces a NULL result + let cases: Vec> = vec![ + // NULL start + vec![ + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Utf8(Some("b".to_string())), + ScalarValue::Int64(None), + ], + // NULL N + vec![ + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Utf8(Some("b".to_string())), + ScalarValue::Int64(Some(1)), + ScalarValue::Int64(None), + ], + // NULL flags + vec![ + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Utf8(Some("b".to_string())), + ScalarValue::Int64(Some(1)), + ScalarValue::Int64(Some(1)), + ScalarValue::Utf8(None), + ], + // NULL subexpr + vec![ + ScalarValue::Utf8(Some("abc".to_string())), + ScalarValue::Utf8(Some("(b)".to_string())), + ScalarValue::Int64(Some(1)), + ScalarValue::Int64(Some(1)), + ScalarValue::Utf8(Some("i".to_string())), + ScalarValue::Int64(None), + ], + ]; + + for args in cases { + let re = regexp_instr_with_scalar_values(&args); + match re { + Ok(ColumnarValue::Scalar(ScalarValue::Int64(v))) => { + assert_eq!(v, None, "regexp_instr null scalar test failed"); + } + _ => panic!("Unexpected result"), + } + } + } + + fn test_regexp_instr_null_array_rows() + where + A: From>> + Array + 'static, + { + let values = A::from(vec![ + None, + Some("abc"), + Some("abc"), + Some("abc"), + Some("abc"), + Some("abc"), + Some("abc"), + ]); + let regex = A::from(vec![ + Some("b"), + None, + Some("b"), + Some("b"), + Some("b"), + Some("(b)"), + Some("b"), + ]); + let start = Int64Array::from(vec![ + Some(1), + Some(1), + None, + Some(1), + Some(1), + Some(1), + Some(1), + ]); + let nth = Int64Array::from(vec![ + Some(1), + Some(1), + Some(1), + None, + Some(1), + Some(1), + Some(1), + ]); + let flags = A::from(vec![ + Some(""), + Some(""), + Some(""), + Some(""), + None, + Some("i"), + Some(""), + ]); + let subexp = Int64Array::from(vec![ + Some(0), + Some(0), + Some(0), + Some(0), + Some(0), + None, + Some(0), + ]); + + let expected = + Int64Array::from(vec![None, None, None, None, None, None, Some(2)]); + + let re = regexp_instr_func(&[ + Arc::new(values), + Arc::new(regex), + Arc::new(start), + Arc::new(nth), + Arc::new(flags), + Arc::new(subexp), + ]) + .unwrap(); + assert_eq!(re.as_ref(), &expected); + } + fn test_case_sensitive_regexp_instr_array() where A: From> + Array + 'static, diff --git a/datafusion/sqllogictest/test_files/regexp/regexp_instr.slt b/datafusion/sqllogictest/test_files/regexp/regexp_instr.slt index f54d4e80cc732..bbe9693736442 100644 --- a/datafusion/sqllogictest/test_files/regexp/regexp_instr.slt +++ b/datafusion/sqllogictest/test_files/regexp/regexp_instr.slt @@ -174,6 +174,34 @@ SELECT regexp_instr('a', NULL); ---- NULL +# Like PostgreSQL, a NULL in any argument produces a NULL result +query I +SELECT regexp_instr('abc', 'b', NULL); +---- +NULL + +query I +SELECT regexp_instr('abc', 'b', 1, NULL); +---- +NULL + +query I +SELECT regexp_instr('abc', 'b', 1, 1, NULL); +---- +NULL + +query I +SELECT regexp_instr('abc', '(b)', 1, 1, 'i', NULL); +---- +NULL + +# NULL start position in one row of a column +query I +SELECT regexp_instr(v, 'b', s) FROM (VALUES ('abc', 1), ('abcb', NULL)) AS t(v, s); +---- +2 +NULL + query I SELECT regexp_instr('😀abcdef', 'abc'); ----