Skip to main content

toasty_core/schema/diff/
index.rs

1use super::Context;
2use crate::schema::db;
3
4use hashbrown::{HashMap, HashSet};
5
6/// A single index-level change between two table versions.
7///
8/// Computed by [`Index::diff`].
9pub enum Index<'a> {
10    /// A new index was created.
11    Create(&'a db::Index),
12    /// An existing index was dropped.
13    Drop(&'a db::Index),
14    /// An index was modified (name, columns, uniqueness, or other property changed).
15    Alter {
16        /// The index definition before the change.
17        previous: &'a db::Index,
18        /// The index definition after the change.
19        next: &'a db::Index,
20    },
21}
22
23impl<'a> Index<'a> {
24    /// Computes the diff between two index slices.
25    ///
26    /// Uses [`Context`] to resolve rename hints for both indices and columns.
27    /// Indices matched by name (or by rename hint) are compared; unmatched
28    /// indices in `previous` become drops, and unmatched indices in `next`
29    /// become creates.
30    ///
31    /// # Examples
32    ///
33    /// ```ignore
34    /// use toasty_core::schema::{db, diff};
35    ///
36    /// let previous = db::Schema::default();
37    /// let next = db::Schema::default();
38    /// let hints = diff::RenameHints::new();
39    /// let cx = diff::Context::new(&previous, &next, &hints);
40    /// assert!(diff::Index::diff(&cx, &[], &[]).is_empty());
41    /// ```
42    pub fn diff(cx: &Context<'a>, previous: &'a [db::Index], next: &'a [db::Index]) -> Vec<Self> {
43        fn has_diff(cx: &Context<'_>, previous: &db::Index, next: &db::Index) -> bool {
44            if previous.name != next.name
45                || previous.columns.len() != next.columns.len()
46                || previous.unique != next.unique
47                || previous.primary_key != next.primary_key
48            {
49                return true;
50            }
51
52            for (previous_col, next_col) in previous.columns.iter().zip(next.columns.iter()) {
53                if previous_col.op != next_col.op || previous_col.scope != next_col.scope {
54                    return true;
55                }
56
57                let columns_match =
58                    if let Some(renamed_to) = cx.rename_hints().get_column(previous_col.column) {
59                        renamed_to == next_col.column
60                    } else {
61                        let previous_column = cx.previous().column(previous_col.column);
62                        let next_column = cx.next().column(next_col.column);
63                        previous_column.name == next_column.name
64                    };
65
66                if !columns_match {
67                    return true;
68                }
69            }
70
71            false
72        }
73
74        let mut changes = vec![];
75        let mut create_ids: HashSet<_> = next.iter().map(|to| to.id).collect();
76
77        let next_map =
78            HashMap::<&str, &'a db::Index>::from_iter(next.iter().map(|to| (to.name.as_str(), to)));
79
80        for previous in previous {
81            let next = if let Some(next_id) = cx.rename_hints().get_index(previous.id) {
82                cx.next().index(next_id)
83            } else if let Some(next) = next_map.get(previous.name.as_str()) {
84                next
85            } else {
86                changes.push(Self::Drop(previous));
87                continue;
88            };
89
90            create_ids.remove(&next.id);
91
92            if has_diff(cx, previous, next) {
93                changes.push(Self::Alter { previous, next });
94            }
95        }
96
97        for index_id in create_ids {
98            changes.push(Self::Create(cx.next().index(index_id)));
99        }
100
101        changes
102    }
103}