rhosql/sqlite/
statement.rs

1use libsqlite3_sys::{self as ffi};
2use std::ptr;
3
4use super::{
5    DataType, DatabaseError, StepResult,
6    database::ffi_db,
7    error::{BindError, DecodeError, PrepareError, ResetError, StepError},
8};
9use crate::common::SqliteStr;
10
11macro_rules! ffi_stmt {
12    (@ $method:ident($db:expr, $stmt:expr $(, $($args:expr),*)?), $into:ty $(, $ret:expr)?) => {
13        match {
14            let db = $db;
15            let result = unsafe { libsqlite3_sys::$method($stmt $(, $($args),*)?) };
16            (db,result)
17        } {
18            (_, libsqlite3_sys::SQLITE_OK) => Ok({ $($ret)? }),
19            (db, result) => Err(<$into>::from(super::DatabaseError::from_code(result, db))),
20        }
21    };
22    ($method:ident($db:expr, $stmt:expr $(, $($args:expr),*)?) as _ $(, $ret:expr)?) => {
23        super::statement::ffi_stmt!(@ $method($db, $stmt $(, $($args),*)?), super::DatabaseError $(, $ret)?)
24    };
25    ($method:ident($db:expr, $stmt:expr $(, $($args:expr),*)?) $(, $ret:expr)?) => {
26        super::statement::ffi_stmt!(@ $method($db, $stmt $(, $($args),*)?), _ $(, $ret)?)
27    };
28}
29
30pub(super) use ffi_stmt;
31
32/// Create a prepared statement.
33///
34/// this is a wrapper for `sqlite3_prepare_v2()`
35///
36/// quoted from sqlite docs:
37///
38/// > If the caller knows that the supplied string is nul-terminated, then there is a small performance
39/// > advantage to passing an nByte parameter that is the number of bytes in the input string
40/// > *including* the nul-terminator.
41///
42/// providing sql via cstr may benefit a small performance advantage
43///
44/// <https://sqlite.org/c3ref/prepare.html>
45pub fn prepare_v2<S: SqliteStr>(db: *mut ffi::sqlite3, sql: S) -> Result<*mut ffi::sqlite3_stmt, PrepareError> {
46    let mut stmt = ptr::null_mut();
47    let (ptr, len, _) = sql.as_nulstr();
48    match ffi_db!(sqlite3_prepare_v2(db, ptr, len, &mut stmt, ptr::null_mut())) {
49        Ok(()) => {
50            #[cfg(feature = "log")]
51            log::debug!("prepared {sql:?}");
52            Ok(stmt)
53        },
54        Err(err) => Err(err),
55    }
56}
57
58/// A trait that represent [`sqlite3_stmt`][1] object.
59///
60/// Statement operation provided by [`StatementExt`].
61///
62/// [1]: <https://sqlite.org/c3ref/sqlite3_stmt.html>
63pub trait Statement {
64    fn as_stmt_ptr(&self) -> *mut ffi::sqlite3_stmt;
65}
66
67impl<S> Statement for &S where S: Statement {
68    fn as_stmt_ptr(&self) -> *mut ffi::sqlite3_stmt {
69        S::as_stmt_ptr(self)
70    }
71}
72
73impl Statement for *mut ffi::sqlite3_stmt {
74    fn as_stmt_ptr(&self) -> *mut ffi::sqlite3_stmt {
75        *self
76    }
77}
78
79impl<T> StatementExt for T where T: Statement { }
80
81/// Statement operation.
82pub trait StatementExt: Statement {
83    /// Returns the database connection handle to which a prepared statement belongs.
84    fn as_db_ptr(&self) -> *mut ffi::sqlite3 {
85        unsafe { ffi::sqlite3_db_handle(self.as_stmt_ptr()) }
86    }
87
88    fn step(&self) -> Result<StepResult, StepError> {
89        match unsafe { ffi::sqlite3_step(self.as_stmt_ptr()) } {
90            ffi::SQLITE_ROW => Ok(StepResult::Row),
91            ffi::SQLITE_DONE => Ok(StepResult::Done),
92            result => Err(DatabaseError::from_code(result, self.as_db_ptr()).into()),
93        }
94    }
95
96    fn reset(&self) -> Result<(), ResetError> {
97        ffi_stmt!(sqlite3_reset(self.as_db_ptr(), self.as_stmt_ptr()))
98    }
99
100    fn clear_bindings(&self) -> Result<(), ResetError> {
101        ffi_stmt!(sqlite3_clear_bindings(self.as_db_ptr(), self.as_stmt_ptr()))
102    }
103
104    // NOTE: parameter encoding
105
106    /// Bind integer to parameter at given index.
107    ///
108    /// Note that parameter index is one based.
109    fn bind_int(&self, idx: i32, value: i32) -> Result<(), BindError> {
110        ffi_stmt!(sqlite3_bind_int(self.as_db_ptr(), self.as_stmt_ptr(), idx, value))
111    }
112
113    /// Bind float to parameter at given index.
114    ///
115    /// Note that parameter index is one based.
116    fn bind_double(&self, idx: i32, value: f64) -> Result<(), BindError> {
117        ffi_stmt!(sqlite3_bind_double(self.as_db_ptr(), self.as_stmt_ptr(), idx, value))
118    }
119
120    /// Bind null to parameter at given index.
121    ///
122    /// Note that parameter index is one based.
123    fn bind_null(&self, idx: i32) -> Result<(), BindError> {
124        ffi_stmt!(sqlite3_bind_null(self.as_db_ptr(), self.as_stmt_ptr(), idx))
125    }
126
127    // todo: maybe choose other than SQLITE_TRANSIENT
128
129    /// Bind text to parameter at given index.
130    ///
131    /// Note that parameter index is one based.
132    fn bind_text<S: SqliteStr>(&self, idx: i32, text: S) -> Result<(), BindError> {
133        let (ptr, len, dtor) = text.as_sqlite_str()?;
134        ffi_stmt!(sqlite3_bind_text(self.as_db_ptr(), self.as_stmt_ptr(), idx, ptr, len, dtor))
135    }
136
137    /// Bind blob to parameter at given index.
138    ///
139    /// Note that parameter index is one based.
140    fn bind_blob(&self, idx: i32, data: &[u8]) -> Result<(), BindError> {
141        ffi_stmt!(sqlite3_bind_blob(
142            self.as_db_ptr(),
143            self.as_stmt_ptr(),
144            idx,
145            data.as_ptr().cast(),
146            i32::try_from(data.len()).unwrap_or(i32::MAX),
147            ffi::SQLITE_TRANSIENT()
148        ))
149    }
150
151    // NOTE: column decoding
152
153
154
155    /// Returns the number of columns, with or without results.
156    fn column_count(&self) -> i32 {
157        unsafe { ffi::sqlite3_column_count(self.as_stmt_ptr()) }
158    }
159
160    /// Returns the number of values (columns) of the currently executing statement.
161    ///
162    /// With no results it returns 0.
163    fn data_count(&self) -> i32 {
164        unsafe { ffi::sqlite3_data_count(self.as_stmt_ptr()) }
165    }
166
167    fn column_type(&self, idx: i32) -> DataType {
168        let code = unsafe { ffi::sqlite3_column_type(self.as_stmt_ptr(), idx) };
169        DataType::from_code(code).expect("sqlite return non datatype from `sqlite3_column_type`")
170    }
171
172    fn column_int(&self, idx: i32) -> i32 {
173        unsafe { ffi::sqlite3_column_int(self.as_stmt_ptr(), idx) }
174    }
175
176    fn column_double(&self, idx: i32) -> f64 {
177        unsafe { ffi::sqlite3_column_double(self.as_stmt_ptr(), idx) }
178    }
179
180    fn column_text(&self, idx: i32) -> Result<&str, DecodeError> {
181        let text = unsafe {
182            let text = ffi::sqlite3_column_text(self.as_stmt_ptr(), idx);
183            std::ffi::CStr::from_ptr(text.cast())
184        };
185        text.to_str().map_err(DecodeError::Utf8)
186    }
187
188    fn column_blob(&self, idx: i32) -> &[u8] {
189        unsafe {
190            let len = self.column_bytes(idx) as usize;
191            let data = ffi::sqlite3_column_blob(self.as_stmt_ptr(), idx).cast();
192            std::slice::from_raw_parts(data, len)
193        }
194    }
195
196    fn column_bytes(&self, idx: i32) -> i32 {
197        unsafe { ffi::sqlite3_column_bytes(self.as_stmt_ptr(), idx) }
198    }
199
200    /// Delete the prepared staement.
201    ///
202    /// <https://sqlite.org/c3ref/finalize.html>
203    fn finalize(&self) -> Result<(), DatabaseError> {
204        ffi_stmt!(sqlite3_finalize(self.as_db_ptr(), self.as_stmt_ptr()) as _)
205    }
206}
207