1use 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
38pub static RLTBL_DEFAULT_DB: &str = ".relatable/relatable.db";
40
41pub static NEW_ORDER_MULTIPLIER: usize = 1000;
43
44pub static HISTORY_MAX: usize = 1000;
47
48pub static DEFAULT_LIMIT: usize = 100;
50
51pub 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#[derive(Debug)]
60pub enum RelatableError {
61 ChangeError(String),
63 ConfigError(String),
65 DataError(String),
69 GitError(String),
73 InitError(String),
75 FormatError(String),
77 InputError(String),
79 IOError(std::io::Error),
81 MissingError(String),
83 SerdeJsonError(serde_json::Error),
85 RegexError(regex::Error),
87 TableError(String),
89 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#[derive(Debug)]
103pub struct Relatable {
104 pub root: String,
105 pub readonly: bool,
106 pub connection: DbConnection,
107 pub default_limit: usize,
109 pub max_limit: usize,
110 pub caching_strategy: CachingStrategy,
111 pub validation_level: ValidationLevel,
113 pub memory_cache_size: usize,
114}
115
116impl Relatable {
117 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 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 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 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 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 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 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 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 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(¶m_values)).await?;
355 }
356
357 Ok(())
358 }
359
360 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 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 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(¶m_values)).await?;
505 Ok(())
506 }
507
508 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(¶m_values)).await?;
637 Ok(())
638 }
639
640 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 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 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 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 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 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 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 pub fn render<T: Serialize>(&self, template: &str, context: T) -> Result<String> {
760 tracing::trace!("Relatable::render({template:?}, context)");
761 let mut env = Environment::new();
764
765 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 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 pub async fn fetch(&self, select: &Select) -> Result<ResultSet> {
798 tracing::trace!("Relatable::fetch({select:?})");
799
800 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 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 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 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 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(¶ms)).await
871 }
872
873 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(¶ms),
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 pub async fn load_table(&self, table_name: &str, path: &str, force: bool) {
897 tracing::trace!("Relatable::load_table({table_name:?}, {path:?}, {force})");
898 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 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 if force {
927 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(¶ms))
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(¶ms))
949 .await
950 .expect("Error inserting to table table");
951 tracing::debug!("Table {table_name} (path: {path}) added to table table");
952
953 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 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 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 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 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 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 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 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 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(¶m_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 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 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 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 self.save_all(None).await?;
1274
1275 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 let (last_commit_author, days_ago) = git::get_last_commit_info()?;
1287 let is_amendment = (last_commit_author == author) && (days_ago < 1);
1288
1289 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 git::commit("commit by rltbl", &author, is_amendment)?;
1305 Ok(())
1306 }
1307
1308 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(¶ms))?;
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 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 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 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(¶ms))?;
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(¶ms))?;
1420 }
1421 Change::Add { row, after: _ } => {
1422 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(¶ms))?
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(¶ms))?;
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(¶ms))?;
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 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(¶ms))?;
1509 }
1510 };
1511 }
1512
1513 match self.caching_strategy {
1515 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 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 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!(
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 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 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 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 tx.commit()?;
1612
1613 Ok(columns)
1614 }
1615
1616 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 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 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 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 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(¶ms))? {
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(¶ms))?;
1701 }
1702
1703 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(¶ms))?;
1725
1726 Ok(())
1727 }
1728
1729 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 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 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 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(¶ms)).await?;
1867
1868 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 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 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 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 (changes_done_stack, changes_undone_stack) =
2049 prune_stacks(&changes_done_stack, &changes_undone_stack);
2050
2051 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 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 let mut changes_undone_stack = match final_action {
2085 ChangeAction::Do => vec![],
2086 _ => changes_undone_stack,
2087 };
2088
2089 (changes_done_stack, changes_undone_stack) =
2092 prune_stacks(&changes_done_stack, &changes_undone_stack);
2093
2094 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 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::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 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(¶ms))
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 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 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 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 let mut tx = self.connection.begin(&mut conn).await?;
2277
2278 self.prepare_user_cursor(changeset, &mut tx)?;
2280
2281 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 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 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 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 cell.has_sql_type_error() {
2347 sql_value = JsonValue::Null;
2348 }
2349 }
2350
2351 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 if tx.query(&sql, Some(¶ms))?.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 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 self.record_changeset(&actual_changeset, &mut tx)?;
2432 }
2433
2434 tx.commit()?;
2436
2437 Ok(actual_changeset)
2438 }
2439
2440 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 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(¶ms))?
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 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 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 tx.commit()?;
2523
2524 Ok((message_id, message))
2525 }
2526
2527 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 let mut tx = self.connection.begin(&mut conn).await?;
2545
2546 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 let row = JsonRow::nullify(row, &table);
2557
2558 let mut new_row = Row::prepare_new(&table, Some(&row), &mut tx)?;
2560
2561 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 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(); }
2579 }
2580 }
2581 let (sql, params) = new_row.as_insert(&table.name, &tx.kind());
2582 tx.query(&sql, Some(¶ms))?;
2583
2584 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 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 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 self.prepare_user_cursor(&changeset, &mut tx)?;
2629
2630 self.record_changeset(&changeset, &mut tx)?;
2632
2633 tx.commit()?;
2635
2636 Ok(new_row)
2637 }
2638
2639 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 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 let mut tx = self.connection.begin(&mut conn).await?;
2679
2680 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 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 self.prepare_user_cursor(&changeset, &mut tx)?;
2703
2704 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 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 self.record_changeset(&changeset, &mut tx)?;
2719
2720 let num_deleted = tx.query(&sql, Some(¶ms))?.len();
2721 if num_deleted < 1 {
2722 tracing::warn!("No row found with _id {row} to delete");
2723 tx.rollback()?;
2727 } else {
2728 tx.commit()?;
2730 }
2731
2732 Ok(num_deleted)
2733 }
2734
2735 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 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 let mut conn = self.connection.reconnect()?;
2764 let mut tx = self.connection.begin(&mut conn).await?;
2765
2766 let num_deleted =
2768 self._delete_message(&mut tx, table, row, column, target_rule, target_user)?;
2769
2770 tx.commit()?;
2772
2773 Ok(num_deleted)
2774 }
2775
2776 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 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 let mut tx = self.connection.begin(&mut conn).await?;
2850
2851 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 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 self.prepare_user_cursor(&changeset, &mut tx)?;
2875
2876 let new_order = self._move_row(&mut tx, &table, id, after_id)?;
2878
2879 if new_order != 0 {
2880 self.record_changeset(&changeset, &mut tx)?;
2882 }
2883
2884 tx.commit()?;
2886
2887 Ok(new_order)
2888 }
2889
2890 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(¶ms))?;
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 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 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 0
2946 }
2947 };
2948
2949 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(¶ms))?;
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 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 order_prev + 1
2992 } else {
2993 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(¶ms))?;
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 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(¶ms))?;
3053 }
3054 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(¶ms))?.len() < 1 {
3076 tracing::warn!("Now row with _id {id} found to move");
3077 new_order = 0;
3080 }
3081 Ok(new_order)
3082 }
3083
3084 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(¶ms))?
3106 .ok_or(RelatableError::DataError(format!("No row with _id = {id}")))?
3107 .get_unsigned("_id")?;
3108 Ok(())
3109 }
3110
3111 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 pub async fn validate_table(&self, table: &Table) -> Result<()> {
3132 tracing::trace!("Relatable::validate_table({self:?}, {table:?})");
3133
3134 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 tx.commit()?;
3142
3143 tracing::info!("Validated table '{}'", table.name);
3144 Ok(())
3145 }
3146
3147 fn _validate_table(&self, table: &Table, tx: &mut DbTransaction<'_>) -> Result<()> {
3149 tracing::trace!("Relatable::_validate_table({self:?}, {table:?}, tx)");
3150
3151 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 pub async fn validate_datatype_for_table(&self, table: &Table) -> Result<()> {
3162 tracing::trace!("Relatable::validate_datatype_for_table({self:?}, {table:?})");
3163
3164 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 tx.commit()?;
3172
3173 tracing::info!("Validated datatype for table '{}'", table.name);
3174 Ok(())
3175 }
3176
3177 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 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 pub async fn validate_structure_for_table(&self, table: &Table) -> Result<()> {
3197 tracing::trace!("Relatable::validate_structure_for_table({self:?}, {table:?})");
3198
3199 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 tx.commit()?;
3207
3208 tracing::info!("Validated structure for table '{}'", table.name);
3209 Ok(())
3210 }
3211
3212 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 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 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 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 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 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 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 self._delete_message(
3297 tx,
3298 table_name,
3299 row.copied(),
3300 Some(&column.name),
3301 Some("datatype:%"),
3302 Some("rltbl"),
3303 )?;
3304
3305 let mut datatypes_to_check = vec![column.datatype.clone()];
3308 datatypes_to_check.append(&mut column.datatype_hierarchy.clone());
3309
3310 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 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 self._delete_message(
3348 tx,
3349 table_name,
3350 row.copied(),
3351 Some(&column.name),
3352 Some("key:%"),
3353 Some("rltbl"),
3354 )?;
3355
3356 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 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 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 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(¶ms))?;
3424 } else {
3425 tracing::debug!("Truncating cache");
3426 tx.query(&sql, None)?;
3427 }
3428
3429 Ok(())
3430 }
3431
3432 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#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
3457pub enum ValidationLevel {
3458 None,
3460 SqlType,
3462 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#[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 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#[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#[derive(Clone, Debug, Serialize, Deserialize)]
3577#[serde(tag = "type")]
3578pub enum Change {
3579 Update {
3580 row: u64,
3582 column: String,
3584 before: JsonValue,
3586 after: JsonValue,
3588 },
3589 Add {
3590 row: u64,
3592 after: u64,
3594 },
3595 Move {
3596 row: u64,
3598 from_after: u64,
3600 to_after: u64,
3602 },
3603 Delete {
3604 row: u64,
3606 after: u64,
3609 },
3610}
3611
3612impl Change {
3613 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 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#[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#[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 pub columns: Vec<Column>,
3773 pub rows: Vec<Row>,
3774}
3775
3776impl ResultSet {
3777 pub fn to_csv(&self) -> String {
3779 let writer = WriterBuilder::new().from_writer(vec![]);
3780 self.to_xsv(writer)
3781 }
3782
3783 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 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 fn format_cell_text_value(column_format: &str, format_regex: &Regex, cell: &str) -> String {
3809 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 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 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#[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}