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