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