use std::collections::HashMap;
use std::ffi::{c_void, CStr};
use libsqlite3_sys as sq;
type RegexCache = HashMap<String, regex::Regex>;
unsafe extern "C" fn regex_func_impl(
ctx: *mut sq::sqlite3_context,
argc: i32,
argv: *mut *mut sq::sqlite3_value,
) {
let regex_cache = (sq::sqlite3_user_data(ctx) as *mut HashMap<String, regex::Regex>)
.as_mut()
.unwrap();
let argv = std::slice::from_raw_parts(argv, argc as usize);
let extract_text = |value: *mut sq::sqlite3_value| {
let raw = sq::sqlite3_value_text(value);
if raw.is_null() {
log::warn!("given NULL text during regexp");
return None;
}
let Ok(s) = CStr::from_ptr(raw as *const i8).to_str() else {
log::warn!("bad string encoding during regexp");
return None;
};
Some(s)
};
let Some(regex_spec) = extract_text(argv[0]) else {
sq::sqlite3_result_int(ctx, 0);
return;
};
let Some(value) = extract_text(argv[1]) else {
sq::sqlite3_result_int(ctx, 0);
return;
};
if !regex_cache.contains_key(regex_spec) {
let r = match regex::Regex::new(regex_spec) {
Ok(r) => r,
Err(e) => {
log::warn!("failed to compile regex: {e:?}");
return;
},
};
regex_cache.insert(regex_spec.to_string(), r);
};
let regex = regex_cache.get(regex_spec).unwrap();
if regex.is_match(value) {
sq::sqlite3_result_int(ctx, 1);
} else {
sq::sqlite3_result_int(ctx, 0);
}
}
unsafe extern "C" fn regex_destructor(data: *mut c_void) {
drop(Box::from_raw(data as *mut RegexCache));
}
pub fn install_regex(conn: *mut sq::sqlite3) -> crate::DBResult<()> {
let regex_cache = Box::leak(Box::new(RegexCache::default()));
let r = unsafe {
sq::sqlite3_create_function_v2(
conn,
"regexp\0".as_ptr() as *const i8,
2,
sq::SQLITE_UTF8 | sq::SQLITE_DETERMINISTIC,
regex_cache as *mut _ as *mut c_void,
Some(regex_func_impl),
None,
None,
Some(regex_destructor),
)
};
super::check_rcode(|| None, r)?;
Ok(())
}