Skip to main content

rango_store/
lib.rs

1//! Typed, transaction-safe storage for Rango over SQLite and PostgreSQL.
2//!
3//! The [`Store`] trait is the database face: typed [`Value`]s in, typed [`Row`]s
4//! out. Transactions come from [`Store::deal`] and [`Store::settle`]. A
5//! [`Schema`] describes a table; the `spec` module defines its queries and
6//! field rules. SQLite is the default backend; enable the `postgres` feature
7//! for PostgreSQL.
8#![warn(missing_docs)]
9
10mod engine;
11/// PostgreSQL backend behind the `postgres` feature.
12#[cfg(feature = "postgres")]
13pub mod postgres;
14/// Query and schema specification types shared by all backends.
15pub mod spec;
16/// SQLite backend, enabled by default.
17#[cfg(feature = "sqlite")]
18pub mod sqlite;
19
20pub use spec::{
21    Action, Check, Field, Filter, Key, Link, Mass, Name, Only, Op, Order, Page, Pick, Query, Rule,
22    Run, Schema, Sort, Table, Tree,
23};
24
25use std::{fmt, future::Future, pin::Pin, str::FromStr, sync::Arc};
26
27use chrono::{DateTime, NaiveDate, NaiveDateTime, NaiveTime, Utc};
28use rust_decimal::Decimal;
29use serde::Serialize;
30
31/// A single typed database value.
32#[derive(Clone, Debug, PartialEq)]
33pub enum Value {
34    /// SQL NULL.
35    Null,
36    /// Signed 64-bit integer.
37    Int(i64),
38    /// Double-precision float.
39    Float(f64),
40    /// UTF-8 string.
41    Str(String),
42    /// Boolean.
43    Bool(bool),
44    /// UTC timestamp.
45    DateTime(DateTime<Utc>),
46    /// Exact decimal.
47    Decimal(Decimal),
48}
49
50impl Serialize for Value {
51    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
52    where
53        S: serde::Serializer,
54    {
55        match self {
56            Self::Null => serializer.serialize_none(),
57            Self::Int(value) => serializer.serialize_i64(*value),
58            Self::Float(value) => serializer.serialize_f64(*value),
59            Self::Str(value) => serializer.serialize_str(value),
60            Self::Bool(value) => serializer.serialize_bool(*value),
61            Self::DateTime(at) => serializer.serialize_str(&at.to_rfc3339()),
62            Self::Decimal(value) => serializer.serialize_str(&value.to_string()),
63        }
64    }
65}
66
67impl Value {
68    /// Shorthand for [`Value::Str`] from anything `Into<String>`.
69    pub fn str(s: impl Into<String>) -> Self {
70        Self::Str(s.into())
71    }
72
73    /// Shorthand for [`Value::Int`].
74    pub fn int(v: i64) -> Self {
75        Self::Int(v)
76    }
77
78    /// Shorthand for [`Value::Float`].
79    pub fn float(v: f64) -> Self {
80        Self::Float(v)
81    }
82
83    /// Shorthand for [`Value::Bool`].
84    pub fn bool(v: bool) -> Self {
85        Self::Bool(v)
86    }
87
88    /// Shorthand for [`Value::DateTime`].
89    pub fn datetime(at: DateTime<Utc>) -> Self {
90        Self::DateTime(at)
91    }
92
93    /// Shorthand for [`Value::Decimal`].
94    pub fn decimal(v: Decimal) -> Self {
95        Self::Decimal(v)
96    }
97}
98
99impl From<&String> for Value {
100    fn from(value: &String) -> Self {
101        Self::Str(value.clone())
102    }
103}
104
105impl From<&Option<String>> for Value {
106    fn from(value: &Option<String>) -> Self {
107        match value {
108            Some(value) => Self::Str(value.clone()),
109            None => Self::Null,
110        }
111    }
112}
113
114/// How a column's cells map to [`Value`]s when read back.
115#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
116pub enum ColumnKind {
117    /// Integer affinity ([`Value::Int`]).
118    Integer,
119    /// Floating-point affinity ([`Value::Float`]).
120    Real,
121    /// Text affinity ([`Value::Str`]).
122    Text,
123}
124
125/// A single column: name plus affinity kind.
126#[derive(Clone, Debug, PartialEq, Serialize)]
127pub struct Column {
128    /// Column name.
129    pub name: String,
130    /// Value affinity of the column.
131    pub kind: ColumnKind,
132}
133
134/// One fetched row, cells aligned with the query's columns.
135#[derive(Clone, Debug, PartialEq, Serialize)]
136pub struct Row {
137    /// Cell values, one per selected column.
138    pub values: Vec<Value>,
139}
140
141impl Row {
142    /// Cell at index `i`, if present.
143    pub fn get(&self, i: usize) -> Option<&Value> {
144        self.values.get(i)
145    }
146
147    /// Cell `i` as `i64`, or [`StoreError::Value`].
148    pub fn int(&self, i: usize) -> Result<i64, StoreError> {
149        match self.values.get(i) {
150            Some(Value::Int(value)) => Ok(*value),
151            _ => Err(StoreError::Value(format!("row column {i} not int"))),
152        }
153    }
154
155    /// Cell `i` as `f64`, or [`StoreError::Value`].
156    pub fn float(&self, i: usize) -> Result<f64, StoreError> {
157        match self.values.get(i) {
158            Some(Value::Float(value)) => Ok(*value),
159            _ => Err(StoreError::Value(format!("row column {i} not float"))),
160        }
161    }
162
163    /// Cell `i` as `String`, or [`StoreError::Value`].
164    pub fn str(&self, i: usize) -> Result<String, StoreError> {
165        match self.values.get(i) {
166            Some(Value::Str(value)) => Ok(value.clone()),
167            _ => Err(StoreError::Value(format!("row column {i} not str"))),
168        }
169    }
170
171    /// Cell `i` as `Option<String>`, `None` when null or not text.
172    pub fn opt_str(&self, i: usize) -> Option<String> {
173        match self.values.get(i) {
174            Some(Value::Str(value)) => Some(value.clone()),
175            _ => None,
176        }
177    }
178
179    /// Cell `i` as `bool` (integers count as truthy), or [`StoreError::Value`].
180    pub fn bool(&self, i: usize) -> Result<bool, StoreError> {
181        match self.values.get(i) {
182            Some(Value::Bool(value)) => Ok(*value),
183            Some(Value::Int(value)) => Ok(*value != 0),
184            _ => Err(StoreError::Value(format!("row column {i} not bool"))),
185        }
186    }
187
188    /// Cell `i` as `DateTime<Utc>` (accepts int timestamps and RFC 3339 text), or [`StoreError::Value`].
189    pub fn datetime(&self, i: usize) -> Result<DateTime<Utc>, StoreError> {
190        let bad = || StoreError::Value(format!("row column {i} not datetime"));
191        match self.values.get(i) {
192            Some(Value::DateTime(at)) => Ok(*at),
193            Some(Value::Int(stamp)) => DateTime::from_timestamp(*stamp, 0).ok_or_else(bad),
194            Some(Value::Str(text)) => text.parse::<DateTime<Utc>>().map_err(|_| bad()),
195            _ => Err(bad()),
196        }
197    }
198
199    /// Cell `i` as `Decimal` (accepts text), or [`StoreError::Value`].
200    pub fn decimal(&self, i: usize) -> Result<Decimal, StoreError> {
201        let bad = || StoreError::Value(format!("row column {i} not decimal"));
202        match self.values.get(i) {
203            Some(Value::Decimal(value)) => Ok(*value),
204            Some(Value::Str(text)) => text.parse::<Decimal>().map_err(|_| bad()),
205            _ => Err(bad()),
206        }
207    }
208
209    /// A [`Reader`] over the row's cells.
210    pub fn cells(&self) -> Cells<'_> {
211        Cells::new(&self.values)
212    }
213}
214
215/// A batch of fetched rows.
216pub type Rows = Vec<Row>;
217
218/// Errors surfaced by the store, backends, and value conversion.
219#[derive(Debug)]
220pub enum StoreError {
221    /// SQL or driver error.
222    Sql(String),
223    /// Invalid value, column, or key conversion.
224    Value(String),
225    /// Streaming or channel error.
226    Channel(String),
227    /// I/O error.
228    Io(String),
229    /// Operation not supported by the backend.
230    Unsupported(String),
231}
232
233impl fmt::Display for StoreError {
234    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
235        match self {
236            Self::Sql(msg)
237            | Self::Value(msg)
238            | Self::Channel(msg)
239            | Self::Io(msg)
240            | Self::Unsupported(msg) => write!(f, "{msg}"),
241        }
242    }
243}
244
245impl std::error::Error for StoreError {}
246
247/// UI hint for how a field is rendered.
248#[derive(Clone, Copy, Debug, PartialEq, Eq)]
249pub enum Widget {
250    /// Plain text input.
251    Text,
252    /// Integer input.
253    Int,
254    /// Float input.
255    Flt,
256    /// Money input.
257    Money,
258    /// Date input.
259    Date,
260    /// Checkbox.
261    Check,
262}
263
264/// Sink for typed cell values during serialization.
265pub trait Writer {
266    /// Writes a null cell.
267    fn nothing(&mut self);
268    /// Writes an integer cell.
269    fn int(&mut self, value: i64);
270    /// Writes a float cell.
271    fn flt(&mut self, value: f64);
272    /// Writes a string cell.
273    fn str(&mut self, value: &str);
274}
275
276/// Source of typed cell values during deserialization.
277pub trait Reader {
278    /// Reads a null cell; `true` when the next cell is null.
279    fn nothing(&mut self) -> bool;
280    /// Reads the next cell as `i64`, or [`StoreError::Value`].
281    fn int(&mut self) -> Result<i64, StoreError>;
282    /// Reads the next cell as `f64`, or [`StoreError::Value`].
283    fn flt(&mut self) -> Result<f64, StoreError>;
284    /// Reads the next cell as `String`, or [`StoreError::Value`].
285    fn str(&mut self) -> Result<String, StoreError>;
286}
287
288/// A type that can be stored in a single column.
289pub trait Storable: Send + Sync + 'static {
290    /// Serializes `self` into a [`Writer`].
291    fn put(&self, w: &mut dyn Writer);
292    /// Deserializes a `Self` from a [`Reader`].
293    fn take(r: &mut dyn Reader) -> Result<Self, StoreError>
294    where
295        Self: Sized;
296    /// SQL column type for this value.
297    fn dtype() -> &'static str;
298}
299
300/// Human presentation of a stored type: widget, parsing, and text.
301pub trait Show: Sized {
302    /// Input widget for this type.
303    fn widget() -> Widget;
304    /// Parses raw user input into a `Self`.
305    fn parse(raw: &str) -> Result<Self, StoreError>;
306    /// Renders this value as display text.
307    fn text(&self) -> String;
308}
309
310macro_rules! cells {
311    ($($cell:ty),*) => {$(
312        impl Storable for $cell {
313            fn put(&self, w: &mut dyn Writer) {
314                w.int(*self as i64);
315            }
316
317            fn take(r: &mut dyn Reader) -> Result<Self, StoreError> {
318                Ok(r.int()? as $cell)
319            }
320
321            fn dtype() -> &'static str {
322                "INTEGER"
323            }
324        }
325
326        impl Show for $cell {
327            fn widget() -> Widget {
328                Widget::Int
329            }
330
331            fn parse(raw: &str) -> Result<Self, StoreError> {
332                raw.parse::<$cell>()
333                    .map_err(|_| StoreError::Value(format!("bad int {raw}")))
334            }
335
336            fn text(&self) -> String {
337                self.to_string()
338            }
339        }
340    )*};
341}
342
343cells!(i64, i32, i16, i8, u64, u32, u16, u8, isize, usize);
344
345impl Storable for String {
346    fn put(&self, w: &mut dyn Writer) {
347        w.str(self);
348    }
349
350    fn take(r: &mut dyn Reader) -> Result<Self, StoreError> {
351        r.str()
352    }
353
354    fn dtype() -> &'static str {
355        "TEXT"
356    }
357}
358
359impl Show for String {
360    fn widget() -> Widget {
361        Widget::Text
362    }
363
364    fn parse(raw: &str) -> Result<Self, StoreError> {
365        Ok(raw.into())
366    }
367
368    fn text(&self) -> String {
369        self.clone()
370    }
371}
372
373impl Storable for f64 {
374    fn put(&self, w: &mut dyn Writer) {
375        w.flt(*self);
376    }
377
378    fn take(r: &mut dyn Reader) -> Result<Self, StoreError> {
379        r.flt()
380    }
381
382    fn dtype() -> &'static str {
383        "REAL"
384    }
385}
386
387impl Show for f64 {
388    fn widget() -> Widget {
389        Widget::Flt
390    }
391
392    fn parse(raw: &str) -> Result<Self, StoreError> {
393        raw.parse::<f64>()
394            .map_err(|_| StoreError::Value(format!("bad float {raw}")))
395    }
396
397    fn text(&self) -> String {
398        self.to_string()
399    }
400}
401
402impl Storable for bool {
403    fn put(&self, w: &mut dyn Writer) {
404        w.int(if *self { 1 } else { 0 });
405    }
406
407    fn take(r: &mut dyn Reader) -> Result<Self, StoreError> {
408        Ok(r.int()? != 0)
409    }
410
411    fn dtype() -> &'static str {
412        "INTEGER"
413    }
414}
415
416impl Show for bool {
417    fn widget() -> Widget {
418        Widget::Check
419    }
420
421    fn parse(raw: &str) -> Result<Self, StoreError> {
422        match raw {
423            "1" | "true" | "yes" | "on" => Ok(true),
424            "0" | "false" | "no" | "off" | "" => Ok(false),
425            _ => Err(StoreError::Value(format!("bad bool {raw}"))),
426        }
427    }
428
429    fn text(&self) -> String {
430        self.to_string()
431    }
432}
433
434impl Storable for Decimal {
435    fn put(&self, w: &mut dyn Writer) {
436        w.str(&self.to_string());
437    }
438
439    fn take(r: &mut dyn Reader) -> Result<Self, StoreError> {
440        Decimal::from_str(&r.str()?).map_err(|_| StoreError::Value("bad decimal".into()))
441    }
442
443    fn dtype() -> &'static str {
444        "TEXT"
445    }
446}
447
448impl Show for Decimal {
449    fn widget() -> Widget {
450        Widget::Money
451    }
452
453    fn parse(raw: &str) -> Result<Self, StoreError> {
454        Decimal::from_str(raw).map_err(|_| StoreError::Value(format!("bad decimal {raw}")))
455    }
456
457    fn text(&self) -> String {
458        self.to_string()
459    }
460}
461
462impl Storable for DateTime<Utc> {
463    fn put(&self, w: &mut dyn Writer) {
464        w.int(self.timestamp());
465    }
466
467    fn take(r: &mut dyn Reader) -> Result<Self, StoreError> {
468        DateTime::from_timestamp(r.int()?, 0)
469            .ok_or_else(|| StoreError::Value("bad datetime".into()))
470    }
471
472    fn dtype() -> &'static str {
473        "INTEGER"
474    }
475}
476
477impl Show for DateTime<Utc> {
478    fn widget() -> Widget {
479        Widget::Date
480    }
481
482    fn parse(raw: &str) -> Result<Self, StoreError> {
483        let bad = || StoreError::Value(format!("bad date {raw}"));
484        if let Ok(at) = DateTime::parse_from_rfc3339(raw) {
485            return Ok(at.with_timezone(&Utc));
486        }
487        if let Ok(at) = NaiveDateTime::parse_from_str(raw, "%Y-%m-%dT%H:%M") {
488            return Ok(at.and_utc());
489        }
490        NaiveDate::parse_from_str(raw, "%Y-%m-%d")
491            .map(|day| day.and_time(NaiveTime::MIN).and_utc())
492            .map_err(|_| bad())
493    }
494
495    fn text(&self) -> String {
496        self.format("%Y-%m-%d %H:%M").to_string()
497    }
498}
499
500impl Storable for NaiveDate {
501    fn put(&self, w: &mut dyn Writer) {
502        w.str(&self.format("%Y-%m-%d").to_string());
503    }
504
505    fn take(r: &mut dyn Reader) -> Result<Self, StoreError> {
506        NaiveDate::parse_from_str(&r.str()?, "%Y-%m-%d")
507            .map_err(|_| StoreError::Value("bad date".into()))
508    }
509
510    fn dtype() -> &'static str {
511        "TEXT"
512    }
513}
514
515impl Show for NaiveDate {
516    fn widget() -> Widget {
517        Widget::Date
518    }
519
520    fn parse(raw: &str) -> Result<Self, StoreError> {
521        NaiveDate::parse_from_str(raw, "%Y-%m-%d")
522            .map_err(|_| StoreError::Value(format!("bad date {raw}")))
523    }
524
525    fn text(&self) -> String {
526        self.format("%Y-%m-%d").to_string()
527    }
528}
529
530impl Storable for NaiveTime {
531    fn put(&self, w: &mut dyn Writer) {
532        w.str(&self.format("%H:%M:%S").to_string());
533    }
534
535    fn take(r: &mut dyn Reader) -> Result<Self, StoreError> {
536        NaiveTime::parse_from_str(&r.str()?, "%H:%M:%S")
537            .map_err(|_| StoreError::Value("bad time".into()))
538    }
539
540    fn dtype() -> &'static str {
541        "TEXT"
542    }
543}
544
545impl Show for NaiveTime {
546    fn widget() -> Widget {
547        Widget::Text
548    }
549
550    fn parse(raw: &str) -> Result<Self, StoreError> {
551        NaiveTime::parse_from_str(raw, "%H:%M:%S")
552            .map_err(|_| StoreError::Value(format!("bad time {raw}")))
553    }
554
555    fn text(&self) -> String {
556        self.format("%H:%M").to_string()
557    }
558}
559
560impl<T: Storable> Storable for Option<T> {
561    fn put(&self, w: &mut dyn Writer) {
562        match self {
563            Some(value) => value.put(w),
564            None => w.nothing(),
565        }
566    }
567
568    fn take(r: &mut dyn Reader) -> Result<Self, StoreError> {
569        if r.nothing() {
570            Ok(None)
571        } else {
572            Ok(Some(T::take(r)?))
573        }
574    }
575
576    fn dtype() -> &'static str {
577        T::dtype()
578    }
579}
580
581/// [`Writer`] that captures a single value.
582pub struct Gather {
583    value: Value,
584}
585
586impl Gather {
587    /// An empty gather starts at [`Value::Null`].
588    pub fn new() -> Self {
589        Self { value: Value::Null }
590    }
591
592    /// The captured value.
593    pub fn value(self) -> Value {
594        self.value
595    }
596}
597
598impl Default for Gather {
599    fn default() -> Self {
600        Self::new()
601    }
602}
603
604impl Writer for Gather {
605    fn nothing(&mut self) {
606        self.value = Value::Null;
607    }
608
609    fn int(&mut self, value: i64) {
610        self.value = Value::Int(value);
611    }
612
613    fn flt(&mut self, value: f64) {
614        self.value = Value::Float(value);
615    }
616
617    fn str(&mut self, value: &str) {
618        self.value = Value::Str(value.into());
619    }
620}
621
622/// [`Writer`] that collects every written value in order.
623pub struct Slots {
624    values: Vec<Value>,
625}
626
627impl Slots {
628    /// An empty slot list.
629    pub fn new() -> Self {
630        Self { values: Vec::new() }
631    }
632
633    /// The collected values.
634    pub fn values(self) -> Vec<Value> {
635        self.values
636    }
637}
638
639impl Default for Slots {
640    fn default() -> Self {
641        Self::new()
642    }
643}
644
645impl Writer for Slots {
646    fn nothing(&mut self) {
647        self.values.push(Value::Null);
648    }
649
650    fn int(&mut self, value: i64) {
651        self.values.push(Value::Int(value));
652    }
653
654    fn flt(&mut self, value: f64) {
655        self.values.push(Value::Float(value));
656    }
657
658    fn str(&mut self, value: &str) {
659        self.values.push(Value::Str(value.into()));
660    }
661}
662
663/// [`Reader`] over a slice of values; the cursor advances as cells are read.
664pub struct Cells<'a> {
665    values: &'a [Value],
666    at: usize,
667}
668
669impl<'a> Cells<'a> {
670    /// A cursor over `values` starting at the first cell.
671    pub fn new(values: &'a [Value]) -> Self {
672        Self { values, at: 0 }
673    }
674}
675
676impl Reader for Cells<'_> {
677    fn nothing(&mut self) -> bool {
678        match self.values.get(self.at) {
679            Some(Value::Null) => {
680                self.at += 1;
681                true
682            }
683            None => true,
684            Some(_) => false,
685        }
686    }
687
688    fn int(&mut self) -> Result<i64, StoreError> {
689        let bad = || StoreError::Value(format!("cell {} not int", self.at));
690        match self.values.get(self.at) {
691            Some(Value::Int(value)) => {
692                self.at += 1;
693                Ok(*value)
694            }
695            Some(Value::Bool(value)) => {
696                self.at += 1;
697                Ok(*value as i64)
698            }
699            Some(Value::DateTime(at)) => {
700                self.at += 1;
701                Ok(at.timestamp())
702            }
703            _ => Err(bad()),
704        }
705    }
706
707    fn flt(&mut self) -> Result<f64, StoreError> {
708        let at = self.at;
709        let bad = || StoreError::Value(format!("cell {at} not float"));
710        match self.values.get(at) {
711            Some(Value::Float(value)) => {
712                self.at += 1;
713                Ok(*value)
714            }
715            Some(Value::Int(value)) => {
716                self.at += 1;
717                Ok(*value as f64)
718            }
719            Some(Value::Bool(value)) => {
720                self.at += 1;
721                Ok(*value as i64 as f64)
722            }
723            Some(Value::Decimal(value)) => {
724                self.at += 1;
725                value.to_string().parse::<f64>().map_err(|_| bad())
726            }
727            _ => Err(bad()),
728        }
729    }
730
731    fn str(&mut self) -> Result<String, StoreError> {
732        let at = self.at;
733        let bad = || StoreError::Value(format!("cell {at} not str"));
734        match self.values.get(at) {
735            Some(Value::Str(value)) => {
736                self.at += 1;
737                Ok(value.clone())
738            }
739            Some(Value::Decimal(value)) => {
740                self.at += 1;
741                Ok(value.to_string())
742            }
743            Some(Value::DateTime(value)) => {
744                self.at += 1;
745                Ok(value.to_rfc3339())
746            }
747            _ => Err(bad()),
748        }
749    }
750}
751
752/// A boxed, `Send`, `'a`-bounded future.
753pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
754
755/// A database backend: SQL and typed schema operations over a connection.
756pub trait Store: Send + Sync + 'static {
757    /// Runs `sql` with `params`, returning rows affected.
758    fn execute<'a>(
759        &'a self,
760        sql: &'a str,
761        params: &'a [Value],
762    ) -> BoxFuture<'a, Result<usize, StoreError>>;
763
764    /// Runs `sql` with `params`, decoding rows per `kinds`.
765    fn fetch<'a>(
766        &'a self,
767        sql: &'a str,
768        params: &'a [Value],
769        kinds: &'a [ColumnKind],
770    ) -> BoxFuture<'a, Result<Rows, StoreError>>;
771
772    /// Introspects the columns of `table`.
773    fn columns<'a>(&'a self, table: &'a str) -> BoxFuture<'a, Result<Vec<Column>, StoreError>> {
774        let _ = table;
775        Box::pin(async { Err(StoreError::Unsupported("columns".into())) })
776    }
777
778    /// Fetches the rows of `schema` matching `query`.
779    fn scan_query<'a>(
780        &'a self,
781        schema: &'a Schema,
782        query: &'a Query,
783    ) -> BoxFuture<'a, Result<Rows, StoreError>>;
784
785    /// Counts the rows of `schema` matching `query`.
786    fn total_query<'a>(
787        &'a self,
788        schema: &'a Schema,
789        query: &'a Query,
790    ) -> BoxFuture<'a, Result<usize, StoreError>>;
791
792    /// Creates `schema`'s table if absent.
793    fn define<'a>(&'a self, schema: &'a Schema) -> BoxFuture<'a, Result<(), StoreError>>;
794
795    /// Inserts a batch of `schema` rows, returning each new row's key.
796    fn create<'a>(
797        &'a self,
798        schema: &'a Schema,
799        batch: &'a [Vec<(Name, Value)>],
800    ) -> BoxFuture<'a, Result<Vec<Key>, StoreError>>;
801
802    /// Overwrites `key`'s row of `schema` with `cells`.
803    fn replace<'a>(
804        &'a self,
805        schema: &'a Schema,
806        key: &'a Key,
807        cells: &'a [(Name, Value)],
808    ) -> BoxFuture<'a, Result<(), StoreError>>;
809
810    /// Inserts a `schema` batch, overwriting any matching keys, returning rows touched.
811    fn upsert<'a>(
812        &'a self,
813        schema: &'a Schema,
814        batch: &'a [Vec<(Name, Value)>],
815    ) -> BoxFuture<'a, Result<usize, StoreError>>;
816
817    /// Deletes `key`'s row of `schema`.
818    fn remove<'a>(
819        &'a self,
820        schema: &'a Schema,
821        key: &'a Key,
822    ) -> BoxFuture<'a, Result<(), StoreError>>;
823
824    /// Migrates `schema`'s table to the latest definition, returning statements run.
825    fn evolve<'a>(
826        &'a self,
827        schema: &'a Schema,
828        drop: bool,
829    ) -> BoxFuture<'a, Result<usize, StoreError>>;
830
831    /// Aggregates `schema` rows matching `query` per `query.mass`.
832    fn mass<'a>(
833        &'a self,
834        schema: &'a Schema,
835        query: &'a Query,
836    ) -> BoxFuture<'a, Result<Value, StoreError>>;
837
838    /// Last autoincrement id inserted into `table`.
839    fn last_id<'a>(&'a self, table: &'a str) -> BoxFuture<'a, Result<i64, StoreError>>;
840
841    /// Raw single-row insert into `table` returning its id.
842    fn insert<'a>(
843        &'a self,
844        table: &'a str,
845        columns: &'a [String],
846        values: &'a [Value],
847    ) -> BoxFuture<'a, Result<i64, StoreError>> {
848        Box::pin(async move {
849            let cols = columns
850                .iter()
851                .map(|name| format!("\"{name}\""))
852                .collect::<Vec<_>>()
853                .join(", ");
854            let marks = vec!["?"; columns.len()].join(", ");
855            let sql = format!("INSERT INTO \"{table}\" ({cols}) VALUES ({marks})");
856            self.execute(&sql, values).await?;
857            self.last_id(table).await
858        })
859    }
860
861    /// Opens a transaction as an independent [`Store`].
862    fn deal<'a>(&'a self) -> BoxFuture<'a, Result<Arc<dyn Store>, StoreError>> {
863        Box::pin(async { Err(StoreError::Unsupported("deal".into())) })
864    }
865
866    /// Commits or rolls back the transaction from [`Store::deal`].
867    fn settle(self: Arc<Self>, commit: bool) -> BoxFuture<'static, Result<(), StoreError>> {
868        Box::pin(async move {
869            let _ = commit;
870            Ok(())
871        })
872    }
873}
874
875#[cfg(test)]
876mod tests {
877    use super::*;
878
879    #[test]
880    fn opt() {
881        let row = Row {
882            values: vec![Value::Str("a".into()), Value::Null, Value::Int(3)],
883        };
884        assert_eq!(row.opt_str(0), Some("a".into()));
885        assert_eq!(row.opt_str(1), None);
886        assert_eq!(row.opt_str(2), None);
887        assert_eq!(row.opt_str(9), None);
888        assert_eq!(Value::from(&"a".to_string()), Value::Str("a".into()));
889        assert_eq!(Value::from(&Some("a".to_string())), Value::Str("a".into()));
890        let none: Option<String> = None;
891        assert_eq!(Value::from(&none), Value::Null);
892    }
893
894    #[test]
895    fn json() {
896        assert_eq!(serde_json::to_string(&Value::Null).unwrap(), "null");
897        assert_eq!(serde_json::to_string(&Value::Int(12)).unwrap(), "12");
898        assert_eq!(serde_json::to_string(&Value::Float(1.5)).unwrap(), "1.5");
899        assert_eq!(
900            serde_json::to_string(&Value::Str("hi".into())).unwrap(),
901            "\"hi\""
902        );
903        assert_eq!(serde_json::to_string(&Value::Bool(true)).unwrap(), "true");
904        let at = DateTime::from_timestamp(0, 0).unwrap();
905        assert_eq!(
906            serde_json::to_string(&Value::datetime(at)).unwrap(),
907            "\"1970-01-01T00:00:00+00:00\""
908        );
909        let money = Decimal::from_str_exact("1.50").unwrap();
910        assert_eq!(
911            serde_json::to_string(&Value::decimal(money)).unwrap(),
912            "\"1.50\""
913        );
914        assert_eq!(
915            serde_json::to_string(&ColumnKind::Integer).unwrap(),
916            "\"Integer\""
917        );
918        let column = Column {
919            name: "posts".into(),
920            kind: ColumnKind::Text,
921        };
922        assert_eq!(
923            serde_json::to_string(&column).unwrap(),
924            "{\"name\":\"posts\",\"kind\":\"Text\"}"
925        );
926        let row = Row {
927            values: vec![Value::Int(1), Value::Null],
928        };
929        assert_eq!(
930            serde_json::to_string(&row).unwrap(),
931            "{\"values\":[1,null]}"
932        );
933    }
934}