1use 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}