[package]
name = "sqlite-regex"
version = "0.1.0"
edition = "2021"
[features]
default = []
build_extension = ["rusqlite/bundled", "rusqlite/functions"]
[lib]
crate-type = ["cdylib"]
[dependencies]
regex = "1.5.4"
log = "0.4.14"
env_logger = "0.9.0"
anyhow = "1.0.54"
[dependencies.rusqlite]
package = "rusqlite"
git = "https://github.com/litements/rusqlite/"
branch = "loadable-extensions-release-2"
default-features = false
features = ["loadable_extension", "vtab", "functions", "bundled"]
#![allow(clippy::missing_safety_doc)]
use crate::ffi::loadable_extension_init;
use anyhow::Context as ACtxt;
use log::LevelFilter;
use regex::bytes::Regex;
use rusqlite::ffi;
use rusqlite::functions::{Context, FunctionFlags};
use rusqlite::types::{ToSqlOutput, Value, ValueRef};
use rusqlite::Connection;
use std::os::raw::c_int;
fn ah(e: anyhow::Error) -> rusqlite::Error {
rusqlite::Error::UserFunctionError(format!("{:?}", e).into())
}
fn init_logging(default_level: LevelFilter) {
let lib_log_env = "SQLITE_REGEX_LOG";
if std::env::var(lib_log_env).is_err() {
std::env::set_var(lib_log_env, format!("{}", default_level))
}
let logger_env = env_logger::Env::new().filter(lib_log_env);
env_logger::try_init_from_env(logger_env).ok();
}
SELECT load_extension('path/to/loadable/extension/regex_ext.[extension]', 'sqlite3_regex_init')
#[no_mangle]
pub unsafe extern "C" fn sqlite3_regex_init(
db: *mut ffi::sqlite3,
_pz_err_msg: &mut &mut std::os::raw::c_char,
p_api: *mut ffi::sqlite3_api_routines,
) -> c_int {
loadable_extension_init(p_api);
match init(db) {
Ok(()) => {
log::info!("[regex-extension] init ok");
ffi::SQLITE_OK
}
Err(e) => {
log::error!("[regex-extension] init error: {:?}", e);
ffi::SQLITE_ERROR
}
}
}
fn init(db_handle: *mut ffi::sqlite3) -> anyhow::Result<()> {
let db = unsafe { rusqlite::Connection::from_handle(db_handle)? };
load(&db)?;
Ok(())
}
fn add_functions(c: &Connection) -> anyhow::Result<()> {
let deterministic = FunctionFlags::SQLITE_DETERMINISTIC | FunctionFlags::SQLITE_UTF8;
c.create_scalar_function("regex_extract", 2, deterministic, |ctx: &Context| {
regex_extract(ctx).map_err(ah)
})?;
c.create_scalar_function("regex_extract", 3, deterministic, |ctx: &Context| {
regex_extract(ctx).map_err(ah)
})?;
Ok(())
}
fn regex_extract<'a>(ctx: &Context) -> anyhow::Result<ToSqlOutput<'a>> {
let arg_pat = 0;
let arg_input_data = 1;
let arg_cap_group = 2;
let empty_return = Ok(ToSqlOutput::Owned(Value::Null));
let pattern = match ctx.get_raw(arg_pat) {
ValueRef::Text(t) => t,
e => anyhow::bail!("regex pattern must be text, got {}", e.data_type()),
};
let re = Regex::new(std::str::from_utf8(pattern)?)?;
let input_value = match ctx.get_raw(arg_input_data) {
ValueRef::Text(t) => t,
ValueRef::Null => return empty_return,
e => anyhow::bail!("regex expects text as input, got {}", e.data_type()),
};
let cap_group: usize = if ctx.len() <= arg_cap_group {
// no capture group, use default
0
} else {
ctx.get(arg_cap_group).context("capture group")?
};
// let mut caploc = re.capture_locations();
// re.captures_read(&mut caploc, input_value);
if let Some(cap) = re.captures(input_value) {
match cap.get(cap_group) {
None => empty_return,
// String::from_utf8_lossy
Some(t) => {
let value = String::from_utf8_lossy(t.as_bytes());
return Ok(ToSqlOutput::Owned(Value::Text(value.to_string())));
}
}
} else {
empty_return
}
}
cargo build --release
SELECT load_extension('target/release/libsqlite_regex.dylib', 'sqlite3_regex_init');
[2022-05-14T23:22:09Z INFO sqlite_regex] [regex-extension] init ok
#!/usr/bin/env python3
import sqlite3
conn = sqlite3.connect("test.db", isolation_level=None)
print(f"Loading SQLite extension in connection: {conn}")
conn.enable_load_extension(True)
conn.execute(
"SELECT load_extension('target/release/libsqlite_regex.dylib', 'sqlite3_regex_init');"
)
conn.enable_load_extension(False)
print("Running tests...")
print("Testing pattern 'x(ab)' WITHOUT capture group")
row = conn.execute("SELECT regex_extract('x(ab)', 'xxabaa')").fetchone()
assert row[0] == "xab", row[0]
print("Testing pattern 'x(ab)' WITH capture group = 1")
row = conn.execute("SELECT regex_extract('x(ab)', 'xxabaa', 1)").fetchone()
assert row[0] == "ab", row[0]
print("Testing pattern 'x(ab)' WITH capture group = 0")
row = conn.execute("SELECT regex_extract('x(ab)', 'xxabaa', 0)").fetchone()
assert row[0] == "xab", row[0]
print("Testing pattern 'g(oog)+le' WITHOUT capture group")
row = conn.execute("SELECT regex_extract('g(oog)+le', 'googoogoogle')").fetchone()
assert row[0] == "googoogoogle", row[0]
print("Testing pattern 'g(oog)+le' WITH capture group = 1")
row = conn.execute("SELECT regex_extract('g(oog)+le', 'googoogoogle', 1)").fetchone()
assert row[0] == "oog", row[0]
print("Testing pattern '[Cc]at' WITHOUT capture group")
row = conn.execute("SELECT regex_extract('[Cc]at', 'cat')").fetchone()
assert row[0] == "cat", row[0]
print("Testing pattern '[Cc]at' WITHOUT capture group, expecting empty return")
row = conn.execute("SELECT regex_extract('[Cc]at', 'hello')").fetchone()
assert row[0] is None, row[0]
From 日报小组 侯盛鑫 坏姐姐
原文作者:Rust语言中文社区;如有侵权,请联系管理员删除。