Skip to main content

sql_peas/
statement.rs

1use core::ffi::c_int;
2use std::collections::HashMap;
3use std::marker::PhantomData;
4use std::rc::Rc;
5
6use crate::columns::{Bindable, ReadableWithIndex};
7use crate::cursor::Cursor;
8use crate::error::Result;
9use crate::value::Type;
10
11/// A prepared statement.
12///
13/// Only `Connection` can own this type, everything else may only borrow it.
14pub struct Statement {
15    pub(crate) raw: (*mut ffi::sqlite3_stmt, *mut ffi::sqlite3),
16    pub(crate) column_names: Rc<Vec<String>>,
17    column_mapping: Rc<HashMap<String, usize>>,
18    phantom: PhantomData<(ffi::sqlite3_stmt, ffi::sqlite3)>,
19}
20
21/// A handle to a prepared statement.
22///
23/// Users of this library can own this type, but they can only call methods on it by giving it to a `Connection` to borrow a full `Statement`.
24#[derive(Clone, Copy, Eq, Hash, PartialEq)]
25pub struct Handle(pub(crate) *mut ffi::sqlite3_stmt);
26
27/// A type suitable for indexing columns in a prepared statement.
28pub trait ColumnIndex: Copy + std::fmt::Debug {
29    /// Identify the ordinal position.
30    ///
31    /// The first column has index 0.
32    fn index(self, statement: &Statement) -> Result<usize>;
33}
34
35/// A type suitable for indexing parameters in a prepared statement.
36pub trait ParameterIndex: Copy + std::fmt::Debug {
37    /// Identify the ordinal position.
38    ///
39    /// The first parameter has index 1.
40    fn index(self, statement: &Statement) -> Result<usize>;
41}
42
43/// The state of a prepared statement.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum State {
46    /// There is a row available for reading.
47    Row,
48    /// The statement has been entirely evaluated.
49    Done,
50}
51
52impl Statement {
53    /// Bind values to parameters.
54    ///
55    /// In case of integer indices, the first parameter has index 1.
56    ///
57    /// # Examples
58    ///
59    /// ```
60    /// # let mut connection = sql_peas::open(":memory:").unwrap();
61    /// # connection.execute("CREATE TABLE users (id INTEGER, name STRING)");
62    /// let query = "SELECT * FROM users WHERE name = ?";
63    /// let handle = connection.prepare(query)?;
64    /// let statement = connection.borrow_statement(handle)?;
65    /// statement.bind((1, "Bob"))?;
66    /// # Ok::<(), sql_peas::Error>(())
67    /// ```
68    ///
69    /// ```
70    /// # let mut connection = sql_peas::open(":memory:").unwrap();
71    /// # connection.execute("CREATE TABLE users (id INTEGER, name STRING)");
72    /// let query = "SELECT * FROM users WHERE name = ?";
73    /// let handle = connection.prepare(query)?;
74    /// let statement = connection.borrow_statement(handle)?;
75    /// statement.bind(&[(1, "Bob")][..])?;
76    /// # Ok::<(), sql_peas::Error>(())
77    /// ```
78    ///
79    /// ```
80    /// # let mut connection = sql_peas::open(":memory:").unwrap();
81    /// # connection.execute("CREATE TABLE users (id INTEGER, name STRING)");
82    /// let query = "SELECT * FROM users WHERE name = :name";
83    /// let handle = connection.prepare(query)?;
84    /// let statement = connection.borrow_statement(handle)?;
85    /// statement.bind((":name", "Bob"))?;
86    /// # Ok::<(), sql_peas::Error>(())
87    /// ```
88    ///
89    /// ```
90    /// # let mut connection = sql_peas::open(":memory:").unwrap();
91    /// # connection.execute("CREATE TABLE users (id INTEGER, name STRING)");
92    /// let query = "SELECT * FROM users WHERE name = :name";
93    /// let handle = connection.prepare(query)?;
94    /// let statement = connection.borrow_statement(handle)?;
95    /// statement.bind(&[(":name", "Bob")][..])?;
96    /// # Ok::<(), sql_peas::Error>(())
97    /// ```
98    ///
99    /// ```
100    /// # use sql_peas::Value;
101    /// # let mut connection = sql_peas::open(":memory:").unwrap();
102    /// # connection.execute("CREATE TABLE users (id INTEGER, name STRING)");
103    /// let query = "SELECT * FROM users WHERE id = :id AND name = :name";
104    /// let handle = connection.prepare(query)?;
105    /// let statement = connection.borrow_statement(handle)?;
106    /// statement.bind::<&[(_, Value)]>(&[
107    ///     (":id", 1.into()),
108    ///     (":name", "Bob".into()),
109    /// ][..])?;
110    /// # Ok::<(), sql_peas::Error>(())
111    /// ```
112    #[inline]
113    pub fn bind<T: Bindable>(&self, value: T) -> Result<()> {
114        value.bind(self)?;
115        Ok(())
116    }
117
118    /// Bind values to parameters via an iterator.
119    ///
120    /// # Examples
121    ///
122    /// ```
123    /// # use sql_peas::Value;
124    /// # let mut connection = sql_peas::open(":memory:").unwrap();
125    /// # connection.execute("CREATE TABLE users (id INTEGER, name STRING)");
126    /// let query = "INSERT INTO users VALUES (:id, :name)";
127    /// let handle = connection.prepare(query)?;
128    /// let mut statement = connection.borrow_statement(handle)?;
129    /// statement.bind_iter::<_, (_, Value)>([
130    ///     (":name", "Bob".into()),
131    ///     (":id", 42.into()),
132    /// ])?;
133    /// # Ok::<(), sql_peas::Error>(())
134    /// ```
135    pub fn bind_iter<T, U>(&self, value: T) -> Result<()>
136    where
137        T: IntoIterator<Item = U>,
138        U: Bindable,
139    {
140        for value in value {
141            self.bind(value)?;
142        }
143        Ok(())
144    }
145
146    /// Create a cursor.
147    #[inline]
148    pub fn iter(&self) -> Cursor<'_> {
149        self.into()
150    }
151
152    /// Advance to the next state.
153    ///
154    /// The function should be called multiple times until `State::Done` is reached in order to
155    /// evaluate the statement entirely.
156    #[allow(clippy::should_implement_trait)]
157    pub fn next(&self) -> Result<State> {
158        Ok(match unsafe { ffi::sqlite3_step(self.raw.0) } {
159            ffi::SQLITE_ROW => State::Row,
160            ffi::SQLITE_DONE => State::Done,
161            code => error!(self.raw.1, code),
162        })
163    }
164
165    /// Read a value from a column.
166    ///
167    /// In case of integer indices, the first column has index 0.
168    #[inline]
169    pub fn read<T, U>(&self, index: U) -> Result<T>
170    where
171        T: ReadableWithIndex,
172        U: ColumnIndex,
173    {
174        ReadableWithIndex::read(self, index)
175    }
176
177    /// Return the number of columns.
178    #[inline]
179    pub fn column_count(&self) -> usize {
180        self.column_names.len()
181    }
182
183    #[doc(hidden)]
184    #[inline]
185    pub fn column_mapping(&self) -> Rc<HashMap<String, usize>> {
186        self.column_mapping.clone()
187    }
188
189    /// Return the name of a column.
190    ///
191    /// In case of integer indices, the first column has index 0.
192    #[inline]
193    pub fn column_name<T: ColumnIndex>(&self, index: T) -> Result<&str> {
194        Ok(&self.column_names[index.index(self)?])
195    }
196
197    /// Return column names.
198    #[inline]
199    pub fn column_names(&self) -> &[String] {
200        &self.column_names
201    }
202
203    /// Return the type of a column.
204    ///
205    /// The type becomes available after taking a step. In case of integer indices, the first
206    /// column has index 0.
207    pub fn column_type<T: ColumnIndex>(&self, index: T) -> Result<Type> {
208        Ok(
209            match unsafe { ffi::sqlite3_column_type(self.raw.0, index.index(self)? as c_int) } {
210                ffi::SQLITE_BLOB => Type::Binary,
211                ffi::SQLITE_FLOAT => Type::Float,
212                ffi::SQLITE_INTEGER => Type::Integer,
213                ffi::SQLITE_TEXT => Type::String,
214                ffi::SQLITE_NULL => Type::Null,
215                _ => unreachable!(),
216            },
217        )
218    }
219
220    /// Return the index for a named parameter if exists.
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// # let mut connection = sql_peas::open(":memory:").unwrap();
226    /// # connection.execute("CREATE TABLE users (name STRING)");
227    /// let query = "SELECT * FROM users WHERE name = :name";
228    /// let handle = connection.prepare(query)?;
229    /// let statement = connection.borrow_statement(handle)?;
230    /// assert_eq!(statement.parameter_index(":name")?.unwrap(), 1);
231    /// assert_eq!(statement.parameter_index(":asdf")?, None);
232    /// # Ok::<(), sql_peas::Error>(())
233    /// ```
234    pub fn parameter_index(&self, parameter: &str) -> Result<Option<usize>> {
235        let index = unsafe {
236            ffi::sqlite3_bind_parameter_index(self.raw.0, str_to_cstr!(parameter).as_ptr())
237        };
238        match index {
239            0 => Ok(None),
240            _ => Ok(Some(index as usize)),
241        }
242    }
243
244    /// Reset the internal state.
245    #[inline]
246    pub fn reset(&self) -> Result<()> {
247        unsafe { ok!(self.raw.1, ffi::sqlite3_reset(self.raw.0)) };
248        Ok(())
249    }
250
251    #[doc(hidden)]
252    #[inline]
253    pub fn as_raw(&self) -> *mut ffi::sqlite3_stmt {
254        self.raw.0
255    }
256}
257
258impl Drop for Statement {
259    #[inline]
260    fn drop(&mut self) {
261        unsafe { ffi::sqlite3_finalize(self.raw.0) };
262    }
263}
264
265impl<'m> From<&'m Statement> for Cursor<'m> {
266    #[inline]
267    fn from(statement: &'m Statement) -> Self {
268        crate::cursor::new(statement)
269    }
270}
271
272/*
273impl<'l> IntoIterator for Statement<'l> {
274    type Item = Result<Row>;
275    type IntoIter = CursorWithOwnership<'l>;
276
277    #[inline]
278    fn into_iter(self) -> Self::IntoIter {
279        crate::cursor::new_with_ownership(self)
280    }
281}
282*/
283
284impl ColumnIndex for &str {
285    #[inline]
286    fn index(self, statement: &Statement) -> Result<usize> {
287        if statement.column_mapping.contains_key(self) {
288            Ok(statement.column_mapping[self])
289        } else {
290            raise!("the index is out of range ({})", self);
291        }
292    }
293}
294
295impl ColumnIndex for usize {
296    #[inline]
297    fn index(self, statement: &Statement) -> Result<usize> {
298        if self < statement.column_count() {
299            Ok(self)
300        } else {
301            raise!("the index is out of range ({})", self);
302        }
303    }
304}
305
306impl ParameterIndex for &str {
307    #[inline]
308    fn index(self, statement: &Statement) -> Result<usize> {
309        match statement.parameter_index(self)? {
310            Some(index) => Ok(index),
311            _ => raise!("the index is out of range ({})", self),
312        }
313    }
314}
315
316impl ParameterIndex for usize {
317    #[inline]
318    fn index(self, _: &Statement) -> Result<usize> {
319        if self > 0 {
320            Ok(self)
321        } else {
322            raise!("the index is out of range ({})", self);
323        }
324    }
325}
326
327pub(crate) fn new<T>(raw_connection: *mut ffi::sqlite3, statement: T) -> Result<Statement>
328where
329    T: AsRef<str>,
330{
331    let mut raw_statement = std::ptr::null_mut();
332    unsafe {
333        ok!(
334            raw_connection,
335            ffi::sqlite3_prepare_v2(
336                raw_connection,
337                str_to_cstr!(statement.as_ref()).as_ptr(),
338                -1,
339                &mut raw_statement,
340                std::ptr::null_mut(),
341            )
342        );
343    }
344    let column_count = unsafe { ffi::sqlite3_column_count(raw_statement) as usize };
345    let column_names = (0..column_count)
346        .map(|index| unsafe {
347            let raw = ffi::sqlite3_column_name(raw_statement, index as c_int);
348            debug_assert!(!raw.is_null());
349            c_str_to_str!(raw).unwrap().to_string()
350        })
351        .collect::<Vec<_>>();
352    let column_mapping = column_names
353        .iter()
354        .enumerate()
355        .map(|(index, name)| (name.to_string(), index))
356        .collect();
357    Ok(Statement {
358        raw: (raw_statement, raw_connection),
359        column_names: Rc::new(column_names),
360        column_mapping: Rc::new(column_mapping),
361        phantom: PhantomData,
362    })
363}