walletkit_db/sqlite/statement.rs
1//! Safe wrapper around a `SQLite` prepared statement.
2//!
3//! This file contains **no `unsafe` code**. All FFI interaction is delegated to
4//! [`ffi::RawStmt`] which encapsulates the raw pointers and C type conversions.
5
6use super::error::DbResult;
7use super::ffi::{self, RawStmt};
8use super::value::Value;
9
10/// Result of a single `sqlite3_step` call.
11pub enum StepResult<'stmt, 'conn> {
12 /// A result row is available.
13 Row(Row<'stmt, 'conn>),
14 /// The statement has finished executing.
15 Done,
16}
17
18/// A guard that represents the current row for a statement.
19///
20/// Values read through this guard are valid for the current row only.
21/// Calling `step`, `reset`, or dropping/finalizing the statement invalidates
22/// `SQLite`'s internal row pointers.
23pub struct Row<'stmt, 'conn> {
24 stmt: &'stmt Statement<'conn>,
25}
26
27impl Row<'_, '_> {
28 /// Reads a column as `i64`.
29 ///
30 /// # Panics
31 ///
32 /// Panics if `idx` exceeds `i32::MAX`.
33 #[must_use]
34 pub fn column_i64(&self, idx: usize) -> i64 {
35 self.stmt
36 .raw
37 .column_i64(i32::try_from(idx).expect("column index overflow"))
38 }
39
40 /// Reads a column as a blob. Returns an empty `Vec` for NULL.
41 ///
42 /// # Panics
43 ///
44 /// Panics if `idx` exceeds `i32::MAX`.
45 #[must_use]
46 pub fn column_blob(&self, idx: usize) -> Vec<u8> {
47 self.stmt
48 .raw
49 .column_blob(i32::try_from(idx).expect("column index overflow"))
50 }
51
52 /// Reads a column as a UTF-8 string. Returns an empty string for NULL.
53 ///
54 /// # Panics
55 ///
56 /// Panics if `idx` exceeds `i32::MAX`.
57 #[must_use]
58 pub fn column_text(&self, idx: usize) -> String {
59 self.stmt
60 .raw
61 .column_text(i32::try_from(idx).expect("column index overflow"))
62 }
63
64 /// Returns `true` if the column is SQL NULL.
65 ///
66 /// # Panics
67 ///
68 /// Panics if `idx` exceeds `i32::MAX`.
69 #[allow(dead_code)]
70 #[must_use]
71 pub fn is_column_null(&self, idx: usize) -> bool {
72 self.stmt
73 .raw
74 .column_type(i32::try_from(idx).expect("column index overflow"))
75 == ffi::SQLITE_NULL
76 }
77}
78
79/// A prepared `SQLite` statement.
80///
81/// Created via [`Connection::prepare`](super::Connection::prepare).
82/// Tied to the lifetime of the connection that created it.
83/// Finalized when dropped.
84pub struct Statement<'conn> {
85 raw: RawStmt<'conn>,
86}
87
88impl<'conn> Statement<'conn> {
89 /// Wraps a raw statement handle.
90 pub(super) const fn new(raw: RawStmt<'conn>) -> Self {
91 Self { raw }
92 }
93
94 /// Binds a slice of [`Value`]s to the statement parameters (1-indexed).
95 ///
96 /// # Errors
97 ///
98 /// Returns `Error` if any bind call fails.
99 ///
100 /// # Panics
101 ///
102 /// Panics if the number of values exceeds `i32::MAX`.
103 pub fn bind_values(&mut self, values: &[Value]) -> DbResult<()> {
104 for (i, val) in values.iter().enumerate() {
105 let idx = i32::try_from(i + 1).expect("parameter index overflow");
106 match val {
107 Value::Integer(v) => self.raw.bind_i64(idx, *v)?,
108 Value::Blob(v) => self.raw.bind_blob(idx, v)?,
109 Value::Text(v) => self.raw.bind_text(idx, v)?,
110 Value::Null => self.raw.bind_null(idx)?,
111 }
112 }
113 Ok(())
114 }
115
116 /// Executes a single step.
117 ///
118 /// # Errors
119 ///
120 /// Returns `Error` if the step fails.
121 pub fn step<'stmt>(&'stmt mut self) -> DbResult<StepResult<'stmt, 'conn>> {
122 let rc = self.raw.step()?;
123 if rc == ffi::SQLITE_ROW {
124 Ok(StepResult::Row(Row { stmt: self }))
125 } else {
126 Ok(StepResult::Done)
127 }
128 }
129}