Skip to main content

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}