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}