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                _ if value.is_null() => sqlite3_bind_null(statement, index),
81                Value::Boolean(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
82                Value::Int8(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
83                Value::Int16(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
84                Value::Int32(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
85                Value::Int64(Some(v), ..) => sqlite3_bind_int64(statement, index, v),
86                Value::Int128(Some(v), ..) => {
87                    if v as sqlite3_int64 as i128 != v {
88                        return Err(Error::msg(format!(
89                            "Cannot bind i128 value `{v}` into sqlite integer because it's out of bounds"
90                        )));
91                    }
92                    sqlite3_bind_int64(statement, index, v as sqlite3_int64)
93                }
94                Value::UInt8(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
95                Value::UInt16(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
96                Value::UInt32(Some(v), ..) => sqlite3_bind_int(statement, index, v as c_int),
97                Value::UInt64(Some(v), ..) => {
98                    if v as sqlite3_int64 as u64 != v {
99                        return Err(Error::msg(format!(
100                            "Cannot bind i128 value `{v}` into sqlite integer because it's out of bounds"
101                        )));
102                    }
103                    sqlite3_bind_int64(statement, index, v as sqlite3_int64)
104                }
105                Value::UInt128(Some(v), ..) => {
106                    if v as sqlite3_int64 as u128 != v {
107                        return Err(Error::msg(format!(
108                            "Cannot bind i128 value `{v}` into sqlite integer because it's out of bounds"
109                        )));
110                    }
111                    sqlite3_bind_int64(statement, index, v as sqlite3_int64)
112                }
113                Value::Float32(Some(v), ..) => sqlite3_bind_double(statement, index, v as f64),
114                Value::Float64(Some(v), ..) => sqlite3_bind_double(statement, index, v),
115                Value::Decimal(Some(v), ..) => sqlite3_bind_double(
116                    statement,
117                    index,
118                    v.to_f64().ok_or_else(|| {
119                        Error::msg(format!("Cannot bind the Decimal value `{v}` to f64"))
120                    })?,
121                ),
122                Value::Char(Some(v), ..) => {
123                    let v = v.to_string();
124                    sqlite3_bind_text(
125                        statement,
126                        index,
127                        v.as_ptr() as *const c_char,
128                        v.len() as c_int,
129                        SQLITE_TRANSIENT(),
130                    )
131                }
132                Value::Varchar(Some(v), ..) => sqlite3_bind_text(
133                    statement,
134                    index,
135                    v.as_ptr() as *const c_char,
136                    v.len() as c_int,
137                    SQLITE_TRANSIENT(),
138                ),
139                Value::Blob(Some(v), ..) => sqlite3_bind_blob(
140                    statement,
141                    index,
142                    v.as_ptr() as *const c_void,
143                    v.len() as c_int,
144                    SQLITE_TRANSIENT(),
145                ),
146                Value::Date(Some(v), ..) => {
147                    let mut out = DynQuery::with_capacity(32);
148                    Self::WRITER.write_date(
149                        &mut Context::fragment(Fragment::ParameterBinding),
150                        &mut out,
151                        &v,
152                    );
153                    sqlite3_bind_text(
154                        statement,
155                        index,
156                        out.as_str().as_ptr() as *const c_char,
157                        out.len() as c_int,
158                        SQLITE_TRANSIENT(),
159                    )
160                }
161                Value::Time(Some(v), ..) => {
162                    let mut out = DynQuery::with_capacity(32);
163                    Self::WRITER.write_time(
164                        &mut Context::fragment(Fragment::ParameterBinding),
165                        &mut out,
166                        &v,
167                    );
168                    sqlite3_bind_text(
169                        statement,
170                        index,
171                        out.as_str().as_ptr() as *const c_char,
172                        out.len() as c_int,
173                        SQLITE_TRANSIENT(),
174                    )
175                }
176                Value::Timestamp(Some(v), ..) => {
177                    let mut out = DynQuery::with_capacity(32);
178                    Self::WRITER.write_timestamp(
179                        &mut Context::fragment(Fragment::ParameterBinding),
180                        &mut out,
181                        &v,
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::TimestampWithTimezone(Some(v), ..) => {
192                    let mut out = DynQuery::with_capacity(32);
193                    Self::WRITER.write_timestamptz(
194                        &mut Context::fragment(Fragment::ParameterBinding),
195                        &mut out,
196                        &v,
197                    );
198                    sqlite3_bind_text(
199                        statement,
200                        index,
201                        out.as_str().as_ptr() as *const c_char,
202                        out.len() as c_int,
203                        SQLITE_TRANSIENT(),
204                    )
205                }
206                Value::Uuid(Some(v), ..) => {
207                    let v = v.to_string();
208                    sqlite3_bind_text(
209                        statement,
210                        index,
211                        v.as_ptr() as *const c_char,
212                        v.len() as c_int,
213                        SQLITE_TRANSIENT(),
214                    )
215                }
216                _ => {
217                    let error =
218                        Error::msg(format!("Cannot use a {:?} as a query parameter", value));
219                    log::error!("{:#}", error);
220                    return Err(error);
221                }
222            };
223            if rc != SQLITE_OK {
224                let db = sqlite3_db_handle(statement);
225                let query = sqlite3_sql(statement);
226                let error = Error::msg(error_message_from_ptr(&sqlite3_errmsg(db)).to_string())
227                    .context(format!(
228                        "Cannot bind parameter {index} to query:\n{}",
229                        truncate_long!(CStr::from_ptr(query).to_string_lossy())
230                    ));
231                log::error!("{:#}", error);
232                return Err(error);
233            }
234            self.index = index as u64 + 1;
235            Ok(self)
236        }
237    }
238}
239
240impl Display for SQLitePrepared {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        write!(f, "{:p}", self.statement())
243    }
244}