tank_sqlite/
prepared.rs

1use crate::{CBox, error_message_from_ptr};
2use libsqlite3_sys::{
3    SQLITE_OK, SQLITE_TRANSIENT, sqlite3_bind_blob, sqlite3_bind_double, sqlite3_bind_int,
4    sqlite3_bind_int64, sqlite3_bind_null, sqlite3_bind_text, sqlite3_clear_bindings,
5    sqlite3_db_handle, sqlite3_errmsg, sqlite3_int64, sqlite3_sql, sqlite3_stmt,
6};
7use rust_decimal::prelude::ToPrimitive;
8use std::{
9    ffi::{CStr, c_int},
10    fmt::{self, Display},
11    os::raw::{c_char, c_void},
12};
13use tank_core::{AsValue, Error, Prepared, Result, Value, printable_query};
14
15pub struct SqlitePrepared {
16    pub(crate) statement: CBox<*mut sqlite3_stmt>,
17    pub(crate) index: u64,
18}
19
20impl SqlitePrepared {
21    pub(crate) fn new(prepared: CBox<*mut sqlite3_stmt>) -> Self {
22        unsafe {
23            sqlite3_clear_bindings(*prepared);
24        }
25        Self {
26            statement: prepared,
27            index: 1,
28        }
29    }
30}
31
32impl Prepared for SqlitePrepared {
33    fn bind<V: AsValue>(&mut self, value: V) -> Result<&mut Self> {
34        self.bind_index(value, self.index)
35    }
36    fn bind_index<V: AsValue>(&mut self, v: V, index: u64) -> Result<&mut Self> {
37        let index = index as c_int;
38        unsafe {
39            let value = v.as_value();
40            let rc = match value {
41                Value::Null
42                | Value::Boolean(None, ..)
43                | Value::Int8(None, ..)
44                | Value::Int16(None, ..)
45                | Value::Int32(None, ..)
46                | Value::Int64(None, ..)
47                | Value::Int128(None, ..)
48                | Value::UInt8(None, ..)
49                | Value::UInt16(None, ..)
50                | Value::UInt32(None, ..)
51                | Value::UInt64(None, ..)
52                | Value::UInt128(None, ..)
53                | Value::Float32(None, ..)
54                | Value::Float64(None, ..)
55                | Value::Decimal(None, ..)
56                | Value::Char(None, ..)
57                | Value::Varchar(None, ..)
58                | Value::Blob(None, ..)
59                | Value::Date(None, ..)
60                | Value::Time(None, ..)
61                | Value::Timestamp(None, ..)
62                | Value::TimestampWithTimezone(None, ..)
63                | Value::Interval(None, ..)
64                | Value::Uuid(None, ..)
65                | Value::Array(None, ..)
66                | Value::List(None, ..)
67                | Value::Map(None, ..)
68                | Value::Struct(None, ..) => sqlite3_bind_null(*self.statement, index),
69                Value::Boolean(Some(v), ..) => sqlite3_bind_int(*self.statement, index, v as c_int),
70                Value::Int8(Some(v), ..) => sqlite3_bind_int(*self.statement, index, v as c_int),
71                Value::Int16(Some(v), ..) => sqlite3_bind_int(*self.statement, index, v as c_int),
72                Value::Int32(Some(v), ..) => sqlite3_bind_int(*self.statement, index, v as c_int),
73                Value::Int64(Some(v), ..) => sqlite3_bind_int64(*self.statement, index, v),
74                Value::Int128(Some(v), ..) => {
75                    if v as sqlite3_int64 as i128 != v {
76                        return Err(Error::msg(
77                            "Cannot bind i128 value `{}` into sqlite integer because it's out of bounds",
78                        ));
79                    }
80                    sqlite3_bind_int64(*self.statement, index, v as sqlite3_int64)
81                }
82                Value::UInt8(Some(v), ..) => sqlite3_bind_int(*self.statement, index, v as c_int),
83                Value::UInt16(Some(v), ..) => sqlite3_bind_int(*self.statement, index, v as c_int),
84                Value::UInt32(Some(v), ..) => sqlite3_bind_int(*self.statement, index, v as c_int),
85                Value::UInt64(Some(v), ..) => {
86                    if v as sqlite3_int64 as u64 != v {
87                        return Err(Error::msg(
88                            "Cannot bind i128 value `{}` into sqlite integer because it's out of bounds",
89                        ));
90                    }
91                    sqlite3_bind_int64(*self.statement, index, v as sqlite3_int64)
92                }
93                Value::UInt128(Some(v), ..) => {
94                    if v as sqlite3_int64 as u128 != v {
95                        return Err(Error::msg(
96                            "Cannot bind i128 value `{}` into sqlite integer because it's out of bounds",
97                        ));
98                    }
99                    sqlite3_bind_int64(*self.statement, index, v as sqlite3_int64)
100                }
101                Value::Float32(Some(v), ..) => {
102                    sqlite3_bind_double(*self.statement, index, v as f64)
103                }
104                Value::Float64(Some(v), ..) => sqlite3_bind_double(*self.statement, index, v),
105                Value::Decimal(Some(v), ..) => sqlite3_bind_double(
106                    *self.statement,
107                    index,
108                    v.to_f64().ok_or_else(|| {
109                        Error::msg(format!("Cannot convert the Decimal value `{}` to f64", v))
110                    })?,
111                ),
112                Value::Char(Some(v), ..) => {
113                    let v = v.to_string();
114                    sqlite3_bind_text(
115                        *self.statement,
116                        index,
117                        v.as_ptr() as *const c_char,
118                        v.len() as c_int,
119                        SQLITE_TRANSIENT(),
120                    )
121                }
122                Value::Varchar(Some(v), ..) => sqlite3_bind_text(
123                    *self.statement,
124                    index,
125                    v.as_ptr() as *const c_char,
126                    v.len() as c_int,
127                    SQLITE_TRANSIENT(),
128                ),
129                Value::Blob(Some(v), ..) => sqlite3_bind_blob(
130                    *self.statement,
131                    index,
132                    v.as_ptr() as *const c_void,
133                    v.len() as c_int,
134                    SQLITE_TRANSIENT(),
135                ),
136                Value::Date(Some(v), ..) => {
137                    let v = v.to_string();
138                    sqlite3_bind_text(
139                        *self.statement,
140                        index,
141                        v.as_ptr() as *const c_char,
142                        v.len() as c_int,
143                        SQLITE_TRANSIENT(),
144                    )
145                }
146                Value::Time(Some(v), ..) => {
147                    let v = v.to_string();
148                    sqlite3_bind_text(
149                        *self.statement,
150                        index,
151                        v.as_ptr() as *const c_char,
152                        v.len() as c_int,
153                        SQLITE_TRANSIENT(),
154                    )
155                }
156                Value::Timestamp(Some(v), ..) => {
157                    let v = v.to_string();
158                    sqlite3_bind_text(
159                        *self.statement,
160                        index,
161                        v.as_ptr() as *const c_char,
162                        v.len() as c_int,
163                        SQLITE_TRANSIENT(),
164                    )
165                }
166                Value::TimestampWithTimezone(Some(v), ..) => {
167                    let v = v.to_string();
168                    sqlite3_bind_text(
169                        *self.statement,
170                        index,
171                        v.as_ptr() as *const c_char,
172                        v.len() as c_int,
173                        SQLITE_TRANSIENT(),
174                    )
175                }
176                Value::Uuid(Some(v), ..) => {
177                    let v = v.to_string();
178                    sqlite3_bind_text(
179                        *self.statement,
180                        index,
181                        v.as_ptr() as *const c_char,
182                        v.len() as c_int,
183                        SQLITE_TRANSIENT(),
184                    )
185                }
186                _ => {
187                    let error =
188                        Error::msg(format!("Cannot use a {:?} as a query parameter", value));
189                    log::error!("{:#}", error);
190                    return Err(error);
191                }
192            };
193            if rc != SQLITE_OK {
194                let db = sqlite3_db_handle(*self.statement);
195                let query = sqlite3_sql(*self.statement);
196                let error = Error::msg(error_message_from_ptr(&sqlite3_errmsg(db)).to_string())
197                    .context(format!(
198                        "Cannot bind parameter {} to query:\n{}",
199                        index,
200                        printable_query!(CStr::from_ptr(query).to_string_lossy())
201                    ));
202                log::error!("{:#}", error);
203                return Err(error);
204            }
205            self.index = index as u64 + 1;
206            Ok(self)
207        }
208    }
209}
210
211impl Display for SqlitePrepared {
212    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213        write!(f, "{:p}", *self.statement)
214    }
215}