Skip to main content

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, DynQuery, Error, Fragment, Prepared, 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}
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}