Skip to main content

toasty_core/schema/diff/
indices.rs

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