microrm 0.6.3

Lightweight ORM using sqlite as a backend
Documentation
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,
            // function name
            "regexp\0".as_ptr() as *const i8,
            // number of args
            2,
            // input / behaviour flags
            sq::SQLITE_UTF8 | sq::SQLITE_DETERMINISTIC,
            // regex cache userdata pointer
            regex_cache as *mut _ as *mut c_void,
            // scalar function ptr
            Some(regex_func_impl),
            // step function ptr (only for aggregate functions)
            None,
            // final function ptr (only for aggregate functions)
            None,
            // destructor function implementation (optional)
            Some(regex_destructor),
        )
    };

    super::check_rcode(|| None, r)?;
    Ok(())
}