1use crate::{CBox, sql_writer::SQLiteSqlWriter};
2use libsqlite3_sys::*;
3use rust_decimal::prelude::ToPrimitive;
4use std::{
5 ffi::{CStr, c_int},
6 fmt::{self, Display},
7 os::raw::{c_char, c_void},
8};
9use tank_core::{
10 DynQuery, AsValue, Context, Error, Fragment, Prepared, QueryMetadata, Result, SqlWriter, Value,
11 error_message_from_ptr, truncate_long,
12};
13
14#[derive(Debug)]
18pub struct SQLitePrepared {
19 pub(crate) statement: CBox<*mut sqlite3_stmt>,
20 pub(crate) index: u64,
21 pub(crate) metadata: QueryMetadata,
22}
23
24impl SQLitePrepared {
25 const WRITER: SQLiteSqlWriter = SQLiteSqlWriter {};
26 pub(crate) fn new(statement: CBox<*mut sqlite3_stmt>) -> Self {
27 Self {
28 statement: statement.into(),
29 index: 1,
30 metadata: Default::default(),
31 }
32 }
33 pub(crate) fn statement(&self) -> *mut sqlite3_stmt {
34 *self.statement
35 }
36 pub fn last_error(&self) -> String {
37 unsafe {
38 let db = sqlite3_db_handle(self.statement());
39 let errcode = sqlite3_errcode(db);
40 format!(
41 "Error ({errcode}): {}",
42 error_message_from_ptr(&sqlite3_errmsg(db)),
43 )
44 }
45 }
46}
47
48impl Prepared for SQLitePrepared {
49 fn as_any(self: Box<Self>) -> Box<dyn std::any::Any> {
50 self
51 }
52 fn clear_bindings(&mut self) -> Result<&mut Self> {
53 self.index = 1;
54 unsafe {
55 let rc = sqlite3_reset(self.statement());
56 let error = || {
57 let e = Error::msg(self.last_error())
58 .context("Could not clear the bindings from Sqlite statement");
59 log::error!("{e:#}");
60 e
61 };
62 if rc != SQLITE_OK {
63 return Err(error());
64 }
65 let rc = sqlite3_clear_bindings(self.statement());
66 if rc != SQLITE_OK {
67 return Err(error());
68 }
69 }
70 Ok(self)
71 }
72 fn bind(&mut self, value: impl AsValue) -> Result<&mut Self> {
73 self.bind_index(value, self.index)?;
74 Ok(self)
75 }
76 fn bind_index(&mut self, v: impl AsValue, index: u64) -> Result<&mut Self> {
77 let index = index as c_int;
78 unsafe {
79 let value = v.as_value();
80 let statement = self.statement();
81 let rc = match value {
82 Value::Null
83 | Value::Boolean(None, ..)
84 | Value::Int8(None, ..)
85 | Value::Int16(None, ..)
86 | Value::Int32(None, ..)
87 | Value::Int64(None, ..)
88 | Value::Int128(None, ..)
89 | Value::UInt8(None, ..)
90 | Value::UInt16(None, ..)
91 | Value::UInt32(None, ..)
92 | Value::UInt64(None, ..)
93 | Value::UInt128(None, ..)
94 | Value::Float32(None, ..)
95 | Value::Float64(None, ..)
96 | Value::Decimal(None, ..)
97 | Value::Char(None, ..)
98 | Value::Varchar(None, ..)
99 | Value::Blob(None, ..)
100 | Value::Date(None, ..)
101 | Value::Time(None, ..)
102 | Value::Timestamp(None, ..)
103 | Value::TimestampWithTimezone(None, ..)
104 | Value::Interval(None, ..)
105 | Value::Uuid(None, ..)
106 | Value::Array(None, ..)
107 | Value::List(None, ..)
108 | Value::Map(None, ..)
109 | Value::Struct(None, ..) => sqlite3_bind_null(statement, index),
110 Value::Boolean(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
111 Value::Int8(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
112 Value::Int16(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
113 Value::Int32(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
114 Value::Int64(Some(v), ..) => sqlite3_bind_int64(statement, index, v),
115 Value::Int128(Some(v), ..) => {
116 if v as sqlite3_int64 as i128 != v {
117 return Err(Error::msg(format!(
118 "Cannot bind i128 value `{v}` into sqlite integer because it's out of bounds"
119 )));
120 }
121 sqlite3_bind_int64(statement, index, v as sqlite3_int64)
122 }
123 Value::UInt8(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
124 Value::UInt16(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
125 Value::UInt32(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
126 Value::UInt64(Some(v), ..) => {
127 if v as sqlite3_int64 as u64 != v {
128 return Err(Error::msg(format!(
129 "Cannot bind i128 value `{v}` into sqlite integer because it's out of bounds"
130 )));
131 }
132 sqlite3_bind_int64(statement, index, v as sqlite3_int64)
133 }
134 Value::UInt128(Some(v), ..) => {
135 if v as sqlite3_int64 as u128 != v {
136 return Err(Error::msg(format!(
137 "Cannot bind i128 value `{v}` into sqlite integer because it's out of bounds"
138 )));
139 }
140 sqlite3_bind_int64(statement, index, v as sqlite3_int64)
141 }
142 Value::Float32(Some(v), ..) => sqlite3_bind_double(statement, index, v as f64),
143 Value::Float64(Some(v), ..) => sqlite3_bind_double(statement, index, v),
144 Value::Decimal(Some(v), ..) => sqlite3_bind_double(
145 statement,
146 index,
147 v.to_f64().ok_or_else(|| {
148 Error::msg(format!("Cannot bind the Decimal value `{v}` to f64"))
149 })?,
150 ),
151 Value::Char(Some(v), ..) => {
152 let v = v.to_string();
153 sqlite3_bind_text(
154 statement,
155 index,
156 v.as_ptr() as *const c_char,
157 v.len() as c_int,
158 SQLITE_TRANSIENT(),
159 )
160 }
161 Value::Varchar(Some(v), ..) => sqlite3_bind_text(
162 statement,
163 index,
164 v.as_ptr() as *const c_char,
165 v.len() as c_int,
166 SQLITE_TRANSIENT(),
167 ),
168 Value::Blob(Some(v), ..) => sqlite3_bind_blob(
169 statement,
170 index,
171 v.as_ptr() as *const c_void,
172 v.len() as c_int,
173 SQLITE_TRANSIENT(),
174 ),
175 Value::Date(Some(v), ..) => {
176 let mut out = DynQuery::with_capacity(32);
177 Self::WRITER.write_value_date(
178 &mut Context::fragment(Fragment::ParameterBinding),
179 &mut out,
180 &v,
181 false,
182 );
183 sqlite3_bind_text(
184 statement,
185 index,
186 out.as_str().as_ptr() as *const c_char,
187 out.len() as c_int,
188 SQLITE_TRANSIENT(),
189 )
190 }
191 Value::Time(Some(v), ..) => {
192 let mut out = DynQuery::with_capacity(32);
193 Self::WRITER.write_value_time(
194 &mut Context::fragment(Fragment::ParameterBinding),
195 &mut out,
196 &v,
197 false,
198 );
199 sqlite3_bind_text(
200 statement,
201 index,
202 out.as_str().as_ptr() as *const c_char,
203 out.len() as c_int,
204 SQLITE_TRANSIENT(),
205 )
206 }
207 Value::Timestamp(Some(v), ..) => {
208 let mut out = DynQuery::with_capacity(32);
209 Self::WRITER.write_value_timestamp(
210 &mut Context::fragment(Fragment::ParameterBinding),
211 &mut out,
212 &v,
213 );
214 sqlite3_bind_text(
215 statement,
216 index,
217 out.as_str().as_ptr() as *const c_char,
218 out.len() as c_int,
219 SQLITE_TRANSIENT(),
220 )
221 }
222 Value::TimestampWithTimezone(Some(v), ..) => {
223 let mut out = DynQuery::with_capacity(32);
224 Self::WRITER.write_value_timestamptz(
225 &mut Context::fragment(Fragment::ParameterBinding),
226 &mut out,
227 &v,
228 );
229 sqlite3_bind_text(
230 statement,
231 index,
232 out.as_str().as_ptr() as *const c_char,
233 out.len() as c_int,
234 SQLITE_TRANSIENT(),
235 )
236 }
237 Value::Uuid(Some(v), ..) => {
238 let v = v.to_string();
239 sqlite3_bind_text(
240 statement,
241 index,
242 v.as_ptr() as *const c_char,
243 v.len() as c_int,
244 SQLITE_TRANSIENT(),
245 )
246 }
247 _ => {
248 let error =
249 Error::msg(format!("Cannot use a {:?} as a query parameter", value));
250 log::error!("{:#}", error);
251 return Err(error);
252 }
253 };
254 if rc != SQLITE_OK {
255 let db = sqlite3_db_handle(statement);
256 let query = sqlite3_sql(statement);
257 let error = Error::msg(error_message_from_ptr(&sqlite3_errmsg(db)).to_string())
258 .context(format!(
259 "Cannot bind parameter {index} to query:\n{}",
260 truncate_long!(CStr::from_ptr(query).to_string_lossy())
261 ));
262 log::error!("{:#}", error);
263 return Err(error);
264 }
265 self.index = index as u64 + 1;
266 Ok(self)
267 }
268 }
269
270 fn metadata(&self) -> &QueryMetadata {
271 &self.metadata
272 }
273
274 fn metadata_mut(&mut self) -> &mut QueryMetadata {
275 &mut self.metadata
276 }
277}
278
279impl Display for SQLitePrepared {
280 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
281 write!(f, "{:p}", self.statement())
282 }
283}