better_duck_core/raw/
instance_cache.rs1#![allow(clippy::not_unsafe_ptr_arg_deref)]
10
11use std::ffi::{c_void, CStr};
12use std::path::Path;
13use std::ptr;
14use std::sync::Arc;
15
16use crate::{
17 config::Config,
18 database::Database,
19 error::{Error, Result},
20 ffi::{
21 duckdb_create_instance_cache, duckdb_database, duckdb_destroy_instance_cache, duckdb_free,
22 duckdb_get_or_create_from_cache, duckdb_instance_cache, DuckDBSuccess,
23 },
24 helpers::path::path_to_cstring,
25 raw::connection::RawDatabase,
26};
27
28pub struct InstanceCache {
33 ptr: duckdb_instance_cache,
34 _not_thread_safe: std::marker::PhantomData<*const ()>,
35}
36
37impl InstanceCache {
38 #[must_use]
40 pub fn new() -> InstanceCache {
41 let ptr = unsafe { duckdb_create_instance_cache() };
43 InstanceCache { ptr, _not_thread_safe: std::marker::PhantomData }
44 }
45
46 pub fn get_or_create<P: AsRef<Path>>(
59 &self,
60 path: P,
61 config: Config,
62 ) -> Result<Database> {
63 let c_path = path_to_cstring(path.as_ref())?;
64 let config = config.with("duckdb_api", "rust")?;
65 let mut db: duckdb_database = ptr::null_mut();
66 let mut c_err: *mut std::os::raw::c_char = ptr::null_mut();
67 let state = unsafe {
71 duckdb_get_or_create_from_cache(
72 self.ptr,
73 c_path.as_ptr(),
74 &mut db,
75 config.duckdb_config(),
76 &mut c_err,
77 )
78 };
79 if state != DuckDBSuccess {
80 let msg = if c_err.is_null() {
81 None
82 } else {
83 let m = unsafe { CStr::from_ptr(c_err) }.to_string_lossy().into_owned();
86 unsafe { duckdb_free(c_err as *mut c_void) };
88 Some(m)
89 };
90 return Err(Error::DuckDBFailure(crate::ffi::Error::new(state), msg));
91 }
92 let raw = unsafe { RawDatabase::new(db) }?;
94 Ok(Database::from_raw(Arc::new(raw)))
95 }
96}
97
98impl Default for InstanceCache {
99 fn default() -> Self {
100 Self::new()
101 }
102}
103
104impl Drop for InstanceCache {
105 fn drop(&mut self) {
106 if !self.ptr.is_null() {
107 unsafe { duckdb_destroy_instance_cache(&mut self.ptr) };
110 }
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn cache_shares_one_in_memory_instance_across_opens() {
120 let cache = InstanceCache::new();
121 let a = cache.get_or_create(":memory:cachetest", Config::default()).unwrap();
123 let b = cache.get_or_create(":memory:cachetest", Config::default()).unwrap();
124 let mut ca = a.connect().unwrap();
125 ca.execute_batch("CREATE TABLE t (v INTEGER)").unwrap();
126 ca.execute_batch("INSERT INTO t VALUES (7)").unwrap();
127 let mut cb = b.connect().unwrap();
129 let mut rows = cb.execute("SELECT v FROM t").unwrap();
130 assert_eq!(
131 rows.next().unwrap().unwrap().get("v"),
132 Some(&crate::types::value::DuckValue::Int(7))
133 );
134 }
135
136 #[test]
137 fn distinct_named_paths_are_independent() {
138 let cache = InstanceCache::new();
139 let a = cache.get_or_create(":memory:one", Config::default()).unwrap();
140 let b = cache.get_or_create(":memory:two", Config::default()).unwrap();
141 let mut ca = a.connect().unwrap();
142 ca.execute_batch("CREATE TABLE t (v INTEGER)").unwrap();
143 let mut cb = b.connect().unwrap();
145 assert!(cb.execute("SELECT v FROM t").is_err());
146 }
147}