Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion datafusion-examples/examples/builtin_functions/regexp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ pub async fn regexp() -> Result<()> {
.collect()
.await;

let expected = "Regular expression did not compile: CompiledTooBig";
let expected =
"Regular expression did not compile: Compiled regex exceeds size limit";
assert_contains!(result.unwrap_err().to_string(), expected);

//
Expand Down
49 changes: 8 additions & 41 deletions datafusion/functions/src/regex/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,16 @@

use arrow::array::ArrayRef;
use arrow::compute::kernels::{cmp::eq, nullif::nullif};
use arrow::error::ArrowError;
use datafusion_common::{Result, ScalarValue};
use regex::Regex;
use std::collections::HashMap;
use std::collections::hash_map::Entry;
use std::sync::Arc;

pub(crate) use datafusion_physical_expr_common::regex::explain_regexp_kernel_error;
// The compilation of a regular expression is shared with the physical
// expressions, so that every caller reports a failure in the same way. These
// re-exports keep the paths that callers of this crate already use.
pub use datafusion_physical_expr_common::regex::{
compile_and_cache_regex, compile_regex,
};
pub mod regexpcount;
pub mod regexpinstr;
pub mod regexplike;
Expand Down Expand Up @@ -139,24 +143,6 @@ pub fn functions() -> Vec<Arc<datafusion_expr::ScalarUDF>> {
]
}

pub fn compile_and_cache_regex<'strings, 'cache>(
regex: &'strings str,
flags: Option<&'strings str>,
regex_cache: &'cache mut HashMap<(&'strings str, Option<&'strings str>), Regex>,
) -> Result<&'cache Regex, ArrowError>
where
'strings: 'cache,
{
let result = match regex_cache.entry((regex, flags)) {
Entry::Occupied(occupied_entry) => occupied_entry.into_mut(),
Entry::Vacant(vacant_entry) => {
let compiled = compile_regex(regex, flags)?;
vacant_entry.insert(compiled)
}
};
Ok(result)
}

/// Maps `start`, a 1-based character position, to a byte offset in `value`.
/// Positions `1..=n` (for an `n`-character string) map to the corresponding
/// character's first byte; position `n + 1`, the end of the string, maps to
Expand All @@ -173,25 +159,6 @@ pub(crate) fn start_to_byte_offset(value: &str, start: i64) -> Option<usize> {
.nth(start_index)
}

pub fn compile_regex(regex: &str, flags: Option<&str>) -> Result<Regex, ArrowError> {
let pattern = match flags {
None | Some("") => regex.to_string(),
Some(flags) => {
if flags.contains('g') {
return Err(ArrowError::ComputeError(
"regexp_count()/regexp_instr() does not support the global flag"
.to_string(),
));
}
format!("(?{flags}){regex}")
}
};

Regex::new(&pattern).map_err(|_| {
ArrowError::ComputeError(format!("Regular expression did not compile: {pattern}"))
})
}

#[cfg(test)]
mod tests {
use super::start_to_byte_offset;
Expand Down
Loading