Skip to main content

rltbl/
core.rs

1//! # rltbl/relatable
2//!
3//! This is [relatable](crate) (rltbl::[core](crate::core)).
4
5use crate::{self as rltbl};
6use rltbl::{
7    git,
8    select::{Select, SelectField},
9    sql::{
10        self, CachingStrategy, DbActiveConnection, DbConnection, DbKind, DbTransaction, JsonRow,
11        MemoryCacheKey, SqlParam, VecInto as _,
12    },
13    table::{Cell, Column, Datatype, Message, Row, Table},
14};
15
16use anyhow::Result;
17use colored::Colorize;
18use csv::{QuoteStyle, ReaderBuilder, Writer, WriterBuilder};
19use indexmap::IndexMap;
20use lazy_static::lazy_static;
21use minijinja::{path_loader, Environment};
22use rand::{rngs::StdRng, seq::IteratorRandom as _, Rng as _, SeedableRng as _};
23use regex::Regex;
24use serde::{Deserialize, Serialize};
25use serde_json::{json, to_value, Value as JsonValue};
26use sprintf::sprintf;
27use std::{
28    collections::{HashMap, HashSet},
29    fmt::Display,
30    fs::File,
31    io::Write,
32    path::Path as FilePath,
33    str::FromStr,
34    sync::Mutex,
35};
36use tabwriter::TabWriter;
37
38/// Default location of the [relatable](crate) database
39pub static RLTBL_DEFAULT_DB: &str = ".relatable/relatable.db";
40
41/// Used to calculate the _order field when a new row is added to a table that has metacolumns
42pub static NEW_ORDER_MULTIPLIER: usize = 1000;
43
44// The maximum length of the list of previously (un)done commands to fetch when retrieving a user's
45// history.
46pub static HISTORY_MAX: usize = 1000;
47
48/// The default limit on the number of rows to return in a fetch.
49pub static DEFAULT_LIMIT: usize = 100;
50
51/// THe maximum number of rows to return in a fetch.
52pub static MAX_LIMIT: usize = 1000;
53
54lazy_static! {
55    pub static ref CACHE: Mutex<HashMap<MemoryCacheKey, Vec<JsonRow>>> = Mutex::new(HashMap::new());
56}
57
58/// Various errors generated by [relatable](crate)
59#[derive(Debug)]
60pub enum RelatableError {
61    /// An error in the configuration of a ChangeSet:
62    ChangeError(String),
63    /// An error in the [relatable](crate) configuration:
64    ConfigError(String),
65    // /// An error that occurred while reading or writing to a CSV/TSV:
66    // CsvError(csv::Error),
67    /// An error involving the data:
68    DataError(String),
69    // /// An error generated by the underlying database:
70    // DatabaseError(sqlx::Error),
71    /// An error that occurred while interacting with git
72    GitError(String),
73    /// An error generated when the database is missing
74    InitError(String),
75    /// An error from an unsupported format
76    FormatError(String),
77    /// An error in the inputs to a function:
78    InputError(String),
79    /// An error that occurred while reading/writing to stdio:
80    IOError(std::io::Error),
81    /// An error when a record cannot be found.
82    MissingError(String),
83    /// An error that occurred while serialising or deserialising to/from JSON:
84    SerdeJsonError(serde_json::Error),
85    /// An error that occurred while parsing a regex:
86    RegexError(regex::Error),
87    /// An error when a table cannot be found.
88    TableError(String),
89    /// An error that occurred because of a user's action
90    UserError(String),
91}
92
93impl Display for RelatableError {
94    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
95        write!(f, "{:?}", self)
96    }
97}
98
99impl std::error::Error for RelatableError {}
100
101/// The main [rltbl](crate) struct.
102#[derive(Debug)]
103pub struct Relatable {
104    pub root: String,
105    pub readonly: bool,
106    pub connection: DbConnection,
107    // pub minijinja: Environment<'static>,
108    pub default_limit: usize,
109    pub max_limit: usize,
110    pub caching_strategy: CachingStrategy,
111    /// The validation level, which defaults to 'full'
112    pub validation_level: ValidationLevel,
113    pub memory_cache_size: usize,
114}
115
116impl Relatable {
117    /// Connect to a relatable database at the given path, or, if not given, at the location
118    /// indicated by the environment variable RLTBL_CONNECTION, or, if that is not given,
119    /// at [RLTBL_DEFAULT_DB]
120    pub async fn connect(path: Option<&str>, caching_strategy: &CachingStrategy) -> Result<Self> {
121        tracing::trace!("Relatable::connect({path:?}, {caching_strategy:?})");
122        let root = std::env::var("RLTBL_ROOT").unwrap_or_default();
123        // Set up database connection.
124        let readonly = match std::env::var("RLTBL_READONLY") {
125            Ok(value) if value.to_lowercase() != "false" => true,
126            _ => false,
127        };
128        let path = match path {
129            Some(path) => path.to_string(),
130            None => {
131                match std::env::var_os("RLTBL_CONNECTION").and_then(|p| Some(p.into_string())) {
132                    Some(Ok(path)) => path,
133                    _ => RLTBL_DEFAULT_DB.to_string(),
134                }
135            }
136        };
137        if !path.starts_with("postgresql://") {
138            let file = FilePath::new(&path);
139            if !file.exists() {
140                return Err(RelatableError::InitError(
141                    "First create a database with `rltbl init`".into(),
142                )
143                .into());
144            }
145        }
146        let (connection, _) = DbConnection::connect(&path).await?;
147        Ok(Self {
148            root,
149            readonly,
150            connection,
151            // minijinja: env,
152            default_limit: DEFAULT_LIMIT,
153            max_limit: MAX_LIMIT,
154            caching_strategy: *caching_strategy,
155            validation_level: ValidationLevel::Full,
156            memory_cache_size: match caching_strategy {
157                CachingStrategy::Memory(size) => {
158                    let mut cache = CACHE.lock().expect("Could not lock cache");
159                    let current_capacity = cache.capacity();
160                    if current_capacity < *size {
161                        cache.reserve(*size - current_capacity);
162                    }
163                    *size
164                }
165                _ => 0,
166            },
167        })
168    }
169
170    /// Initialize a [relatable](crate) database at the given path, or, if not given, at
171    /// the location indicated by the environment variable RLTBL_CONNECTION, or, if that is not
172    /// given, at [RLTBL_DEFAULT_DB]. Overwrites an existing database if `force` is set to true.
173    pub async fn init(
174        force: &bool,
175        path: Option<&str>,
176        caching_strategy: &CachingStrategy,
177    ) -> Result<Self> {
178        tracing::trace!("Relatable::init({force:?}, {path:?}, {caching_strategy:?})");
179        let path = match path {
180            Some(path) => path.to_string(),
181            None => {
182                match std::env::var_os("RLTBL_CONNECTION").and_then(|p| Some(p.into_string())) {
183                    Some(Ok(path)) => path,
184                    _ => RLTBL_DEFAULT_DB.to_string(),
185                }
186            }
187        };
188        if !path.starts_with("postgresql://") {
189            let dir: &std::path::Path =
190                FilePath::new(&path)
191                    .parent()
192                    .ok_or(RelatableError::InputError(
193                        "Parent path must be defined".to_string(),
194                    ))?;
195            if !dir.exists() {
196                std::fs::create_dir_all(&dir)?;
197                tracing::info!("Created '{dir:?}' directory");
198            }
199            let file = FilePath::new(&path);
200            if file.exists() {
201                if *force {
202                    std::fs::remove_file(&file)?;
203                    tracing::info!("Removed '{file:?}' file");
204                } else {
205                    return Err(RelatableError::InitError(format!(
206                        "File {file:?} already exists. Use --force to overwrite"
207                    ))
208                    .into());
209                }
210            }
211            File::create(&path)?;
212        }
213
214        // Create the meta tables:
215        let rltbl = Relatable::connect(Some(&path), caching_strategy).await?;
216        let ddl = sql::generate_meta_tables_ddl(*force, &rltbl.connection.kind());
217        for sql in ddl {
218            rltbl.connection.query(&sql, None).await?;
219        }
220
221        Ok(rltbl)
222    }
223
224    /// Build a demonstration database. Based on <https://github.com/allisonhorst/palmerpenguins>.
225    pub async fn build_demo(
226        database: Option<&str>,
227        force: &bool,
228        size: usize,
229        caching_strategy: &CachingStrategy,
230    ) -> Result<Self> {
231        tracing::trace!(
232            "Relatable::build_demo({database:?}, {force}, {size}, {caching_strategy:?})"
233        );
234        let rltbl = Relatable::init(force, database.as_deref(), caching_strategy).await?;
235
236        rltbl.create_demo_column_table(force).await?;
237        rltbl.create_demo_datatype_table(force).await?;
238        rltbl.create_penguin_table(None, force, size).await?;
239        rltbl.create_island_table(None, force).await?;
240        Ok(rltbl)
241    }
242
243    /// Create a demonstration table similar to the penguin table, but with the given name,
244    /// and add `size` rows of data to it. Drop the table first if `force` is set.
245    pub async fn create_penguin_table(
246        &self,
247        table: Option<&str>,
248        force: &bool,
249        size: usize,
250    ) -> Result<()> {
251        tracing::trace!("create_penguin_table({self:?}, {table:?}, {force}, {size})");
252        let table = match table {
253            Some(table) => table,
254            None => "penguin",
255        };
256        if *force {
257            if let DbKind::Postgres = self.connection.kind() {
258                self.connection
259                    .query(&format!(r#"DROP TABLE IF EXISTS "{table}" CASCADE"#), None)
260                    .await?;
261            }
262        }
263
264        let sql =
265            format!(r#"INSERT INTO "table" ("table", "path") VALUES ('{table}', '{table}.tsv')"#);
266        self.connection.query(&sql, None).await?;
267
268        let pkey_clause = match self.connection.kind() {
269            DbKind::Sqlite => "INTEGER PRIMARY KEY AUTOINCREMENT",
270            DbKind::Postgres => "SERIAL PRIMARY KEY",
271        };
272
273        // Create the demo table:
274        let sql = format!(
275            r#"CREATE TABLE "{table}" (
276             _id {pkey_clause},
277             _order INTEGER UNIQUE,
278             study_name TEXT,
279             sample_number INTEGER,
280             species TEXT,
281             island TEXT,
282             individual_id TEXT,
283             bill_length REAL,
284             bill_depth NUMERIC,
285             body_mass BIGINT
286           )"#,
287        );
288        self.connection.query(&sql, None).await?;
289
290        let mut ddl = vec![];
291        sql::add_metacolumn_trigger_ddl(&mut ddl, table, &self.connection.kind());
292        if let CachingStrategy::Trigger = self.caching_strategy {
293            sql::add_caching_trigger_ddl(&mut ddl, table, &self.connection.kind());
294        }
295        for sql in ddl {
296            self.connection.query(&sql, None).await?;
297        }
298        // Populate the demo table with random data.
299        let islands = vec!["Biscoe", "Dream", "Torgersen"];
300        let mut rng = StdRng::seed_from_u64(0);
301        let sql_first_part = format!(r#"INSERT INTO "{table}" VALUES "#);
302        let mut sql_value_parts = vec![];
303        let mut sql_param = SqlParam::new(&self.connection.kind());
304        let mut param_values = vec![];
305        let max_params = match self.connection.kind() {
306            DbKind::Sqlite => sql::MAX_PARAMS_SQLITE,
307            DbKind::Postgres => sql::MAX_PARAMS_POSTGRES,
308        };
309        for i in 0..size {
310            if (param_values.len() + 8) >= max_params {
311                let sql = format!(
312                    "{sql_first_part} {sql_value_part}",
313                    sql_value_part = sql_value_parts.join(", ")
314                );
315                let values_so_far = json!(param_values);
316                self.connection.query(&sql, Some(&values_so_far)).await?;
317                tracing::info!(
318                    "{num_rows} rows loaded to table '{table}'",
319                    num_rows = i - 1
320                );
321                param_values.clear();
322                sql_value_parts.clear();
323                sql_param.reset();
324            }
325
326            let id = i + 1;
327            let order = id * NEW_ORDER_MULTIPLIER;
328            let island = islands.iter().choose(&mut rng);
329            let bill_length = rng.gen_range(300..500) as f64 / 10.0;
330            let bill_depth = rng.gen_range(200..400) as f64 / 10.0;
331            let body_mass = rng.gen_range(1000..5000);
332            sql_value_parts.push(format!(
333                "({sql_param_list_1}, 'FAKE123', {lone_sql_param}, 'Pygoscelis adeliae', \
334                 {sql_param_list_2})",
335                sql_param_list_1 = sql_param.get_as_list(2),
336                lone_sql_param = sql_param.next(),
337                sql_param_list_2 = sql_param.get_as_list(5),
338            ));
339            param_values.push(json!(id));
340            param_values.push(json!(order));
341            param_values.push(json!(id));
342            param_values.push(json!(island));
343            param_values.push(json!(format!("N{}A{}", (i / 2) + 1, (i % 2) + 1)));
344            param_values.push(json!(bill_length));
345            param_values.push(json!(bill_depth));
346            param_values.push(json!(body_mass));
347        }
348        if param_values.len() > 0 {
349            let sql = format!(
350                "{sql_first_part} {sql_value_part}",
351                sql_value_part = sql_value_parts.join(", ")
352            );
353            let param_values = json!(param_values);
354            self.connection.query(&sql, Some(&param_values)).await?;
355        }
356
357        Ok(())
358    }
359
360    /// Create a demonstration table similar to the island table, but with the given name,
361    /// and add `size` rows of data to it. Drop the table first if `force` is set.
362    pub async fn create_island_table(&self, table: Option<&str>, force: &bool) -> Result<()> {
363        tracing::trace!("create_island_table({self:?}, {table:?}, {force})");
364        let table = match table {
365            Some(table) => table,
366            None => "island",
367        };
368        if *force {
369            if let DbKind::Postgres = self.connection.kind() {
370                self.connection
371                    .query(&format!(r#"DROP TABLE IF EXISTS "{table}" CASCADE"#), None)
372                    .await?;
373            }
374        }
375
376        let sql =
377            format!(r#"INSERT INTO "table" ("table", "path") VALUES ('{table}', '{table}.tsv')"#);
378        self.connection.query(&sql, None).await?;
379
380        let pkey_clause = match self.connection.kind() {
381            DbKind::Sqlite => "INTEGER PRIMARY KEY AUTOINCREMENT",
382            DbKind::Postgres => "SERIAL PRIMARY KEY",
383        };
384
385        // Create the demo table:
386        let sql = format!(
387            r#"CREATE TABLE "{table}" (
388                 _id {pkey_clause},
389                 _order INTEGER UNIQUE,
390                 island_id INTEGER,
391                 island TEXT
392               )"#,
393        );
394        self.connection.query(&sql, None).await?;
395
396        let mut ddl = vec![];
397        sql::add_metacolumn_trigger_ddl(&mut ddl, table, &self.connection.kind());
398        if let CachingStrategy::Trigger = self.caching_strategy {
399            sql::add_caching_trigger_ddl(&mut ddl, table, &self.connection.kind());
400        }
401        for sql in ddl {
402            self.connection.query(&sql, None).await?;
403        }
404
405        let sql = format!(
406            r#"INSERT INTO "{table}" ("island_id", "island")
407               VALUES (1, 'Torgersen'), (2, 'Biscoe'), (3, 'Dream')"#
408        );
409
410        self.connection.query(&sql, None).await?;
411        Ok(())
412    }
413
414    /// Create the datatype table for the demonstration database
415    pub async fn create_demo_datatype_table(&self, force: &bool) -> Result<()> {
416        tracing::trace!("create_demo_datatype_table({self:?}, {force})");
417        if *force {
418            if let DbKind::Postgres = self.connection.kind() {
419                self.connection
420                    .query(r#"DROP TABLE IF EXISTS "datatype" CASCADE"#, None)
421                    .await?;
422            }
423        }
424
425        let pkey_clause = match self.connection.kind() {
426            DbKind::Sqlite => "INTEGER PRIMARY KEY AUTOINCREMENT",
427            DbKind::Postgres => "SERIAL PRIMARY KEY",
428        };
429
430        let sql = format!(
431            r#"CREATE TABLE "datatype" (
432             _id {pkey_clause},
433             _order INTEGER UNIQUE,
434             "datatype" TEXT,
435             "description" TEXT,
436             "parent" TEXT,
437             "condition" TEXT,
438             "sql_type" TEXT,
439             "format" TEXT
440           )"#,
441        );
442        self.connection.query(&sql, None).await?;
443
444        let mut ddl = vec![];
445        sql::add_metacolumn_trigger_ddl(&mut ddl, "datatype", &self.connection.kind());
446        for sql in ddl {
447            self.connection.query(&sql, None).await?;
448        }
449
450        let datatype_contents = [
451            json!({
452                "datatype": "decimal",
453                "description": "A decimal number",
454                "parent": "",
455                "condition": "",
456                "sql_type": "NUMERIC",
457                "format": "%.1f"
458            }),
459            json!({
460                "datatype": "study_name",
461                "description": "",
462                "parent": "text",
463                "condition": "in(FAKE123, FAKE456)",
464                "sql_type": "",
465                "format": ""
466            }),
467        ]
468        .iter()
469        .map(|content| JsonRow {
470            content: content.as_object().expect("Not a map").clone(),
471        })
472        .collect::<Vec<_>>();
473
474        let mut sql_param_gen = SqlParam::new(&self.connection.kind());
475        let mut param_values = vec![];
476        let mut get_param = |row: &JsonRow, cname: &str| -> Result<String> {
477            match row.get_value(cname)? {
478                JsonValue::Null => Ok("NULL".to_string()),
479                JsonValue::String(value) => {
480                    param_values.push(value.to_string());
481                    Ok(sql_param_gen.next().to_string())
482                }
483                _ => panic!("Invalid value type for datatype table"),
484            }
485        };
486        let mut value_clauses = vec![];
487        for row in &datatype_contents {
488            let s1 = get_param(row, "datatype")?;
489            let s2 = get_param(row, "description")?;
490            let s3 = get_param(row, "parent")?;
491            let s4 = get_param(row, "condition")?;
492            let s5 = get_param(row, "sql_type")?;
493            let s6 = get_param(row, "format")?;
494            value_clauses.push(format!("({s1}, {s2}, {s3}, {s4}, {s5}, {s6})"));
495        }
496
497        let sql = format!(
498            r#"INSERT INTO "datatype"
499               ("datatype", "description", "parent", "condition", "sql_type", "format")
500               VALUES {values}"#,
501            values = value_clauses.join(", ")
502        );
503        let param_values = json!(param_values);
504        self.connection.query(&sql, Some(&param_values)).await?;
505        Ok(())
506    }
507
508    /// Create the column table for the demonstration database
509    pub async fn create_demo_column_table(&self, force: &bool) -> Result<()> {
510        tracing::trace!("create_demo_column_table({self:?}, {force})");
511        if *force {
512            if let DbKind::Postgres = self.connection.kind() {
513                self.connection
514                    .query(r#"DROP TABLE IF EXISTS "column" CASCADE"#, None)
515                    .await?;
516            }
517        }
518
519        let pkey_clause = match self.connection.kind() {
520            DbKind::Sqlite => "INTEGER PRIMARY KEY AUTOINCREMENT",
521            DbKind::Postgres => "SERIAL PRIMARY KEY",
522        };
523
524        let sql = format!(
525            r#"CREATE TABLE "column" (
526             _id {pkey_clause},
527             _order INTEGER UNIQUE,
528             "table" TEXT,
529             "column" TEXT,
530             "label" TEXT,
531             "description" TEXT,
532             "datatype" TEXT,
533             "nulltype" TEXT,
534             "structure" TEXT
535           )"#,
536        );
537        self.connection.query(&sql, None).await?;
538
539        let mut ddl = vec![];
540        sql::add_metacolumn_trigger_ddl(&mut ddl, "column", &self.connection.kind());
541        for sql in ddl {
542            self.connection.query(&sql, None).await?;
543        }
544
545        let column_contents = [
546            json!({
547                "table": "penguin",
548                "column": "study_name",
549                "label": "study name",
550                "datatype": "study_name",
551            }),
552            json!({
553                "table": "penguin",
554                "column": "sample_number",
555                "label": "sample number",
556                "description": "a sample number",
557                "datatype": "integer",
558            }),
559            json!({
560                "table": "penguin",
561                "column": "species",
562                "label": "species",
563                "nulltype": "empty",
564            }),
565            json!({
566                "table": "penguin",
567                "column": "island",
568                "label": "island",
569                "datatype": "text",
570                "structure": "from(island.island)",
571            }),
572            json!({
573                "table": "penguin",
574                "column": "individual_id",
575                "label": "individual id",
576                "nulltype": "empty",
577                "datatype": "text",
578            }),
579            json!({
580                "table": "penguin",
581                "column": "bill_length",
582                "label": "bill length (mm)",
583                "datatype": "decimal",
584            }),
585            json!({
586                "table": "penguin",
587                "column": "bill_depth",
588                "label": "bill depth (mm)",
589                "datatype": "decimal",
590            }),
591            json!({
592                "table": "penguin",
593                "column": "body_mass",
594                "label": "body mass (g)",
595                "nulltype": "empty",
596                "datatype": "integer",
597            }),
598        ]
599        .iter()
600        .map(|content| JsonRow {
601            content: content.as_object().expect("Not a map").clone(),
602        })
603        .collect::<Vec<_>>();
604
605        let mut sql_param_gen = SqlParam::new(&self.connection.kind());
606        let mut param_values = vec![];
607        let mut get_param = |row: &JsonRow, cname: &str| -> Result<String> {
608            match row.get_value(cname).unwrap_or_default() {
609                JsonValue::Null => Ok("NULL".to_string()),
610                JsonValue::String(value) => {
611                    param_values.push(value.to_string());
612                    Ok(sql_param_gen.next().to_string())
613                }
614                _ => panic!("Invalid value type for column table"),
615            }
616        };
617        let mut value_clauses = vec![];
618        for row in &column_contents {
619            let s1 = get_param(row, "table")?;
620            let s2 = get_param(row, "column")?;
621            let s3 = get_param(row, "label")?;
622            let s4 = get_param(row, "description")?;
623            let s5 = get_param(row, "nulltype")?;
624            let s6 = get_param(row, "datatype")?;
625            let s7 = get_param(row, "structure")?;
626            value_clauses.push(format!("({s1}, {s2}, {s3}, {s4}, {s5}, {s6}, {s7})"));
627        }
628
629        let sql = format!(
630            r#"INSERT INTO "column"
631               ("table", "column", "label", "description", "nulltype", "datatype", "structure")
632               VALUES {values}"#,
633            values = value_clauses.join(", ")
634        );
635        let param_values = json!(param_values);
636        self.connection.query(&sql, Some(&param_values)).await?;
637        Ok(())
638    }
639
640    /// Create a tableset for the demonstration database
641    pub async fn create_demo_tableset(&self, force: &bool, size: usize) -> Result<()> {
642        tracing::trace!("create_demo_tableset({self:?}, {force}, {size})");
643        if *force {
644            if let DbKind::Postgres = self.connection.kind() {
645                self.connection
646                    .query(&format!(r#"DROP TABLE IF EXISTS "study" CASCADE"#), None)
647                    .await?;
648                self.connection
649                    .query(&format!(r#"DROP TABLE IF EXISTS "penguin" CASCADE"#), None)
650                    .await?;
651                self.connection
652                    .query(&format!(r#"DROP TABLE IF EXISTS "egg" CASCADE"#), None)
653                    .await?;
654            }
655        }
656
657        let sql = r#"INSERT INTO "table" ('table', 'path') VALUES ('tableset', 'tableset.tsv')"#;
658        self.connection.query(sql, None).await.unwrap();
659
660        // Create the tableset table.
661        let sql = r#"CREATE TABLE tableset (
662              _id INTEGER PRIMARY KEY AUTOINCREMENT,
663              _order INTEGER UNIQUE,
664              tableset TEXT,
665              left_table TEXT,
666              left_column TEXT,
667              right_table TEXT,
668              right_column TEXT
669            )"#;
670        self.connection.query(sql, None).await.unwrap();
671
672        let sql = r#"INSERT INTO "tableset" VALUES
673              (1, 1000, 'combined', NULL, NULL, 'study', 'study_name'),
674              (2, 2000, 'combined', 'study', 'study_name', 'penguin', 'individual_id'),
675              (3, 3000, 'combined', 'penguin', 'individual_id', 'egg', 'egg_id')
676            "#;
677        self.connection.query(sql, None).await.unwrap();
678
679        let sql = r#"INSERT INTO "table" ('table', 'path') VALUES ('study', 'study.tsv')"#;
680        self.connection.query(sql, None).await.unwrap();
681
682        // Create the study table.
683        let sql = r#"CREATE TABLE study (
684              _id INTEGER PRIMARY KEY AUTOINCREMENT,
685              _order INTEGER UNIQUE,
686              study_name TEXT UNIQUE,
687              description TEXT
688            )"#;
689        self.connection.query(sql, None).await.unwrap();
690
691        let sql = r#"INSERT INTO study VALUES
692            (0, 0, 'FAKE123', 'Fake Study 123')"#;
693        self.connection.query(sql, None).await.unwrap();
694
695        self.create_penguin_table(None, force, size).await?;
696
697        let sql = r#"INSERT INTO "table" ('table', 'path') VALUES ('egg', 'egg.tsv')"#;
698        self.connection.query(sql, None).await.unwrap();
699
700        // Create the egg table.
701        let sql = r#"CREATE TABLE egg (
702      _id INTEGER PRIMARY KEY AUTOINCREMENT,
703      _order INTEGER UNIQUE,
704      egg_id TEXT UNIQUE,
705      individual_id TEXT
706    )"#;
707        self.connection.query(sql, None).await.unwrap();
708
709        let sql = r#"INSERT INTO egg VALUES
710        (0, 0, 'E1', 'N1')"#;
711        self.connection.query(sql, None).await.unwrap();
712
713        Ok(())
714    }
715
716    // Drop all of the tables in the table table
717    pub async fn drop_data_tables(&self) -> Result<()> {
718        tracing::trace!("Relatable::drop_data_tables({self:?})");
719        if !Table::table_exists("table", self).await? {
720            tracing::warn!("Can't get list of tables to drop: The table table does not exist");
721        } else {
722            let mut tables = self.get_tables().await?;
723            for (_, table) in tables.iter_mut() {
724                let mut dependent_tables = table.get_dependent_tables(None, &self).await?;
725                dependent_tables.reverse();
726                for table in &mut dependent_tables {
727                    table.drop_table(self).await?;
728                }
729                table.drop_table(self).await?;
730            }
731        }
732        Ok(())
733    }
734
735    // Drop all of the meta tables
736    pub async fn drop_meta_tables(&self) -> Result<()> {
737        tracing::trace!("Relatable::drop_meta_tables({self:?})");
738        for table_name in [
739            "cache", "history", "change", "user", "message", "datatype", "column", "table",
740        ] {
741            let mut table = Table {
742                name: table_name.to_string(),
743                ..Default::default()
744            };
745            table.drop_table(self).await?;
746        }
747        Ok(())
748    }
749
750    // Drop all of the data tables and metatables in the database
751    pub async fn drop_database(&self) -> Result<()> {
752        tracing::trace!("Relatable::drop_database({self:?})");
753        self.drop_data_tables().await?;
754        self.drop_meta_tables().await?;
755        Ok(())
756    }
757
758    /// Render this relatable instance in HTML according to the given template and context
759    pub fn render<T: Serialize>(&self, template: &str, context: T) -> Result<String> {
760        tracing::trace!("Relatable::render({template:?}, context)");
761        // TODO: Optionally we should set up the environment once and store it,
762        // but during development it's very convenient to rebuild every time.
763        let mut env = Environment::new();
764
765        // Load default template strings at compile time.
766        let templates = IndexMap::from([
767            ("page.html", include_str!("templates/page.html")),
768            ("table.html", include_str!("templates/table.html")),
769            ("row_menu.html", include_str!("templates/row_menu.html")),
770            (
771                "column_menu.html",
772                include_str!("templates/column_menu.html"),
773            ),
774            ("cell_menu.html", include_str!("templates/cell_menu.html")),
775        ]);
776
777        // Load templates dynamically if src/templates/ exists,
778        // otherwise use strings from compile time.
779        // TODO: This should be a configuration option.
780        let dir = std::env::var("RLTBL_TEMPLATES").unwrap_or("src/templates/".to_string());
781        if FilePath::new(&dir).is_dir() {
782            env.set_loader(path_loader(dir));
783        };
784        for (name, content) in templates {
785            match env.get_template(name) {
786                Ok(_) => (),
787                Err(_) => env.add_template(name, content).unwrap(),
788            }
789        }
790
791        env.get_template(template)?
792            .render(context)
793            .map_err(|e| e.into())
794    }
795
796    /// Use the given [Select] to fetch data from the database.
797    pub async fn fetch(&self, select: &Select) -> Result<ResultSet> {
798        tracing::trace!("Relatable::fetch({select:?})");
799
800        // Get the table and columns information and use the given select to set the table's view:
801        let mut table = Table::get_table(select.table_name.as_str(), self).await?;
802        if select.view_name == format!("{}_default_view", table.name) || select.view_name == "" {
803            table.set_view(self, "default").await?;
804        } else if select.view_name == format!("{}_text_view", table.name) {
805            table.set_view(self, "text").await?;
806        } else {
807            tracing::warn!(
808                "Unsupported view name: '{}'. Falling back to default view",
809                select.view_name
810            );
811            table.set_view(self, "default").await?;
812        }
813        let mut columns = table.columns.values().cloned().collect::<Vec<_>>();
814
815        // Fetch the data
816        let (statement, parameters) = select.to_sql(&self.connection.kind())?;
817        let json_params = json!(parameters);
818        let json_rows = self
819            .connection
820            .query(&statement, Some(&json_params))
821            .await?;
822        let count = json_rows.len();
823        tracing::info!("Fetched {count} rows");
824
825        // Filter out the table's columns that do not occur in the select:
826        if select.select.len() > 0 {
827            columns = columns
828                .iter()
829                .filter(|column| {
830                    select.select.iter().any(|sel| match sel {
831                        SelectField::Column {
832                            table: select_table,
833                            column: select_column,
834                            ..
835                        } => {
836                            *select_column == column.name
837                                && (select_table == "" || *select_table == table.name)
838                        }
839                        SelectField::Expression { alias, .. } => *alias == column.name,
840                    })
841                })
842                .map(|c| c.clone())
843                .collect();
844        }
845
846        // Return the data:
847        let rows: Vec<Row> = json_rows.clone().vec_into();
848        let total = self.count(&select).await?;
849        Ok(ResultSet {
850            select: select.clone(),
851            statement,
852            parameters,
853            range: Range {
854                count,
855                total,
856                start: (select.offset + 1) as u64,
857                end: (select.offset + count) as u64,
858            },
859            table,
860            columns,
861            rows,
862        })
863    }
864
865    /// Use the given [Select] to fetch data from the database.
866    pub async fn fetch_rows(&self, select: &Select) -> Result<Vec<JsonRow>> {
867        tracing::trace!("Relatable::fetch_rows({select:?})");
868        let (statement, params) = select.to_sql(&self.connection.kind())?;
869        let params = json!(params);
870        self.connection.query(&statement, Some(&params)).await
871    }
872
873    /// Get the number of rows returned by this [Select] using the given caching strategy.
874    pub async fn count(&self, select: &Select) -> Result<u64> {
875        tracing::trace!("Relatable::count({select:?})");
876        let (statement, params) = select.to_sql_count(&self.connection.kind())?;
877        let params = json!(params);
878        let json_rows = self
879            .connection
880            .cache(
881                &statement,
882                Some(&params),
883                &select.get_tables().into_iter().collect(),
884                &self.caching_strategy,
885            )
886            .await?;
887        match json_rows.get(0) {
888            Some(json_row) => json_row.get_unsigned("count"),
889            None => Ok(0),
890        }
891    }
892
893    /// Loads the given table from the given path. When `force` is set to true, deletes any
894    /// existing table of the same name in the database first. When `validate` is set to true,
895    /// Validates each row before loading it. Note that this function may panic.
896    pub async fn load_table(&self, table_name: &str, path: &str, force: bool) {
897        tracing::trace!("Relatable::load_table({table_name:?}, {path:?}, {force})");
898        // Read the records from the given TSV file:
899        let mut rdr = ReaderBuilder::new()
900            .has_headers(false)
901            .delimiter(b'\t')
902            .from_reader(File::open(path).expect(&format!("Unable to open '{path}'")));
903        let mut records = rdr.records();
904
905        // Extract the headers from the first line of the file, which we will need for the CREATE
906        // TABLE statement:
907        let headers = {
908            let headers = match records.next() {
909                None => panic!("'{path}' is empty"),
910                Some(record) => match record {
911                    Err(err) => panic!("Error reading from '{path}': {err}"),
912                    Ok(headers) => headers.iter().map(|s| s.to_string()).collect::<Vec<_>>(),
913                },
914            };
915            for header in &headers {
916                if header.trim().is_empty() {
917                    panic!("One or more of the header fields is empty for table '{table_name}'");
918                }
919            }
920            headers
921        };
922
923        let db_kind = self.connection.kind();
924
925        // Add an entry corresponding to the table being loaded to the table table:
926        if force {
927            // Delete any messages associated with the table and then delete the table:
928            self.delete_message(table_name, None, None, None, None)
929                .await
930                .expect("Error deleting messages");
931
932            let sql = format!(
933                r#"DELETE FROM "table" WHERE "table" = {sql_param}"#,
934                sql_param = SqlParam::new(&db_kind).next(),
935            );
936            let params = json!([table_name]);
937            self.connection
938                .query(&sql, Some(&params))
939                .await
940                .expect("Error deleting from table table");
941        }
942        let sql = format!(
943            r#"INSERT INTO "table" ("table", "path") VALUES ({sql_params})"#,
944            sql_params = SqlParam::new(&db_kind).get_as_list(2)
945        );
946        let params = json!([table_name, path]);
947        self.connection
948            .query(&sql, Some(&params))
949            .await
950            .expect("Error inserting to table table");
951        tracing::debug!("Table {table_name} (path: {path}) added to table table");
952
953        // Initialize a new table struct and collect its columns configuration:
954        let table = {
955            let mut table = Table {
956                name: table_name.to_string(),
957                ..Default::default()
958            };
959            let table_columns = Table::get_column_table_columns(table_name, self)
960                .await
961                .expect(&format!("Error getting columns for table '{table_name}'"));
962            for column_name in headers.iter() {
963                let datatype = match table_columns.get(column_name) {
964                    None => Datatype {
965                        name: "text".to_string(),
966                        ..Default::default()
967                    },
968                    Some(col) => col.datatype.clone(),
969                };
970                let column = Column {
971                    name: column_name.to_string(),
972                    table: table_name.to_string(),
973                    datatype_hierarchy: datatype.get_all_ancestors(self).await.expect(&format!(
974                        "Error getting datatype hierarchy for '{}'",
975                        datatype.name
976                    )),
977                    datatype: datatype,
978                    nulltype: table_columns
979                        .get(column_name)
980                        .and_then(|col| col.nulltype.clone()),
981                    structure: table_columns
982                        .get(column_name)
983                        .and_then(|col| col.structure.clone()),
984                    ..Default::default()
985                };
986                table.columns.insert(column_name.to_string(), column);
987            }
988            table
989        };
990
991        // Generate the SQL statements needed to create the table and execute them:
992        for sql in sql::generate_table_ddl(&table, force, &db_kind, &self.caching_strategy)
993            .expect("Error getting DDL")
994        {
995            self.connection
996                .query(&sql, None)
997                .await
998                .expect("Error creating table");
999        }
1000
1001        // Insert the data into the table:
1002        let mut columns = vec!["_id".to_string(), "_order".to_string()];
1003        columns.append(
1004            &mut headers
1005                .iter()
1006                .map(|k| format!(r#"{k}"#))
1007                .collect::<Vec<_>>(),
1008        );
1009        let columns_line = columns
1010            .iter()
1011            .map(|k| format!(r#""{k}""#))
1012            .collect::<Vec<_>>()
1013            .join(", ");
1014        let mut id: u64 = 1;
1015        let mut order = id * NEW_ORDER_MULTIPLIER as u64;
1016        let sql_first_part = format!(r#"INSERT INTO "{table_name}" ({columns_line}) VALUES "#);
1017        let mut sql_value_parts = vec![];
1018        let mut sql_param_gen = SqlParam::new(&self.connection.kind());
1019        let mut param_values = vec![];
1020        let max_params = match db_kind {
1021            DbKind::Sqlite => sql::MAX_PARAMS_SQLITE,
1022            DbKind::Postgres => sql::MAX_PARAMS_POSTGRES,
1023        };
1024        while let Some(row) = records.next() {
1025            let row = row.expect("Error processing row");
1026            // We add 2 here because of _id and _order:
1027            if (param_values.len() + row.len() + 2) >= max_params {
1028                let sql = format!(
1029                    "{sql_first_part} {sql_value_part}",
1030                    sql_value_part = sql_value_parts.join(", ")
1031                );
1032                let values_so_far = json!(param_values);
1033                self.connection
1034                    .query(&sql, Some(&values_so_far))
1035                    .await
1036                    .expect("Error inserting to table");
1037                tracing::info!(
1038                    "{num_rows} rows loaded to table {table_name}",
1039                    num_rows = id - 1
1040                );
1041                param_values.clear();
1042                sql_value_parts.clear();
1043                sql_param_gen.reset()
1044            }
1045
1046            let mut sql_params = vec![];
1047            param_values.push(json!(id));
1048            sql_params.push(sql_param_gen.next());
1049            param_values.push(json!(order));
1050            sql_params.push(sql_param_gen.next());
1051            let sql_params = {
1052                for (i, value) in row.iter().enumerate() {
1053                    let (column, nulltype) = {
1054                        // We add 2 here because of _id and _order:
1055                        let column = match columns.get(i + 2) {
1056                            Some(column) => column,
1057                            None => panic!("Unable to retrieve column {}", i + 2),
1058                        };
1059                        let nulltype = table
1060                            .columns
1061                            .get(column)
1062                            .expect(&format!("Column '{column}' not found"))
1063                            .nulltype
1064                            .to_owned();
1065                        (column, nulltype)
1066                    };
1067                    match nulltype {
1068                        Some(nulltype) if nulltype.name == "empty" && value == "" => {
1069                            sql_params.push("NULL".to_string());
1070                        }
1071                        _ => {
1072                            if let Some(nulltype) = nulltype {
1073                                if nulltype.name != "empty" {
1074                                    tracing::warn!("Nulltype '{}' not supported", nulltype.name);
1075                                }
1076                            }
1077                            // Use the value to create a cell:
1078                            let mut cell = {
1079                                let value = match serde_json::from_str::<JsonValue>(value) {
1080                                    Ok(JsonValue::Number(num)) => JsonValue::Number(num),
1081                                    _ => json!(value),
1082                                };
1083                                let value = JsonRow::nullify_value(&table, column, &value);
1084                                Cell {
1085                                    text: sql::json_to_string(&value),
1086                                    value: value,
1087                                    ..Default::default()
1088                                }
1089                            };
1090
1091                            // Validate the cell and add any messages to the message table:
1092                            if self.validation_level != ValidationLevel::None {
1093                                cell.validate_sql_type(&table.get_config_for_column(column))
1094                                    .expect("Error validating cell");
1095                                for message in cell.messages.iter() {
1096                                    let (msg_id, msg) = self
1097                                        .add_message(
1098                                            "rltbl",
1099                                            &table.name,
1100                                            id,
1101                                            column,
1102                                            &cell.value,
1103                                            &message.level,
1104                                            &message.rule,
1105                                            &message.message,
1106                                        )
1107                                        .await
1108                                        .expect("Error adding message");
1109                                    tracing::debug!("Added message (ID {msg_id}): {msg:?}");
1110                                }
1111                            }
1112
1113                            // Add the parameter for the value to the SQL insert statement:
1114                            if cell.has_sql_type_error() || cell.value == JsonValue::Null {
1115                                sql_params.push("NULL".to_string());
1116                            } else {
1117                                sql_params.push(sql_param_gen.next());
1118                                param_values.push(cell.value);
1119                            }
1120                        }
1121                    };
1122                }
1123                sql_params.join(", ")
1124            };
1125            // Add two extra SQL_PARAM for _id and _order:
1126            sql_value_parts.push(format!("({sql_params})"));
1127            id += 1;
1128            order += NEW_ORDER_MULTIPLIER as u64;
1129        }
1130        if param_values.len() > 0 {
1131            let sql = format!(
1132                "{sql_first_part} {sql_value_part}",
1133                sql_value_part = sql_value_parts.join(", ")
1134            );
1135            let param_values = json!(param_values);
1136            self.connection
1137                .query(&sql, Some(&param_values))
1138                .await
1139                .expect(&format!("Error inserting to {table_name}"));
1140            tracing::info!(
1141                "{num_rows} rows loaded to table {table_name}",
1142                num_rows = id - 1
1143            );
1144        }
1145
1146        if self.validation_level == ValidationLevel::Full {
1147            self.validate_table(&table)
1148                .await
1149                .expect("Error validating table");
1150            let dependent_tables = table
1151                .get_dependent_tables(None, &self)
1152                .await
1153                .expect("Error getting dependent tables");
1154            for table in &dependent_tables {
1155                tracing::debug!("Validating dependent table '{}'", table.name);
1156                self.validate_structure_for_table(&table)
1157                    .await
1158                    .expect("Error validating table");
1159            }
1160        }
1161
1162        self.commit_to_git().await.expect("Error committing to git");
1163    }
1164
1165    /// Save all of the tables that have entries in the table table to the path indicated for each
1166    /// table there, unless `save_dir` has been given, in which case save them all there instead.
1167    pub async fn save_all(&self, save_dir: Option<&str>) -> Result<()> {
1168        tracing::trace!("Relatable::save_all({save_dir:?})");
1169        let sql = format!(
1170            r#"SELECT "table", "path" FROM "table" WHERE "path" {is_not} NULL"#,
1171            is_not = sql::is_not_clause(&self.connection.kind())
1172        );
1173        let table_rows = self.connection.query(&sql, None).await?;
1174        for table_row in table_rows {
1175            let table_name = table_row.get_string("table")?;
1176            let mut table = Table::get_table(&table_name, self).await?;
1177            table.set_view(self, "text").await?;
1178
1179            let path = match save_dir {
1180                Some(save_dir) => format!("{save_dir}/{table_name}.tsv"),
1181                None => table_row.get_string("path")?,
1182            };
1183            let mut writer = WriterBuilder::new()
1184                .delimiter(b'\t')
1185                .quote_style(QuoteStyle::Never)
1186                .from_path(path)?;
1187            let header_row = self
1188                .fetch_columns(&table_name)
1189                .await?
1190                .iter()
1191                .map(|c| c.name.to_string())
1192                .collect::<Vec<_>>();
1193            writer.write_record(header_row.clone())?;
1194
1195            let sql = format!(
1196                r#"SELECT {columns} FROM "{table_name}_text_view" ORDER BY "_order""#,
1197                columns = header_row
1198                    .iter()
1199                    .map(|c| format!(r#""{c}""#))
1200                    .collect::<Vec<_>>()
1201                    .join(", ")
1202            );
1203            let data_rows = self.connection.query(&sql, None).await?;
1204            for data_row in data_rows {
1205                let values = {
1206                    let mut str_values = vec![];
1207                    for (column, value) in data_row.content.iter() {
1208                        match value {
1209                            JsonValue::String(s) => str_values.push(s.to_string()),
1210                            JsonValue::Number(n) => str_values.push(n.to_string()),
1211                            JsonValue::Null => {
1212                                match &table
1213                                    .columns
1214                                    .get(column)
1215                                    .ok_or(RelatableError::InputError(format!(
1216                                        "Column '{column}' not found"
1217                                    )))?
1218                                    .nulltype
1219                                {
1220                                    // Note that the behaviour for the 'empty' nulltype happens
1221                                    // to be the same as that for no nulltype, but in general
1222                                    // that won't be true for every nulltype.
1223                                    Some(nulltype) if nulltype.name == "empty" => {
1224                                        str_values.push("".to_string());
1225                                    }
1226                                    Some(unsup) => {
1227                                        tracing::warn!("Unsupported nulltype: '{}'", unsup.name);
1228                                        str_values.push("".to_string());
1229                                    }
1230                                    None => {
1231                                        str_values.push("".to_string());
1232                                    }
1233                                };
1234                            }
1235                            _ => {
1236                                return Err(RelatableError::DataError(format!(
1237                                    "Value {value} is not a string, number or NULL"
1238                                ))
1239                                .into());
1240                            }
1241                        }
1242                    }
1243                    str_values
1244                };
1245                writer.write_record(values)?;
1246            }
1247        }
1248
1249        Ok(())
1250    }
1251
1252    /// Save all of the tables and commit the changes to git.
1253    pub async fn commit_to_git(&self) -> Result<()> {
1254        tracing::trace!("Relatable::commit_to_git()");
1255        let author = match std::env::var("RLTBL_GIT_AUTHOR") {
1256            Err(err) => match err {
1257                std::env::VarError::NotPresent => {
1258                    tracing::debug!("Not committing to git because RLTBL_GIT_AUTHOR not defined");
1259                    return Ok(());
1260                }
1261                _ => {
1262                    return Err(RelatableError::InputError(format!(
1263                        "Could not read from the environment: {err}"
1264                    ))
1265                    .into())
1266                }
1267            },
1268            Ok(author) => author,
1269        };
1270        tracing::info!("Committing to git on behalf of RLTBL_GIT_AUTHOR: '{author}'");
1271
1272        // Save all the tables:
1273        self.save_all(None).await?;
1274
1275        // Get the git status:
1276        let status = git::get_status()?;
1277        if status.behind != 0 {
1278            return Err(RelatableError::GitError(
1279                "Refusing to commit to a local repository that is behind the remote".to_string(),
1280            )
1281            .into());
1282        }
1283
1284        // Possibly only amend the last commit, if it is by the same author and performed
1285        // on the same day:
1286        let (last_commit_author, days_ago) = git::get_last_commit_info()?;
1287        let is_amendment = (last_commit_author == author) && (days_ago < 1);
1288
1289        // Stage any modified table files that have a path in the table table:
1290        let sql = format!(
1291            r#"SELECT "path" FROM "table" WHERE "path" {is_not} NULL"#,
1292            is_not = sql::is_not_clause(&self.connection.kind()),
1293        );
1294        let paths = self
1295            .connection
1296            .query(&sql, None)
1297            .await?
1298            .iter()
1299            .map(|row| row.get_string("path").expect("No 'path' found"))
1300            .collect::<Vec<_>>();
1301        git::add(&paths)?;
1302
1303        // Finally, commit to git:
1304        git::commit("commit by rltbl", &author, is_amendment)?;
1305        Ok(())
1306    }
1307
1308    /// Get the details of the last change made by the user from the change table.
1309    fn _get_last_change_for_user(
1310        &self,
1311        tx: &mut DbTransaction<'_>,
1312        user: &str,
1313        action: &ChangeAction,
1314    ) -> Result<Option<(u64, ChangeSet)>> {
1315        tracing::trace!("Relatable::_get_last_change_for_user(tx, {user:?}, {action:?})");
1316        let mut sql_param = SqlParam::new(&tx.kind());
1317        let sql = format!(
1318            r#"SELECT "change_id", "user", "table", "description", "content"
1319               FROM "change"
1320               WHERE "user" = {sql_param_1} AND "action" = {sql_param_2}
1321               ORDER BY "change_id" DESC LIMIT 1"#,
1322            sql_param_1 = sql_param.next(),
1323            sql_param_2 = sql_param.next(),
1324        );
1325        let params = json!([user, format!("{action}")]);
1326        let records = tx.query(&sql, Some(&params))?;
1327        match records.len() {
1328            0 => Ok(None),
1329            _ => {
1330                let change_id = records[0].get_unsigned("change_id")?;
1331                let user = records[0].get_string("user")?;
1332                let table = records[0].get_string("table")?;
1333                let description = records[0].get_string("description")?;
1334                let content = records[0].get_string("content")?;
1335                let changes = Change::many_from_str(&content)?;
1336                Ok(Some((
1337                    change_id,
1338                    ChangeSet {
1339                        action: *action,
1340                        table: table,
1341                        user: user,
1342                        description: description,
1343                        changes: changes,
1344                    },
1345                )))
1346            }
1347        }
1348    }
1349
1350    /// Record the given [ChangeSet] to the change and history tables.
1351    pub fn record_changeset(
1352        &self,
1353        changeset: &ChangeSet,
1354        tx: &mut DbTransaction<'_>,
1355    ) -> Result<()> {
1356        tracing::trace!("Relatable::record_changeset({changeset:?}, tx)");
1357        let user = changeset.user.clone();
1358        let action = changeset.action.to_string();
1359        let table = changeset.table.clone();
1360        let description = changeset.description.clone();
1361
1362        // Begin by getting the current last change_id for this user, which we may need to look
1363        // up previous values of the row's columns in the history table later:
1364        let old_change_id = match &changeset.action {
1365            ChangeAction::Undo => {
1366                let (change_id, _) = self
1367                    ._get_last_change_for_user(tx, &changeset.user, &ChangeAction::Do)?
1368                    .ok_or(RelatableError::DataError(
1369                        "No action for user found".to_string(),
1370                    ))?;
1371                Some(change_id)
1372            }
1373            ChangeAction::Redo => {
1374                let (change_id, _) = self
1375                    ._get_last_change_for_user(tx, &changeset.user, &ChangeAction::Undo)?
1376                    .ok_or(RelatableError::DataError(
1377                        "No undo for user found".to_string(),
1378                    ))?;
1379                Some(change_id)
1380            }
1381            ChangeAction::Do => None,
1382        };
1383
1384        // Now write the current change, which will generate a new last change_id:
1385        let statement = format!(
1386            r#"INSERT INTO change("user", "action", "table", "description", "content")
1387               VALUES ({sql_params})
1388               RETURNING change_id"#,
1389            sql_params = SqlParam::new(&tx.kind()).get_as_list(5)
1390        );
1391        let content = to_value(&changeset.changes).unwrap_or_default();
1392        let params = json!([user, action, table, description, content]);
1393        let change_id = tx.query_value(&statement, Some(&params))?;
1394        let change_id = change_id
1395            .ok_or(RelatableError::DataError(
1396                "Expected a change_id".to_string(),
1397            ))?
1398            .as_u64()
1399            .ok_or(RelatableError::DataError("Expected an integer".to_string()))?;
1400
1401        for change in &changeset.changes {
1402            match change {
1403                Change::Update {
1404                    row,
1405                    column,
1406                    before,
1407                    after,
1408                } => {
1409                    let sql = format!(
1410                        r#"INSERT INTO "history"
1411                           ("change_id", "table", "row", "before", "after")
1412                           VALUES ({sql_params})
1413                           RETURNING "history_id""#,
1414                        sql_params = SqlParam::new(&tx.kind()).get_as_list(5)
1415                    );
1416                    let before = json!({column: before}).to_string();
1417                    let after = json!({column: after}).to_string();
1418                    let params = json!([change_id, table, row, before, after]);
1419                    tx.query_value(&sql, Some(&params))?;
1420                }
1421                Change::Add { row, after: _ } => {
1422                    // If the row has just been newly added, it will be found in the table,
1423                    // otherwise we will use the old_change_id to look for it in the history
1424                    // table:
1425                    let json_row = match Table::_get_row(&table, *row, tx)? {
1426                        Some(json_row) => json_row,
1427                        None => match old_change_id {
1428                            Some(change_id) => {
1429                                let sql = format!(
1430                                    r#"SELECT "before"
1431                                         FROM "history"
1432                                        WHERE "change_id" = {sql_param}"#,
1433                                    sql_param = SqlParam::new(&tx.kind()).next()
1434                                );
1435                                let params = json!([change_id]);
1436                                let before = tx
1437                                    .query_one(&sql, Some(&params))?
1438                                    .ok_or(RelatableError::DataError(format!(
1439                                        "No history row found with change_id {change_id}"
1440                                    )))?
1441                                    .get_string("before")?;
1442                                let before = match serde_json::from_str::<JsonValue>(&before) {
1443                                    Err(err) => return Err(err.into()),
1444                                    Ok(JsonValue::Object(o)) => o,
1445                                    Ok(_) => {
1446                                        return Err(RelatableError::InputError(
1447                                            "The content parameter is not an object".to_string(),
1448                                        )
1449                                        .into());
1450                                    }
1451                                };
1452                                JsonRow { content: before }
1453                            }
1454                            None => {
1455                                return Err(RelatableError::DataError(format!(
1456                                    "Row {row} not found"
1457                                ))
1458                                .into())
1459                            }
1460                        },
1461                    };
1462                    let sql = format!(
1463                        r#"INSERT INTO "history"
1464                           ("change_id", "table", "row", "after")
1465                           VALUES ({sql_params})
1466                           RETURNING "history_id""#,
1467                        sql_params = SqlParam::new(&tx.kind()).get_as_list(4)
1468                    );
1469                    let json_row_str = json!(json_row.content).to_string();
1470                    let params = json!([change_id, table, row, json_row_str]);
1471                    tx.query_value(&sql, Some(&params))?;
1472                }
1473                Change::Move {
1474                    row,
1475                    from_after: _,
1476                    to_after: _,
1477                } => {
1478                    let sql = format!(
1479                        r#"INSERT INTO "history"
1480                           ("change_id", "table", "row")
1481                           VALUES ({sql_params})
1482                           RETURNING "history_id""#,
1483                        sql_params = SqlParam::new(&tx.kind()).get_as_list(3)
1484                    );
1485                    let params = json!([change_id, table, row]);
1486                    tx.query_value(&sql, Some(&params))?;
1487                }
1488                Change::Delete { row, after: _ } => {
1489                    let json_row = match Table::_get_row(&table, *row, tx)? {
1490                        Some(json_row) => json_row,
1491                        None => {
1492                            // It must be there since we supposedly just added it, so if it is
1493                            // not found return an error.
1494                            return Err(
1495                                RelatableError::DataError(format!("Row {row} not found")).into()
1496                            );
1497                        }
1498                    };
1499                    let sql = format!(
1500                        r#"INSERT INTO "history"
1501                           ("change_id", "table", "row", "before")
1502                           VALUES ({sql_params})
1503                           RETURNING "history_id""#,
1504                        sql_params = SqlParam::new(&tx.kind()).get_as_list(4)
1505                    );
1506                    let json_row_str = json!(json_row.content).to_string();
1507                    let params = json!([change_id, table, row, json_row_str]);
1508                    tx.query_value(&sql, Some(&params))?;
1509                }
1510            };
1511        }
1512
1513        // Possibly delete dirty entries from the cache in accordance with our caching strategy:
1514        match self.caching_strategy {
1515            // Trigger has the same behaviour as None here, since the database will be triggering
1516            // this step automatically every time the table is edited in that case.
1517            CachingStrategy::None | CachingStrategy::Trigger => (),
1518            CachingStrategy::Memory(_) => self.clear_mem_cache(&table),
1519            CachingStrategy::TruncateAll => Relatable::clear_cache(tx, None)?,
1520            CachingStrategy::Truncate => Relatable::clear_cache(tx, Some(&table))?,
1521        };
1522
1523        Ok(())
1524    }
1525
1526    /// Get information about the given user from the database and return it as an [Account]. If
1527    /// there is no user with the given username, return a default Account.
1528    pub async fn get_user(&self, username: &str) -> Account {
1529        tracing::trace!("Relatable::get_user({username:?})");
1530        let statement = format!(
1531            r#"SELECT "name", "color", "cursor", "datetime"
1532               FROM "user" WHERE name = '{username}' LIMIT 1"#
1533        );
1534        let user = self.connection.query_one(&statement, None).await;
1535        match user {
1536            Ok(user) => match user {
1537                Some(user) => Account {
1538                    name: username.to_string(),
1539                    color: user.get_string("color").expect("No 'color' found"),
1540                },
1541                None => Account {
1542                    ..Default::default()
1543                },
1544            },
1545            Err(err) => {
1546                tracing::warn!("Error while querying user table: '{err}'");
1547                Account {
1548                    ..Default::default()
1549                }
1550            }
1551        }
1552    }
1553
1554    /// Returns a map with information about all of the users who have corresponding records in
1555    /// the user table.
1556    pub async fn get_users(&self) -> Result<IndexMap<String, UserCursor>> {
1557        tracing::trace!("Relatable::get_users()");
1558        let mut users = IndexMap::new();
1559        // let statement = format!(
1560        //     r#"SELECT "name", color", "cursor", "datetime" FROM "user" WHERE cursor IS NOT NULL
1561        //        AND "datetime" >= DATETIME('now', '-10 minutes')"#
1562        // );
1563        let statement = format!(
1564            r#"SELECT "name", "color", "cursor", "datetime"
1565               FROM "user"
1566               WHERE cursor {is_not} NULL"#,
1567            is_not = sql::is_not_clause(&self.connection.kind()),
1568        );
1569        let rows = self.connection.query(&statement, None).await?;
1570        for row in rows {
1571            let name = row.get_string("name")?;
1572            if name.trim() == "" {
1573                continue;
1574            }
1575            users.insert(
1576                name.clone(),
1577                UserCursor {
1578                    name: name.clone(),
1579                    color: row.get_string("color")?,
1580                    cursor: serde_json::from_str(&row.get_string("cursor")?)?,
1581                    datetime: row.get_string("datetime")?,
1582                },
1583            );
1584        }
1585        Ok(users)
1586    }
1587
1588    /// Returns a list of the given table's columns, not including metacolumns
1589    pub async fn fetch_columns(&self, table_name: &str) -> Result<Vec<Column>> {
1590        tracing::trace!("Relatable::fetch_columns({table_name:?})");
1591        let table = Table::get_table(table_name, self).await?;
1592        Ok(table.columns.values().cloned().collect::<Vec<_>>())
1593    }
1594
1595    /// Returns a list of the given table's columns, including metacolumns
1596    pub async fn fetch_all_columns(&self, table_name: &str) -> Result<Vec<Column>> {
1597        tracing::trace!("Relatable::fetch_all_columns({table_name:?})");
1598        let mut conn = self.connection.reconnect()?;
1599        // Begin a transaction:
1600        let mut tx = self.connection.begin(&mut conn).await?;
1601
1602        let columns = {
1603            let (mut normal_columns, meta_columns) =
1604                Table::_collect_column_info(table_name, &mut tx)?;
1605            let mut all_columns = meta_columns;
1606            all_columns.append(&mut normal_columns);
1607            all_columns
1608        };
1609
1610        // Commit the transaction:
1611        tx.commit()?;
1612
1613        Ok(columns)
1614    }
1615
1616    /// Returns a vector of the names of the tables that have entries in the table table
1617    pub async fn list_tables(&self) -> Result<Vec<String>> {
1618        tracing::trace!("Relatable::list_tables({self:?})");
1619        let statement = format!(r#"SELECT "table" FROM "table" ORDER BY _order"#);
1620        let rows = self.connection.query(&statement, None).await?;
1621        rows.iter().map(|row| row.get_string("table")).collect()
1622    }
1623
1624    /// Returns all of the tables that have entries in the table table as a map from table names
1625    /// to Table structs.
1626    pub async fn get_tables(&self) -> Result<IndexMap<String, Table>> {
1627        tracing::trace!("Relatable::get_tables({self:?})");
1628        let mut tables = IndexMap::new();
1629        let statement = format!(
1630            r#"SELECT "_id", "_order", "table", "path",
1631                 (SELECT MAX(change_id)
1632                  FROM "history"
1633                  WHERE "history"."table" = "table"."table"
1634                 ) AS "_change_id"
1635               FROM "table""#
1636        );
1637
1638        let rows = self.connection.query(&statement, None).await?;
1639        for row in rows {
1640            let name = row.get_string("table")?;
1641            tables.insert(
1642                name.clone(),
1643                Table {
1644                    name: name.clone(),
1645                    change_id: row
1646                        .content
1647                        .get("_change_id")
1648                        .and_then(|i| i.as_u64())
1649                        .unwrap_or_default() as u64,
1650                    columns: self
1651                        .fetch_columns(&name)
1652                        .await?
1653                        .into_iter()
1654                        .map(|column| (name.clone(), column))
1655                        .collect::<IndexMap<_, _>>(),
1656                    ..Default::default()
1657                },
1658            );
1659        }
1660        Ok(tables)
1661    }
1662
1663    /// Returns a [Site] corresponding to the given username.
1664    pub async fn get_site(&self, username: &str) -> Site {
1665        tracing::trace!("Relatable::get_site({username:?})");
1666        let mut users = self.get_users().await.unwrap_or_default();
1667        users.shift_remove(username);
1668        Site {
1669            title: "RLTBL".to_string(),
1670            root: self.root.clone(),
1671            editable: !self.readonly,
1672            user: self.get_user(username).await,
1673            users,
1674            tables: self.list_tables().await.unwrap_or_default(),
1675        }
1676    }
1677
1678    /// Updates the cursor field in the user table for the user associated with the given
1679    /// changeset.
1680    pub fn prepare_user_cursor(
1681        &self,
1682        changeset: &ChangeSet,
1683        tx: &mut DbTransaction<'_>,
1684    ) -> Result<()> {
1685        tracing::trace!("Relatable::prepare_user_cursor({changeset:?}, tx)");
1686        // Make sure the user is present in the user table
1687        let user = changeset.user.clone();
1688        let color = random_color::RandomColor::new().to_hex();
1689        let statement = format!(
1690            r#"SELECT 1 FROM "user" WHERE "name" = {sql_param}"#,
1691            sql_param = SqlParam::new(&tx.kind()).next()
1692        );
1693        let params = json!([user]);
1694        if let None = tx.query_value(&statement, Some(&params))? {
1695            let statement = format!(
1696                r#"INSERT INTO "user" ("name", "color") VALUES ({sql_params})"#,
1697                sql_params = SqlParam::new(&tx.kind()).get_as_list(2)
1698            );
1699            let params = json!([user, color]);
1700            tx.query(&statement, Some(&params))?;
1701        }
1702
1703        // Update the user's cursor position.
1704        let mut cursor = changeset.to_cursor()?;
1705        match changeset.action {
1706            ChangeAction::Undo | ChangeAction::Redo => match changeset.changes.first() {
1707                Some(Change::Delete { row, after: _ }) => {
1708                    cursor.row = Table::_get_previous_row_id(&changeset.table, *row, tx)?;
1709                }
1710                _ => (),
1711            },
1712            ChangeAction::Do => (),
1713        };
1714
1715        let mut sql_param = SqlParam::new(&tx.kind());
1716        let statement = format!(
1717            r#"UPDATE "user"
1718               SET "cursor" = {sql_param_1}, "datetime" = CURRENT_TIMESTAMP
1719               WHERE "name" = {sql_param_2}"#,
1720            sql_param_1 = sql_param.next(),
1721            sql_param_2 = sql_param.next(),
1722        );
1723        let params = json!([to_value(cursor).unwrap_or_default(), user]);
1724        tx.query_value(&statement, Some(&params))?;
1725
1726        Ok(())
1727    }
1728
1729    /// Get the last set of changes that can be redone for the given user
1730    pub async fn get_last_redoable_changeset_for_user(
1731        &self,
1732        user: &str,
1733    ) -> Result<Option<(u64, ChangeSet)>> {
1734        tracing::trace!("Relatable::get_last_redoable_changeset_for_user({user:?})");
1735        let history = self.get_user_history(user, Some(1)).await?;
1736        match history.changes_undone_stack.first() {
1737            None => Ok(None),
1738            Some(change) => {
1739                let change_id = change.get_unsigned("change_id")?;
1740                let content = change.get_string("content")?;
1741                let changes = Change::many_from_str(&content)?;
1742                Ok(Some((
1743                    change_id,
1744                    ChangeSet {
1745                        action: ChangeAction::from_str(&change.get_string("action")?)?,
1746                        table: change.get_string("table")?,
1747                        user: change.get_string("user")?,
1748                        description: change.get_string("user")?,
1749                        changes: changes,
1750                    },
1751                )))
1752            }
1753        }
1754    }
1755
1756    /// Get the last set of changes that can be undone for the given user
1757    pub async fn get_last_undoable_changeset_for_user(
1758        &self,
1759        user: &str,
1760    ) -> Result<Option<(u64, ChangeSet)>> {
1761        tracing::trace!("Relatable::get_last_undoable_changeset_for_user({user:?})");
1762        let history = self.get_user_history(user, Some(1)).await?;
1763        match history.changes_done_stack.first() {
1764            None => Ok(None),
1765            Some(change) => {
1766                let change_id = change.get_unsigned("change_id")?;
1767                let content = change.get_string("content")?;
1768                let changes = Change::many_from_str(&content)?;
1769                Ok(Some((
1770                    change_id,
1771                    ChangeSet {
1772                        action: ChangeAction::from_str(&change.get_string("action")?)?,
1773                        table: change.get_string("table")?,
1774                        user: change.get_string("user")?,
1775                        description: change.get_string("user")?,
1776                        changes: changes,
1777                    },
1778                )))
1779            }
1780        }
1781    }
1782
1783    /// Return a [History] for the given user with at most `context` (or [HISTORY_MAX] if this
1784    /// is not given) undoable and/or redoable previous changes.
1785    pub async fn get_user_history(&self, user: &str, context: Option<usize>) -> Result<History> {
1786        tracing::trace!("Relatable::get_user_history({user:?}, {context:?})");
1787        fn content_to_json_row(content: &str) -> Result<JsonRow> {
1788            tracing::debug!("Entering content_to_json_row(content: {content})");
1789            match serde_json::from_str::<JsonValue>(content) {
1790                Ok(content) => match content
1791                    .as_array()
1792                    .and_then(|a| a.first())
1793                    .and_then(|o| o.as_object())
1794                {
1795                    Some(object) => Ok(JsonRow {
1796                        content: object.clone(),
1797                    }),
1798                    None => {
1799                        return Err(RelatableError::InputError(format!(
1800                            "Received invalid or empty content: {content}. Expected a non-empty \
1801                             object array."
1802                        ))
1803                        .into())
1804                    }
1805                },
1806                Err(err) => {
1807                    return Err(
1808                        RelatableError::InputError(format!("Error reading content: {err}")).into(),
1809                    )
1810                }
1811            }
1812        }
1813
1814        fn on_the_same_target(change1: &JsonRow, change2: &JsonRow) -> Result<bool> {
1815            tracing::trace!("Relatable::on_the_same_target({change1:?}, {change2:?})");
1816            let change1 = content_to_json_row(&change1.get_string("content")?)?;
1817            let change2 = content_to_json_row(&change2.get_string("content")?)?;
1818            let row1 = change1.get_unsigned("row")?;
1819            let row2 = change2.get_unsigned("row")?;
1820            if row1 != row2 {
1821                return Ok(false);
1822            }
1823            if let Ok(column1) = change1.get_string("column") {
1824                if let Ok(column2) = change2.get_string("column") {
1825                    return Ok(column1 == column2);
1826                }
1827            }
1828            Ok(true)
1829        }
1830
1831        fn prune_stacks(
1832            changes_done_stack: &Vec<JsonRow>,
1833            changes_undone_stack: &Vec<JsonRow>,
1834        ) -> (Vec<JsonRow>, Vec<JsonRow>) {
1835            tracing::trace!(
1836                "Relatable::prune_stacks({changes_done_stack:?}, {changes_undone_stack:?})"
1837            );
1838            let mut pruned_dones = vec![];
1839            let mut pruned_undones = vec![];
1840            for change in changes_done_stack.iter() {
1841                if !pruned_dones.iter().any(|done: &JsonRow| {
1842                    on_the_same_target(&done, &change).expect("Error looking for a common target")
1843                }) {
1844                    pruned_dones.push(change.clone());
1845                }
1846            }
1847            for change in changes_undone_stack.iter() {
1848                if !pruned_undones.iter().any(|undone: &JsonRow| {
1849                    on_the_same_target(&undone, &change).expect("Error looking for a common target")
1850                }) {
1851                    pruned_undones.push(change.clone());
1852                }
1853            }
1854            (pruned_dones, pruned_undones)
1855        }
1856
1857        // TODO: Think about paging when there are a lot of change records to go through.
1858        let sql = format!(
1859            r#"SELECT "change_id", "user", "table", "description", "action", "content"
1860                 FROM "change"
1861                WHERE "user" = {sql_param}
1862                ORDER BY "change_id" DESC"#,
1863            sql_param = SqlParam::new(&self.connection.kind()).next()
1864        );
1865        let params = json!([user]);
1866        let history = self.connection.query(&sql, Some(&params)).await?;
1867
1868        // Initialize the stacks to be returned and counters:
1869        let mut changes_done_stack = vec![];
1870        let mut changes_undone_stack = vec![];
1871        let mut do_redo_count: usize;
1872        let mut undo_count: usize;
1873
1874        // Begin with the last change that was made:
1875        let (final_change, final_action) = match history.first() {
1876            None => return Ok(History::default()),
1877            Some(final_change) => {
1878                let final_action = ChangeAction::from_str(&final_change.get_string("action")?)?;
1879                (final_change, final_action)
1880            }
1881        };
1882        match final_action {
1883            ChangeAction::Do | ChangeAction::Redo => {
1884                do_redo_count = 1;
1885                undo_count = 0;
1886            }
1887            ChangeAction::Undo => {
1888                do_redo_count = 0;
1889                undo_count = 1;
1890            }
1891        };
1892        let mut change_to_push = final_change;
1893        let mut action_to_push = final_action;
1894        tracing::debug!("Setting the change to push ({action_to_push}) to: {change_to_push:?}");
1895        tracing::debug!(
1896            "The do/redo count is now {do_redo_count}, and the undo count is \
1897             {undo_count}."
1898        );
1899
1900        // For each action, find the point where it began, and then place it onto changes_done_stack
1901        // or changes_undone_stack, as appropriate:
1902        for prior_change in &history[1..] {
1903            let prior_action = ChangeAction::from_str(&prior_change.get_string("action")?)?;
1904            tracing::debug!("The change prior to it is a {prior_action}: {prior_change:?}.");
1905            match action_to_push {
1906                ChangeAction::Do => match prior_action {
1907                    ChangeAction::Do | ChangeAction::Redo => {
1908                        tracing::debug!(
1909                            "Pushing change {cid} to changes_done",
1910                            cid = change_to_push.get_string("change_id")?
1911                        );
1912                        changes_done_stack.push(change_to_push.clone());
1913                        change_to_push = prior_change;
1914                        action_to_push =
1915                            ChangeAction::from_str(&change_to_push.get_string("action")?)?;
1916                        do_redo_count = 1;
1917                        undo_count = 0;
1918                        tracing::debug!(
1919                            "The next change to push is a {action_to_push}: {change_to_push:?}"
1920                        );
1921                    }
1922                    ChangeAction::Undo => {
1923                        tracing::debug!(
1924                            "Pushing change {cid} to changes_done",
1925                            cid = change_to_push.get_string("change_id")?
1926                        );
1927                        changes_done_stack.push(change_to_push.clone());
1928                        change_to_push = prior_change;
1929                        action_to_push =
1930                            ChangeAction::from_str(&change_to_push.get_string("action")?)?;
1931                        do_redo_count = 0;
1932                        undo_count = 1;
1933                        tracing::debug!(
1934                            "The next change to push is a {action_to_push}: {change_to_push:?}"
1935                        );
1936                    }
1937                },
1938                ChangeAction::Undo => match prior_action {
1939                    ChangeAction::Undo => {
1940                        if do_redo_count == 0 {
1941                            tracing::debug!(
1942                                "Pushing change {cid} to changes_undone",
1943                                cid = change_to_push.get_string("change_id")?
1944                            );
1945                            changes_undone_stack.push(change_to_push.clone());
1946                            change_to_push = prior_change;
1947                            action_to_push =
1948                                ChangeAction::from_str(&change_to_push.get_string("action")?)?;
1949                            tracing::debug!(
1950                                "The next change to push is a {action_to_push}: {change_to_push:?}"
1951                            );
1952                            undo_count += 1;
1953                        } else {
1954                            do_redo_count -= 1;
1955                            undo_count += 1;
1956                        }
1957                    }
1958                    ChangeAction::Do | ChangeAction::Redo => {
1959                        if undo_count == 0 {
1960                            tracing::debug!(
1961                                "Pushing change {cid} to changes_undone",
1962                                cid = change_to_push.get_string("change_id")?
1963                            );
1964                            changes_undone_stack.push(change_to_push.clone());
1965                            change_to_push = prior_change;
1966                            action_to_push =
1967                                ChangeAction::from_str(&change_to_push.get_string("action")?)?;
1968                            do_redo_count = 1;
1969                            tracing::debug!(
1970                                "The next change to push is a {action_to_push}: {change_to_push:?}"
1971                            );
1972                        } else {
1973                            do_redo_count += 1;
1974                            undo_count -= 1;
1975                        }
1976                    }
1977                },
1978                ChangeAction::Redo => match prior_action {
1979                    ChangeAction::Redo => {
1980                        if undo_count == 0 {
1981                            tracing::debug!(
1982                                "Pushing change {cid} to changes_done",
1983                                cid = change_to_push.get_string("change_id")?
1984                            );
1985                            changes_done_stack.push(change_to_push.clone());
1986                            change_to_push = prior_change;
1987                            action_to_push =
1988                                ChangeAction::from_str(&change_to_push.get_string("action")?)?;
1989                            tracing::debug!(
1990                                "The next change to push is a {action_to_push}: {change_to_push:?}"
1991                            );
1992                            do_redo_count += 1;
1993                        } else {
1994                            do_redo_count += 1;
1995                            undo_count -= 1;
1996                        }
1997                    }
1998                    ChangeAction::Do => {
1999                        if undo_count == 0 {
2000                            tracing::debug!(
2001                                "Pushing change {cid} to changes_done",
2002                                cid = change_to_push.get_string("change_id")?
2003                            );
2004                            changes_done_stack.push(change_to_push.clone());
2005                            change_to_push = prior_change;
2006                            action_to_push =
2007                                ChangeAction::from_str(&change_to_push.get_string("action")?)?;
2008                            tracing::debug!(
2009                                "The next change to push is a {action_to_push}: {change_to_push:?}"
2010                            );
2011                            // Dos begin anew.
2012                            do_redo_count = 1;
2013                        } else {
2014                            do_redo_count += 1;
2015                            undo_count -= 1;
2016                        }
2017                    }
2018                    ChangeAction::Undo => {
2019                        if do_redo_count == 0 {
2020                            tracing::debug!(
2021                                "Pushing change {cid} to changes_done",
2022                                cid = change_to_push.get_string("change_id")?
2023                            );
2024                            changes_done_stack.push(change_to_push.clone());
2025                            change_to_push = prior_change;
2026                            action_to_push =
2027                                ChangeAction::from_str(&change_to_push.get_string("action")?)?;
2028                            tracing::debug!(
2029                                "The next change to push is a {action_to_push}: {change_to_push:?}"
2030                            );
2031                            undo_count = 1;
2032                        } else {
2033                            do_redo_count -= 1;
2034                            undo_count += 1;
2035                        }
2036                    }
2037                },
2038            };
2039
2040            tracing::debug!(
2041                "Updated the do/redo count to {do_redo_count}, and the undo count to \
2042                 {undo_count}."
2043            );
2044
2045            // Remove duplicate entries from the stacks. These can result when a row is repeatedly
2046            // undone and redone. These are harmless, logically speaking, but they may potentially
2047            // confuse the user if they are included in the output.
2048            (changes_done_stack, changes_undone_stack) =
2049                prune_stacks(&changes_done_stack, &changes_undone_stack);
2050
2051            // Check if we have exceeded the (max) context, and if so, stop looking for more
2052            // actions:
2053            let mut done_len = changes_done_stack.len();
2054            let mut undone_len = changes_undone_stack.len();
2055            match action_to_push {
2056                ChangeAction::Do | ChangeAction::Redo => done_len += 1,
2057                ChangeAction::Undo => undone_len += 1,
2058            };
2059            if let Some(context) = context {
2060                if done_len >= context && undone_len >= context {
2061                    break;
2062                }
2063            } else if done_len >= HISTORY_MAX || undone_len >= HISTORY_MAX {
2064                break;
2065            }
2066        }
2067
2068        // Once we have finished iterating, there will be one action left over to push, which we
2069        // do now:
2070        match action_to_push {
2071            ChangeAction::Do | ChangeAction::Redo => {
2072                tracing::debug!("Pushing the last change to changes_done: {change_to_push:?}");
2073                changes_done_stack.push(change_to_push.clone());
2074            }
2075            ChangeAction::Undo => {
2076                tracing::debug!("Pushing the last change to changes_undone: {change_to_push:?}");
2077                changes_undone_stack.push(change_to_push.clone());
2078            }
2079        };
2080
2081        // Don't return the contents of changes_undone if the last change was a do. Dos can never be
2082        // redone. If the last change was an undo or a redo, the logic will take care of itself and
2083        // it should never be possible to undo or redo inappropriately.
2084        let mut changes_undone_stack = match final_action {
2085            ChangeAction::Do => vec![],
2086            _ => changes_undone_stack,
2087        };
2088
2089        // Prune the stacks one last time, in case by adding the final action we created a situation
2090        // in which one of the stacks contains duplicates:
2091        (changes_done_stack, changes_undone_stack) =
2092            prune_stacks(&changes_done_stack, &changes_undone_stack);
2093
2094        // Similarly, crop for context one last time if a context has been defined:
2095        let history = match context {
2096            None => History {
2097                changes_done_stack,
2098                changes_undone_stack,
2099            },
2100            Some(context) => {
2101                let mut done_len = changes_done_stack.len();
2102                if done_len > context {
2103                    done_len = context;
2104                }
2105                let changes_done_stack = changes_done_stack[..done_len].to_vec();
2106
2107                let mut undone_len = changes_undone_stack.len();
2108                if undone_len > context {
2109                    undone_len = context;
2110                }
2111                let changes_undone_stack = changes_undone_stack[..undone_len].to_vec();
2112                History {
2113                    changes_done_stack,
2114                    changes_undone_stack,
2115                }
2116            }
2117        };
2118        tracing::debug!("Returning history: {history:#?}");
2119        Ok(history)
2120    }
2121
2122    /// Reverse the given changeset in the database
2123    async fn _revert(&self, change_id: u64, changeset: &ChangeSet) -> Result<Option<ChangeSet>> {
2124        tracing::trace!("Relatable::_revert({change_id}, {changeset:?})");
2125        match changeset.changes.first() {
2126            None => Ok(None),
2127            Some(change) => {
2128                if let Change::Update { .. } = change {
2129                    let conn = self.connection.reconnect()?;
2130                    let actual_changes = self._set_values(conn, &changeset).await?;
2131                    Ok(Some(actual_changes))
2132                } else {
2133                    let mut actual_changes = vec![];
2134                    for change in changeset.changes.iter() {
2135                        let conn = self.connection.reconnect()?;
2136                        match change {
2137                            Change::Update { .. } => (), // Change::Update already handled above.
2138                            Change::Add { row, after: _ } => {
2139                                let num_deleted = self
2140                                    ._delete_row(
2141                                        conn,
2142                                        &changeset.action,
2143                                        &changeset.table,
2144                                        &changeset.user,
2145                                        *row,
2146                                    )
2147                                    .await?;
2148                                if num_deleted > 0 {
2149                                    actual_changes.push(change.clone());
2150                                }
2151                            }
2152                            Change::Move {
2153                                row,
2154                                from_after,
2155                                to_after: _,
2156                            } => {
2157                                let new_order = self
2158                                    ._move_and_record_row(
2159                                        conn,
2160                                        &changeset.action,
2161                                        &changeset.table,
2162                                        &changeset.user,
2163                                        *row,
2164                                        *from_after,
2165                                    )
2166                                    .await?;
2167                                if new_order > 0 {
2168                                    actual_changes.push(change.clone());
2169                                }
2170                            }
2171                            Change::Delete { row, after } => {
2172                                // Get the row, as it was before it was deleted, from the history
2173                                // table:
2174                                let sql = format!(
2175                                    r#"SELECT "before" FROM "history"
2176                                       WHERE "change_id" = {sql_param}"#,
2177                                    sql_param = SqlParam::new(&self.connection.kind()).next()
2178                                );
2179                                let params = json!([change_id]);
2180                                let before = self
2181                                    .connection
2182                                    .query_one(&sql, Some(&params))
2183                                    .await?
2184                                    .ok_or(RelatableError::DataError(format!(
2185                                        "No history row found with change_id {change_id}"
2186                                    )))?
2187                                    .get_string("before")?;
2188                                let before = match serde_json::from_str::<JsonValue>(&before) {
2189                                    Err(err) => return Err(err.into()),
2190                                    Ok(JsonValue::Object(o)) => o,
2191                                    Ok(_) => {
2192                                        return Err(RelatableError::InputError(
2193                                            "The content parameter is not an object".to_string(),
2194                                        )
2195                                        .into());
2196                                    }
2197                                };
2198                                let before = JsonRow { content: before };
2199                                tracing::debug!(
2200                                    "Re-adding row '{before}' to table '{}'",
2201                                    changeset.table
2202                                );
2203                                self._add_row(
2204                                    conn,
2205                                    &changeset.action,
2206                                    &changeset.table,
2207                                    &changeset.user,
2208                                    Some(*row),
2209                                    Some(*after),
2210                                    &before,
2211                                )
2212                                .await?;
2213                                actual_changes.push(change.clone());
2214                            }
2215                        };
2216                    }
2217                    Ok(Some(ChangeSet {
2218                        action: changeset.action,
2219                        table: changeset.table.clone(),
2220                        user: changeset.user.clone(),
2221                        description: changeset.description.clone(),
2222                        changes: actual_changes,
2223                    }))
2224                }
2225            }
2226        }
2227    }
2228
2229    /// Undo the last change made by the given user
2230    pub async fn undo(&self, user: &str) -> Result<Option<ChangeSet>> {
2231        tracing::trace!("Relatable::undo({user:?})");
2232        let (change_id, mut changeset) =
2233            match self.get_last_undoable_changeset_for_user(user).await? {
2234                None => {
2235                    tracing::warn!("Nothing to undo for '{user}'");
2236                    return Ok(None);
2237                }
2238                Some(changeset) => changeset,
2239            };
2240        changeset.action = ChangeAction::Undo;
2241        let changeset = self._revert(change_id, &changeset).await?;
2242        if let Some(_) = changeset {
2243            self.commit_to_git().await?;
2244        }
2245        Ok(changeset)
2246    }
2247
2248    /// Redo the last change undone by the given user
2249    pub async fn redo(&self, user: &str) -> Result<Option<ChangeSet>> {
2250        tracing::trace!("Relatable::redo({user:?})");
2251        let (change_id, mut changeset) =
2252            match self.get_last_redoable_changeset_for_user(user).await? {
2253                None => {
2254                    tracing::warn!("Nothing to redo for '{user}'");
2255                    return Ok(None);
2256                }
2257                Some(changeset) => changeset,
2258            };
2259        tracing::debug!("Last redoable action (ID {change_id}) for user {user} was {changeset:?}");
2260        changeset.action = ChangeAction::Redo;
2261        let changeset = self._revert(change_id, &changeset).await?;
2262        if let Some(_) = changeset {
2263            self.commit_to_git().await?;
2264        }
2265        Ok(changeset)
2266    }
2267
2268    /// Update the database using the given [ChangeSet]
2269    async fn _set_values(
2270        &self,
2271        mut conn: Option<DbActiveConnection>,
2272        changeset: &ChangeSet,
2273    ) -> Result<ChangeSet> {
2274        tracing::trace!("Relatable::set_values(conn, {changeset:?})");
2275        // Begin a transaction:
2276        let mut tx = self.connection.begin(&mut conn).await?;
2277
2278        // Update the user cursor
2279        self.prepare_user_cursor(changeset, &mut tx)?;
2280
2281        // Actually make the changes:
2282        let table = Table::_get_table(&changeset.table, &mut tx)?;
2283        let mut actual_changes = vec![];
2284        for change in &changeset.changes {
2285            match change {
2286                Change::Update {
2287                    row,
2288                    column,
2289                    before,
2290                    after,
2291                } => {
2292                    // Delete any existing messages associated with this row and column:
2293                    tracing::debug!(
2294                        "Deleting existing messages for column '{}.{}'",
2295                        table.name,
2296                        column
2297                    );
2298                    self._delete_message(
2299                        &mut tx,
2300                        &table.name,
2301                        Some(*row),
2302                        Some(column),
2303                        None,
2304                        None,
2305                    )?;
2306
2307                    // Depending on whether this is an undo/redo or an original action, the
2308                    // new value will be taken from either `before` or `after`.
2309                    let before = JsonRow::nullify_value(&table, column, before);
2310                    let after = JsonRow::nullify_value(&table, column, after);
2311                    let mut cell = match &changeset.action {
2312                        ChangeAction::Undo | ChangeAction::Redo => Cell {
2313                            value: before.clone(),
2314                            text: sql::json_to_string(&before),
2315                            ..Default::default()
2316                        },
2317                        ChangeAction::Do => Cell {
2318                            value: after.clone(),
2319                            text: sql::json_to_string(&after),
2320                            ..Default::default()
2321                        },
2322                    };
2323
2324                    // Validate the cell's SQL type and add any messages to the message table:
2325                    let column_config = table.get_config_for_column(column);
2326                    let mut sql_value = cell.value.clone();
2327                    if self.validation_level != ValidationLevel::None {
2328                        cell.validate_sql_type(&column_config)
2329                            .expect("Error validating cell");
2330                        for message in cell.messages.iter() {
2331                            let (msg_id, msg) = Relatable::_add_message(
2332                                "rltbl",
2333                                &table.name,
2334                                &row,
2335                                column,
2336                                &cell.value,
2337                                &message.level,
2338                                &message.rule,
2339                                &message.message,
2340                                &mut tx,
2341                            )?;
2342                            tracing::debug!("Added message (ID {msg_id}): {msg:?}");
2343                        }
2344
2345                        // If the cell is invalid, insert a NULL instead of its actual value
2346                        if cell.has_sql_type_error() {
2347                            sql_value = JsonValue::Null;
2348                        }
2349                    }
2350
2351                    // Generate the UPDATE statement:
2352                    let (sql, params) = {
2353                        let mut sql_param = SqlParam::new(&self.connection.kind());
2354                        let sql = format!(
2355                            r#"UPDATE "{table}"
2356                               SET "{column}" = {sql_value}
2357                               WHERE _id = {sql_param}
2358                               RETURNING 1 AS "updated""#,
2359                            table = changeset.table,
2360                            sql_value = match sql_value {
2361                                JsonValue::Null => "NULL".to_string(),
2362                                _ => sql_param.next(),
2363                            },
2364                            sql_param = sql_param.next()
2365                        );
2366                        let params = match sql_value {
2367                            JsonValue::Null => json!([row]),
2368                            _ => json!([sql_value, row]),
2369                        };
2370                        (sql, params)
2371                    };
2372
2373                    tracing::debug!(
2374                        "Updating value of row {row} in {table}.{column} to {sql_value:?}",
2375                        table = table.name
2376                    );
2377
2378                    // Execute the UPDATE statement.
2379                    if tx.query(&sql, Some(&params))?.len() < 1 {
2380                        tracing::warn!("No row with _id {row} found to update");
2381                    } else {
2382                        actual_changes.push(Change::Update {
2383                            row: *row,
2384                            column: column.clone(),
2385                            before: match &changeset.action {
2386                                ChangeAction::Undo | ChangeAction::Redo => after.clone(),
2387                                ChangeAction::Do => before.clone(),
2388                            },
2389                            after: match &changeset.action {
2390                                ChangeAction::Undo | ChangeAction::Redo => before.clone(),
2391                                ChangeAction::Do => after.clone(),
2392                            },
2393                        });
2394                    }
2395
2396                    // Optionally do full validation on the newly updated cell and add further
2397                    // messages to the message table:
2398                    if self.validation_level == ValidationLevel::Full {
2399                        self._validate_column_optionally_for_row(
2400                            &column_config,
2401                            Some(row),
2402                            &mut tx,
2403                        )?;
2404                        for column in &column_config._get_dependent_columns(&mut tx)? {
2405                            tracing::debug!("Validating dependent column '{}'", column.name);
2406                            self._validate_structure_for_column_and_optionally_for_row(
2407                                column, None, &mut tx,
2408                            )?;
2409                        }
2410                    }
2411                }
2412                _ => {
2413                    return Err(RelatableError::InputError(format!(
2414                        "Invalid change in changeset argument to set_values(): {change:?}"
2415                    ))
2416                    .into());
2417                }
2418            };
2419        }
2420
2421        let num_changes = actual_changes.len();
2422        let actual_changeset = ChangeSet {
2423            action: changeset.action,
2424            table: changeset.table.clone(),
2425            user: changeset.user.clone(),
2426            description: changeset.description.clone(),
2427            changes: actual_changes,
2428        };
2429        if num_changes > 0 {
2430            // Record the changes to the change and history tables:
2431            self.record_changeset(&actual_changeset, &mut tx)?;
2432        }
2433
2434        // Commit the transaction:
2435        tx.commit()?;
2436
2437        Ok(actual_changeset)
2438    }
2439
2440    /// Update the database using the given [ChangeSet]
2441    pub async fn set_values(&self, changeset: &ChangeSet) -> Result<ChangeSet> {
2442        tracing::trace!("Relatable::set_values({changeset:?})");
2443        let conn = self.connection.reconnect()?;
2444        let changeset = self._set_values(conn, changeset).await?;
2445        if changeset.changes.len() > 0 {
2446            self.commit_to_git().await?;
2447        }
2448        Ok(changeset)
2449    }
2450
2451    /// Add a message to the message table using the given [DbTransaction]
2452    pub fn _add_message(
2453        user: &str,
2454        table_name: &str,
2455        row: &u64,
2456        column: &str,
2457        value: &JsonValue,
2458        level: &str,
2459        rule: &str,
2460        message: &str,
2461        tx: &mut DbTransaction<'_>,
2462    ) -> Result<(u64, Message)> {
2463        tracing::trace!(
2464            "Relatable::add_message({user:?}, {table_name:?}, {row}, \
2465             {column:?}, {value:?}, {level:?}, {rule:?}, {message:?}, tx)"
2466        );
2467
2468        let sql = format!(
2469            r#"INSERT INTO "message"
2470               ("added_by", "table", "row", "column", "value",
2471                "level", "rule", "message")
2472               VALUES
2473               ({sql_params})
2474               RETURNING "message_id""#,
2475            sql_params = SqlParam::new(&tx.kind()).get_as_list(8)
2476        );
2477        let params = json!([user, table_name, row, column, value, level, rule, message]);
2478        let message_id = tx
2479            .query_one(&sql, Some(&params))?
2480            .ok_or(RelatableError::DataError(
2481                "Error inserting message".to_string(),
2482            ))?
2483            .get_unsigned("message_id")?;
2484
2485        Ok((
2486            message_id,
2487            Message {
2488                value: value.clone(),
2489                level: level.to_string(),
2490                rule: rule.to_string(),
2491                message: message.to_string(),
2492            },
2493        ))
2494    }
2495
2496    /// Add a message to the message table.
2497    pub async fn add_message(
2498        &self,
2499        user: &str,
2500        table_name: &str,
2501        row: u64,
2502        column: &str,
2503        value: &JsonValue,
2504        level: &str,
2505        rule: &str,
2506        message: &str,
2507    ) -> Result<(u64, Message)> {
2508        tracing::trace!(
2509            "Relatable::add_message({self:?},  {user:?}, {table_name:?}, {row}, \
2510             {column:?}, {value:?}, {level:?}, {rule:?}, {message:?})"
2511        );
2512
2513        // Begin a transaction:
2514        let mut conn = self.connection.reconnect()?;
2515        let mut tx = self.connection.begin(&mut conn).await?;
2516
2517        let (message_id, message) = Relatable::_add_message(
2518            user, table_name, &row, column, value, level, rule, message, &mut tx,
2519        )?;
2520
2521        // Commit the transaction:
2522        tx.commit()?;
2523
2524        Ok((message_id, message))
2525    }
2526
2527    /// Add a row to the given table
2528    async fn _add_row(
2529        &self,
2530        mut conn: Option<DbActiveConnection>,
2531        action: &ChangeAction,
2532        table_name: &str,
2533        user: &str,
2534        new_row_id: Option<u64>,
2535        after_id: Option<u64>,
2536        row: &JsonRow,
2537    ) -> Result<Row> {
2538        tracing::trace!(
2539            "Relatable::_add_row(conn, {action:?}, {user:?}, {new_row_id:?}, \
2540                         {after_id:?}, {row:?})"
2541        );
2542
2543        // Begin a transaction:
2544        let mut tx = self.connection.begin(&mut conn).await?;
2545
2546        // Get the current database information for the table:
2547        let table = Table::_get_table(table_name, &mut tx)?;
2548        if !table.editable {
2549            return Err(
2550                RelatableError::InputError(format!("{} is not editable.", table_name,)).into(),
2551            );
2552        }
2553
2554        // Nullify the JSON row by setting any column values whose content matches the column's
2555        // nulltype to Null:
2556        let row = JsonRow::nullify(row, &table);
2557
2558        // Prepare a new row to be inserted using the JSON row as a base:
2559        let mut new_row = Row::prepare_new(&table, Some(&row), &mut tx)?;
2560
2561        // A new_row_id will have been passed if the row is being added as part of an undo/redo.
2562        // In that case an after_id must have been passed as well but we leave the row order as
2563        // is for now, since we are not assured that the old row order is actually still free in
2564        // the table (recall that there is a unique constraint on _order). However the row_order
2565        // currently assigned is at the end of the table so there should not be any conflicts.
2566        if let Some(new_row_id) = new_row_id {
2567            tracing::debug!("Changing new row ID to {new_row_id}");
2568            new_row.id = new_row_id;
2569        }
2570
2571        // Validate the row and add it to the table:
2572        if self.validation_level != ValidationLevel::None {
2573            new_row.validate_sql_types(&table, &mut tx)?;
2574            for (_column, cell) in new_row.cells.iter_mut() {
2575                if cell.has_sql_type_error() {
2576                    cell.value = JsonValue::Null;
2577                    cell.text = "".to_string(); // Should it be "null" instead of blank?
2578                }
2579            }
2580        }
2581        let (sql, params) = new_row.as_insert(&table.name, &tx.kind());
2582        tx.query(&sql, Some(&params))?;
2583
2584        // Optionally do full validation on the row after it has been inserted:
2585        if self.validation_level == ValidationLevel::Full {
2586            self._validate_row(&table, &new_row.id, &mut tx)?;
2587            for table in &table._get_dependent_tables(None, &mut tx)? {
2588                tracing::debug!("Validating dependent table '{}'", table.name);
2589                self._validate_structure_for_table(table, &mut tx)?;
2590            }
2591        }
2592
2593        let after_id = match after_id {
2594            None => Table::_get_previous_row_id(&table.name, new_row.id, &mut tx)?,
2595            Some(after_id) => {
2596                // Move the row to its assigned spot within the table:
2597                tracing::debug!(
2598                    "Moving new row {id} to after row {after_id} in '{table}'",
2599                    id = new_row.id,
2600                    table = table.name
2601                );
2602                let new_order = self._move_row(&mut tx, &table, new_row.id, after_id)?;
2603                new_row.order = new_order;
2604                after_id
2605            }
2606        };
2607
2608        tracing::debug!(
2609            "Added new row {id} to table '{table}' after row {after_id}",
2610            id = new_row.id,
2611            table = table.name
2612        );
2613
2614        // Prepare a changeset to be recorded, consisting of a single change record indicating
2615        // the addition of one new row with the new_row's id and position in the table:
2616        let changeset = ChangeSet {
2617            action: *action,
2618            table: table_name.to_string(),
2619            user: user.to_string(),
2620            description: "Add one row".to_string(),
2621            changes: vec![Change::Add {
2622                row: new_row.id,
2623                after: after_id,
2624            }],
2625        };
2626
2627        // Use the changeset to prepare the user cursor:
2628        self.prepare_user_cursor(&changeset, &mut tx)?;
2629
2630        // Record the changes to the history table:
2631        self.record_changeset(&changeset, &mut tx)?;
2632
2633        // Commit the transaction:
2634        tx.commit()?;
2635
2636        Ok(new_row)
2637    }
2638
2639    /// Add a row to the given table
2640    pub async fn add_row(
2641        &self,
2642        table_name: &str,
2643        user: &str,
2644        after_id: Option<u64>,
2645        row: &JsonRow,
2646    ) -> Result<Row> {
2647        tracing::trace!("Relatable::add_row({table_name:?}, {user:?}, {after_id:?}, {row:?})");
2648        let conn = self.connection.reconnect()?;
2649        let new_row = self
2650            ._add_row(
2651                conn,
2652                &ChangeAction::Do,
2653                table_name,
2654                user,
2655                None,
2656                after_id,
2657                row,
2658            )
2659            .await?;
2660        self.commit_to_git().await?;
2661        Ok(new_row)
2662    }
2663
2664    /// Delete a row from the table. Returns the number of rows deleted.
2665    async fn _delete_row(
2666        &self,
2667        mut conn: Option<DbActiveConnection>,
2668        action: &ChangeAction,
2669        table_name: &str,
2670        user: &str,
2671        row: u64,
2672    ) -> Result<usize> {
2673        tracing::trace!(
2674            "Relatable::_delete_row(conn, {action:?}, {table_name:?}, {user:?} \
2675                         {row})"
2676        );
2677        // Begin a transaction:
2678        let mut tx = self.connection.begin(&mut conn).await?;
2679
2680        // Get the current database information for the table:
2681        let table = Table::_get_table(table_name, &mut tx)?;
2682        if !table.editable {
2683            return Err(
2684                RelatableError::InputError(format!("{} is not editable.", table_name,)).into(),
2685            );
2686        }
2687
2688        // Prepare a changeset to be recorded, consisting of a single change record indicating
2689        // that a row with the given row number at the given table position has been deleted:
2690        let changeset = ChangeSet {
2691            action: *action,
2692            table: table_name.to_string(),
2693            user: user.to_string(),
2694            description: "Delete one row".to_string(),
2695            changes: vec![Change::Delete {
2696                row: row,
2697                after: Table::_get_previous_row_id(table_name, row, &mut tx)?,
2698            }],
2699        };
2700
2701        // Use the changeset to prepare the user cursor:
2702        self.prepare_user_cursor(&changeset, &mut tx)?;
2703
2704        // Delete the row:
2705        let sql = format!(
2706            r#"DELETE FROM "{}" WHERE "_id" = {sql_param} RETURNING 1 AS "deleted""#,
2707            table.name,
2708            sql_param = SqlParam::new(&self.connection.kind()).next()
2709        );
2710        let params = json!([row]);
2711        tracing::debug!("Deleted row {row} from table {table_name}");
2712
2713        // Delete any messages associated with the row
2714        self._delete_message(&mut tx, table_name, Some(row), None, None, None)?;
2715        tracing::debug!("Deleted messages for deleted row {row} of table {table_name}");
2716
2717        // Record the change to the history table:
2718        self.record_changeset(&changeset, &mut tx)?;
2719
2720        let num_deleted = tx.query(&sql, Some(&params))?.len();
2721        if num_deleted < 1 {
2722            tracing::warn!("No row found with _id {row} to delete");
2723            // Roll back the changes to the history and change table. The reason we made these
2724            // prior to the actual delete was so that we could record the row's position in the
2725            // table before it was deleted.
2726            tx.rollback()?;
2727        } else {
2728            // Commit the transaction:
2729            tx.commit()?;
2730        }
2731
2732        Ok(num_deleted)
2733    }
2734
2735    /// Delete a row from a given table
2736    pub async fn delete_row(&self, table_name: &str, user: &str, row: u64) -> Result<usize> {
2737        tracing::trace!("Relatable::delete_row({table_name:?}, {user:?}, {row})");
2738        let conn = self.connection.reconnect()?;
2739        let num_deleted = self
2740            ._delete_row(conn, &ChangeAction::Do, table_name, user, row)
2741            .await?;
2742        if num_deleted > 0 {
2743            self.commit_to_git().await?;
2744        }
2745        Ok(num_deleted)
2746    }
2747
2748    /// Delete messages from the message table. Returns the number of messages deleted.
2749    pub async fn delete_message(
2750        &self,
2751        table: &str,
2752        row: Option<u64>,
2753        column: Option<&str>,
2754        target_rule: Option<&str>,
2755        target_user: Option<&str>,
2756    ) -> Result<usize> {
2757        tracing::trace!(
2758            "Relatable::delete_message({self:?}, {table:?}, {row:?}, {column:?}, \
2759             {target_rule:?}, {target_user:?})"
2760        );
2761
2762        // Begin a transaction:
2763        let mut conn = self.connection.reconnect()?;
2764        let mut tx = self.connection.begin(&mut conn).await?;
2765
2766        // Delete the messages using the transaction
2767        let num_deleted =
2768            self._delete_message(&mut tx, table, row, column, target_rule, target_user)?;
2769
2770        // Commit the transaction:
2771        tx.commit()?;
2772
2773        Ok(num_deleted)
2774    }
2775
2776    /// Delete messages from the message table using the given transaction. Returns the
2777    /// number of messages deleted.
2778    fn _delete_message(
2779        &self,
2780        tx: &mut DbTransaction<'_>,
2781        table: &str,
2782        row: Option<u64>,
2783        column: Option<&str>,
2784        target_rule: Option<&str>,
2785        target_user: Option<&str>,
2786    ) -> Result<usize> {
2787        tracing::trace!(
2788            "Relatable::_delete_message({self:?}, tx, {table:?}, {row:?}, {column:?}, \
2789             {target_rule:?}, {target_user:?})"
2790        );
2791
2792        let mut sql_param = SqlParam::new(&self.connection.kind());
2793        let mut sql = format!(
2794            r#"DELETE FROM "message" WHERE "table" = {sql_param}"#,
2795            sql_param = sql_param.next()
2796        );
2797        let mut params = vec![json!(table)];
2798
2799        if let Some(row) = row {
2800            sql.push_str(&format!(
2801                r#" AND "row" = {sql_param}"#,
2802                sql_param = sql_param.next(),
2803            ));
2804            params.push(json!(row));
2805        }
2806        if let Some(column) = column {
2807            sql.push_str(&format!(
2808                r#" AND "column" = {sql_param}"#,
2809                sql_param = sql_param.next()
2810            ));
2811            params.push(json!(column));
2812        }
2813        if let Some(target_rule) = target_rule {
2814            sql.push_str(&format!(
2815                r#" AND "rule" LIKE {sql_param}"#,
2816                sql_param = sql_param.next()
2817            ));
2818            params.push(json!(target_rule));
2819        }
2820        if let Some(target_user) = target_user {
2821            sql.push_str(&format!(
2822                r#" AND "added_by" = {sql_param}"#,
2823                sql_param = sql_param.next()
2824            ));
2825            params.push(json!(target_user));
2826        }
2827
2828        sql.push_str(r#" RETURNING 1 AS "deleted""#);
2829        let num_deleted = tx.query(&sql, Some(&json!(params)))?.len();
2830        Ok(num_deleted)
2831    }
2832
2833    /// Move a row and record the change in the change table
2834    async fn _move_and_record_row(
2835        &self,
2836        mut conn: Option<DbActiveConnection>,
2837        action: &ChangeAction,
2838        table_name: &str,
2839        user: &str,
2840        id: u64,
2841        after_id: u64,
2842    ) -> Result<u64> {
2843        tracing::trace!(
2844            "Relatable::_move_and_record_row(conn, {action:?}, {table_name:?}, \
2845                         {user:?}, {id}, {after_id})"
2846        );
2847
2848        // Begin a transaction:
2849        let mut tx = self.connection.begin(&mut conn).await?;
2850
2851        // Get the current database information for the table:
2852        let table = Table::_get_table(table_name, &mut tx)?;
2853        if !table.editable {
2854            return Err(
2855                RelatableError::InputError(format!("{} is not editable.", table_name,)).into(),
2856            );
2857        }
2858
2859        // Prepare a changeset to be recorded, consisting of a single change record indicating
2860        // that a row has been displaced from somewhere to somewhere else.
2861        let changeset = ChangeSet {
2862            action: *action,
2863            table: table_name.to_string(),
2864            user: user.to_string(),
2865            description: "Move one row".to_string(),
2866            changes: vec![Change::Move {
2867                row: id,
2868                from_after: Table::_get_previous_row_id(table_name, id, &mut tx)?,
2869                to_after: after_id,
2870            }],
2871        };
2872
2873        // Use the changeset to prepare the user cursor:
2874        self.prepare_user_cursor(&changeset, &mut tx)?;
2875
2876        // Move the row within the table:
2877        let new_order = self._move_row(&mut tx, &table, id, after_id)?;
2878
2879        if new_order != 0 {
2880            // Record the change to the history table:
2881            self.record_changeset(&changeset, &mut tx)?;
2882        }
2883
2884        // Commit the transaction:
2885        tx.commit()?;
2886
2887        Ok(new_order)
2888    }
2889
2890    /// Move a row to a different position in a given table
2891    fn _move_row(
2892        &self,
2893        tx: &mut DbTransaction<'_>,
2894        table: &Table,
2895        id: u64,
2896        after_id: u64,
2897    ) -> Result<u64> {
2898        tracing::trace!("Relatable::_move_row(tx, {table:?}, {id}, {after_id})");
2899        fn get_row_order(tx: &mut DbTransaction<'_>, table: &Table, row_id: u64) -> Result<u64> {
2900            let sql = format!(
2901                r#"SELECT "_order" FROM "{}" WHERE "_id" = {sql_param}"#,
2902                table.name,
2903                sql_param = SqlParam::new(&tx.kind()).next()
2904            );
2905            let params = json!([row_id]);
2906            let rows = tx.query(&sql, Some(&params))?;
2907            if rows.is_empty() {
2908                return Err(RelatableError::DataError(format!(
2909                    "Unable to fetch _order for row {row_id} of table '{table}'",
2910                    table = table.name
2911                ))
2912                .into());
2913            }
2914            match rows[0].content.get("_order").and_then(|o| o.as_u64()) {
2915                Some(order) => Ok(order as u64),
2916                None => {
2917                    return Err(
2918                        RelatableError::DataError("No integer '_order' in row".to_string()).into(),
2919                    )
2920                }
2921            }
2922        }
2923
2924        // Get the order, (A), of `after_id`:
2925        let order_prev = {
2926            if after_id > 0 {
2927                let mut id_to_try = after_id;
2928                let mut result = get_row_order(tx, table, id_to_try);
2929                // This handles the case in which the after row has been deleted for some reason
2930                // (this might happen if we are redoing).
2931                while let Err(_) = result {
2932                    if id_to_try == 0 {
2933                        break;
2934                    }
2935                    tracing::debug!("Could not obtain _order for row {id_to_try}");
2936                    id_to_try -= 1;
2937                    tracing::debug!("Trying to find the _order of row {id_to_try}");
2938                    result = get_row_order(tx, table, id_to_try);
2939                }
2940                result?
2941            } else {
2942                // It is not possible for a row to be assigned a order of zero. We allow it as a
2943                // possible value of `after_id`, however, which is used as a special value that we
2944                // should move the row identified by `id` to the beginning of the table.
2945                0
2946            }
2947        };
2948
2949        // Run a query to get the minimum order, (B), that is greater than (A).
2950        let order_next = {
2951            let sql = format!(
2952                r#"SELECT MIN("_order") AS "_order" FROM "{}" WHERE "_order" > {sql_param}"#,
2953                table.name,
2954                sql_param = SqlParam::new(&tx.kind()).next()
2955            );
2956            let params = json!([order_prev]);
2957            let rows = tx.query(&sql, Some(&params))?;
2958            if rows.is_empty() {
2959                return Err(RelatableError::DataError(format!(
2960                    "Could not determine the minimum row order greater than {order_prev}"
2961                ))
2962                .into());
2963            }
2964
2965            match rows[0].content.get("_order") {
2966                Some(value) => match value {
2967                    JsonValue::Null => {
2968                        // The row_order will be null if we ask Relatable to move a row to
2969                        // a position after the last row in the table.
2970                        order_prev + NEW_ORDER_MULTIPLIER as u64
2971                    }
2972                    _ => match value.as_u64() {
2973                        Some(order) => order as u64,
2974                        None => {
2975                            return Err(RelatableError::DataError(
2976                                "Field '_order' in row is not an integer".to_string(),
2977                            )
2978                            .into());
2979                        }
2980                    },
2981                },
2982                None => {
2983                    return Err(RelatableError::DataError("No '_order' in row".to_string()).into());
2984                }
2985            }
2986        };
2987
2988        let mut new_order = {
2989            if order_prev + 1 < order_next {
2990                // If the next order is not occupied just use it:
2991                order_prev + 1
2992            } else {
2993                // Otherwise, get all the orders that need to be moved. We sort the results in
2994                // descending order so that when we later update each value, no duplicate key
2995                // violations will ensue:
2996                let upper_bound = (order_next as f32 / NEW_ORDER_MULTIPLIER as f32).ceil() as u64
2997                    * NEW_ORDER_MULTIPLIER as u64;
2998                let mut sql_param = SqlParam::new(&tx.kind());
2999                let sql = format!(
3000                    r#"SELECT "_order"
3001                         FROM "{}"
3002                        WHERE "_order" >= {sql_param_1} AND "_order" < {sql_param_2}
3003                     ORDER BY "_order" DESC"#,
3004                    table.name,
3005                    sql_param_1 = sql_param.next(),
3006                    sql_param_2 = sql_param.next()
3007                );
3008                let params = json!([order_next, upper_bound]);
3009                let rows = tx.query(&sql, Some(&params))?;
3010                if rows.is_empty() {
3011                    return Err(RelatableError::DataError(
3012                        "Could not determine the highest row order".to_string(),
3013                    )
3014                    .into());
3015                }
3016                let highest_order = match rows[0].content.get("_order").and_then(|o| o.as_u64()) {
3017                    Some(order) => order as u64,
3018                    None => {
3019                        return Err(RelatableError::DataError(
3020                            "No field '_order' in row or it is not an integer".to_string(),
3021                        )
3022                        .into())
3023                    }
3024                };
3025                if highest_order + 1 >= upper_bound {
3026                    // Return an error
3027                    return Err(RelatableError::DataError(format!(
3028                        "Impossible to move row {} after row {}: No more room",
3029                        id, after_id
3030                    ))
3031                    .into());
3032                }
3033
3034                for row in rows {
3035                    let current_order = match row.content.get("_order").and_then(|o| o.as_u64()) {
3036                        Some(order) => order as u64,
3037                        None => {
3038                            return Err(RelatableError::DataError(
3039                                "No field '_order' in row or it is not an integer".to_string(),
3040                            )
3041                            .into())
3042                        }
3043                    };
3044                    let sql = format!(
3045                        r#"UPDATE "{}"
3046                              SET "_order" = "_order" + 1
3047                            WHERE "_order" = {sql_param}"#,
3048                        table.name,
3049                        sql_param = SqlParam::new(&tx.kind()).next()
3050                    );
3051                    let params = json!([current_order]);
3052                    tx.query(&sql, Some(&params))?;
3053                }
3054                // Now that we have made some room, we can use order_prev + 1,
3055                // which should no longer be occupied:
3056                order_prev + 1
3057            }
3058        };
3059
3060        tracing::debug!(
3061            "Updating _order in table '{table}' for row {id} to {new_order}",
3062            table = table.name
3063        );
3064
3065        let mut sql_param = SqlParam::new(&tx.kind());
3066        let sql = format!(
3067            r#"UPDATE "{}" SET "_order" = {sql_param_1}
3068               WHERE "_id" = {sql_param_2}
3069               RETURNING 1 AS "moved""#,
3070            table.name,
3071            sql_param_1 = sql_param.next(),
3072            sql_param_2 = sql_param.next(),
3073        );
3074        let params = json!([new_order, id]);
3075        if tx.query(&sql, Some(&params))?.len() < 1 {
3076            tracing::warn!("Now row with _id {id} found to move");
3077            // It is not possible for a row to have an order of zero. It is used here to
3078            // represent the case where no row was actually moved to the caller.
3079            new_order = 0;
3080        }
3081        Ok(new_order)
3082    }
3083
3084    /// Change the _id of the given row in the given table.
3085    fn _change_row_id(
3086        &self,
3087        tx: &mut DbTransaction<'_>,
3088        table: &Table,
3089        id: u64,
3090        new_id: u64,
3091    ) -> Result<()> {
3092        tracing::trace!("Relatable::_change_row_id(tx, {table:?}, {id}, {new_id})");
3093        let mut sql_param = SqlParam::new(&tx.kind());
3094        let sql = format!(
3095            r#"UPDATE "{table}"
3096                  SET "_id" = {sql_param_1}, "_order" = {sql_param_2}
3097                WHERE "_id" = {sql_param_3}
3098            RETURNING "_id" AS "_id""#,
3099            table = table.name,
3100            sql_param_1 = sql_param.next(),
3101            sql_param_2 = sql_param.next(),
3102            sql_param_3 = sql_param.next(),
3103        );
3104        let params = json!([new_id, id, id * NEW_ORDER_MULTIPLIER as u64]);
3105        tx.query_one(&sql, Some(&params))?
3106            .ok_or(RelatableError::DataError(format!("No row with _id = {id}")))?
3107            .get_unsigned("_id")?;
3108        Ok(())
3109    }
3110
3111    /// Move a row to a different position in a given table.
3112    pub async fn move_row(
3113        &self,
3114        table_name: &str,
3115        user: &str,
3116        id: u64,
3117        after_id: u64,
3118    ) -> Result<u64> {
3119        tracing::trace!("Relatable::move_row({table_name:?}, {user:?}, {after_id:?})");
3120        let conn = self.connection.reconnect()?;
3121        let new_order = self
3122            ._move_and_record_row(conn, &ChangeAction::Do, table_name, user, id, after_id)
3123            .await?;
3124        if new_order != 0 {
3125            self.commit_to_git().await?;
3126        }
3127        Ok(new_order)
3128    }
3129
3130    /// Validate all of the data in the given database table
3131    pub async fn validate_table(&self, table: &Table) -> Result<()> {
3132        tracing::trace!("Relatable::validate_table({self:?}, {table:?})");
3133
3134        // Reconnect and begin a transaction:
3135        let mut conn = self.connection.reconnect()?;
3136        let mut tx = self.connection.begin(&mut conn).await?;
3137
3138        self._validate_table(table, &mut tx)?;
3139
3140        // Commit the transaction
3141        tx.commit()?;
3142
3143        tracing::info!("Validated table '{}'", table.name);
3144        Ok(())
3145    }
3146
3147    /// Validate all of the data in the given database table using the given transaction
3148    fn _validate_table(&self, table: &Table, tx: &mut DbTransaction<'_>) -> Result<()> {
3149        tracing::trace!("Relatable::_validate_table({self:?}, {table:?}, tx)");
3150
3151        // Validate each table column
3152        for (_, column) in table.columns.iter() {
3153            self._validate_column_optionally_for_row(column, None, tx)?;
3154        }
3155
3156        tracing::debug!("Validated table '{}'", table.name);
3157        Ok(())
3158    }
3159
3160    /// Do datatype validation on all of the data in the given database table
3161    pub async fn validate_datatype_for_table(&self, table: &Table) -> Result<()> {
3162        tracing::trace!("Relatable::validate_datatype_for_table({self:?}, {table:?})");
3163
3164        // Reconnect and begin a transaction:
3165        let mut conn = self.connection.reconnect()?;
3166        let mut tx = self.connection.begin(&mut conn).await?;
3167
3168        self._validate_datatype_for_table(table, &mut tx)?;
3169
3170        // Commit the transaction
3171        tx.commit()?;
3172
3173        tracing::info!("Validated datatype for table '{}'", table.name);
3174        Ok(())
3175    }
3176
3177    /// Do datatype validation on all of the data in the given table using the given database
3178    /// transaction
3179    fn _validate_datatype_for_table(
3180        &self,
3181        table: &Table,
3182        tx: &mut DbTransaction<'_>,
3183    ) -> Result<()> {
3184        tracing::trace!("Relatable::_validate_datatype_for_table({self:?}, {table:?}, tx)");
3185
3186        // Validate each table column
3187        for (_, column) in table.columns.iter() {
3188            self._validate_datatype_for_column_and_optionally_for_row(column, None, tx)?;
3189        }
3190
3191        tracing::debug!("Validated datatype for table '{}'", table.name);
3192        Ok(())
3193    }
3194
3195    /// Do structure validation on all of the data in the given database table
3196    pub async fn validate_structure_for_table(&self, table: &Table) -> Result<()> {
3197        tracing::trace!("Relatable::validate_structure_for_table({self:?}, {table:?})");
3198
3199        // Reconnect and begin a transaction:
3200        let mut conn = self.connection.reconnect()?;
3201        let mut tx = self.connection.begin(&mut conn).await?;
3202
3203        self._validate_structure_for_table(table, &mut tx)?;
3204
3205        // Commit the transaction
3206        tx.commit()?;
3207
3208        tracing::info!("Validated structure for table '{}'", table.name);
3209        Ok(())
3210    }
3211
3212    /// Do structure validation on all of the data in the given database table using the given
3213    /// database transation
3214    fn _validate_structure_for_table(
3215        &self,
3216        table: &Table,
3217        tx: &mut DbTransaction<'_>,
3218    ) -> Result<()> {
3219        tracing::trace!("Relatable::_validate_structure_for_table({self:?}, {table:?}, tx)");
3220
3221        // Validate each table column
3222        for (_, column) in table.columns.iter() {
3223            self._validate_structure_for_column_and_optionally_for_row(column, None, tx)?;
3224        }
3225
3226        tracing::debug!("Validated structure for table '{}'", table.name);
3227        Ok(())
3228    }
3229
3230    /// Validate the data in the given column associated with a table in the database
3231    pub async fn validate_column(&self, column: &Column) -> Result<()> {
3232        tracing::trace!("Relatable::validate_column({self:?}, {column:?})");
3233        let mut conn = self.connection.reconnect()?;
3234        let mut tx = self.connection.begin(&mut conn).await?;
3235        self._validate_column_optionally_for_row(column, None, &mut tx)?;
3236        tx.commit()?;
3237        tracing::info!("Validated column '{}.{}'", column.table, column.name);
3238        Ok(())
3239    }
3240
3241    /// Validate the value of the given column in the given row in the associated database
3242    /// table
3243    pub async fn validate_value(&self, column: &Column, row: &u64) -> Result<()> {
3244        tracing::trace!("Relatable::validate_value({self:?}, {column:?}, {row})");
3245        let mut conn = self.connection.reconnect()?;
3246        let mut tx = self.connection.begin(&mut conn).await?;
3247        self._validate_column_optionally_for_row(column, Some(row), &mut tx)?;
3248        tx.commit()?;
3249        tracing::info!(
3250            "Validated value at row {}, column '{}.{}'",
3251            row,
3252            column.table,
3253            column.name
3254        );
3255        Ok(())
3256    }
3257
3258    /// Validate the given row of the given table
3259    pub async fn validate_row(&self, table: &Table, row: &u64) -> Result<()> {
3260        tracing::trace!("Relatable::validate_row({self:?}, {table:?}, {row})");
3261        let mut conn = self.connection.reconnect()?;
3262        let mut tx = self.connection.begin(&mut conn).await?;
3263        self._validate_row(table, row, &mut tx)?;
3264        tx.commit()?;
3265        tracing::info!("Validated row {} of table '{}'", row, table.name);
3266        Ok(())
3267    }
3268
3269    /// Validate the given row of the given table using the given database transaction
3270    fn _validate_row(&self, table: &Table, row: &u64, tx: &mut DbTransaction<'_>) -> Result<()> {
3271        tracing::trace!("Relatable::_validate_row({self:?}, {table:?}, {row}, tx)");
3272        for (_, column) in table.columns.iter() {
3273            self._validate_column_optionally_for_row(column, Some(row), tx)?;
3274        }
3275        tracing::debug!("Validated row {} of table '{}'", row, table.name);
3276        Ok(())
3277    }
3278
3279    /// Validate the datatype of the given column in its associated database table using the
3280    /// given transaction. If `row` is given, only validate the column for that row.
3281    fn _validate_datatype_for_column_and_optionally_for_row(
3282        &self,
3283        column: &Column,
3284        row: Option<&u64>,
3285        tx: &mut DbTransaction<'_>,
3286    ) -> Result<()> {
3287        tracing::trace!(
3288            "Relatable::_validate_datatype_for_column_and_optionally_for_row(\
3289             {self:?}, {column:?}, {row:?}, tx)"
3290        );
3291
3292        let table_name = column.table.as_str();
3293
3294        // Delete pre-existing datatype validation messages for this column and then
3295        // validate the datatype conditions for each datatype in the column's datatype hierarchy.
3296        self._delete_message(
3297            tx,
3298            table_name,
3299            row.copied(),
3300            Some(&column.name),
3301            Some("datatype:%"),
3302            Some("rltbl"),
3303        )?;
3304
3305        // Gather the datatypes to check: The column's datatype, plus any further datatypes in
3306        // the datatype hierarchy:
3307        let mut datatypes_to_check = vec![column.datatype.clone()];
3308        datatypes_to_check.append(&mut column.datatype_hierarchy.clone());
3309
3310        // Validate the column against each datatype in the hierarchy:
3311        for datatype in datatypes_to_check {
3312            let inserted = datatype.validate(column, row, tx)?;
3313            if !inserted {
3314                break;
3315            }
3316        }
3317
3318        tracing::debug!(
3319            "Validated datatype for column: '{}.{}'{}",
3320            column.table,
3321            column.name,
3322            match row {
3323                None => "".to_string(),
3324                Some(row) => format!(", row: {row}"),
3325            }
3326        );
3327        Ok(())
3328    }
3329
3330    /// Validate the structure of the given column in its associated database table using the
3331    /// given transaction. If `row` is given, only validate the column for that row.
3332    fn _validate_structure_for_column_and_optionally_for_row(
3333        &self,
3334        column: &Column,
3335        row: Option<&u64>,
3336        tx: &mut DbTransaction<'_>,
3337    ) -> Result<()> {
3338        tracing::trace!(
3339            "Relatable::_validate_structure_for_column_and_optionally_for_row(\
3340             {self:?}, {column:?}, {row:?}, tx)"
3341        );
3342
3343        let table_name = column.table.as_str();
3344
3345        // Delete pre-existing structure validation messages for this column and then re-validate
3346        // the structure condition for this column and (optionally) row:
3347        self._delete_message(
3348            tx,
3349            table_name,
3350            row.copied(),
3351            Some(&column.name),
3352            Some("key:%"),
3353            Some("rltbl"),
3354        )?;
3355
3356        // Validate the cell's structure condition:
3357        if let Some(structure) = &column.structure {
3358            structure.validate(column, row, tx)?;
3359        }
3360
3361        tracing::debug!(
3362            "Validated structure for column: '{}.{}'{}",
3363            column.table,
3364            column.name,
3365            match row {
3366                None => "".to_string(),
3367                Some(row) => format!(", row: {row}"),
3368            }
3369        );
3370        Ok(())
3371    }
3372
3373    /// Validate the given column in its associated database table using the given transaction.
3374    /// If `row` is given, only validate the column for that row.
3375    fn _validate_column_optionally_for_row(
3376        &self,
3377        column: &Column,
3378        row: Option<&u64>,
3379        tx: &mut DbTransaction<'_>,
3380    ) -> Result<()> {
3381        tracing::trace!(
3382            "Relatable::_validate_column_optionally_for_row({self:?}, {column:?}, {row:?}, tx)"
3383        );
3384        self._validate_datatype_for_column_and_optionally_for_row(column, row, tx)?;
3385        self._validate_structure_for_column_and_optionally_for_row(column, row, tx)?;
3386        tracing::debug!(
3387            "Validated column: '{}.{}'{}",
3388            column.table,
3389            column.name,
3390            match row {
3391                None => "".to_string(),
3392                Some(row) => format!(", row: {row}"),
3393            }
3394        );
3395        Ok(())
3396    }
3397
3398    /// Delete all entries from the cache corresponding to the given table, or clear it completely
3399    /// if no table is given.
3400    pub(crate) fn clear_cache(tx: &mut DbTransaction<'_>, table: Option<&str>) -> Result<()> {
3401        let mut sql = r#"DELETE FROM "cache""#.to_string();
3402        if let Some(table) = table {
3403            let mut table = table.to_string();
3404            tracing::debug!("Deleting entries for table '{table}' from cache");
3405            match tx.kind() {
3406                DbKind::Postgres => {
3407                    // Note that the '?' is *not* being used as a parameter placeholder here
3408                    // but a JSONB operator.
3409                    sql.push_str(&format!(
3410                        r#" WHERE "tables" ? {}"#,
3411                        SqlParam::new(&tx.kind()).next()
3412                    ));
3413                }
3414                DbKind::Sqlite => {
3415                    sql.push_str(&format!(
3416                        r#" WHERE "tables" LIKE {}"#,
3417                        SqlParam::new(&tx.kind()).next()
3418                    ));
3419                    table = format!(r#"%"{table}"%"#);
3420                }
3421            };
3422            let params = json!([table]);
3423            tx.query(&sql, Some(&params))?;
3424        } else {
3425            tracing::debug!("Truncating cache");
3426            tx.query(&sql, None)?;
3427        }
3428
3429        Ok(())
3430    }
3431
3432    /// Delete all entries from the in-memory cache corresponding to the given table
3433    pub(crate) fn clear_mem_cache(&self, table: &str) {
3434        let table = format!("\"{table}\"");
3435        let mut cache = CACHE.lock().expect("Could not lock cache");
3436        let keys = cache
3437            .keys()
3438            .map(|k| k)
3439            .cloned()
3440            .collect::<HashSet<_>>()
3441            .into_iter()
3442            .collect::<Vec<_>>();
3443        for key in keys.iter() {
3444            if key.tables.contains(&table) {
3445                tracing::debug!("Removing {key:?} from cache");
3446                cache.remove(key);
3447            }
3448        }
3449    }
3450}
3451
3452// Validation
3453
3454/// The level at which Relatable will perform validation when adding to or modifying data in the
3455/// database
3456#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
3457pub enum ValidationLevel {
3458    /// Perform no validateion
3459    None,
3460    /// Perform only SQL type validation
3461    SqlType,
3462    /// Perform full validation
3463    Full,
3464}
3465
3466impl FromStr for ValidationLevel {
3467    type Err = anyhow::Error;
3468
3469    fn from_str(level: &str) -> Result<Self> {
3470        tracing::trace!("ValidationLevel::from_str({level:?})");
3471        match level.to_lowercase().as_str() {
3472            "none" => Ok(ValidationLevel::None),
3473            "sql_type" => Ok(ValidationLevel::SqlType),
3474            "full" => Ok(ValidationLevel::Full),
3475            _ => {
3476                return Err(
3477                    RelatableError::InputError(format!("Unrecognized level: {level}")).into(),
3478                );
3479            }
3480        }
3481    }
3482}
3483
3484// Changes and History
3485
3486/// A set of changes made by a user to a table.
3487#[derive(Clone, Debug, Serialize, Deserialize)]
3488pub struct ChangeSet {
3489    pub action: ChangeAction,
3490    pub table: String,
3491    pub user: String,
3492    pub description: String,
3493    pub changes: Vec<Change>,
3494}
3495
3496impl ChangeSet {
3497    /// Given a change, returns the a [Cursor] representing where the user's cursor
3498    /// should be placed in the frontend.
3499    fn to_cursor(&self) -> Result<Cursor> {
3500        tracing::trace!("ChangeSet::to_cursor()");
3501        let table = self.table.clone();
3502        match self.changes.first() {
3503            Some(change) => match change {
3504                Change::Update {
3505                    row,
3506                    column,
3507                    before: _,
3508                    after: _,
3509                } => Ok(Cursor {
3510                    table,
3511                    row: *row,
3512                    column: column.to_string(),
3513                }),
3514                Change::Add { row, after: _ } => Ok(Cursor {
3515                    table,
3516                    row: *row,
3517                    column: "".to_string(),
3518                }),
3519                Change::Move {
3520                    row,
3521                    from_after: _,
3522                    to_after: _,
3523                } => Ok(Cursor {
3524                    table,
3525                    row: *row,
3526                    column: "".to_string(),
3527                }),
3528                Change::Delete { row, after: _ } => Ok(Cursor {
3529                    table,
3530                    row: *row,
3531                    column: "".to_string(),
3532                }),
3533            },
3534            None => Err(RelatableError::ChangeError("No changes in set".into()).into()),
3535        }
3536    }
3537}
3538
3539/// The kind of action that is performed by a change
3540#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq)]
3541pub enum ChangeAction {
3542    Do,
3543    Undo,
3544    Redo,
3545}
3546
3547impl FromStr for ChangeAction {
3548    type Err = anyhow::Error;
3549
3550    fn from_str(action: &str) -> Result<Self> {
3551        tracing::trace!("ChangeAction::from_str({action:?})");
3552        match action.to_lowercase().as_str() {
3553            "do" => Ok(ChangeAction::Do),
3554            "undo" => Ok(ChangeAction::Undo),
3555            "redo" => Ok(ChangeAction::Redo),
3556            _ => {
3557                return Err(
3558                    RelatableError::InputError(format!("Unrecognized action: {action}")).into(),
3559                );
3560            }
3561        }
3562    }
3563}
3564
3565impl Display for ChangeAction {
3566    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3567        match self {
3568            ChangeAction::Do => write!(f, "do"),
3569            ChangeAction::Undo => write!(f, "undo"),
3570            ChangeAction::Redo => write!(f, "redo"),
3571        }
3572    }
3573}
3574
3575/// A change to a table in the database
3576#[derive(Clone, Debug, Serialize, Deserialize)]
3577#[serde(tag = "type")]
3578pub enum Change {
3579    Update {
3580        /// The id of the row that was updated
3581        row: u64,
3582        /// The column whose value was updated
3583        column: String,
3584        /// The value of the column before the change
3585        before: JsonValue,
3586        /// The value of the column after the change
3587        after: JsonValue,
3588    },
3589    Add {
3590        /// The id of the row that was added
3591        row: u64,
3592        /// The _id of the row whose _order this comes immediately after in the table
3593        after: u64,
3594    },
3595    Move {
3596        /// The id of the row that was moved
3597        row: u64,
3598        /// The row that this row came after before the change
3599        from_after: u64,
3600        /// The row that this row came after after the change
3601        to_after: u64,
3602    },
3603    Delete {
3604        /// The id of the row that was deleted
3605        row: u64,
3606        /// The _id of the row whose _order this row came immediately after in the table before
3607        /// being deleted.
3608        after: u64,
3609    },
3610}
3611
3612impl Change {
3613    /// Converts a JSON string representing an array of changes to an array of [Change] structs.
3614    pub fn many_from_str(content: &str) -> Result<Vec<Self>> {
3615        tracing::trace!("Change::many_from_str({content:?})");
3616        let json_content = match serde_json::from_str::<JsonValue>(content) {
3617            Err(err) => return Err(err.into()),
3618            Ok(JsonValue::Array(v)) => v,
3619            Ok(_) => {
3620                return Err(RelatableError::InputError(
3621                    "The content parameter is not an array".to_string(),
3622                )
3623                .into());
3624            }
3625        };
3626
3627        let mut changes = vec![];
3628        for change_json in json_content.iter() {
3629            let change_json = match change_json.as_object() {
3630                Some(change_object) => JsonRow {
3631                    content: change_object.clone(),
3632                },
3633                None => {
3634                    return Err(RelatableError::InputError(format!(
3635                        "Not an object: {change_json}"
3636                    ))
3637                    .into());
3638                }
3639            };
3640
3641            let change_type = change_json.get_string("type")?;
3642            let row = change_json.get_unsigned("row")?;
3643            match change_type.as_str() {
3644                "Update" => changes.push(Change::Update {
3645                    row: row,
3646                    column: change_json.get_string("column")?,
3647                    before: change_json.get_value("before")?,
3648                    after: change_json.get_value("after")?,
3649                }),
3650                "Add" => changes.push(Change::Add {
3651                    row: row,
3652                    after: change_json.get_unsigned("after")?,
3653                }),
3654                "Delete" => changes.push(Change::Delete {
3655                    row: row,
3656                    after: change_json.get_unsigned("after")?,
3657                }),
3658                "Move" => changes.push(Change::Move {
3659                    row: row,
3660                    from_after: change_json.get_unsigned("from_after")?,
3661                    to_after: change_json.get_unsigned("to_after")?,
3662                }),
3663                _ => {
3664                    return Err(RelatableError::InputError(format!(
3665                        "Unrecognized change type for change: {change_json}"
3666                    ))
3667                    .into());
3668                }
3669            };
3670        }
3671        Ok(changes)
3672    }
3673
3674    /// Convers a [JsonRow] to a [Change]
3675    pub fn from_json_row(json_row: &JsonRow) -> Result<Self> {
3676        tracing::trace!("Change::from_json_row({json_row:?})");
3677        match json_row.get_string("type")?.as_str() {
3678            "Update" => Ok(Change::Update {
3679                row: json_row.get_unsigned("row")?,
3680                column: json_row.get_string("column")?,
3681                before: json_row.get_value("before")?,
3682                after: json_row.get_value("after")?,
3683            }),
3684            "Add" => Ok(Change::Add {
3685                row: json_row.get_unsigned("row")?,
3686                after: json_row.get_unsigned("after")?,
3687            }),
3688            "Move" => Ok(Change::Move {
3689                row: json_row.get_unsigned("row")?,
3690                from_after: json_row.get_unsigned("from_after")?,
3691                to_after: json_row.get_unsigned("to_after")?,
3692            }),
3693            "Delete" => Ok(Change::Delete {
3694                row: json_row.get_unsigned("row")?,
3695                after: json_row.get_unsigned("after")?,
3696            }),
3697            _ => {
3698                return Err(RelatableError::InputError(format!(
3699                    "Unrecognized action type for change {json_row}"
3700                ))
3701                .into());
3702            }
3703        }
3704    }
3705}
3706
3707/// Describes a history of changes that have been done and undone.
3708#[derive(Default, Debug, Serialize, Deserialize)]
3709pub struct History {
3710    pub changes_done_stack: Vec<JsonRow>,
3711    pub changes_undone_stack: Vec<JsonRow>,
3712}
3713
3714impl Display for Change {
3715    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3716        match self {
3717            Change::Update {
3718                row,
3719                column,
3720                before,
3721                after,
3722            } => {
3723                write!(
3724                    f,
3725                    "Update '{column}' in row {row} from {before} to {after}",
3726                    before = sql::json_to_string(before),
3727                    after = sql::json_to_string(after)
3728                )
3729            }
3730            Change::Add { row, after } => {
3731                write!(f, "Add row {row} after row {after}")
3732            }
3733            Change::Move {
3734                row,
3735                from_after,
3736                to_after,
3737            } => {
3738                write!(
3739                    f,
3740                    "Move row {row} from after row {from_after} to after row {to_after}"
3741                )
3742            }
3743            Change::Delete { row, after: _ } => write!(f, "Delete row {row}"),
3744        }
3745    }
3746}
3747
3748// Ranges and Results
3749
3750#[derive(Clone, Debug, Default, Serialize, Deserialize)]
3751pub struct Range {
3752    count: usize,
3753    total: u64,
3754    start: u64,
3755    end: u64,
3756}
3757
3758impl std::fmt::Display for Range {
3759    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3760        write!(f, "Rows {}-{} of {}", self.start, self.end, self.total)
3761    }
3762}
3763
3764#[derive(Clone, Debug, Default, Serialize, Deserialize)]
3765pub struct ResultSet {
3766    pub select: Select,
3767    pub statement: String,
3768    pub parameters: Vec<JsonValue>,
3769    pub range: Range,
3770    pub table: Table,
3771    /// The columns (and only the columns) used in the Select statement
3772    pub columns: Vec<Column>,
3773    pub rows: Vec<Row>,
3774}
3775
3776impl ResultSet {
3777    /// Write the result set to CSV
3778    pub fn to_csv(&self) -> String {
3779        let writer = WriterBuilder::new().from_writer(vec![]);
3780        self.to_xsv(writer)
3781    }
3782
3783    /// Write the result set to TSV
3784    pub fn to_tsv(&self) -> String {
3785        let writer = WriterBuilder::new()
3786            .delimiter(b'\t')
3787            .quote_style(QuoteStyle::Never)
3788            .from_writer(vec![]);
3789        self.to_xsv(writer)
3790    }
3791
3792    /// Write the result set to XSV
3793    pub fn to_xsv(&self, mut writer: Writer<Vec<u8>>) -> String {
3794        let header_row = &self
3795            .columns
3796            .iter()
3797            .map(|c| c.name.clone())
3798            .collect::<Vec<String>>();
3799        writer.write_record(header_row.clone()).unwrap();
3800        for row in &self.rows {
3801            writer.write_record(row.to_strings()).unwrap();
3802        }
3803        String::from_utf8(writer.into_inner().unwrap()).unwrap()
3804    }
3805
3806    /// Uses the given (unverified) printf-style format string and the given compiled regular
3807    /// expression (which is used to verify the given format) to format the given cell.
3808    fn format_cell_text_value(column_format: &str, format_regex: &Regex, cell: &str) -> String {
3809        // If the cell is an empty string, just return it as is:
3810        if cell == "" {
3811            return "".to_string();
3812        }
3813
3814        let conversion_spec = match format_regex.captures(column_format) {
3815            Some(c) => c[1].to_lowercase(),
3816            None => {
3817                tracing::warn!("Illegal format: '{}'", column_format);
3818                "s".to_string()
3819            }
3820        };
3821        let generic_error = format!("Error applying format '{}' to '{}'", column_format, cell);
3822        match conversion_spec.as_str() {
3823            "d" | "i" | "c" => match cell.parse::<isize>() {
3824                Ok(cell) => match sprintf!(&column_format, cell) {
3825                    Ok(cell) => {
3826                        // For some reason sprintf converts signed ints to unsigned ints before
3827                        // converting them to a string. So we have to workaround this here:
3828                        let cell = cell.parse::<usize>().unwrap();
3829                        let cell = cell as isize;
3830                        cell.to_string()
3831                    }
3832                    Err(e) => {
3833                        tracing::warn!("{}: {}", generic_error, e);
3834                        cell.to_string()
3835                    }
3836                },
3837                Err(e) => {
3838                    tracing::warn!("{}: {}", generic_error, e);
3839                    cell.to_string()
3840                }
3841            },
3842            "o" | "u" | "x" => match cell.parse::<usize>() {
3843                Ok(cell) => sprintf!(&column_format, cell).unwrap_or(cell.to_string()),
3844                Err(e) => {
3845                    tracing::warn!("{}: {}", generic_error, e);
3846                    cell.to_string()
3847                }
3848            },
3849            "e" | "f" | "g" | "a" => match cell.parse::<f64>() {
3850                Ok(cell) => sprintf!(&column_format, cell).unwrap_or(cell.to_string()),
3851                Err(e) => {
3852                    tracing::warn!("{}: {}", generic_error, e);
3853                    cell.to_string()
3854                }
3855            },
3856            "s" => sprintf!(&column_format, cell).unwrap_or(cell.to_string()),
3857            _ => {
3858                tracing::warn!(
3859                    "Unsupported conversion specifier '{}' in column format '{}'",
3860                    conversion_spec,
3861                    column_format
3862                );
3863                cell.to_string()
3864            }
3865        }
3866    }
3867
3868    /// Write the result set to the console
3869    pub fn to_console(&self) -> String {
3870        let tw = TabWriter::new(vec![]);
3871        let mut tw = tw.ansi(true);
3872        tw.write(format!("{}\n", self.range).as_bytes())
3873            .unwrap_or_default();
3874        let header = &self
3875            .columns
3876            .iter()
3877            .map(|c| c.name.clone())
3878            .collect::<Vec<String>>();
3879        tw.write(format!("{}\n", header.join("\t")).as_bytes())
3880            .unwrap_or_default();
3881
3882        let format_regex = Regex::new(r#"^%.*([\w%])$"#).expect("Invalid regular expression");
3883        let mut contains_errors = false;
3884        for row in &self.rows {
3885            let cells = row
3886                .cells
3887                .iter()
3888                .map(|(column_name, cell)| {
3889                    let value_to_print = {
3890                        let column_format = match self.table.columns.get(column_name) {
3891                            Some(column) if column.datatype.format == "" => "%s",
3892                            Some(column) => &column.datatype.format,
3893                            None => {
3894                                tracing::warn!(
3895                                    "Can't determine cell format. No column found: '{column_name}'"
3896                                );
3897                                "%s"
3898                            }
3899                        };
3900                        ResultSet::format_cell_text_value(&column_format, &format_regex, &cell.text)
3901                    };
3902                    if cell.message_level() >= 2 {
3903                        contains_errors = true;
3904                        format!("{}", value_to_print.red())
3905                    } else {
3906                        value_to_print
3907                    }
3908                })
3909                .collect::<Vec<_>>();
3910            tw.write(format!("{}\n", cells.join("\t")).as_bytes())
3911                .unwrap_or_default();
3912        }
3913        tw.flush().expect("TabWriter to flush");
3914        let written = String::from_utf8(tw.into_inner().unwrap()).unwrap();
3915        written
3916    }
3917}
3918
3919impl std::fmt::Display for ResultSet {
3920    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3921        let mut tw = TabWriter::new(vec![]);
3922        tw.write(format!("{}\n", self.range).as_bytes())
3923            .unwrap_or_default();
3924        let header = &self
3925            .columns
3926            .iter()
3927            .map(|c| c.name.clone())
3928            .collect::<Vec<String>>();
3929        tw.write(format!("{}\n", header.join("\t")).as_bytes())
3930            .unwrap_or_default();
3931        for row in &self.rows {
3932            tw.write(format!("{}\n", row.to_strings().join("\t")).as_bytes())
3933                .unwrap_or_default();
3934        }
3935        tw.flush().expect("TabWriter to flush");
3936        let written = String::from_utf8(tw.into_inner().unwrap()).unwrap();
3937        write!(f, "{written}")
3938    }
3939}
3940
3941// Web Site Stuff
3942
3943#[derive(Clone, Debug, Serialize, Deserialize)]
3944pub struct Site {
3945    pub title: String,
3946    pub root: String,
3947    pub editable: bool,
3948    pub user: Account,
3949    pub users: IndexMap<String, UserCursor>,
3950    pub tables: Vec<String>,
3951}
3952
3953#[derive(Clone, Debug, Default, Serialize, Deserialize)]
3954pub struct Account {
3955    name: String,
3956    color: String,
3957}
3958
3959#[derive(Clone, Debug, Serialize, Deserialize)]
3960pub struct Cursor {
3961    table: String,
3962    row: u64,
3963    column: String,
3964}
3965
3966#[derive(Clone, Debug, Serialize, Deserialize)]
3967pub struct UserCursor {
3968    name: String,
3969    color: String,
3970    cursor: Cursor,
3971    datetime: String,
3972}
3973
3974#[derive(Clone, Debug, Default, Serialize, Deserialize)]
3975pub struct Page {
3976    pub path: String,
3977    pub formats: IndexMap<String, String>,
3978    pub tabs: Vec<Tab>,
3979}
3980
3981#[derive(Clone, Debug, Serialize, Deserialize)]
3982pub struct Tab {
3983    pub table: String,
3984    pub active: bool,
3985    pub url: String,
3986    pub count: String,
3987}