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