1use crate::moves::{Moves, targets};
2use crate::{Error, Result, Uuid};
3use jiff::Timestamp;
4use jiff::civil::{Date, DateTime};
5
6#[derive(Clone, Debug, PartialEq)]
7pub enum SqlValue {
8 Null,
9 Bool(bool),
10 Int(i64),
11 Float(f64),
12 Text(String),
13 Bytes(Vec<u8>),
14 Uuid(Uuid),
15 Timestamp(Timestamp),
16 Date(Date),
17 DateTime(DateTime),
18 Json(serde_json::Value),
19 Array(Vec<SqlValue>),
21}
22
23impl SqlValue {
24 #[must_use]
26 pub fn kind(&self) -> &'static str {
27 match self {
28 Self::Null => "null",
29 Self::Bool(_) => "bool",
30 Self::Int(_) => "integer",
31 Self::Float(_) => "float",
32 Self::Text(_) => "text",
33 Self::Bytes(_) => "bytes",
34 Self::Uuid(_) => "uuid",
35 Self::Timestamp(_) => "timestamp",
36 Self::Date(_) => "date",
37 Self::DateTime(_) => "date-time",
38 Self::Json(_) => "json",
39 Self::Array(_) => "array",
40 }
41 }
42}
43
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub enum SqlType {
46 Bool,
47 Int,
48 Float,
49 Text,
50 Bytes,
51 Uuid,
52 Timestamp,
53 Date,
54 DateTime,
55}
56
57#[derive(Clone, Copy, Debug, PartialEq, Eq)]
58pub struct ColumnSpec {
59 pub suffix: &'static str,
60 pub ty: SqlType,
61 pub orders: bool,
64 pub values: &'static [&'static str],
66}
67
68impl ColumnSpec {
69 #[must_use]
70 pub const fn new(suffix: &'static str, ty: SqlType) -> Self {
71 Self {
72 suffix,
73 ty,
74 orders: true,
75 values: &[],
76 }
77 }
78
79 #[must_use]
80 pub const fn with_values(self, values: &'static [&'static str]) -> Self {
81 Self { values, ..self }
82 }
83
84 #[must_use]
85 pub const fn unordered(self) -> Self {
86 Self {
87 orders: false,
88 ..self
89 }
90 }
91}
92
93#[doc(hidden)]
95#[must_use]
96pub const fn stores_earlier_column(fields: &[(&str, &[ColumnSpec])], index: usize) -> bool {
97 let (name, columns) = fields[index];
98 let mut earlier = 0;
99 while earlier < index {
100 let (earlier_name, earlier_columns) = fields[earlier];
101 let mut column = 0;
102 while column < columns.len() {
103 if names_column(earlier_name, earlier_columns, name, columns[column].suffix) {
104 return true;
105 }
106 column += 1;
107 }
108 earlier += 1;
109 }
110 false
111}
112
113const fn names_column(name: &str, columns: &[ColumnSpec], other: &str, suffix: &str) -> bool {
114 let mut column = 0;
115 while column < columns.len() {
116 if same_column(name, columns[column].suffix, other, suffix) {
117 return true;
118 }
119 column += 1;
120 }
121 false
122}
123
124const fn same_column(left: &str, left_suffix: &str, right: &str, right_suffix: &str) -> bool {
125 let length = left.len() + left_suffix.len();
126 if length != right.len() + right_suffix.len() {
127 return false;
128 }
129 let mut index = 0;
130 while index < length {
131 if byte_at(left, left_suffix, index) != byte_at(right, right_suffix, index) {
132 return false;
133 }
134 index += 1;
135 }
136 true
137}
138
139const fn byte_at(name: &str, suffix: &str, index: usize) -> u8 {
140 if index < name.len() {
141 return name.as_bytes()[index];
142 }
143 suffix.as_bytes()[index - name.len()]
144}
145
146pub trait Cast: Sized {
147 const COLUMNS: &'static [ColumnSpec];
148 const NULLABLE: bool = false;
149 const SENSITIVE: bool = false;
151 const MOVES: Option<Moves<Self>> = None;
153 const HOLDS_STATES: bool = Self::MOVES.is_some();
155
156 fn encode(&self, out: &mut Vec<SqlValue>);
157
158 fn decode(row: &mut dyn ColumnReader) -> Result<Self>;
159
160 fn refuse_unlisted_move(field: &str, stored: &Self, current: &Self) -> Result<()> {
161 crate::moves::refuse_unlisted(field, stored, current)
162 }
163}
164
165pub trait ColumnReader {
167 fn remaining(&self) -> usize;
168
169 fn is_null_at(&self, ahead: usize) -> Result<bool>;
171
172 fn is_null(&self) -> Result<bool> {
173 self.is_null_at(0)
174 }
175
176 fn skip(&mut self) -> Result<()>;
177
178 fn bool(&mut self) -> Result<bool>;
179
180 fn int(&mut self) -> Result<i64>;
181
182 fn float(&mut self) -> Result<f64>;
183
184 fn text(&mut self) -> Result<&str>;
185
186 fn bytes(&mut self) -> Result<&[u8]>;
187
188 fn uuid(&mut self) -> Result<Uuid>;
189
190 fn timestamp(&mut self) -> Result<Timestamp>;
191
192 fn date(&mut self) -> Result<Date>;
193
194 fn date_time(&mut self) -> Result<DateTime>;
195
196 fn json(&mut self) -> Result<serde_json::Value>;
197
198 fn value(&mut self) -> Result<SqlValue>;
199}
200
201pub struct RowReader<'a> {
202 values: std::slice::Iter<'a, SqlValue>,
203}
204
205impl<'a> RowReader<'a> {
206 #[must_use]
207 pub fn new(values: &'a [SqlValue]) -> Self {
208 Self {
209 values: values.iter(),
210 }
211 }
212
213 fn take(&mut self) -> Result<&'a SqlValue> {
214 self.values.next().ok_or_else(short_row)
215 }
216
217 fn read<T>(
218 &mut self,
219 expected: &str,
220 pick: impl FnOnce(&'a SqlValue) -> Option<T>,
221 ) -> Result<T> {
222 let value = self.take()?;
223 pick(value).ok_or_else(|| mismatch(expected, value))
224 }
225}
226
227macro_rules! pick {
228 ($pattern:pat => $value:expr) => {
229 |value| match value {
230 $pattern => Some($value),
231 _ => None,
232 }
233 };
234}
235
236impl ColumnReader for RowReader<'_> {
237 fn remaining(&self) -> usize {
238 self.values.len()
239 }
240
241 fn is_null_at(&self, ahead: usize) -> Result<bool> {
242 let column = self.values.as_slice().get(ahead).ok_or_else(short_row)?;
243 Ok(*column == SqlValue::Null)
244 }
245
246 fn skip(&mut self) -> Result<()> {
247 self.take().map(drop)
248 }
249
250 fn bool(&mut self) -> Result<bool> {
251 self.read("bool", pick!(SqlValue::Bool(value) => *value))
252 }
253
254 fn int(&mut self) -> Result<i64> {
255 self.read("integer", pick!(SqlValue::Int(value) => *value))
256 }
257
258 fn float(&mut self) -> Result<f64> {
259 self.read("float", pick!(SqlValue::Float(value) => *value))
260 }
261
262 fn text(&mut self) -> Result<&str> {
263 self.read("text", pick!(SqlValue::Text(value) => value.as_str()))
264 }
265
266 fn bytes(&mut self) -> Result<&[u8]> {
267 self.read("bytes", pick!(SqlValue::Bytes(value) => value.as_slice()))
268 }
269
270 fn uuid(&mut self) -> Result<Uuid> {
271 self.read("uuid", pick!(SqlValue::Uuid(value) => *value))
272 }
273
274 fn timestamp(&mut self) -> Result<Timestamp> {
275 self.read("timestamp", pick!(SqlValue::Timestamp(value) => *value))
276 }
277
278 fn date(&mut self) -> Result<Date> {
279 self.read("date", pick!(SqlValue::Date(value) => *value))
280 }
281
282 fn date_time(&mut self) -> Result<DateTime> {
283 self.read("date-time", pick!(SqlValue::DateTime(value) => *value))
284 }
285
286 fn json(&mut self) -> Result<serde_json::Value> {
287 self.read("json", pick!(SqlValue::Json(value) => value.clone()))
288 }
289
290 fn value(&mut self) -> Result<SqlValue> {
291 self.take().cloned()
292 }
293}
294
295fn short_row() -> Error {
296 Error::internal_message("row has fewer columns than expected")
297}
298
299fn mismatch(expected: &str, found: &SqlValue) -> Error {
300 Error::internal_message(format!(
301 "expected a {expected} column, found a {} value",
302 found.kind()
303 ))
304}
305
306macro_rules! scalar_cast {
307 ($type:ty, $sql:ident, |$value:ident| $encode:expr, |$row:ident| $decode:expr) => {
308 impl Cast for $type {
309 const COLUMNS: &'static [ColumnSpec] = &[ColumnSpec::new("", SqlType::$sql)];
310
311 fn encode(&self, out: &mut Vec<SqlValue>) {
312 let $value = self;
313 out.push($encode);
314 }
315
316 fn decode($row: &mut dyn ColumnReader) -> Result<Self> {
317 $decode
318 }
319 }
320 };
321}
322
323scalar_cast!(bool, Bool, |value| SqlValue::Bool(*value), |row| row.bool());
324scalar_cast!(i64, Int, |value| SqlValue::Int(*value), |row| row.int());
325scalar_cast!(i32, Int, |value| SqlValue::Int(i64::from(*value)), |row| {
326 i32::try_from(row.int()?).map_err(|error| Error::internal("integer column out of range", error))
327});
328scalar_cast!(f64, Float, |value| SqlValue::Float(*value), |row| row
329 .float());
330scalar_cast!(String, Text, |value| SqlValue::Text(value.clone()), |row| {
331 row.text().map(str::to_owned)
332});
333scalar_cast!(Uuid, Uuid, |value| SqlValue::Uuid(*value), |row| row.uuid());
334scalar_cast!(
335 Timestamp,
336 Timestamp,
337 |value| SqlValue::Timestamp(*value),
338 |row| row.timestamp()
339);
340scalar_cast!(Date, Date, |value| SqlValue::Date(*value), |row| row.date());
341scalar_cast!(
342 DateTime,
343 DateTime,
344 |value| SqlValue::DateTime(*value),
345 |row| row.date_time()
346);
347
348pub trait Ordered: Cast {}
349
350impl Ordered for i64 {}
351impl Ordered for i32 {}
352impl Ordered for f64 {}
353impl Ordered for String {}
354impl Ordered for Timestamp {}
355impl Ordered for Date {}
356impl Ordered for DateTime {}
357impl<T: Ordered> Ordered for Option<T> {}
358
359impl<T: Cast> Cast for Option<T> {
368 const COLUMNS: &'static [ColumnSpec] = {
369 assert!(
370 !T::NULLABLE,
371 "an `Option` of a nullable value cannot be stored: `Some(None)` would read back as `None`"
372 );
373 T::COLUMNS
374 };
375 const NULLABLE: bool = true;
376 const SENSITIVE: bool = T::SENSITIVE;
377 const MOVES: Option<Moves<Self>> = if T::MOVES.is_some() {
378 Some(|value| value.as_ref().and_then(targets))
379 } else {
380 None
381 };
382 const HOLDS_STATES: bool = T::HOLDS_STATES;
383
384 fn encode(&self, out: &mut Vec<SqlValue>) {
385 match self {
386 Some(value) => value.encode(out),
387 None => out.extend(Self::COLUMNS.iter().map(|_| SqlValue::Null)),
388 }
389 }
390
391 fn decode(row: &mut dyn ColumnReader) -> Result<Self> {
392 for ahead in 0..Self::COLUMNS.len() {
393 if !row.is_null_at(ahead)? {
394 return T::decode(row).map(Some);
395 }
396 }
397 for _ in Self::COLUMNS {
398 row.skip()?;
399 }
400 Ok(None)
401 }
402
403 fn refuse_unlisted_move(field: &str, stored: &Self, current: &Self) -> Result<()> {
404 match (stored, current) {
405 (Some(stored), Some(current)) => T::refuse_unlisted_move(field, stored, current),
406 _ => crate::moves::refuse_unlisted(field, stored, current),
407 }
408 }
409}
410
411#[cfg(test)]
412mod tests {
413 use super::*;
414
415 #[test]
416 fn option_reads_none_from_null_and_some_from_a_value() {
417 let values = [SqlValue::Null, SqlValue::Int(7)];
418 let mut row = RowReader::new(&values);
419
420 assert_eq!(Option::<i64>::decode(&mut row).unwrap(), None);
421 assert_eq!(Option::<i64>::decode(&mut row).unwrap(), Some(7));
422 }
423
424 #[derive(Debug, PartialEq)]
425 struct Period {
426 from: Option<i64>,
427 until: i64,
428 }
429
430 impl Cast for Period {
431 const COLUMNS: &'static [ColumnSpec] = &[
432 ColumnSpec::new("_from", SqlType::Int),
433 ColumnSpec::new("_until", SqlType::Int),
434 ];
435
436 fn encode(&self, out: &mut Vec<SqlValue>) {
437 self.from.encode(out);
438 self.until.encode(out);
439 }
440
441 fn decode(row: &mut dyn ColumnReader) -> Result<Self> {
442 Ok(Self {
443 from: Cast::decode(row)?,
444 until: Cast::decode(row)?,
445 })
446 }
447 }
448
449 #[test]
450 fn an_option_of_several_columns_is_none_only_when_every_column_is_null() {
451 let period = Some(Period {
452 from: None,
453 until: 9,
454 });
455 let mut values = Vec::new();
456 period.encode(&mut values);
457 None::<Period>.encode(&mut values);
458 values.push(SqlValue::Int(1));
459 let mut row = RowReader::new(&values);
460
461 assert_eq!(Option::<Period>::decode(&mut row).unwrap(), period);
462 assert_eq!(Option::<Period>::decode(&mut row).unwrap(), None);
463 assert_eq!(i64::decode(&mut row).unwrap(), 1);
464 }
465
466 #[test]
467 fn a_value_of_the_wrong_type_is_an_internal_error_that_does_not_quote_it() {
468 let values = [SqlValue::Text("seven".into())];
469
470 let error = i64::decode(&mut RowReader::new(&values)).unwrap_err();
471
472 assert!(matches!(error, Error::Internal { .. }));
473 assert!(!error.to_string().contains("seven"), "{error}");
474 }
475
476 #[test]
477 fn a_short_row_is_refused() {
478 let values = [SqlValue::Int(1)];
479 let mut row = RowReader::new(&values);
480
481 i64::decode(&mut row).unwrap();
482
483 assert!(matches!(i64::decode(&mut row), Err(Error::Internal { .. })));
484 assert!(Option::<i64>::decode(&mut row).is_err());
485 }
486
487 #[test]
488 fn a_field_storing_an_earlier_field_s_column_is_found() {
489 const AMOUNT: &[ColumnSpec] = &[
490 ColumnSpec::new("_minor", SqlType::Int),
491 ColumnSpec::new("_currency", SqlType::Text),
492 ];
493 let fields = [
494 ("total", AMOUNT),
495 ("total_currency", String::COLUMNS),
496 ("totals", String::COLUMNS),
497 ("note", AMOUNT),
498 ("totals", String::COLUMNS),
499 ];
500
501 assert!(stores_earlier_column(&fields, 1));
502 assert!(!stores_earlier_column(&fields, 2));
503 assert!(!stores_earlier_column(&fields, 3));
504 assert!(stores_earlier_column(&fields, 4));
505 }
506
507 #[test]
508 fn an_integer_too_large_for_i32_is_refused() {
509 let values = [SqlValue::Int(i64::MAX)];
510
511 assert!(i32::decode(&mut RowReader::new(&values)).is_err());
512 }
513}