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