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}