| use std::ffi::c_void; | |
| use std::ffi::CStr; | |
| use crate::error::{BoxDynError, Error}; | |
| use crate::type_info::DataType; | |
| use crate::{SqliteError, SqliteTypeInfo}; | |
| use libsqlite3_sys::{ | |
| sqlite3, sqlite3_bind_blob64, sqlite3_bind_double, sqlite3_bind_int, sqlite3_bind_int64, | |
| sqlite3_bind_null, sqlite3_bind_parameter_count, sqlite3_bind_parameter_name, | |
| sqlite3_bind_text64, sqlite3_changes, sqlite3_clear_bindings, sqlite3_column_blob, | |
| sqlite3_column_bytes, sqlite3_column_count, sqlite3_column_database_name, | |
| sqlite3_column_decltype, sqlite3_column_double, sqlite3_column_int, sqlite3_column_int64, | |
| sqlite3_column_name, sqlite3_column_origin_name, sqlite3_column_table_name, | |
| sqlite3_column_type, sqlite3_column_value, sqlite3_db_handle, sqlite3_finalize, sqlite3_reset, | |
| sqlite3_sql, sqlite3_step, sqlite3_stmt, sqlite3_stmt_readonly, sqlite3_table_column_metadata, | |
| sqlite3_value, SQLITE_DONE, SQLITE_MISUSE, SQLITE_OK, SQLITE_ROW, SQLITE_TRANSIENT, | |
| SQLITE_UTF8, | |
| }; | |
| use sqlx_core::column::{ColumnOrigin, TableColumn}; | |
| use std::os::raw::{c_char, c_int}; | |
| use std::ptr; | |
| use std::ptr::NonNull; | |
| use std::slice::from_raw_parts; | |
| use std::str::from_utf8; | |
| use std::sync::Arc; | |
| #[derive(Debug)] | |
| pub(crate) struct StatementHandle(NonNull<sqlite3_stmt>); | |
| // access to SQLite3 statement handles are safe to send and share between threads | |
| // as long as the `sqlite3_step` call is serialized. | |
| unsafe impl Send for StatementHandle {} | |
| // Most of the getters below allocate internally, and unsynchronized access is undefined. | |
| // unsafe impl !Sync for StatementHandle {} | |
| macro_rules! expect_ret_valid { | |
| ($fn_name:ident($($args:tt)*)) => {{ | |
| let val = $fn_name($($args)*); | |
| TryFrom::try_from(val) | |
| // This likely means UB in SQLite itself or our usage of it; | |
| // signed integer overflow is UB in the C standard. | |
| .unwrap_or_else(|_| panic!("{}() returned invalid value: {val:?}", stringify!($fn_name))) | |
| }} | |
| } | |
| macro_rules! check_col_idx { | |
| ($idx:ident) => { | |
| c_int::try_from($idx).unwrap_or_else(|_| panic!("invalid column index: {}", $idx)) | |
| }; | |
| } | |
| // might use some of this later | |
| #[allow(dead_code)] | |
| impl StatementHandle { | |
| pub(super) fn new(ptr: NonNull<sqlite3_stmt>) -> Self { | |
| Self(ptr) | |
| } | |
| #[inline] | |
| pub(super) unsafe fn db_handle(&self) -> *mut sqlite3 { | |
| // O(c) access to the connection handle for this statement handle | |
| // https://sqlite.org/c3ref/db_handle.html | |
| sqlite3_db_handle(self.0.as_ptr()) | |
| } | |
| pub(crate) fn read_only(&self) -> bool { | |
| // https://sqlite.org/c3ref/stmt_readonly.html | |
| unsafe { sqlite3_stmt_readonly(self.0.as_ptr()) != 0 } | |
| } | |
| pub(crate) fn sql(&self) -> &str { | |
| // https://sqlite.org/c3ref/expanded_sql.html | |
| unsafe { | |
| let raw = sqlite3_sql(self.0.as_ptr()); | |
| debug_assert!(!raw.is_null()); | |
| from_utf8(CStr::from_ptr(raw).to_bytes()) | |
| .expect("sqlite3_sql() returned non-UTF-8 string") | |
| } | |
| } | |
| #[inline] | |
| pub(crate) fn last_error(&mut self) -> SqliteError { | |
| unsafe { SqliteError::new(self.db_handle()) } | |
| } | |
| #[inline] | |
| pub(crate) fn column_count(&self) -> usize { | |
| // https://sqlite.org/c3ref/column_count.html | |
| unsafe { expect_ret_valid!(sqlite3_column_count(self.0.as_ptr())) } | |
| } | |
| #[inline] | |
| pub(crate) fn changes(&self) -> u64 { | |
| // returns the number of changes of the *last* statement; not | |
| // necessarily this statement. | |
| // https://sqlite.org/c3ref/changes.html | |
| unsafe { expect_ret_valid!(sqlite3_changes(self.db_handle())) } | |
| } | |
| #[inline] | |
| pub(crate) fn column_name(&self, index: usize) -> &str { | |
| // https://sqlite.org/c3ref/column_name.html | |
| unsafe { | |
| let name = sqlite3_column_name(self.0.as_ptr(), check_col_idx!(index)); | |
| debug_assert!(!name.is_null()); | |
| from_utf8(CStr::from_ptr(name).to_bytes()) | |
| .expect("sqlite3_column_name() returned non-UTF-8 column name") | |
| } | |
| } | |
| pub(crate) fn column_origin(&self, index: usize) -> ColumnOrigin { | |
| if let Some((table, name)) = self | |
| .column_table_name(index) | |
| .zip(self.column_origin_name(index)) | |
| { | |
| let table: Arc<str> = self | |
| .column_db_name(index) | |
| .filter(|&db| db != "main") | |
| .map_or_else( | |
| || table.into(), | |
| // TODO: check that SQLite returns the names properly quoted if necessary | |
| |db| format!("{db}.{table}").into(), | |
| ); | |
| ColumnOrigin::Table(TableColumn { | |
| table, | |
| name: name.into(), | |
| }) | |
| } else { | |
| ColumnOrigin::Expression | |
| } | |
| } | |
| fn column_db_name(&self, index: usize) -> Option<&str> { | |
| unsafe { | |
| let db_name = sqlite3_column_database_name(self.0.as_ptr(), check_col_idx!(index)); | |
| if !db_name.is_null() { | |
| Some( | |
| from_utf8(CStr::from_ptr(db_name).to_bytes()) | |
| .expect("sqlite3_column_database_name() returned non-UTF-8 string"), | |
| ) | |
| } else { | |
| None | |
| } | |
| } | |
| } | |
| fn column_table_name(&self, index: usize) -> Option<&str> { | |
| unsafe { | |
| let table_name = sqlite3_column_table_name(self.0.as_ptr(), check_col_idx!(index)); | |
| if !table_name.is_null() { | |
| Some( | |
| from_utf8(CStr::from_ptr(table_name).to_bytes()) | |
| .expect("sqlite3_column_table_name() returned non-UTF-8 string"), | |
| ) | |
| } else { | |
| None | |
| } | |
| } | |
| } | |
| fn column_origin_name(&self, index: usize) -> Option<&str> { | |
| unsafe { | |
| let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), check_col_idx!(index)); | |
| if !origin_name.is_null() { | |
| Some( | |
| from_utf8(CStr::from_ptr(origin_name).to_bytes()) | |
| .expect("sqlite3_column_origin_name() returned non-UTF-8 string"), | |
| ) | |
| } else { | |
| None | |
| } | |
| } | |
| } | |
| pub(crate) fn column_type_info(&self, index: usize) -> SqliteTypeInfo { | |
| SqliteTypeInfo(DataType::from_code(self.column_type(index))) | |
| } | |
| pub(crate) fn column_type_info_opt(&self, index: usize) -> Option<SqliteTypeInfo> { | |
| match DataType::from_code(self.column_type(index)) { | |
| DataType::Null => None, | |
| dt => Some(SqliteTypeInfo(dt)), | |
| } | |
| } | |
| #[inline] | |
| pub(crate) fn column_decltype(&self, index: usize) -> Option<SqliteTypeInfo> { | |
| unsafe { | |
| let decl = sqlite3_column_decltype(self.0.as_ptr(), check_col_idx!(index)); | |
| if decl.is_null() { | |
| // If the Nth column of the result set is an expression or subquery, | |
| // then a NULL pointer is returned. | |
| return None; | |
| } | |
| let decl = from_utf8(CStr::from_ptr(decl).to_bytes()) | |
| .expect("sqlite3_column_decltype() returned non-UTF-8 string"); | |
| let ty: DataType = decl.parse().ok()?; | |
| Some(SqliteTypeInfo(ty)) | |
| } | |
| } | |
| /// Use sqlite3_column_metadata to determine if a specific column is nullable. | |
| /// | |
| /// Returns None in the case of INTEGER PRIMARY KEYs | |
| /// This is because this column is an alias to rowid if the table does not use a compound | |
| /// primary key. In this case the row is not nullable, and the output of | |
| /// sqlite3_column_metadata may be incorrect. | |
| pub(crate) fn column_nullable(&self, index: usize) -> Result<Option<bool>, Error> { | |
| unsafe { | |
| let index = check_col_idx!(index); | |
| // https://sqlite.org/c3ref/column_database_name.html | |
| // | |
| // ### Note | |
| // The returned string is valid until the prepared statement is destroyed using | |
| // sqlite3_finalize() or until the statement is automatically reprepared by the | |
| // first call to sqlite3_step() for a particular run or until the same information | |
| // is requested again in a different encoding. | |
| let db_name = sqlite3_column_database_name(self.0.as_ptr(), index); | |
| let table_name = sqlite3_column_table_name(self.0.as_ptr(), index); | |
| let origin_name = sqlite3_column_origin_name(self.0.as_ptr(), index); | |
| if db_name.is_null() || table_name.is_null() || origin_name.is_null() { | |
| return Ok(None); | |
| } | |
| let mut not_null: c_int = 0; | |
| let mut datatype: *const c_char = ptr::null(); | |
| let mut primary_key: c_int = 0; | |
| // https://sqlite.org/c3ref/table_column_metadata.html | |
| let status = sqlite3_table_column_metadata( | |
| self.db_handle(), | |
| db_name, | |
| table_name, | |
| origin_name, | |
| &mut datatype, | |
| // function docs state to provide NULL for return values you don't care about | |
| ptr::null_mut(), | |
| &mut not_null, | |
| &mut primary_key, | |
| ptr::null_mut(), | |
| ); | |
| if status != SQLITE_OK { | |
| // implementation note: the docs for sqlite3_table_column_metadata() specify | |
| // that an error can be returned if the column came from a view; however, | |
| // experimentally we found that the above functions give us the true origin | |
| // for columns in views that came from real tables and so we should never hit this | |
| // error; for view columns that are expressions we are given NULL for their origins | |
| // so we don't need special handling for that case either. | |
| // | |
| // this is confirmed in the `tests/sqlite-macros.rs` integration test | |
| return Err(SqliteError::new(self.db_handle()).into()); | |
| } | |
| let datatype = CStr::from_ptr(datatype); | |
| Ok( | |
| if primary_key != 0 | |
| && datatype | |
| .to_bytes() | |
| .eq_ignore_ascii_case("integer".as_bytes()) | |
| { | |
| None | |
| } else { | |
| Some(not_null == 0) | |
| }, | |
| ) | |
| } | |
| } | |
| // Number Of SQL Parameters | |
| #[inline] | |
| pub(crate) fn bind_parameter_count(&self) -> usize { | |
| // https://www.sqlite.org/c3ref/bind_parameter_count.html | |
| unsafe { expect_ret_valid!(sqlite3_bind_parameter_count(self.0.as_ptr())) } | |
| } | |
| // Name Of A Host Parameter | |
| // NOTE: The first host parameter has an index of 1, not 0. | |
| #[inline] | |
| pub(crate) fn bind_parameter_name(&self, index: usize) -> Option<&str> { | |
| unsafe { | |
| // https://www.sqlite.org/c3ref/bind_parameter_name.html | |
| let name = sqlite3_bind_parameter_name(self.0.as_ptr(), check_col_idx!(index)); | |
| if name.is_null() { | |
| return None; | |
| } | |
| Some( | |
| from_utf8(CStr::from_ptr(name).to_bytes()) | |
| .expect("sqlite3_bind_parameter_name() returned non-UTF-8 string"), | |
| ) | |
| } | |
| } | |
| // Binding Values To Prepared Statements | |
| // https://www.sqlite.org/c3ref/bind_blob.html | |
| #[inline] | |
| pub(crate) fn bind_blob(&self, index: usize, v: &[u8]) -> c_int { | |
| unsafe { | |
| sqlite3_bind_blob64( | |
| self.0.as_ptr(), | |
| check_col_idx!(index), | |
| v.as_ptr() as *const c_void, | |
| v.len() as u64, | |
| SQLITE_TRANSIENT(), | |
| ) | |
| } | |
| } | |
| #[inline] | |
| pub(crate) fn bind_text(&self, index: usize, v: &str) -> c_int { | |
| #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] | |
| let encoding = SQLITE_UTF8 as u8; | |
| unsafe { | |
| sqlite3_bind_text64( | |
| self.0.as_ptr(), | |
| check_col_idx!(index), | |
| v.as_ptr() as *const c_char, | |
| v.len() as u64, | |
| SQLITE_TRANSIENT(), | |
| encoding, | |
| ) | |
| } | |
| } | |
| #[inline] | |
| pub(crate) fn bind_int(&self, index: usize, v: i32) -> c_int { | |
| unsafe { sqlite3_bind_int(self.0.as_ptr(), check_col_idx!(index), v as c_int) } | |
| } | |
| #[inline] | |
| pub(crate) fn bind_int64(&self, index: usize, v: i64) -> c_int { | |
| unsafe { sqlite3_bind_int64(self.0.as_ptr(), check_col_idx!(index), v) } | |
| } | |
| #[inline] | |
| pub(crate) fn bind_double(&self, index: usize, v: f64) -> c_int { | |
| unsafe { sqlite3_bind_double(self.0.as_ptr(), check_col_idx!(index), v) } | |
| } | |
| #[inline] | |
| pub(crate) fn bind_null(&self, index: usize) -> c_int { | |
| unsafe { sqlite3_bind_null(self.0.as_ptr(), check_col_idx!(index)) } | |
| } | |
| // result values from the query | |
| // https://www.sqlite.org/c3ref/column_blob.html | |
| #[inline] | |
| pub(crate) fn column_type(&self, index: usize) -> c_int { | |
| unsafe { sqlite3_column_type(self.0.as_ptr(), check_col_idx!(index)) } | |
| } | |
| #[inline] | |
| pub(crate) fn column_int(&self, index: usize) -> i32 { | |
| unsafe { sqlite3_column_int(self.0.as_ptr(), check_col_idx!(index)) as i32 } | |
| } | |
| #[inline] | |
| pub(crate) fn column_int64(&self, index: usize) -> i64 { | |
| unsafe { sqlite3_column_int64(self.0.as_ptr(), check_col_idx!(index)) as i64 } | |
| } | |
| #[inline] | |
| pub(crate) fn column_double(&self, index: usize) -> f64 { | |
| unsafe { sqlite3_column_double(self.0.as_ptr(), check_col_idx!(index)) } | |
| } | |
| #[inline] | |
| pub(crate) fn column_value(&self, index: usize) -> *mut sqlite3_value { | |
| unsafe { sqlite3_column_value(self.0.as_ptr(), check_col_idx!(index)) } | |
| } | |
| pub(crate) fn column_blob(&self, index: usize) -> &[u8] { | |
| let len = unsafe { | |
| expect_ret_valid!(sqlite3_column_bytes(self.0.as_ptr(), check_col_idx!(index))) | |
| }; | |
| if len == 0 { | |
| // empty blobs are NULL so just return an empty slice | |
| return &[]; | |
| } | |
| let ptr = | |
| unsafe { sqlite3_column_blob(self.0.as_ptr(), check_col_idx!(index)) } as *const u8; | |
| debug_assert!(!ptr.is_null()); | |
| unsafe { from_raw_parts(ptr, len) } | |
| } | |
| pub(crate) fn column_text(&self, index: usize) -> Result<&str, BoxDynError> { | |
| Ok(from_utf8(self.column_blob(index))?) | |
| } | |
| pub(crate) fn clear_bindings(&self) { | |
| unsafe { sqlite3_clear_bindings(self.0.as_ptr()) }; | |
| } | |
| pub(crate) fn reset(&mut self) -> Result<(), SqliteError> { | |
| // SAFETY: we have exclusive access to the handle | |
| unsafe { | |
| if sqlite3_reset(self.0.as_ptr()) != SQLITE_OK { | |
| return Err(SqliteError::new(self.db_handle())); | |
| } | |
| } | |
| Ok(()) | |
| } | |
| pub(crate) fn step(&mut self) -> Result<bool, SqliteError> { | |
| // SAFETY: we have exclusive access to the handle | |
| unsafe { | |
| #[cfg_attr(not(feature = "unlock-notify"), expect(clippy::never_loop))] | |
| loop { | |
| match sqlite3_step(self.0.as_ptr()) { | |
| SQLITE_ROW => return Ok(true), | |
| SQLITE_DONE => return Ok(false), | |
| SQLITE_MISUSE => panic!("misuse!"), | |
| #[cfg(feature = "unlock-notify")] | |
| libsqlite3_sys::SQLITE_LOCKED_SHAREDCACHE => { | |
| // The shared cache is locked by another connection. Wait for unlock | |
| // notification and try again. | |
| super::unlock_notify::wait(self.db_handle())?; | |
| // Need to reset the handle after the unlock | |
| // (https://www.sqlite.org/unlock_notify.html) | |
| sqlite3_reset(self.0.as_ptr()); | |
| } | |
| _ => return Err(SqliteError::new(self.db_handle())), | |
| } | |
| } | |
| } | |
| } | |
| } | |
| impl Drop for StatementHandle { | |
| fn drop(&mut self) { | |
| // SAFETY: we have exclusive access to the `StatementHandle` here | |
| unsafe { | |
| // https://sqlite.org/c3ref/finalize.html | |
| let status = sqlite3_finalize(self.0.as_ptr()); | |
| if status == SQLITE_MISUSE { | |
| // Panic in case of detected misuse of SQLite API. | |
| // | |
| // sqlite3_finalize returns it at least in the | |
| // case of detected double free, i.e. calling | |
| // sqlite3_finalize on already finalized | |
| // statement. | |
| panic!("Detected sqlite3_finalize misuse."); | |
| } | |
| } | |
| } | |
| } |