Skip to main content

lezeh_db/psql/
relation_insert.rs

1use std::collections::{HashMap, HashSet};
2use std::rc::Rc;
3
4use itertools::Itertools;
5
6use crate::psql::dto::FromSqlSink;
7use crate::psql::dto::PsqlTable;
8use crate::psql::dto::PsqlTableIdentity;
9use crate::psql::dto::PsqlTableRow;
10use lezeh_common::types::ResultAnyError;
11
12pub struct TableInsertStatement<'a> {
13  table: PsqlTable,
14  columns: TableInsertRowColumns<'a>,
15  row_values: Vec<TableInsertRowValues>,
16}
17
18impl<'a> std::fmt::Display for TableInsertStatement<'a> {
19  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
20    // let template = ;
21
22    return write!(
23      f,
24      indoc::indoc! {"
25        ------------------------------------------------
26        -- insert into table {}
27        ------------------------------------------------
28        insert into {} ({}) VALUES
29          {};
30        ---------------
31
32      "},
33      self.table.id,
34      self.table.id,
35      self.columns,
36      self
37        .row_values
38        .iter()
39        .map(|val| format!("{}", val))
40        .collect::<Vec<String>>()
41        .join(",\n"),
42    );
43  }
44}
45
46pub struct TableInsertRowColumns<'a> {
47  column_names: Vec<&'a str>,
48}
49
50impl<'a> std::fmt::Display for TableInsertRowColumns<'a> {
51  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52    let column_string: String = self
53      .column_names
54      .iter()
55      .map(|column_name| {
56        return format!("\"{}\"", column_name);
57      })
58      .collect::<Vec<String>>()
59      .join(", ");
60
61    return write!(f, "{}", column_string);
62  }
63}
64
65pub struct TableInsertRowValues {
66  values: Vec<String>,
67}
68
69impl std::fmt::Display for TableInsertRowValues {
70  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
71    return write!(f, "({})", self.values.join(", "));
72  }
73}
74
75pub struct RelationInsert {}
76
77impl RelationInsert {
78  pub fn into_insert_statements(
79    mut rows_by_level: HashMap<i32, HashSet<&Rc<PsqlTableRow>>>,
80  ) -> ResultAnyError<Vec<String>> {
81    let mut levels: Vec<i32> = rows_by_level.keys().cloned().collect();
82    let mut insert_statement_map: HashMap<String, bool> = Default::default();
83
84    levels.sort();
85
86    let insert_statements: ResultAnyError<Vec<Vec<String>>> = levels
87      .iter()
88      .map(|level| {
89        let rows: &mut HashSet<_> = rows_by_level.get_mut(level).unwrap();
90
91        rows.retain(|row| {
92          let row_key = format!("{}.{}", row.table.id, row.row_id_representation);
93          let found = insert_statement_map.contains_key(&row_key);
94
95          insert_statement_map.insert(row_key, true);
96
97          // We don't want duplicate insert statement
98          // TODO: Maybe we could use BTreeSet instead?
99          return !found;
100        });
101
102        return RelationInsert::table_rows_into_insert_statement(rows);
103      })
104      .collect();
105
106    return Ok(insert_statements?.into_iter().flatten().collect());
107  }
108
109  pub fn table_rows_into_insert_statement(
110    rows: &HashSet<&Rc<PsqlTableRow>>,
111  ) -> ResultAnyError<Vec<String>> {
112    // Rows of the same table can be scattered through vec of psql table rows,
113    // remember Vec<PsqlTableRows> meaning Vec<Vec<Row>> due to PsqlTableRows
114    // contains `rows: Vec<Row>`. So here we're trying to group
115    // scattered rows by table id
116    let psql_table_by_id: HashMap<PsqlTableIdentity, PsqlTable> = rows
117      .iter()
118      .map(|row| (row.table.id.clone(), row.table.clone()))
119      .collect();
120
121    let psql_rows_by_table_id: HashMap<PsqlTableIdentity, Vec<&Rc<PsqlTableRow>>> = rows
122      .iter()
123      .map(|psql_table_row| (psql_table_row.table.id.clone(), psql_table_row.clone()))
124      .into_group_map();
125
126    let rows_by_table_id: HashMap<PsqlTableIdentity, Vec<&Rc<PsqlTableRow>>> =
127      psql_rows_by_table_id
128        .into_iter()
129        .map(
130          |(table_identity, row): (PsqlTableIdentity, Vec<&Rc<PsqlTableRow>>)| {
131            return (table_identity, row);
132          },
133        )
134        .collect();
135
136    return rows_by_table_id
137      .iter()
138      .map(|(table_id, rows)| {
139        return RelationInsert::table_row_into_insert_statement(
140          psql_table_by_id.get(table_id).unwrap(),
141          rows,
142        );
143      })
144      .collect::<ResultAnyError<Vec<String>>>();
145  }
146
147  pub fn table_row_into_insert_statement(
148    table: &PsqlTable,
149    rows: &Vec<&Rc<PsqlTableRow>>,
150  ) -> ResultAnyError<String> {
151    let first_row: &PsqlTableRow = rows.get(0).unwrap();
152    let table_insert_row_columns = TableInsertRowColumns {
153      column_names: first_row.get_column_names(),
154    };
155
156    let row_values: Vec<TableInsertRowValues> = rows
157      .iter()
158      .map(|row| {
159        let column_value_map: HashMap<&str, FromSqlSink> = row.get_column_value_map();
160
161        // Use ordering on table insert row columns to preserve ordering
162        return table_insert_row_columns
163          .column_names
164          .iter()
165          .map(|column_name| {
166            let from_sql_sink = column_value_map.get(column_name).unwrap();
167
168            return from_sql_sink.to_string_for_statement();
169          })
170          .collect::<ResultAnyError<Vec<String>>>()
171          .map(|values_in_string| {
172            return TableInsertRowValues {
173              values: values_in_string,
174            };
175          });
176      })
177      .collect::<ResultAnyError<Vec<TableInsertRowValues>>>()?;
178
179    let table_insert_statement = TableInsertStatement {
180      table: table.clone(),
181      columns: table_insert_row_columns,
182      row_values,
183    };
184
185    return Ok(format!("{}", table_insert_statement));
186  }
187}