| use super::ffi; |
| use super::StatementStatus; |
| use crate::util::ParamIndexCache; |
| #[cfg(feature = "modern_sqlite")] |
| use crate::util::SqliteMallocString; |
| use std::ffi::CStr; |
| use std::os::raw::c_int; |
| use std::ptr; |
| use std::sync::Arc; |
| |
| // Private newtype for raw sqlite3_stmts that finalize themselves when dropped. |
| #[derive(Debug)] |
| pub struct RawStatement { |
| ptr: *mut ffi::sqlite3_stmt, |
| tail: usize, |
| // Cached indices of named parameters, computed on the fly. |
| cache: ParamIndexCache, |
| // Cached SQL (trimmed) that we use as the key when we're in the statement |
| // cache. This is None for statements which didn't come from the statement |
| // cache. |
| // |
| // This is probably the same as `self.sql()` in most cases, but we don't |
| // care either way -- It's a better cache key as it is anyway since it's the |
| // actual source we got from rust. |
| // |
| // One example of a case where the result of `sqlite_sql` and the value in |
| // `statement_cache_key` might differ is if the statement has a `tail`. |
| statement_cache_key: Option<Arc<str>>, |
| } |
| |
| impl RawStatement { |
| #[inline] |
| pub unsafe fn new(stmt: *mut ffi::sqlite3_stmt, tail: usize) -> RawStatement { |
| RawStatement { |
| ptr: stmt, |
| tail, |
| cache: ParamIndexCache::default(), |
| statement_cache_key: None, |
| } |
| } |
| |
| #[inline] |
| pub fn is_null(&self) -> bool { |
| self.ptr.is_null() |
| } |
| |
| #[inline] |
| pub(crate) fn set_statement_cache_key(&mut self, p: impl Into<Arc<str>>) { |
| self.statement_cache_key = Some(p.into()); |
| } |
| |
| #[inline] |
| pub(crate) fn statement_cache_key(&self) -> Option<Arc<str>> { |
| self.statement_cache_key.clone() |
| } |
| |
| #[inline] |
| pub unsafe fn ptr(&self) -> *mut ffi::sqlite3_stmt { |
| self.ptr |
| } |
| |
| #[inline] |
| pub fn column_count(&self) -> usize { |
| // Note: Can't cache this as it changes if the schema is altered. |
| unsafe { ffi::sqlite3_column_count(self.ptr) as usize } |
| } |
| |
| #[inline] |
| pub fn column_type(&self, idx: usize) -> c_int { |
| unsafe { ffi::sqlite3_column_type(self.ptr, idx as c_int) } |
| } |
| |
| #[inline] |
| #[cfg(feature = "column_decltype")] |
| pub fn column_decltype(&self, idx: usize) -> Option<&CStr> { |
| unsafe { |
| let decltype = ffi::sqlite3_column_decltype(self.ptr, idx as c_int); |
| if decltype.is_null() { |
| None |
| } else { |
| Some(CStr::from_ptr(decltype)) |
| } |
| } |
| } |
| |
| #[inline] |
| pub fn column_name(&self, idx: usize) -> Option<&CStr> { |
| let idx = idx as c_int; |
| if idx < 0 || idx >= self.column_count() as c_int { |
| return None; |
| } |
| unsafe { |
| let ptr = ffi::sqlite3_column_name(self.ptr, idx); |
| // If ptr is null here, it's an OOM, so there's probably nothing |
| // meaningful we can do. Just assert instead of returning None. |
| assert!( |
| !ptr.is_null(), |
| "Null pointer from sqlite3_column_name: Out of memory?" |
| ); |
| Some(CStr::from_ptr(ptr)) |
| } |
| } |
| |
| #[inline] |
| #[cfg(not(feature = "unlock_notify"))] |
| pub fn step(&self) -> c_int { |
| unsafe { ffi::sqlite3_step(self.ptr) } |
| } |
| |
| #[cfg(feature = "unlock_notify")] |
| pub fn step(&self) -> c_int { |
| use crate::unlock_notify; |
| let mut db = ptr::null_mut::<ffi::sqlite3>(); |
| loop { |
| unsafe { |
| let mut rc = ffi::sqlite3_step(self.ptr); |
| // Bail out early for success and errors unrelated to locking. We |
| // still need check `is_locked` after this, but checking now lets us |
| // avoid one or two (admittedly cheap) calls into SQLite that we |
| // don't need to make. |
| if (rc & 0xff) != ffi::SQLITE_LOCKED { |
| break rc; |
| } |
| if db.is_null() { |
| db = ffi::sqlite3_db_handle(self.ptr); |
| } |
| if !unlock_notify::is_locked(db, rc) { |
| break rc; |
| } |
| rc = unlock_notify::wait_for_unlock_notify(db); |
| if rc != ffi::SQLITE_OK { |
| break rc; |
| } |
| self.reset(); |
| } |
| } |
| } |
| |
| #[inline] |
| pub fn reset(&self) -> c_int { |
| unsafe { ffi::sqlite3_reset(self.ptr) } |
| } |
| |
| #[inline] |
| pub fn bind_parameter_count(&self) -> usize { |
| unsafe { ffi::sqlite3_bind_parameter_count(self.ptr) as usize } |
| } |
| |
| #[inline] |
| pub fn bind_parameter_index(&self, name: &str) -> Option<usize> { |
| self.cache.get_or_insert_with(name, |param_cstr| { |
| let r = unsafe { ffi::sqlite3_bind_parameter_index(self.ptr, param_cstr.as_ptr()) }; |
| match r { |
| 0 => None, |
| i => Some(i as usize), |
| } |
| }) |
| } |
| |
| #[inline] |
| pub fn bind_parameter_name(&self, index: i32) -> Option<&CStr> { |
| unsafe { |
| let name = ffi::sqlite3_bind_parameter_name(self.ptr, index); |
| if name.is_null() { |
| None |
| } else { |
| Some(CStr::from_ptr(name)) |
| } |
| } |
| } |
| |
| #[inline] |
| pub fn clear_bindings(&self) -> c_int { |
| unsafe { ffi::sqlite3_clear_bindings(self.ptr) } |
| } |
| |
| #[inline] |
| pub fn sql(&self) -> Option<&CStr> { |
| if self.ptr.is_null() { |
| None |
| } else { |
| Some(unsafe { CStr::from_ptr(ffi::sqlite3_sql(self.ptr)) }) |
| } |
| } |
| |
| #[inline] |
| pub fn finalize(mut self) -> c_int { |
| self.finalize_() |
| } |
| |
| #[inline] |
| fn finalize_(&mut self) -> c_int { |
| let r = unsafe { ffi::sqlite3_finalize(self.ptr) }; |
| self.ptr = ptr::null_mut(); |
| r |
| } |
| |
| // does not work for PRAGMA |
| #[inline] |
| #[cfg(all(feature = "extra_check", feature = "modern_sqlite"))] // 3.7.4 |
| pub fn readonly(&self) -> bool { |
| unsafe { ffi::sqlite3_stmt_readonly(self.ptr) != 0 } |
| } |
| |
| #[inline] |
| #[cfg(feature = "modern_sqlite")] // 3.14.0 |
| pub(crate) fn expanded_sql(&self) -> Option<SqliteMallocString> { |
| unsafe { SqliteMallocString::from_raw(ffi::sqlite3_expanded_sql(self.ptr)) } |
| } |
| |
| #[inline] |
| pub fn get_status(&self, status: StatementStatus, reset: bool) -> i32 { |
| assert!(!self.ptr.is_null()); |
| unsafe { ffi::sqlite3_stmt_status(self.ptr, status as i32, reset as i32) } |
| } |
| |
| #[inline] |
| #[cfg(feature = "extra_check")] |
| pub fn has_tail(&self) -> bool { |
| self.tail != 0 |
| } |
| |
| #[inline] |
| pub fn tail(&self) -> usize { |
| self.tail |
| } |
| |
| #[inline] |
| #[cfg(feature = "modern_sqlite")] // 3.28.0 |
| pub fn is_explain(&self) -> i32 { |
| unsafe { ffi::sqlite3_stmt_isexplain(self.ptr) } |
| } |
| |
| // TODO sqlite3_normalized_sql (https://sqlite.org/c3ref/expanded_sql.html) // 3.27.0 + SQLITE_ENABLE_NORMALIZE |
| } |
| |
| impl Drop for RawStatement { |
| fn drop(&mut self) { |
| self.finalize_(); |
| } |
| } |