Skip to main content

ic_sqlite_vfs/db/
value.rs

1//! Typed SQLite parameter binding.
2//!
3//! Values are copied into SQLite with `SQLITE_TRANSIENT`, so caller-owned text
4//! and blob buffers only need to live until the bind call returns.
5
6use crate::db::DbError;
7use crate::sqlite_vfs::ffi;
8use std::collections::BTreeSet;
9use std::ffi::{c_int, c_void, CStr, CString};
10
11static EMPTY_BLOB: u8 = 0;
12
13pub struct Null;
14
15pub const NULL: Null = Null;
16
17pub enum Value<'value> {
18    Text(&'value str),
19    Integer(i64),
20    Real(f64),
21    Blob(&'value [u8]),
22    Null,
23}
24
25pub trait ToSql {
26    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError>;
27}
28
29#[doc(hidden)]
30pub fn to_sql_ref<T: ToSql>(value: &T) -> &dyn ToSql {
31    value
32}
33
34impl ToSql for Value<'_> {
35    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
36        match self {
37            Value::Text(value) => bind_text(statement, index, value),
38            Value::Integer(value) => bind_i64(statement, index, *value),
39            Value::Real(value) => bind_f64(statement, index, *value),
40            Value::Blob(value) => bind_blob(statement, index, value),
41            Value::Null => bind_null(statement, index),
42        }
43    }
44}
45
46impl ToSql for &str {
47    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
48        bind_text(statement, index, self)
49    }
50}
51
52impl ToSql for String {
53    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
54        bind_text(statement, index, self)
55    }
56}
57
58impl ToSql for i64 {
59    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
60        bind_i64(statement, index, *self)
61    }
62}
63
64impl ToSql for f64 {
65    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
66        bind_f64(statement, index, *self)
67    }
68}
69
70impl ToSql for Vec<u8> {
71    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
72        bind_blob(statement, index, self)
73    }
74}
75
76impl ToSql for &[u8] {
77    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
78        bind_blob(statement, index, self)
79    }
80}
81
82impl ToSql for Null {
83    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
84        bind_null(statement, index)
85    }
86}
87
88pub(crate) fn bind_all(
89    statement: *mut ffi::sqlite3_stmt,
90    values: &[&dyn ToSql],
91) -> Result<(), DbError> {
92    let expected = parameter_count(statement)?;
93    if values.len() != expected {
94        return Err(DbError::ParameterCountMismatch {
95            expected,
96            actual: values.len(),
97        });
98    }
99    for (index, value) in values.iter().enumerate() {
100        let param = c_int::try_from(index + 1).map_err(|_| DbError::TooManyParameters)?;
101        value.bind_to(statement, param)?;
102    }
103    Ok(())
104}
105
106pub(crate) fn bind_named_all(
107    statement: *mut ffi::sqlite3_stmt,
108    values: &[(&str, &dyn ToSql)],
109) -> Result<(), DbError> {
110    let expected = named_parameters(statement)?;
111    let provided = values
112        .iter()
113        .map(|(name, _)| *name)
114        .collect::<BTreeSet<_>>();
115    if provided.len() != values.len() || values.len() != expected.len() {
116        return Err(DbError::ParameterCountMismatch {
117            expected: expected.len(),
118            actual: values.len(),
119        });
120    }
121    for name in &expected {
122        if !provided.contains(name.as_str()) {
123            return Err(DbError::ParameterNotFound(name.clone()));
124        }
125    }
126    for (name, value) in values {
127        if !expected.iter().any(|expected| expected == name) {
128            return Err(DbError::ParameterNotFound((*name).to_string()));
129        }
130        let name_text = CString::new(*name).map_err(|_| DbError::InteriorNul)?;
131        let index = unsafe { ffi::sqlite3_bind_parameter_index(statement, name_text.as_ptr()) };
132        if index == 0 {
133            return Err(DbError::ParameterNotFound((*name).to_string()));
134        }
135        value.bind_to(statement, index)?;
136    }
137    Ok(())
138}
139
140fn bind_text(statement: *mut ffi::sqlite3_stmt, index: c_int, value: &str) -> Result<(), DbError> {
141    let len = c_int::try_from(value.len()).map_err(|_| DbError::TextTooLarge)?;
142    let rc = unsafe {
143        ffi::sqlite3_bind_text(
144            statement,
145            index,
146            value.as_ptr().cast(),
147            len,
148            ffi::SQLITE_TRANSIENT(),
149        )
150    };
151    bind_result(rc)
152}
153
154fn bind_i64(statement: *mut ffi::sqlite3_stmt, index: c_int, value: i64) -> Result<(), DbError> {
155    bind_result(unsafe { ffi::sqlite3_bind_int64(statement, index, value) })
156}
157
158fn bind_f64(statement: *mut ffi::sqlite3_stmt, index: c_int, value: f64) -> Result<(), DbError> {
159    bind_result(unsafe { ffi::sqlite3_bind_double(statement, index, value) })
160}
161
162fn bind_blob(statement: *mut ffi::sqlite3_stmt, index: c_int, value: &[u8]) -> Result<(), DbError> {
163    let len = c_int::try_from(value.len()).map_err(|_| DbError::BlobTooLarge)?;
164    let ptr = if value.is_empty() {
165        (&EMPTY_BLOB as *const u8).cast::<c_void>()
166    } else {
167        value.as_ptr().cast::<c_void>()
168    };
169    let rc = unsafe { ffi::sqlite3_bind_blob(statement, index, ptr, len, ffi::SQLITE_TRANSIENT()) };
170    bind_result(rc)
171}
172
173fn bind_null(statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
174    bind_result(unsafe { ffi::sqlite3_bind_null(statement, index) })
175}
176
177fn bind_result(rc: c_int) -> Result<(), DbError> {
178    if rc == ffi::SQLITE_OK {
179        Ok(())
180    } else {
181        Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()))
182    }
183}
184
185fn parameter_count(statement: *mut ffi::sqlite3_stmt) -> Result<usize, DbError> {
186    let count = unsafe { ffi::sqlite3_bind_parameter_count(statement) };
187    usize::try_from(count).map_err(|_| DbError::TooManyParameters)
188}
189
190fn named_parameters(statement: *mut ffi::sqlite3_stmt) -> Result<Vec<String>, DbError> {
191    let count = parameter_count(statement)?;
192    let mut names = Vec::with_capacity(count);
193    for index in 1..=count {
194        let index_i32 = c_int::try_from(index).map_err(|_| DbError::TooManyParameters)?;
195        let name = unsafe { ffi::sqlite3_bind_parameter_name(statement, index_i32) };
196        if name.is_null() {
197            return Err(DbError::AnonymousParameterInNamedBind { index });
198        }
199        let name = unsafe { CStr::from_ptr(name) }
200            .to_string_lossy()
201            .into_owned();
202        names.push(name);
203    }
204    Ok(names)
205}