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