Skip to main content

toasty_core/schema/diff/
table.rs

1use super::{Column, Context, Index};
2use crate::schema::db;
3
4use hashbrown::{HashMap, HashSet};
5
6/// A single table-level change between two schema versions.
7///
8/// Computed by [`Table::diff`].
9pub enum Table<'a> {
10    /// A new table was created.
11    Create(&'a db::Table),
12    /// An existing table was dropped.
13    Drop(&'a db::Table),
14    /// A table was modified (name, columns, or indices changed).
15    Alter {
16        /// The table definition before the change.
17        previous: &'a db::Table,
18        /// The table definition after the change.
19        next: &'a db::Table,
20        /// Column-level changes within this table.
21        columns: Vec<Column<'a>>,
22        /// Index-level changes within this table.
23        indices: Vec<Index<'a>>,
24    },
25}
26
27impl<'a> Table<'a> {
28    /// Computes the diff between two table slices.
29    ///
30    /// Uses [`Context`] to resolve rename hints. Tables matched by name (or
31    /// by rename hint) are compared for column and index changes; unmatched
32    /// tables in `previous` become drops, and unmatched tables in `next`
33    /// become creates.
34    ///
35    /// # Examples
36    ///
37    /// ```ignore
38    /// use toasty_core::schema::{db, diff};
39    ///
40    /// let previous = db::Schema::default();
41    /// let next = db::Schema::default();
42    /// let hints = diff::RenameHints::new();
43    /// let cx = diff::Context::new(&previous, &next, &hints);
44    /// assert!(diff::Table::diff(&cx, &[], &[]).is_empty());
45    /// ```
46    pub fn diff(cx: &Context<'a>, previous: &'a [db::Table], next: &'a [db::Table]) -> Vec<Self> {
47        let mut changes = vec![];
48        let mut create_ids: HashSet<_> = next.iter().map(|next| next.id).collect();
49
50        let next_map = HashMap::<&str, &'a db::Table>::from_iter(
51            next.iter().map(|next| (next.name.as_str(), next)),
52        );
53
54        for previous in previous {
55            let next = if let Some(next_id) = cx.rename_hints().get_table(previous.id) {
56                cx.next().table(next_id)
57            } else if let Some(to) = next_map.get(previous.name.as_str()) {
58                to
59            } else {
60                changes.push(Self::Drop(previous));
61                continue;
62            };
63
64            create_ids.remove(&next.id);
65
66            let columns = Column::diff(cx, &previous.columns, &next.columns);
67            let indices = Index::diff(cx, &previous.indices, &next.indices);
68            if previous.name != next.name || !columns.is_empty() || !indices.is_empty() {
69                changes.push(Self::Alter {
70                    previous,
71                    next,
72                    columns,
73                    indices,
74                });
75            }
76        }
77
78        for table_id in create_ids {
79            changes.push(Self::Create(cx.next().table(table_id)));
80        }
81
82        changes
83    }
84}