extern "C" {
fn quack_rs_create_api_v1() -> libduckdb_sys::duckdb_ext_api_v1;
}
fn init_dispatch_table_once() {
static INIT: std::sync::Once = std::sync::Once::new();
INIT.call_once(|| {
let api = unsafe { quack_rs_create_api_v1() };
let api_ptr = Box::into_raw(Box::new(api));
std::thread_local! {
static TL_API_PTR: std::cell::Cell<*const libduckdb_sys::duckdb_ext_api_v1> =
const { std::cell::Cell::new(std::ptr::null()) };
}
TL_API_PTR.with(|cell| cell.set(api_ptr));
unsafe extern "C" fn get_api_fn(
_info: libduckdb_sys::duckdb_extension_info,
_version: *const std::os::raw::c_char,
) -> *const std::os::raw::c_void {
TL_API_PTR.with(|cell| cell.get().cast())
}
let access = libduckdb_sys::duckdb_extension_access {
set_error: None,
get_database: None,
get_api: Some(get_api_fn),
};
unsafe {
libduckdb_sys::duckdb_rs_extension_api_init(
std::ptr::null_mut(),
std::ptr::addr_of!(access),
"v1",
)
.expect("failed to initialise DuckDB loadable-extension dispatch table");
}
});
}
pub struct InMemoryDb {
conn: duckdb::Connection,
}
impl InMemoryDb {
pub fn open() -> Result<Self, duckdb::Error> {
init_dispatch_table_once();
Ok(Self {
conn: duckdb::Connection::open_in_memory()?,
})
}
pub fn execute_batch(&self, sql: &str) -> Result<(), duckdb::Error> {
self.conn.execute_batch(sql)
}
pub fn execute(&self, sql: &str) -> Result<usize, duckdb::Error> {
self.conn.execute(sql, [])
}
pub fn query_one<T>(&self, sql: &str) -> Result<T, duckdb::Error>
where
T: duckdb::types::FromSql,
{
let mut stmt = self.conn.prepare(sql)?;
stmt.query_row([], |row| row.get(0))
}
pub const fn conn(&self) -> &duckdb::Connection {
&self.conn
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn in_memory_db_opens() {
let db = InMemoryDb::open().expect("should open in-memory db");
let _: i64 = db.query_one("SELECT 1").expect("should query 1");
}
#[test]
fn in_memory_db_execute_batch_and_query() {
let db = InMemoryDb::open().unwrap();
db.execute_batch("CREATE TABLE t(v INTEGER); INSERT INTO t VALUES (10), (20), (30)")
.unwrap();
let total: i64 = db.query_one("SELECT SUM(v) FROM t").unwrap();
assert_eq!(total, 60);
}
#[test]
fn in_memory_db_sql_macro() {
use crate::sql_macro::SqlMacro;
let db = InMemoryDb::open().unwrap();
let macro_ = SqlMacro::scalar("triple", &["x"], "x * 3").unwrap();
db.execute_batch(¯o_.to_sql()).unwrap();
let result: i64 = db.query_one("SELECT triple(14)").unwrap();
assert_eq!(result, 42);
}
}