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    AsValue, Context, Error, Fragment, Prepared, Result, SqlWriter, Value, error_message_from_ptr,
11    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}
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}