tank_sqlite/
prepared.rs

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/// Prepared statement wrapper for SQLite.
15///
16/// Manages the native statement pointer and binding index, and implements  the `Prepared` trait to bind parameters from `tank_core::Value`.
17#[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}