blob: 5c02a46b5fa701a5e98e3bce6250b238ad16bc36 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
//! Shared helpers for `regexp_extract` and `regexp_extract_all`. Both UDFs accept
//! `(subject, pattern, [idx])` and need to validate the same things in the same way; this
//! module centralizes that parsing and the null short-circuit so each UDF is left with
//! its own per-row loop.
use datafusion::common::{exec_err, DataFusionError, Result as DataFusionResult, ScalarValue};
use datafusion::logical_expr::ColumnarValue;
use regex::Regex;
/// Result of parsing the `(subject, pattern, [idx])` arguments shared by `regexp_extract`
/// and `regexp_extract_all`.
pub(super) enum ParsedArgs<'a> {
/// Arguments validated; the caller can run its per-row extraction loop.
Parsed {
regex: Regex,
group_idx: usize,
subject: &'a ColumnarValue,
},
/// A scalar pattern or idx was null; the result is null for every input row. `len` is
/// `Some(n)` when the subject is an array (one null per row) and `None` for a scalar
/// subject.
NullResult { len: Option<usize> },
}
/// Validate `(subject, pattern, [idx])` and return either a compiled `Regex` and group index
/// ready for extraction, or a request to short-circuit to a null result.
pub(super) fn parse_args<'a>(
fn_name: &'static str,
args: &'a [ColumnarValue],
) -> DataFusionResult<ParsedArgs<'a>> {
if args.len() < 2 || args.len() > 3 {
return exec_err!(
"{fn_name} expects 2 or 3 arguments (subject, pattern, [idx]), got {}",
args.len()
);
}
let idx: i32 = if args.len() == 3 {
match &args[2] {
ColumnarValue::Scalar(ScalarValue::Int32(Some(i))) => *i,
ColumnarValue::Scalar(ScalarValue::Int32(None)) => {
return Ok(ParsedArgs::NullResult {
len: subject_len(&args[0]),
});
}
_ => {
return exec_err!("{fn_name} idx must be an Int32 scalar");
}
}
} else {
1
};
let pattern: &str = match &args[1] {
ColumnarValue::Scalar(ScalarValue::Utf8(Some(p)))
| ColumnarValue::Scalar(ScalarValue::LargeUtf8(Some(p))) => p,
ColumnarValue::Scalar(ScalarValue::Utf8(None))
| ColumnarValue::Scalar(ScalarValue::LargeUtf8(None)) => {
return Ok(ParsedArgs::NullResult {
len: subject_len(&args[0]),
});
}
_ => {
return exec_err!("{fn_name} pattern must be a scalar string");
}
};
let regex = Regex::new(pattern).map_err(|e| {
DataFusionError::Execution(format!(
"The value of parameter `regexp` in `{fn_name}` is invalid: '{pattern}' ({e})"
))
})?;
let group_count = regex.captures_len() as i32 - 1;
if idx < 0 || idx > group_count {
return Err(DataFusionError::Execution(format!(
"The value of parameter `idx` in `{fn_name}` is invalid: \
Expects group index between 0 and {group_count}, but got {idx}."
)));
}
Ok(ParsedArgs::Parsed {
regex,
group_idx: idx as usize,
subject: &args[0],
})
}
/// Returns `Some(row_count)` for an array subject and `None` for a scalar.
pub(super) fn subject_len(value: &ColumnarValue) -> Option<usize> {
match value {
ColumnarValue::Array(a) => Some(a.len()),
ColumnarValue::Scalar(_) => None,
}
}