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