tank_sqlite/
prepared.rs

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}