1use std::ffi::{c_char, c_int, CStr};
2#[cfg(feature = "load_extension")]
3use std::path::Path;
4use std::ptr;
5use std::str;
6use std::sync::{Arc, Mutex};
7
8use super::ffi;
9use super::str_for_sqlite;
10use super::{Connection, InterruptHandle, Name, OpenFlags, PrepFlags, Result};
11use crate::error::{decode_result_raw, error_from_handle, error_with_offset, Error};
12use crate::raw_statement::RawStatement;
13use crate::statement::Statement;
14use crate::version_number;
15
16pub struct InnerConnection {
17 pub db: *mut ffi::sqlite3,
18 interrupt_lock: Arc<Mutex<*mut ffi::sqlite3>>,
25 #[cfg(feature = "hooks")]
26 pub commit_hook: Option<Box<dyn FnMut() -> bool + Send>>,
27 #[cfg(feature = "hooks")]
28 pub rollback_hook: Option<Box<dyn FnMut() + Send>>,
29 #[cfg(feature = "hooks")]
30 #[expect(clippy::type_complexity)]
31 pub update_hook: Option<Box<dyn FnMut(crate::hooks::Action, &str, &str, i64) + Send>>,
32 #[cfg(feature = "hooks")]
33 pub progress_handler: Option<Box<dyn FnMut() -> bool + Send>>,
34 #[cfg(feature = "hooks")]
35 pub authorizer: Option<crate::hooks::BoxedAuthorizer>,
36 #[cfg(feature = "preupdate_hook")]
37 #[expect(clippy::type_complexity)]
38 pub preupdate_hook: Option<
39 Box<dyn FnMut(crate::hooks::Action, &str, &str, &crate::hooks::PreUpdateCase) + Send>,
40 >,
41 owned: bool,
42}
43
44unsafe impl Send for InnerConnection {}
45
46impl InnerConnection {
47 #[expect(clippy::mutex_atomic, clippy::arc_with_non_send_sync)] #[inline]
49 pub unsafe fn new(db: *mut ffi::sqlite3, owned: bool) -> Self {
50 Self {
51 db,
52 interrupt_lock: Arc::new(Mutex::new(if owned { db } else { ptr::null_mut() })),
53 #[cfg(feature = "hooks")]
54 commit_hook: None,
55 #[cfg(feature = "hooks")]
56 rollback_hook: None,
57 #[cfg(feature = "hooks")]
58 update_hook: None,
59 #[cfg(feature = "hooks")]
60 progress_handler: None,
61 #[cfg(feature = "hooks")]
62 authorizer: None,
63 #[cfg(feature = "preupdate_hook")]
64 preupdate_hook: None,
65 owned,
66 }
67 }
68
69 pub fn open_with_flags(
70 c_path: &CStr,
71 mut flags: OpenFlags,
72 vfs: Option<&CStr>,
73 ) -> Result<Self> {
74 ensure_safe_sqlite_threading_mode()?;
75
76 let z_vfs = match vfs {
77 Some(c_vfs) => c_vfs.as_ptr(),
78 None => ptr::null(),
79 };
80
81 let exrescode = if version_number() >= 3_037_000 {
83 flags |= OpenFlags::SQLITE_OPEN_EXRESCODE;
84 true
85 } else {
86 false };
88
89 unsafe {
90 let mut db: *mut ffi::sqlite3 = ptr::null_mut();
91 let r = ffi::sqlite3_open_v2(c_path.as_ptr(), &mut db, flags.bits(), z_vfs);
92 if r != ffi::SQLITE_OK {
93 let e = if db.is_null() {
94 err!(r, "{}", c_path.to_string_lossy())
95 } else {
96 let mut e = error_from_handle(db, r);
97 if let Error::SqliteFailure(
98 ffi::Error {
99 code: ffi::ErrorCode::CannotOpen,
100 ..
101 },
102 Some(msg),
103 ) = e
104 {
105 e = err!(r, "{msg}: {}", c_path.to_string_lossy());
106 }
107 ffi::sqlite3_close(db);
108 e
109 };
110
111 return Err(e);
112 }
113
114 if !exrescode {
116 ffi::sqlite3_extended_result_codes(db, 1);
117 }
118
119 let r = ffi::sqlite3_busy_timeout(db, 5000);
120 if r != ffi::SQLITE_OK {
121 let e = error_from_handle(db, r);
122 ffi::sqlite3_close(db);
123 return Err(e);
124 }
125
126 Ok(Self::new(db, true))
127 }
128 }
129
130 #[inline]
131 pub fn db(&self) -> *mut ffi::sqlite3 {
132 self.db
133 }
134
135 #[inline]
136 pub fn decode_result(&self, code: c_int) -> Result<()> {
137 unsafe { decode_result_raw(self.db(), code) }
138 }
139
140 pub fn close(&mut self) -> Result<()> {
141 if self.db.is_null() {
142 return Ok(());
143 }
144 self.remove_hooks();
145 self.remove_preupdate_hook();
146 let mut shared_handle = self.interrupt_lock.lock().unwrap();
147 assert!(
148 !self.owned || !shared_handle.is_null(),
149 "Bug: Somehow interrupt_lock was cleared before the DB was closed"
150 );
151 if !self.owned {
152 self.db = ptr::null_mut();
153 return Ok(());
154 }
155 unsafe {
156 let r = ffi::sqlite3_close(self.db);
157 let r = decode_result_raw(self.db, r);
160 if r.is_ok() {
161 *shared_handle = ptr::null_mut();
162 self.db = ptr::null_mut();
163 }
164 r
165 }
166 }
167
168 #[inline]
169 pub fn get_interrupt_handle(&self) -> InterruptHandle {
170 InterruptHandle {
171 db_lock: Arc::clone(&self.interrupt_lock),
172 }
173 }
174
175 #[inline]
176 #[cfg(feature = "load_extension")]
177 pub unsafe fn enable_load_extension(&mut self, onoff: c_int) -> Result<()> {
178 let r = ffi::sqlite3_enable_load_extension(self.db, onoff);
179 self.decode_result(r)
180 }
181
182 #[cfg(feature = "load_extension")]
183 pub unsafe fn load_extension<N: Name>(
184 &self,
185 dylib_path: &Path,
186 entry_point: Option<N>,
187 ) -> Result<()> {
188 let dylib_str = super::path_to_cstring(dylib_path)?;
189 let mut errmsg: *mut c_char = ptr::null_mut();
190 let cs = entry_point.as_ref().map(N::as_cstr).transpose()?;
191 let c_entry = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
192 let r = ffi::sqlite3_load_extension(self.db, dylib_str.as_ptr(), c_entry, &mut errmsg);
193 if r == ffi::SQLITE_OK {
194 Ok(())
195 } else {
196 let message = super::errmsg_to_string(errmsg);
197 ffi::sqlite3_free(errmsg.cast::<std::ffi::c_void>());
198 Err(crate::error::error_from_sqlite_code(r, Some(message)))
199 }
200 }
201
202 #[inline]
203 pub fn last_insert_rowid(&self) -> i64 {
204 unsafe { ffi::sqlite3_last_insert_rowid(self.db()) }
205 }
206
207 pub fn prepare<'a>(
208 &mut self,
209 conn: &'a Connection,
210 sql: &str,
211 flags: PrepFlags,
212 ) -> Result<(Statement<'a>, usize)> {
213 let mut c_stmt: *mut ffi::sqlite3_stmt = ptr::null_mut();
214 let (c_sql, len, _) = str_for_sqlite(sql.as_bytes())?;
215 let mut c_tail: *const c_char = ptr::null();
216 #[cfg(not(feature = "unlock_notify"))]
217 let r = unsafe { self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail) };
218 #[cfg(feature = "unlock_notify")]
219 let r = unsafe {
220 use crate::unlock_notify;
221 let mut rc;
222 loop {
223 rc = self.prepare_(c_sql, len, flags, &mut c_stmt, &mut c_tail);
224 if !unlock_notify::is_locked(self.db, rc) {
225 break;
226 }
227 rc = unlock_notify::wait_for_unlock_notify(self.db);
228 if rc != ffi::SQLITE_OK {
229 break;
230 }
231 }
232 rc
233 };
234 if r != ffi::SQLITE_OK {
236 return Err(unsafe { error_with_offset(self.db, r, sql) });
237 }
238 let tail = if c_tail.is_null() {
241 0
242 } else {
243 let n = (c_tail as isize) - (c_sql as isize);
244 if n <= 0 || n >= len as isize {
245 0
246 } else {
247 n as usize
248 }
249 };
250 Ok((
251 Statement::new(conn, unsafe { RawStatement::new(c_stmt) }),
252 tail,
253 ))
254 }
255
256 #[inline]
257 #[cfg(not(feature = "modern_sqlite"))]
258 unsafe fn prepare_(
259 &self,
260 z_sql: *const c_char,
261 n_byte: c_int,
262 _: PrepFlags,
263 pp_stmt: *mut *mut ffi::sqlite3_stmt,
264 pz_tail: *mut *const c_char,
265 ) -> c_int {
266 ffi::sqlite3_prepare_v2(self.db(), z_sql, n_byte, pp_stmt, pz_tail)
267 }
268
269 #[inline]
270 #[cfg(feature = "modern_sqlite")]
271 unsafe fn prepare_(
272 &self,
273 z_sql: *const c_char,
274 n_byte: c_int,
275 flags: PrepFlags,
276 pp_stmt: *mut *mut ffi::sqlite3_stmt,
277 pz_tail: *mut *const c_char,
278 ) -> c_int {
279 ffi::sqlite3_prepare_v3(self.db(), z_sql, n_byte, flags.bits(), pp_stmt, pz_tail)
280 }
281
282 #[inline]
283 pub fn changes(&self) -> u64 {
284 #[cfg(not(feature = "modern_sqlite"))]
285 unsafe {
286 ffi::sqlite3_changes(self.db()) as u64
287 }
288 #[cfg(feature = "modern_sqlite")] unsafe {
290 ffi::sqlite3_changes64(self.db()) as u64
291 }
292 }
293
294 #[inline]
295 pub fn total_changes(&self) -> u64 {
296 #[cfg(not(feature = "modern_sqlite"))]
297 unsafe {
298 ffi::sqlite3_total_changes(self.db()) as u64
299 }
300 #[cfg(feature = "modern_sqlite")] unsafe {
302 ffi::sqlite3_total_changes64(self.db()) as u64
303 }
304 }
305
306 #[inline]
307 pub fn is_autocommit(&self) -> bool {
308 unsafe { get_autocommit(self.db()) }
309 }
310
311 pub fn is_busy(&self) -> bool {
312 let db = self.db();
313 unsafe {
314 let mut stmt = ffi::sqlite3_next_stmt(db, ptr::null_mut());
315 while !stmt.is_null() {
316 if ffi::sqlite3_stmt_busy(stmt) != 0 {
317 return true;
318 }
319 stmt = ffi::sqlite3_next_stmt(db, stmt);
320 }
321 }
322 false
323 }
324
325 pub fn cache_flush(&mut self) -> Result<()> {
326 crate::error::check(unsafe { ffi::sqlite3_db_cacheflush(self.db()) })
327 }
328
329 #[cfg(not(feature = "hooks"))]
330 #[inline]
331 fn remove_hooks(&mut self) {}
332
333 #[cfg(not(feature = "preupdate_hook"))]
334 #[inline]
335 fn remove_preupdate_hook(&mut self) {}
336
337 pub fn db_readonly<N: Name>(&self, db_name: N) -> Result<bool> {
338 let name = db_name.as_cstr()?;
339 let r = unsafe { ffi::sqlite3_db_readonly(self.db, name.as_ptr()) };
340 match r {
341 0 => Ok(false),
342 1 => Ok(true),
343 -1 => Err(err!(
344 ffi::SQLITE_MISUSE,
345 "{db_name:?} is not the name of a database"
346 )),
347 _ => Err(err!(r, "Unexpected result")),
348 }
349 }
350
351 #[cfg(feature = "modern_sqlite")] pub fn txn_state<N: Name>(
353 &self,
354 db_name: Option<N>,
355 ) -> Result<super::transaction::TransactionState> {
356 let cs = db_name.as_ref().map(N::as_cstr).transpose()?;
357 let name = cs.as_ref().map(|s| s.as_ptr()).unwrap_or(ptr::null());
358 let r = unsafe { ffi::sqlite3_txn_state(self.db, name) };
359 match r {
360 0 => Ok(super::transaction::TransactionState::None),
361 1 => Ok(super::transaction::TransactionState::Read),
362 2 => Ok(super::transaction::TransactionState::Write),
363 -1 => Err(err!(
364 ffi::SQLITE_MISUSE,
365 "{db_name:?} is not the name of a valid schema"
366 )),
367 _ => Err(err!(r, "Unexpected result")),
368 }
369 }
370
371 #[inline]
372 pub fn release_memory(&self) -> Result<()> {
373 self.decode_result(unsafe { ffi::sqlite3_db_release_memory(self.db) })
374 }
375
376 #[cfg(feature = "modern_sqlite")] pub fn is_interrupted(&self) -> bool {
378 unsafe { ffi::sqlite3_is_interrupted(self.db) == 1 }
379 }
380}
381
382#[inline]
383pub(crate) unsafe fn get_autocommit(ptr: *mut ffi::sqlite3) -> bool {
384 ffi::sqlite3_get_autocommit(ptr) != 0
385}
386
387#[inline]
388pub(crate) unsafe fn db_filename<N: Name>(
389 _: std::marker::PhantomData<&()>,
390 ptr: *mut ffi::sqlite3,
391 db_name: N,
392) -> Option<&str> {
393 let db_name = db_name.as_cstr().unwrap();
394 let db_filename = ffi::sqlite3_db_filename(ptr, db_name.as_ptr());
395 if db_filename.is_null() {
396 None
397 } else {
398 CStr::from_ptr(db_filename).to_str().ok()
399 }
400}
401
402impl Drop for InnerConnection {
403 #[expect(unused_must_use)]
404 #[inline]
405 fn drop(&mut self) {
406 self.close();
407 }
408}
409
410#[cfg(target_arch = "wasm32")]
413fn ensure_safe_sqlite_threading_mode() -> Result<()> {
414 Ok(())
415}
416
417#[cfg(not(any(target_arch = "wasm32")))]
418fn ensure_safe_sqlite_threading_mode() -> Result<()> {
419 if unsafe { ffi::sqlite3_threadsafe() == 0 } {
421 return Err(Error::SqliteSingleThreadedMode);
422 }
423
424 const SQLITE_SINGLETHREADED_MUTEX_MAGIC: usize = 8;
433 let is_singlethreaded = unsafe {
434 let mutex_ptr = ffi::sqlite3_mutex_alloc(0);
435 let is_singlethreaded = mutex_ptr as usize == SQLITE_SINGLETHREADED_MUTEX_MAGIC;
436 ffi::sqlite3_mutex_free(mutex_ptr);
437 is_singlethreaded
438 };
439 if is_singlethreaded {
440 Err(Error::SqliteSingleThreadedMode)
441 } else {
442 Ok(())
443 }
444}