quaint_forked/connector/sqlite/
conversion.rs

1use std::convert::TryFrom;
2
3use crate::{
4    ast::Value,
5    connector::{
6        queryable::{GetRow, ToColumnNames},
7        TypeIdentifier,
8    },
9    error::{Error, ErrorKind},
10};
11
12use rusqlite::{
13    types::{Null, ToSql, ToSqlOutput, ValueRef},
14    Column, Error as RusqlError, Row as SqliteRow, Rows as SqliteRows,
15};
16
17#[cfg(feature = "chrono")]
18use chrono::TimeZone;
19
20impl TypeIdentifier for Column<'_> {
21    fn is_real(&self) -> bool {
22        match self.decl_type() {
23            Some(n) if n.starts_with("DECIMAL") => true,
24            Some(n) if n.starts_with("decimal") => true,
25            _ => false,
26        }
27    }
28
29    fn is_float(&self) -> bool {
30        matches!(self.decl_type(), Some("FLOAT") | Some("float"))
31    }
32
33    fn is_double(&self) -> bool {
34        matches!(
35            self.decl_type(),
36            Some("DOUBLE")
37                | Some("double")
38                | Some("DOUBLE PRECISION")
39                | Some("double precision")
40                | Some("numeric")
41                | Some("NUMERIC")
42                | Some("real")
43                | Some("REAL")
44        )
45    }
46
47    fn is_int32(&self) -> bool {
48        matches!(
49            self.decl_type(),
50            Some("TINYINT")
51                | Some("tinyint")
52                | Some("SMALLINT")
53                | Some("smallint")
54                | Some("MEDIUMINT")
55                | Some("mediumint")
56                | Some("INT")
57                | Some("int")
58                | Some("INTEGER")
59                | Some("integer")
60                | Some("SERIAL")
61                | Some("serial")
62                | Some("INT2")
63                | Some("int2")
64        )
65    }
66
67    fn is_int64(&self) -> bool {
68        matches!(
69            self.decl_type(),
70            Some("BIGINT")
71                | Some("bigint")
72                | Some("UNSIGNED BIG INT")
73                | Some("unsigned big int")
74                | Some("INT8")
75                | Some("int8")
76        )
77    }
78
79    fn is_datetime(&self) -> bool {
80        matches!(
81            self.decl_type(),
82            Some("DATETIME") | Some("datetime") | Some("TIMESTAMP") | Some("timestamp")
83        )
84    }
85
86    fn is_time(&self) -> bool {
87        false
88    }
89
90    fn is_date(&self) -> bool {
91        matches!(self.decl_type(), Some("DATE") | Some("date"))
92    }
93
94    fn is_text(&self) -> bool {
95        match self.decl_type() {
96            Some("TEXT") | Some("text") => true,
97            Some("CLOB") | Some("clob") => true,
98            Some(n) if n.starts_with("CHARACTER") => true,
99            Some(n) if n.starts_with("character") => true,
100            Some(n) if n.starts_with("VARCHAR") => true,
101            Some(n) if n.starts_with("varchar") => true,
102            Some(n) if n.starts_with("VARYING CHARACTER") => true,
103            Some(n) if n.starts_with("varying character") => true,
104            Some(n) if n.starts_with("NCHAR") => true,
105            Some(n) if n.starts_with("nchar") => true,
106            Some(n) if n.starts_with("NATIVE CHARACTER") => true,
107            Some(n) if n.starts_with("native character") => true,
108            Some(n) if n.starts_with("NVARCHAR") => true,
109            Some(n) if n.starts_with("nvarchar") => true,
110            _ => false,
111        }
112    }
113
114    fn is_bytes(&self) -> bool {
115        matches!(self.decl_type(), Some("BLOB") | Some("blob"))
116    }
117
118    fn is_bool(&self) -> bool {
119        matches!(self.decl_type(), Some("BOOLEAN") | Some("boolean"))
120    }
121
122    fn is_json(&self) -> bool {
123        false
124    }
125    fn is_enum(&self) -> bool {
126        false
127    }
128    fn is_null(&self) -> bool {
129        self.decl_type().is_none()
130    }
131}
132
133impl<'a> GetRow for SqliteRow<'a> {
134    fn get_result_row(&self) -> crate::Result<Vec<Value<'static>>> {
135        let mut row = Vec::with_capacity(self.columns().len());
136
137        for (i, column) in self.columns().iter().enumerate() {
138            let pv = match self.get_ref_unwrap(i) {
139                ValueRef::Null => match column {
140                    // NOTE: A value without decl_type would be Int32(None)
141                    c if c.is_int32() | c.is_null() => Value::Int32(None),
142                    c if c.is_int64() => Value::Int64(None),
143                    c if c.is_text() => Value::Text(None),
144                    c if c.is_bytes() => Value::Bytes(None),
145                    c if c.is_float() => Value::Float(None),
146                    c if c.is_double() => Value::Double(None),
147                    #[cfg(feature = "bigdecimal")]
148                    c if c.is_real() => Value::Numeric(None),
149                    #[cfg(feature = "chrono")]
150                    c if c.is_datetime() => Value::DateTime(None),
151                    #[cfg(feature = "chrono")]
152                    c if c.is_date() => Value::Date(None),
153                    c if c.is_bool() => Value::Boolean(None),
154                    c => match c.decl_type() {
155                        Some(n) => {
156                            let msg = format!("Value {n} not supported");
157                            let kind = ErrorKind::conversion(msg);
158
159                            return Err(Error::builder(kind).build());
160                        }
161                        // When we don't know what to do, the default value would be Int32(None)
162                        None => Value::Int32(None),
163                    },
164                },
165                ValueRef::Integer(i) => {
166                    match column {
167                        c if c.is_bool() => {
168                            if i == 0 {
169                                Value::boolean(false)
170                            } else {
171                                Value::boolean(true)
172                            }
173                        }
174                        #[cfg(feature = "chrono")]
175                        c if c.is_date() => {
176                            let dt = chrono::NaiveDateTime::from_timestamp_opt(i / 1000, 0).unwrap();
177                            Value::date(dt.date())
178                        }
179                        #[cfg(feature = "chrono")]
180                        c if c.is_datetime() => {
181                            let dt = chrono::Utc.timestamp_millis_opt(i).unwrap();
182                            Value::datetime(dt)
183                        }
184                        c if c.is_int32() => {
185                            if let Ok(converted) = i32::try_from(i) {
186                                Value::int32(converted)
187                            } else {
188                                let msg = format!("Value {} does not fit in an INT column, try migrating the '{}' column type to BIGINT", i, c.name());
189                                let kind = ErrorKind::conversion(msg);
190
191                                return Err(Error::builder(kind).build());
192                            }
193                        }
194                        // NOTE: When SQLite does not know what type the return is (for example at explicit values and RETURNING statements) we will 'assume' int64
195                        _ => Value::int64(i),
196                    }
197                }
198                #[cfg(feature = "bigdecimal")]
199                ValueRef::Real(f) if column.is_real() => {
200                    use bigdecimal::{BigDecimal, FromPrimitive};
201
202                    Value::numeric(BigDecimal::from_f64(f).unwrap())
203                }
204                ValueRef::Real(f) => Value::double(f),
205                #[cfg(feature = "chrono")]
206                ValueRef::Text(bytes) if column.is_datetime() => {
207                    let parse_res = std::str::from_utf8(bytes).map_err(|_| {
208                        let builder = Error::builder(ErrorKind::ConversionError(
209                            "Failed to read contents of SQLite datetime column as UTF-8".into(),
210                        ));
211                        builder.build()
212                    });
213
214                    parse_res.and_then(|s| {
215                        chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")
216                            .map(|nd| chrono::DateTime::<chrono::Utc>::from_utc(nd, chrono::Utc))
217                            .or_else(|_| {
218                                chrono::DateTime::parse_from_rfc3339(s).map(|dt| dt.with_timezone(&chrono::Utc))
219                            })
220                            .or_else(|_| {
221                                chrono::DateTime::parse_from_rfc2822(s).map(|dt| dt.with_timezone(&chrono::Utc))
222                            })
223                            .map(Value::datetime)
224                            .map_err(|chrono_error| {
225                                let builder =
226                                    Error::builder(ErrorKind::ConversionError(chrono_error.to_string().into()));
227                                builder.build()
228                            })
229                    })?
230                }
231                ValueRef::Text(bytes) => Value::text(String::from_utf8(bytes.to_vec())?),
232                ValueRef::Blob(bytes) => Value::bytes(bytes.to_owned()),
233            };
234
235            row.push(pv);
236        }
237
238        Ok(row)
239    }
240}
241
242impl<'a> ToColumnNames for SqliteRows<'a> {
243    fn to_column_names(&self) -> Vec<String> {
244        match self.column_names() {
245            Some(columns) => columns.into_iter().map(|c| c.into()).collect(),
246            None => vec![],
247        }
248    }
249}
250
251impl<'a> ToSql for Value<'a> {
252    fn to_sql(&self) -> Result<ToSqlOutput, RusqlError> {
253        let value = match self {
254            Value::Int32(integer) => integer.map(ToSqlOutput::from),
255            Value::Int64(integer) => integer.map(ToSqlOutput::from),
256            Value::Float(float) => float.map(|f| f as f64).map(ToSqlOutput::from),
257            Value::Double(double) => double.map(ToSqlOutput::from),
258            Value::Text(cow) => cow.as_ref().map(|cow| ToSqlOutput::from(cow.as_ref())),
259            Value::Enum(cow) => cow.as_ref().map(|cow| ToSqlOutput::from(cow.as_ref())),
260            Value::Boolean(boo) => boo.map(ToSqlOutput::from),
261            Value::Char(c) => c.map(|c| ToSqlOutput::from(c as u8)),
262            Value::Bytes(bytes) => bytes.as_ref().map(|bytes| ToSqlOutput::from(bytes.as_ref())),
263            Value::Array(_) => {
264                let msg = "Arrays are not supported in SQLite.";
265                let kind = ErrorKind::conversion(msg);
266
267                let mut builder = Error::builder(kind);
268                builder.set_original_message(msg);
269
270                return Err(RusqlError::ToSqlConversionFailure(Box::new(builder.build())));
271            }
272            #[cfg(feature = "bigdecimal")]
273            Value::Numeric(d) => d
274                .as_ref()
275                .map(|d| ToSqlOutput::from(d.to_string().parse::<f64>().expect("BigDecimal is not a f64."))),
276            #[cfg(feature = "json")]
277            Value::Json(value) => value.as_ref().map(|value| {
278                let stringified = serde_json::to_string(value)
279                    .map_err(|err| RusqlError::ToSqlConversionFailure(Box::new(err)))
280                    .unwrap();
281
282                ToSqlOutput::from(stringified)
283            }),
284            Value::Xml(cow) => cow.as_ref().map(|cow| ToSqlOutput::from(cow.as_ref())),
285            #[cfg(feature = "uuid")]
286            Value::Uuid(value) => value.map(|value| ToSqlOutput::from(value.hyphenated().to_string())),
287            #[cfg(feature = "chrono")]
288            Value::DateTime(value) => value.map(|value| ToSqlOutput::from(value.timestamp_millis())),
289            #[cfg(feature = "chrono")]
290            Value::Date(date) => date
291                .and_then(|date| date.and_hms_opt(0, 0, 0))
292                .map(|dt| ToSqlOutput::from(dt.timestamp_millis())),
293            #[cfg(feature = "chrono")]
294            Value::Time(time) => time
295                .and_then(|time| chrono::NaiveDate::from_ymd_opt(1970, 1, 1).map(|d| (d, time)))
296                .and_then(|(date, time)| {
297                    use chrono::Timelike;
298                    date.and_hms_opt(time.hour(), time.minute(), time.second())
299                })
300                .map(|dt| ToSqlOutput::from(dt.timestamp_millis())),
301        };
302
303        match value {
304            Some(value) => Ok(value),
305            None => Ok(ToSqlOutput::from(Null)),
306        }
307    }
308}