Skip to main content

toasty_core/schema/diff/
columns.rs

1use super::Context;
2use crate::schema::db::Column;
3
4use hashbrown::{HashMap, HashSet};
5use std::ops::Deref;
6
7/// The set of differences between two column lists.
8///
9/// Computed by [`Columns::from`] and dereferences to `Vec<ColumnsItem>` 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::Columns::from(&cx, &[], &[]);
22/// assert!(d.is_empty());
23/// ```
24pub struct Columns<'a> {
25    items: Vec<ColumnsItem<'a>>,
26}
27
28impl<'a> Columns<'a> {
29    /// Computes the diff between two column slices.
30    ///
31    /// Uses [`Context`] to resolve rename hints. Columns matched by name (or
32    /// by rename hint) are compared field-by-field; unmatched columns in
33    /// `previous` become drops, and unmatched columns in `next` become adds.
34    pub fn from(cx: &Context<'a>, previous: &'a [Column], next: &'a [Column]) -> Self {
35        fn has_diff(previous: &Column, next: &Column) -> bool {
36            previous.name != next.name
37                || previous.storage_ty != next.storage_ty
38                || previous.nullable != next.nullable
39                || previous.primary_key != next.primary_key
40                || previous.auto_increment != next.auto_increment
41                || previous.versionable != next.versionable
42        }
43
44        let mut items = vec![];
45        let mut add_ids: HashSet<_> = next.iter().map(|next| next.id).collect();
46
47        let next_map =
48            HashMap::<&str, &'a Column>::from_iter(next.iter().map(|to| (to.name.as_str(), to)));
49
50        for previous in previous {
51            let next = if let Some(next_id) = cx.rename_hints().get_column(previous.id) {
52                cx.next().column(next_id)
53            } else if let Some(next) = next_map.get(previous.name.as_str()) {
54                next
55            } else {
56                items.push(ColumnsItem::DropColumn(previous));
57                continue;
58            };
59
60            add_ids.remove(&next.id);
61
62            if has_diff(previous, next) {
63                items.push(ColumnsItem::AlterColumn { previous, next });
64            }
65        }
66
67        for column_id in add_ids {
68            items.push(ColumnsItem::AddColumn(cx.next().column(column_id)));
69        }
70
71        Self { items }
72    }
73
74    /// Returns `true` if there are no column changes.
75    pub const fn is_empty(&self) -> bool {
76        self.items.is_empty()
77    }
78}
79
80impl<'a> Deref for Columns<'a> {
81    type Target = Vec<ColumnsItem<'a>>;
82
83    fn deref(&self) -> &Self::Target {
84        &self.items
85    }
86}
87
88/// A single change detected between two column lists.
89pub enum ColumnsItem<'a> {
90    /// A new column was added.
91    AddColumn(&'a Column),
92    /// An existing column was removed.
93    DropColumn(&'a Column),
94    /// A column was modified (name, type, nullability, or other property changed).
95    AlterColumn {
96        /// The column definition before the change.
97        previous: &'a Column,
98        /// The column definition after the change.
99        next: &'a Column,
100    },
101}