quaint_forked/connector/sqlite/
conversion.rs1use 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 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 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 _ => 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}