Skip to main content

duckdb/
inner_connection.rs

1use std::{
2    ffi::{CStr, CString},
3    mem,
4    os::raw::c_char,
5    ptr, str,
6    sync::{Arc, Mutex},
7};
8
9use super::{Appender, Config, Connection, Result, ffi};
10use crate::{
11    error::{
12        Error, result_from_duckdb_appender, result_from_duckdb_extract, result_from_duckdb_prepare,
13        result_from_duckdb_result,
14    },
15    raw_statement::RawStatement,
16    statement::Statement,
17};
18
19#[derive(Debug)]
20struct DatabaseHandle {
21    db: ffi::duckdb_database,
22    close_on_drop: bool,
23}
24
25// `duckdb_database` is an opaque C pointer. We share it via `Arc` so the database
26// is closed exactly once when the last connection referencing it is dropped.
27// This means the handle may be dropped on any thread that owns a `Connection`.
28unsafe impl Send for DatabaseHandle {}
29
30impl DatabaseHandle {
31    #[inline]
32    pub fn new(db: ffi::duckdb_database, close_on_drop: bool) -> Self {
33        Self { db, close_on_drop }
34    }
35
36    #[inline]
37    pub fn raw(&self) -> ffi::duckdb_database {
38        self.db
39    }
40}
41
42impl Drop for DatabaseHandle {
43    fn drop(&mut self) {
44        if !self.close_on_drop {
45            return;
46        }
47        if self.db.is_null() {
48            return;
49        }
50        unsafe {
51            ffi::duckdb_close(&mut self.db);
52            self.db = ptr::null_mut();
53        }
54    }
55}
56
57pub struct InnerConnection {
58    // Mutex makes the Send-only handle `Sync` so the `Arc` can be shared across threads;
59    // it is not used for concurrent access to the database itself.
60    database: Arc<Mutex<DatabaseHandle>>,
61    pub con: ffi::duckdb_connection,
62    interrupt: Arc<InterruptHandle>,
63}
64
65impl InnerConnection {
66    #[inline]
67    unsafe fn new(database: Arc<Mutex<DatabaseHandle>>) -> Result<Self> {
68        unsafe {
69            let mut con: ffi::duckdb_connection = ptr::null_mut();
70            let db_raw = database.lock().expect("database handle mutex poisoned").raw();
71            let r = ffi::duckdb_connect(db_raw, &mut con);
72            if r != ffi::DuckDBSuccess {
73                ffi::duckdb_disconnect(&mut con);
74                return Err(Error::DuckDBFailure(
75                    ffi::Error::new(r),
76                    Some("connect error".to_owned()),
77                ));
78            }
79            let interrupt = Arc::new(InterruptHandle::new(con));
80
81            Ok(Self {
82                database,
83                con,
84                interrupt,
85            })
86        }
87    }
88
89    pub fn open_with_flags(c_path: &CStr, config: Config) -> Result<Self> {
90        unsafe {
91            let mut db: ffi::duckdb_database = ptr::null_mut();
92            let mut c_err = std::ptr::null_mut();
93            let r = ffi::duckdb_open_ext(c_path.as_ptr(), &mut db, config.duckdb_config(), &mut c_err);
94            if r != ffi::DuckDBSuccess {
95                let msg = ffi::DuckDbString::from_nullable_ptr(c_err).map(|c_err| c_err.to_string_lossy().to_string());
96                return Err(Error::DuckDBFailure(ffi::Error::new(r), msg));
97            }
98            Self::new_from_raw_db(db, true)
99        }
100    }
101
102    #[inline]
103    pub(crate) unsafe fn new_from_raw_db(raw: ffi::duckdb_database, close_on_drop: bool) -> Result<Self> {
104        unsafe { Self::new(Arc::new(Mutex::new(DatabaseHandle::new(raw, close_on_drop)))) }
105    }
106
107    pub fn close(&mut self) -> Result<()> {
108        if self.con.is_null() {
109            return Ok(());
110        }
111        unsafe {
112            ffi::duckdb_disconnect(&mut self.con);
113            self.con = ptr::null_mut();
114            self.interrupt.clear();
115        }
116        Ok(())
117    }
118
119    /// Creates a new connection to the already-opened database.
120    pub fn try_clone(&self) -> Result<Self> {
121        unsafe { Self::new(self.database.clone()) }
122    }
123
124    pub fn execute(&mut self, sql: &str) -> Result<()> {
125        let c_str = CString::new(sql)?;
126        unsafe {
127            let mut out = mem::zeroed();
128            let r = ffi::duckdb_query(self.con, c_str.as_ptr() as *const c_char, &mut out);
129            result_from_duckdb_result(r, &mut out)?;
130            ffi::duckdb_destroy_result(&mut out);
131            Ok(())
132        }
133    }
134
135    pub fn prepare<'a>(&mut self, conn: &'a Connection, sql: &str) -> Result<Statement<'a>> {
136        let c_str = CString::new(sql)?;
137
138        // Extract statements (handles both single and multi-statement queries)
139        let mut extracted = ptr::null_mut();
140        let num_stmts =
141            unsafe { ffi::duckdb_extract_statements(self.con, c_str.as_ptr() as *const c_char, &mut extracted) };
142        result_from_duckdb_extract(num_stmts, extracted)?;
143
144        // Auto-cleanup on drop
145        let _guard = ExtractedStatementsGuard(extracted);
146
147        // Execute all intermediate statements
148        for i in 0..num_stmts - 1 {
149            self.execute_extracted_statement(extracted, i)?;
150        }
151
152        // Prepare and return final statement
153        let final_stmt = self.prepare_extracted_statement(extracted, num_stmts - 1)?;
154        Ok(Statement::new(conn, unsafe { RawStatement::new(final_stmt) }))
155    }
156
157    fn prepare_extracted_statement(
158        &self,
159        extracted: ffi::duckdb_extracted_statements,
160        index: ffi::idx_t,
161    ) -> Result<ffi::duckdb_prepared_statement> {
162        let mut stmt = ptr::null_mut();
163        let res = unsafe { ffi::duckdb_prepare_extracted_statement(self.con, extracted, index, &mut stmt) };
164        result_from_duckdb_prepare(res, stmt)?;
165        Ok(stmt)
166    }
167
168    fn execute_extracted_statement(
169        &self,
170        extracted: ffi::duckdb_extracted_statements,
171        index: ffi::idx_t,
172    ) -> Result<()> {
173        let mut stmt = self.prepare_extracted_statement(extracted, index)?;
174
175        let mut result = unsafe { mem::zeroed() };
176        let rc = unsafe { ffi::duckdb_execute_prepared(stmt, &mut result) };
177
178        let error = if rc != ffi::DuckDBSuccess {
179            unsafe {
180                let c_err = ffi::duckdb_result_error(&mut result as *mut _);
181                let msg = if c_err.is_null() {
182                    None
183                } else {
184                    Some(CStr::from_ptr(c_err).to_string_lossy().to_string())
185                };
186                Some(Error::DuckDBFailure(ffi::Error::new(rc), msg))
187            }
188        } else {
189            None
190        };
191
192        unsafe {
193            ffi::duckdb_destroy_prepare(&mut stmt);
194            ffi::duckdb_destroy_result(&mut result);
195        }
196
197        error.map_or(Ok(()), Err)
198    }
199
200    pub fn appender<'a>(
201        &mut self,
202        conn: &'a Connection,
203        catalog: Option<&str>,
204        schema: &str,
205        table: &str,
206    ) -> Result<Appender<'a>> {
207        let c_app = self.create_table_appender(catalog, schema, table)?;
208        Ok(Appender::new(conn, c_app))
209    }
210
211    fn create_table_appender(
212        &mut self,
213        catalog: Option<&str>,
214        schema: &str,
215        table: &str,
216    ) -> Result<ffi::duckdb_appender> {
217        let mut c_app: ffi::duckdb_appender = ptr::null_mut();
218        let c_catalog = catalog.map(CString::new).transpose()?;
219        let c_table = CString::new(table)?;
220        let c_schema = CString::new(schema)?;
221        let r = unsafe {
222            ffi::duckdb_appender_create_ext(
223                self.con,
224                c_catalog.as_ref().map_or(ptr::null(), |c| c.as_ptr() as *const c_char),
225                c_schema.as_ptr() as *const c_char,
226                c_table.as_ptr() as *const c_char,
227                &mut c_app,
228            )
229        };
230        result_from_duckdb_appender(r, &mut c_app)?;
231        Ok(c_app)
232    }
233
234    pub fn appender_with_columns<'a>(
235        &mut self,
236        conn: &'a Connection,
237        catalog: Option<&str>,
238        schema: &str,
239        table: &str,
240        columns: &[&str],
241    ) -> Result<Appender<'a>> {
242        // The C API only supports narrowing columns after the appender is created.
243        // Create the appender first, then activate the requested column subset.
244        let c_app = self.create_table_appender(catalog, schema, table)?;
245        let mut appender = Appender::new(conn, c_app);
246        for column in columns {
247            appender.add_column(column)?;
248        }
249        Ok(appender)
250    }
251
252    pub fn get_interrupt_handle(&self) -> Arc<InterruptHandle> {
253        self.interrupt.clone()
254    }
255
256    #[inline]
257    pub fn is_autocommit(&self) -> bool {
258        true
259    }
260}
261
262struct ExtractedStatementsGuard(ffi::duckdb_extracted_statements);
263
264impl Drop for ExtractedStatementsGuard {
265    fn drop(&mut self) {
266        unsafe { ffi::duckdb_destroy_extracted(&mut self.0) }
267    }
268}
269
270impl Drop for InnerConnection {
271    #[inline]
272    fn drop(&mut self) {
273        use std::thread::panicking;
274        if let Err(e) = self.close() {
275            if panicking() {
276                eprintln!("Error while closing DuckDB connection: {e:?}");
277            } else {
278                panic!("Error while closing DuckDB connection: {e:?}");
279            }
280        }
281    }
282}
283
284/// A handle that allows interrupting long-running queries.
285pub struct InterruptHandle {
286    conn: Mutex<ffi::duckdb_connection>,
287}
288
289unsafe impl Send for InterruptHandle {}
290unsafe impl Sync for InterruptHandle {}
291
292impl InterruptHandle {
293    fn new(conn: ffi::duckdb_connection) -> Self {
294        Self { conn: Mutex::new(conn) }
295    }
296
297    fn clear(&self) {
298        *(self.conn.lock().unwrap()) = ptr::null_mut();
299    }
300
301    /// Interrupt the query currently running on the connection this handle was
302    /// obtained from. The interrupt will cause that query to fail with
303    /// `Error::DuckDBFailure`. If the connection was dropped after obtaining
304    /// this interrupt handle, calling this method results in a noop.
305    ///
306    /// See [`crate::Connection::interrupt_handle`] for an example.
307    pub fn interrupt(&self) {
308        let db_handle = self.conn.lock().unwrap();
309
310        if !db_handle.is_null() {
311            unsafe {
312                ffi::duckdb_interrupt(*db_handle);
313            }
314        }
315    }
316}