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::ffi::{c_int, c_void, CString};
9use std::ptr;
10
11pub struct Null;
12
13pub const NULL: Null = Null;
14
15pub enum Value<'value> {
16    Text(&'value str),
17    Integer(i64),
18    Real(f64),
19    Blob(&'value [u8]),
20    Null,
21}
22
23pub trait ToSql {
24    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError>;
25}
26
27impl ToSql for Value<'_> {
28    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
29        match self {
30            Value::Text(value) => bind_text(statement, index, value),
31            Value::Integer(value) => bind_i64(statement, index, *value),
32            Value::Real(value) => bind_f64(statement, index, *value),
33            Value::Blob(value) => bind_blob(statement, index, value),
34            Value::Null => bind_null(statement, index),
35        }
36    }
37}
38
39impl ToSql for &str {
40    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
41        bind_text(statement, index, self)
42    }
43}
44
45impl ToSql for String {
46    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
47        bind_text(statement, index, self)
48    }
49}
50
51impl ToSql for i64 {
52    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
53        bind_i64(statement, index, *self)
54    }
55}
56
57impl ToSql for f64 {
58    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
59        bind_f64(statement, index, *self)
60    }
61}
62
63impl ToSql for Vec<u8> {
64    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
65        bind_blob(statement, index, self)
66    }
67}
68
69impl ToSql for &[u8] {
70    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
71        bind_blob(statement, index, self)
72    }
73}
74
75impl ToSql for Null {
76    fn bind_to(&self, statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
77        bind_null(statement, index)
78    }
79}
80
81pub(crate) fn bind_all(
82    statement: *mut ffi::sqlite3_stmt,
83    values: &[&dyn ToSql],
84) -> Result<(), DbError> {
85    for (index, value) in values.iter().enumerate() {
86        let param = c_int::try_from(index + 1).map_err(|_| DbError::TooManyParameters)?;
87        value.bind_to(statement, param)?;
88    }
89    Ok(())
90}
91
92pub(crate) fn bind_named_all(
93    statement: *mut ffi::sqlite3_stmt,
94    values: &[(&str, &dyn ToSql)],
95) -> Result<(), DbError> {
96    for (name, value) in values {
97        let name_text = CString::new(*name).map_err(|_| DbError::InteriorNul)?;
98        let index = unsafe { ffi::sqlite3_bind_parameter_index(statement, name_text.as_ptr()) };
99        if index == 0 {
100            return Err(DbError::ParameterNotFound((*name).to_string()));
101        }
102        value.bind_to(statement, index)?;
103    }
104    Ok(())
105}
106
107fn bind_text(statement: *mut ffi::sqlite3_stmt, index: c_int, value: &str) -> Result<(), DbError> {
108    let value = CString::new(value).map_err(|_| DbError::InteriorNul)?;
109    let len = c_int::try_from(value.as_bytes().len()).map_err(|_| DbError::TextTooLarge)?;
110    let rc = unsafe {
111        ffi::sqlite3_bind_text(
112            statement,
113            index,
114            value.as_ptr(),
115            len,
116            ffi::SQLITE_TRANSIENT(),
117        )
118    };
119    bind_result(rc)
120}
121
122fn bind_i64(statement: *mut ffi::sqlite3_stmt, index: c_int, value: i64) -> Result<(), DbError> {
123    bind_result(unsafe { ffi::sqlite3_bind_int64(statement, index, value) })
124}
125
126fn bind_f64(statement: *mut ffi::sqlite3_stmt, index: c_int, value: f64) -> Result<(), DbError> {
127    bind_result(unsafe { ffi::sqlite3_bind_double(statement, index, value) })
128}
129
130fn bind_blob(statement: *mut ffi::sqlite3_stmt, index: c_int, value: &[u8]) -> Result<(), DbError> {
131    let len = c_int::try_from(value.len()).map_err(|_| DbError::BlobTooLarge)?;
132    let ptr = if value.is_empty() {
133        ptr::null()
134    } else {
135        value.as_ptr().cast::<c_void>()
136    };
137    let rc = unsafe { ffi::sqlite3_bind_blob(statement, index, ptr, len, ffi::SQLITE_TRANSIENT()) };
138    bind_result(rc)
139}
140
141fn bind_null(statement: *mut ffi::sqlite3_stmt, index: c_int) -> Result<(), DbError> {
142    bind_result(unsafe { ffi::sqlite3_bind_null(statement, index) })
143}
144
145fn bind_result(rc: c_int) -> Result<(), DbError> {
146    if rc == ffi::SQLITE_OK {
147        Ok(())
148    } else {
149        Err(DbError::Sqlite(rc, "sqlite bind failed".to_string()))
150    }
151}