tank_sqlite/
prepared.rs

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