Skip to main content

rango_store/
sqlite.rs

1use std::{path::Path, sync::Arc};
2
3use sea_orm::{ConnectionTrait, Database, DbBackend};
4
5use crate::engine::{Dialect, Engine, sql_err};
6use crate::{ColumnKind, Name, Schema, Store, StoreError, Value};
7
8/// SQLite backend dialect.
9#[derive(Clone)]
10pub struct Sqlite;
11
12impl Dialect for Sqlite {
13    fn backend(&self) -> DbBackend {
14        DbBackend::Sqlite
15    }
16
17    fn mark(&self, _at: usize) -> String {
18        "?".into()
19    }
20
21    fn sql(&self, dtype: &str) -> &'static str {
22        match dtype {
23            "REAL" => "REAL",
24            "INTEGER" => "INTEGER",
25            _ => "TEXT",
26        }
27    }
28
29    fn create(
30        &self,
31        schema: &Schema,
32        _head: &[(Name, Value)],
33        columns: &str,
34        groups: &str,
35    ) -> String {
36        format!(
37            "INSERT OR REPLACE INTO \"{}\" ({columns}) VALUES {groups}",
38            schema.table
39        )
40    }
41
42    fn introspect(&self, table: &str) -> String {
43        format!("PRAGMA table_info(\"{table}\")")
44    }
45
46    fn name_at(&self) -> usize {
47        1
48    }
49
50    fn type_at(&self) -> usize {
51        2
52    }
53
54    fn reflect(&self, sql: &str) -> ColumnKind {
55        let sql = sql.to_uppercase();
56        if sql.contains("INT") {
57            ColumnKind::Integer
58        } else if sql.contains("CHAR") || sql.contains("CLOB") || sql.contains("TEXT") {
59            ColumnKind::Text
60        } else if sql.contains("REAL") || sql.contains("FLOA") || sql.contains("DOUB") {
61            ColumnKind::Real
62        } else {
63            ColumnKind::Text
64        }
65    }
66
67    fn latest(&self) -> Option<&'static str> {
68        Some("SELECT last_insert_rowid()")
69    }
70
71    fn sum(&self, _kind: ColumnKind, column: &str) -> String {
72        format!("SUM(\"{column}\")")
73    }
74
75    fn mean(&self, _kind: ColumnKind, column: &str) -> String {
76        format!("AVG(\"{column}\")")
77    }
78}
79
80/// Opens a SQLite database at `path`, creating it when absent, as a [`Store`].
81pub async fn open(path: impl AsRef<Path>) -> Result<Arc<dyn Store>, StoreError> {
82    let url = format!("sqlite://{}?mode=rwc", path.as_ref().display());
83    let conn = Database::connect(&url).await.map_err(sql_err)?;
84    Ok(Arc::new(Engine::new(Sqlite, conn)))
85}
86
87/// Opens a SQLite database at `path` in WAL mode (busy timeout 5s) as a [`Store`].
88pub async fn open_wal(path: impl AsRef<Path>) -> Result<Arc<dyn Store>, StoreError> {
89    let url = format!("sqlite://{}?mode=rwc", path.as_ref().display());
90    let conn = Database::connect(&url).await.map_err(sql_err)?;
91    for pragma in [
92        "PRAGMA journal_mode=WAL",
93        "PRAGMA synchronous=NORMAL",
94        "PRAGMA busy_timeout=5000",
95    ] {
96        conn.execute_unprepared(pragma).await.map_err(sql_err)?;
97    }
98    Ok(Arc::new(Engine::new(Sqlite, conn)))
99}
100
101#[cfg(test)]
102mod tests {
103    use super::*;
104    use crate::engine::{alter, ddl, drop, kinds, rename};
105    use crate::{
106        Column, Field, Filter, Key, Mass, Name, Only, Op, Order, Page, Query, Sort, Table, Tree,
107    };
108    use chrono::{DateTime, NaiveDate, NaiveTime, Utc};
109    use rust_decimal::Decimal;
110
111    async fn open_db(name: &str) -> Arc<dyn Store> {
112        let path =
113            std::env::temp_dir().join(format!("rango-test-{}-{}.sqlite", std::process::id(), name));
114        let _ = std::fs::remove_file(&path);
115        open(&path).await.unwrap()
116    }
117
118    async fn store() -> Arc<dyn Store> {
119        open_db("base").await
120    }
121
122    fn schema() -> Schema {
123        Schema {
124            table: Table("w"),
125            fields: vec![
126                Field::id(),
127                Field::str("name"),
128                Field::cell::<i64>("age").optional(),
129                Field::cell::<DateTime<Utc>>("at").optional(),
130            ],
131            rules: Vec::new(),
132        }
133    }
134
135    fn col(name: &str, kind: ColumnKind) -> Column {
136        Column {
137            name: name.to_string(),
138            kind,
139        }
140    }
141
142    fn posts() -> Schema {
143        Schema {
144            table: Table("posts"),
145            fields: vec![Field::id(), Field::str("title")],
146            rules: Vec::new(),
147        }
148    }
149
150    fn keyed() -> Schema {
151        Schema {
152            table: Table("products"),
153            fields: vec![Field::key::<String>("sku"), Field::cell::<Decimal>("price")],
154            rules: Vec::new(),
155        }
156    }
157
158    #[test]
159    fn renders() {
160        assert_eq!(
161            ddl(&Sqlite, &posts()),
162            "CREATE TABLE IF NOT EXISTS \"posts\" (\"id\" INTEGER PRIMARY KEY AUTOINCREMENT, \"title\" TEXT NOT NULL)"
163        );
164        assert_eq!(
165            ddl(&Sqlite, &keyed()),
166            "CREATE TABLE IF NOT EXISTS \"products\" (\"sku\" TEXT PRIMARY KEY, \"price\" TEXT NOT NULL)"
167        );
168        assert_eq!(kinds(&posts()), vec![ColumnKind::Integer, ColumnKind::Text]);
169    }
170
171    #[test]
172    fn alters() {
173        let schema = posts();
174        let have = vec![
175            col("id", ColumnKind::Integer),
176            col("title", ColumnKind::Text),
177        ];
178        assert!(alter(&Sqlite, &schema, &have).is_empty());
179        let missing = vec![col("id", ColumnKind::Integer)];
180        assert_eq!(alter(&Sqlite, &schema, &missing).len(), 1);
181    }
182
183    #[test]
184    fn drops() {
185        let schema = posts();
186        let have = vec![
187            col("id", ColumnKind::Integer),
188            col("title", ColumnKind::Text),
189            col("junk", ColumnKind::Text),
190        ];
191        assert_eq!(drop(&schema, &have).len(), 1);
192        assert!(drop(&schema, &have[..2]).is_empty());
193    }
194
195    #[test]
196    fn renames() {
197        let schema = posts();
198        let have = vec![
199            col("id", ColumnKind::Integer),
200            col("name", ColumnKind::Text),
201        ];
202        assert_eq!(rename(&schema, &have).len(), 1);
203        assert!(alter(&Sqlite, &schema, &have).is_empty());
204        assert!(drop(&schema, &have).is_empty());
205        let mixed = vec![
206            col("id", ColumnKind::Integer),
207            col("name", ColumnKind::Text),
208            col("age", ColumnKind::Integer),
209        ];
210        assert_eq!(rename(&schema, &mixed).len(), 1);
211        assert!(alter(&Sqlite, &schema, &mixed).is_empty());
212        assert_eq!(drop(&schema, &mixed).len(), 1);
213    }
214
215    #[tokio::test]
216    async fn upserts() {
217        let db = open_db("upserts").await;
218        let schema = Schema {
219            table: Table("p"),
220            fields: vec![Field::key::<String>("code"), Field::str("name")],
221            rules: Vec::new(),
222        };
223        db.define(&schema).await.unwrap();
224        let rows = vec![
225            vec![
226                (Name("code"), Value::str("x")),
227                (Name("name"), Value::str("n1")),
228            ],
229            vec![
230                (Name("code"), Value::str("x")),
231                (Name("name"), Value::str("n2")),
232            ],
233            vec![
234                (Name("code"), Value::str("y")),
235                (Name("name"), Value::str("m")),
236            ],
237        ];
238        db.create(&schema, &rows).await.unwrap();
239        let query = |api| async { db.total_query(&schema, &ask(api)).await.unwrap() };
240        assert_eq!(query(Tree::And(Vec::new())).await, 2);
241        let row = db
242            .scan_query(&schema, &ask(Tree::And(Vec::new())))
243            .await
244            .unwrap()
245            .into_iter()
246            .find(|row| row.str(0).unwrap() == "x")
247            .expect("x row");
248        assert_eq!(row.str(1).unwrap(), "n2");
249    }
250
251    #[tokio::test]
252    async fn upsert_merge() {
253        let db = open_db("upsert_merge").await;
254        let schema = Schema {
255            table: Table("p2"),
256            fields: vec![Field::key::<String>("code"), Field::str("name")],
257            rules: Vec::new(),
258        };
259        db.define(&schema).await.unwrap();
260        let one = vec![vec![
261            (Name("code"), Value::str("x")),
262            (Name("name"), Value::str("n1")),
263        ]];
264        assert_eq!(db.upsert(&schema, &one).await.unwrap(), 1);
265        let two = vec![
266            vec![
267                (Name("code"), Value::str("x")),
268                (Name("name"), Value::str("n2")),
269            ],
270            vec![
271                (Name("code"), Value::str("y")),
272                (Name("name"), Value::str("m")),
273            ],
274        ];
275        assert_eq!(db.upsert(&schema, &two).await.unwrap(), 2);
276        let query = |api| async { db.total_query(&schema, &ask(api)).await.unwrap() };
277        assert_eq!(query(Tree::And(Vec::new())).await, 2);
278        let row = db
279            .scan_query(&schema, &ask(Tree::And(Vec::new())))
280            .await
281            .unwrap()
282            .into_iter()
283            .find(|row| row.str(0).unwrap() == "x")
284            .expect("x row");
285        assert_eq!(row.str(1).unwrap(), "n2");
286    }
287
288    fn ask(tree: Tree) -> Query {
289        Query {
290            tree,
291            sort: Vec::new(),
292            page: Page::all(),
293            only: Only::All,
294            mass: None,
295        }
296    }
297
298    fn leaf(field: &'static str, op: Op, value: Value) -> Tree {
299        Tree::Leaf(Filter {
300            field: Name(field),
301            op,
302            value,
303        })
304    }
305
306    async fn seed() -> Arc<dyn Store> {
307        let db = open_db("trees").await;
308        db.define(&schema()).await.unwrap();
309        let day = NaiveDate::from_ymd_opt(2026, 1, 15)
310            .unwrap()
311            .and_time(NaiveTime::MIN)
312            .and_utc();
313        let rows = vec![
314            (Value::str("ann"), Value::int(30), Value::datetime(day)),
315            (Value::str("bob"), Value::int(40), Value::Null),
316            (Value::str("ann"), Value::int(30), Value::datetime(day)),
317            (Value::str("zed"), Value::Null, Value::Null),
318        ];
319        for (name, age, at) in rows {
320            db.execute(
321                "INSERT INTO w (name, age, at) VALUES (?, ?, ?)",
322                &[name, age, at],
323            )
324            .await
325            .unwrap();
326        }
327        db
328    }
329
330    #[tokio::test]
331    async fn trees() {
332        let db = seed().await;
333        let schema = schema();
334        let total = |tree: Tree| {
335            let db = db.clone();
336            let schema = schema.clone();
337            async move { db.total_query(&schema, &ask(tree)).await.unwrap() }
338        };
339        assert_eq!(total(Tree::And(Vec::new())).await, 4);
340        assert_eq!(total(leaf("name", Op::Eq, Value::str("ann"))).await, 2);
341        assert_eq!(total(leaf("name", Op::Like, Value::str("%ann%"))).await, 2);
342        assert_eq!(
343            total(Tree::Or(vec![
344                leaf("name", Op::Eq, Value::str("ann")),
345                leaf("age", Op::Eq, Value::int(40)),
346            ]))
347            .await,
348            3
349        );
350        assert_eq!(
351            total(Tree::Cut(Box::new(leaf("name", Op::Eq, Value::str("ann"))))).await,
352            2
353        );
354        assert_eq!(total(leaf("name", Op::Ne, Value::str("bob"))).await, 3);
355        assert_eq!(total(leaf("age", Op::More, Value::int(30))).await, 1);
356        assert_eq!(total(leaf("age", Op::Less, Value::int(40))).await, 2);
357        assert_eq!(total(leaf("age", Op::Bare, Value::Null)).await, 1);
358        let day = NaiveDate::from_ymd_opt(2026, 1, 15)
359            .unwrap()
360            .and_time(NaiveTime::MIN)
361            .and_utc();
362        assert_eq!(total(leaf("at", Op::At, Value::datetime(day))).await, 2);
363        let rows = db
364            .scan_query(
365                &schema,
366                &Query {
367                    tree: Tree::And(Vec::new()),
368                    sort: vec![Sort {
369                        field: Name("age"),
370                        order: Order::Desc,
371                    }],
372                    page: Page {
373                        count: 2,
374                        offset: 0,
375                    },
376                    only: Only::All,
377                    mass: None,
378                },
379            )
380            .await
381            .unwrap();
382        assert_eq!(rows.len(), 2);
383        assert_eq!(rows[0].str(1).unwrap(), "bob");
384        let rows = db
385            .scan_query(
386                &schema,
387                &Query {
388                    tree: Tree::And(Vec::new()),
389                    sort: Vec::new(),
390                    page: Page::all(),
391                    only: Only::Some(vec![Name("name")]),
392                    mass: None,
393                },
394            )
395            .await
396            .unwrap();
397        assert_eq!(rows.len(), 4);
398        assert!(rows.iter().all(|row| row.values.len() == 1));
399        assert_eq!(rows[0].str(0).unwrap(), "ann");
400        let rows = db
401            .scan_query(
402                &schema,
403                &Query {
404                    tree: Tree::And(Vec::new()),
405                    sort: vec![Sort {
406                        field: Name("name"),
407                        order: Order::Asc,
408                    }],
409                    page: Page::all(),
410                    only: Only::Lone,
411                    mass: None,
412                },
413            )
414            .await
415            .unwrap();
416        assert_eq!(rows.len(), 1);
417        assert_eq!(rows[0].str(1).unwrap(), "ann");
418    }
419
420    #[tokio::test]
421    async fn writes() {
422        let db = open_db("writes").await;
423        let schema = Schema {
424            table: Table("p"),
425            fields: vec![Field::id(), Field::str("name")],
426            rules: Vec::new(),
427        };
428        db.define(&schema).await.unwrap();
429        let keys = db
430            .create(
431                &schema,
432                &[
433                    vec![(Name("name"), Value::str("a"))],
434                    vec![(Name("name"), Value::str("b"))],
435                ],
436            )
437            .await
438            .unwrap();
439        assert_eq!(keys, vec![Key::Int(1), Key::Int(2)]);
440        let keyed = Schema {
441            table: Table("k"),
442            fields: vec![Field::key::<String>("sku"), Field::cell::<i64>("price")],
443            rules: Vec::new(),
444        };
445        db.define(&keyed).await.unwrap();
446        let keys = db
447            .create(
448                &keyed,
449                &[vec![
450                    (Name("sku"), Value::str("s1")),
451                    (Name("price"), Value::int(5)),
452                ]],
453            )
454            .await
455            .unwrap();
456        assert_eq!(keys, vec![Key::Text("s1".into())]);
457        db.replace(&schema, &Key::Int(1), &[(Name("name"), Value::str("a2"))])
458            .await
459            .unwrap();
460        let rows = db
461            .scan_query(&schema, &ask(Tree::And(Vec::new())))
462            .await
463            .unwrap();
464        assert_eq!(rows.len(), 2);
465        assert_eq!(rows[0].str(1).unwrap(), "a2");
466        db.remove(&schema, &Key::Int(2)).await.unwrap();
467        assert_eq!(
468            db.total_query(&schema, &ask(Tree::And(Vec::new())))
469                .await
470                .unwrap(),
471            1
472        );
473    }
474
475    #[tokio::test]
476    async fn evolves() {
477        let db = open_db("evolves").await;
478        let one = Schema {
479            table: Table("e"),
480            fields: vec![Field::id(), Field::str("name")],
481            rules: Vec::new(),
482        };
483        assert_eq!(db.evolve(&one, false).await.unwrap(), 1);
484        assert_eq!(db.evolve(&one, false).await.unwrap(), 1);
485        let two = Schema {
486            table: Table("e"),
487            fields: vec![
488                Field::id(),
489                Field::str("name"),
490                Field::cell::<i64>("age").optional(),
491            ],
492            rules: Vec::new(),
493        };
494        assert_eq!(db.evolve(&two, false).await.unwrap(), 2);
495        let names = db
496            .columns("e")
497            .await
498            .unwrap()
499            .into_iter()
500            .map(|col| col.name)
501            .collect::<Vec<_>>();
502        assert!(names.contains(&"age".to_string()));
503        assert_eq!(db.evolve(&one, false).await.unwrap(), 1);
504        assert_eq!(db.evolve(&one, true).await.unwrap(), 2);
505        let names = db
506            .columns("e")
507            .await
508            .unwrap()
509            .into_iter()
510            .map(|col| col.name)
511            .collect::<Vec<_>>();
512        assert!(!names.contains(&"age".to_string()));
513    }
514
515    #[tokio::test]
516    async fn masses() {
517        let db = open_db("masses").await;
518        let schema = Schema {
519            table: Table("g"),
520            fields: vec![
521                Field::id(),
522                Field::str("name"),
523                Field::cell::<i64>("age").optional(),
524            ],
525            rules: Vec::new(),
526        };
527        db.define(&schema).await.unwrap();
528        for (name, age) in [
529            (Value::str("ann"), Value::int(30)),
530            (Value::str("bob"), Value::int(40)),
531            (Value::str("cid"), Value::Null),
532        ] {
533            db.execute("INSERT INTO g (name, age) VALUES (?, ?)", &[name, age])
534                .await
535                .unwrap();
536        }
537        let mass = |mass: Mass, tree: Tree| {
538            let db = db.clone();
539            let schema = schema.clone();
540            async move {
541                db.mass(
542                    &schema,
543                    &Query {
544                        tree,
545                        sort: Vec::new(),
546                        page: Page::all(),
547                        only: Only::All,
548                        mass: Some(mass),
549                    },
550                )
551                .await
552                .unwrap()
553            }
554        };
555        assert_eq!(
556            mass(Mass::Count, Tree::And(Vec::new())).await,
557            Value::int(3)
558        );
559        assert_eq!(
560            mass(Mass::Count, leaf("name", Op::Eq, Value::str("ann"))).await,
561            Value::int(1)
562        );
563        assert_eq!(
564            mass(Mass::Sum(Name("age")), Tree::And(Vec::new())).await,
565            Value::int(70)
566        );
567        assert_eq!(
568            mass(Mass::Low(Name("age")), Tree::And(Vec::new())).await,
569            Value::int(30)
570        );
571        assert_eq!(
572            mass(Mass::High(Name("age")), Tree::And(Vec::new())).await,
573            Value::int(40)
574        );
575        match mass(Mass::Mean(Name("age")), Tree::And(Vec::new())).await {
576            Value::Float(mean) => assert!((mean - 35.0).abs() < 0.001),
577            _ => panic!("not a mean"),
578        }
579    }
580
581    #[tokio::test]
582    async fn roundtrip() {
583        let db = store().await;
584        db.execute(
585            "CREATE TABLE IF NOT EXISTS t (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER, score REAL, flag INTEGER, at INTEGER)",
586            &[],
587        )
588        .await
589        .unwrap();
590        db.execute(
591            "INSERT INTO t (name, age, score, flag, at) VALUES (?, ?, ?, ?, ?)",
592            &[
593                Value::str("a"),
594                Value::int(1),
595                Value::float(1.5),
596                Value::bool(true),
597                Value::int(1700000001),
598            ],
599        )
600        .await
601        .unwrap();
602        assert_eq!(db.last_id("t").await.unwrap(), 1);
603        let kinds = [
604            ColumnKind::Integer,
605            ColumnKind::Text,
606            ColumnKind::Integer,
607            ColumnKind::Real,
608            ColumnKind::Integer,
609            ColumnKind::Integer,
610        ];
611        let rows = db
612            .fetch("SELECT * FROM t ORDER BY id", &[], &kinds)
613            .await
614            .unwrap();
615        assert_eq!(rows.len(), 1);
616        assert_eq!(rows[0].str(1).unwrap(), "a");
617        assert_eq!(rows[0].int(2).unwrap(), 1);
618        assert_eq!(rows[0].float(3).unwrap(), 1.5);
619        assert!(rows[0].bool(4).unwrap());
620        assert_eq!(rows[0].datetime(5).unwrap().timestamp(), 1700000001);
621        let cols = db.columns("t").await.unwrap();
622        assert_eq!(
623            cols.iter().map(|col| col.name.as_str()).collect::<Vec<_>>(),
624            vec!["id", "name", "age", "score", "flag", "at"]
625        );
626        let count = db
627            .fetch("SELECT COUNT(*) FROM t", &[], &[ColumnKind::Integer])
628            .await
629            .unwrap();
630        assert_eq!(count[0].int(0).unwrap(), 1);
631        let touched = db
632            .execute(
633                "UPDATE t SET age = ? WHERE id = ?",
634                &[Value::int(2), Value::int(1)],
635            )
636            .await
637            .unwrap();
638        assert_eq!(touched, 1);
639        let gone = db
640            .execute("DELETE FROM t WHERE id = ?", &[Value::int(1)])
641            .await
642            .unwrap();
643        assert_eq!(gone, 1);
644    }
645
646    #[tokio::test]
647    async fn missing() {
648        let db = store().await;
649        assert!(db.columns("nope").await.unwrap().is_empty());
650        assert!(db.fetch("SELECT * FROM nope", &[], &[]).await.is_err());
651    }
652
653    #[tokio::test]
654    async fn wipes() {
655        use crate::Action;
656        let db = open_db("wipes").await;
657        let schema = Schema {
658            table: Table("w"),
659            fields: vec![Field::id(), Field::str("name")],
660            rules: Vec::new(),
661        };
662        db.define(&schema).await.unwrap();
663        let keys = db
664            .create(
665                &schema,
666                &[
667                    vec![(Name("name"), Value::str("a"))],
668                    vec![(Name("name"), Value::str("b"))],
669                    vec![(Name("name"), Value::str("c"))],
670                ],
671            )
672            .await
673            .unwrap();
674        let deed = Action::wipe();
675        assert_eq!(deed.name, Name("wipe"));
676        assert!(deed.logged);
677        let msg = (deed.run)(db.clone(), schema.clone(), keys[..2].to_vec())
678            .await
679            .unwrap();
680        assert_eq!(msg, "Deleted 2 rows.");
681        assert_eq!(
682            db.total_query(&schema, &ask(Tree::And(Vec::new())))
683                .await
684                .unwrap(),
685            1
686        );
687        let msg = (deed.run)(db.clone(), schema, keys[2..].to_vec())
688            .await
689            .unwrap();
690        assert_eq!(msg, "Deleted 1 row.");
691    }
692
693    fn deals() -> Schema {
694        Schema {
695            table: Table("deals"),
696            fields: vec![Field::id(), Field::str("name")],
697            rules: Vec::new(),
698        }
699    }
700
701    fn one() -> Vec<Vec<(Name, Value)>> {
702        vec![vec![(Name("name"), Value::str("a"))]]
703    }
704
705    #[tokio::test]
706    async fn commits() {
707        let db = open_db("commits").await;
708        let schema = deals();
709        db.define(&schema).await.unwrap();
710        let tx = db.deal().await.unwrap();
711        tx.create(&schema, &one()).await.unwrap();
712        assert_eq!(
713            tx.total_query(&schema, &ask(Tree::And(Vec::new())))
714                .await
715                .unwrap(),
716            1
717        );
718        tx.settle(true).await.unwrap();
719        assert_eq!(
720            db.total_query(&schema, &ask(Tree::And(Vec::new())))
721                .await
722                .unwrap(),
723            1
724        );
725    }
726
727    #[tokio::test]
728    async fn isolates() {
729        let path =
730            std::env::temp_dir().join(format!("rango-test-{}-isolates.sqlite", std::process::id()));
731        let _ = std::fs::remove_file(&path);
732        let first = open_wal(&path).await.unwrap();
733        let schema = deals();
734        first.define(&schema).await.unwrap();
735        let tx = first.deal().await.unwrap();
736        tx.create(&schema, &one()).await.unwrap();
737        let second = open_wal(&path).await.unwrap();
738        let ask_all = || ask(Tree::And(Vec::new()));
739        assert_eq!(second.total_query(&schema, &ask_all()).await.unwrap(), 0);
740        tx.settle(true).await.unwrap();
741        assert_eq!(second.total_query(&schema, &ask_all()).await.unwrap(), 1);
742    }
743
744    #[tokio::test]
745    async fn aborts() {
746        let db = open_db("aborts").await;
747        let schema = deals();
748        db.define(&schema).await.unwrap();
749        let tx = db.deal().await.unwrap();
750        tx.create(&schema, &one()).await.unwrap();
751        tx.settle(false).await.unwrap();
752        assert_eq!(
753            db.total_query(&schema, &ask(Tree::And(Vec::new())))
754                .await
755                .unwrap(),
756            0
757        );
758    }
759
760    #[tokio::test]
761    async fn nested() {
762        let db = open_db("nested").await;
763        let tx = db.deal().await.unwrap();
764        let err = match tx.deal().await {
765            Err(err) => err,
766            Ok(_) => panic!("nested deal"),
767        };
768        assert!(matches!(err, StoreError::Unsupported(_)));
769        tx.settle(false).await.unwrap();
770    }
771
772    #[tokio::test]
773    async fn abandons() {
774        let path =
775            std::env::temp_dir().join(format!("rango-test-{}-abandons.sqlite", std::process::id()));
776        let _ = std::fs::remove_file(&path);
777        let db = open_wal(&path).await.unwrap();
778        let schema = deals();
779        db.define(&schema).await.unwrap();
780        {
781            let tx = db.deal().await.unwrap();
782            tx.create(&schema, &one()).await.unwrap();
783        }
784        assert_eq!(
785            db.total_query(&schema, &ask(Tree::And(Vec::new())))
786                .await
787                .unwrap(),
788            0
789        );
790    }
791}