Skip to main content

toasty_core/schema/
diff.rs

1//! Schema-diff types.
2//!
3//! Compares two [`db::Schema`](super::db::Schema) versions and produces
4//! structured changes consumed by drivers to generate migrations.
5//!
6//! # Examples
7//!
8//! ```ignore
9//! use toasty_core::schema::{db, diff};
10//!
11//! let previous = db::Schema::default();
12//! let next = db::Schema::default();
13//! let hints = diff::RenameHints::new();
14//! let d = diff::Schema::from(&previous, &next, &hints);
15//! assert!(d.is_empty());
16//! ```
17
18mod column;
19mod index;
20mod schema;
21mod table;
22mod ty;
23
24pub use column::Column;
25pub use index::Index;
26pub use schema::Schema;
27pub use table::Table;
28pub use ty::Type;
29
30use hashbrown::HashMap;
31
32use crate::schema::db::{ColumnId, IndexId, Schema as DbSchema, TableId};
33
34/// Hints that tell the diff algorithm which schema items were renamed.
35///
36/// Without rename hints, a renamed table/column/index appears as a drop
37/// followed by a create. Adding a hint maps the old ID to the new ID so
38/// the diff produces an alter instead.
39///
40/// # Examples
41///
42/// ```ignore
43/// use toasty_core::schema::{db::TableId, diff};
44///
45/// let mut hints = diff::RenameHints::new();
46/// hints.add_table_hint(TableId(0), TableId(1));
47/// ```
48#[derive(Default)]
49pub struct RenameHints {
50    tables: HashMap<TableId, TableId>,
51    columns: HashMap<ColumnId, ColumnId>,
52    indices: HashMap<IndexId, IndexId>,
53}
54
55impl RenameHints {
56    /// Creates an empty set of rename hints.
57    pub fn new() -> Self {
58        Self::default()
59    }
60
61    /// Records that the table previously identified by `from` is now identified by `to`.
62    pub fn add_table_hint(&mut self, from: TableId, to: TableId) {
63        self.tables.insert(from, to);
64    }
65
66    /// Records that the column previously identified by `from` is now identified by `to`.
67    pub fn add_column_hint(&mut self, from: ColumnId, to: ColumnId) {
68        self.columns.insert(from, to);
69    }
70
71    /// Records that the index previously identified by `from` is now identified by `to`.
72    pub fn add_index_hint(&mut self, from: IndexId, to: IndexId) {
73        self.indices.insert(from, to);
74    }
75
76    /// Returns the new [`TableId`] if a rename hint exists for `from`.
77    pub(super) fn get_table(&self, from: TableId) -> Option<TableId> {
78        self.tables.get(&from).copied()
79    }
80
81    /// Returns the new [`ColumnId`] if a rename hint exists for `from`.
82    pub(super) fn get_column(&self, from: ColumnId) -> Option<ColumnId> {
83        self.columns.get(&from).copied()
84    }
85
86    /// Returns the new [`IndexId`] if a rename hint exists for `from`.
87    pub(super) fn get_index(&self, from: IndexId) -> Option<IndexId> {
88        self.indices.get(&from).copied()
89    }
90}
91
92/// Shared context passed to all diff computations.
93///
94/// Holds references to both the previous and next [`db::Schema`](super::db::Schema)
95/// versions and the [`RenameHints`] that guide rename detection.
96///
97/// # Examples
98///
99/// ```ignore
100/// use toasty_core::schema::{db, diff};
101///
102/// let previous = db::Schema::default();
103/// let next = db::Schema::default();
104/// let hints = diff::RenameHints::new();
105/// let cx = diff::Context::new(&previous, &next, &hints);
106/// assert!(cx.next().tables.is_empty());
107/// ```
108pub struct Context<'a> {
109    previous: &'a DbSchema,
110    next: &'a DbSchema,
111
112    rename_hints: &'a RenameHints,
113}
114
115impl<'a> Context<'a> {
116    /// Returns the rename hints for this diff.
117    pub(super) fn rename_hints(&self) -> &'a RenameHints {
118        self.rename_hints
119    }
120
121    /// Returns the schema before the change.
122    pub(super) fn previous(&self) -> &'a DbSchema {
123        self.previous
124    }
125
126    /// Creates a new diff context from the previous schema, the next schema,
127    /// and the rename hints that map old IDs to new IDs.
128    pub fn new(previous: &'a DbSchema, next: &'a DbSchema, rename_hints: &'a RenameHints) -> Self {
129        Self {
130            previous,
131            next,
132            rename_hints,
133        }
134    }
135
136    /// Returns the schema after the change.
137    pub fn next(&self) -> &'a DbSchema {
138        self.next
139    }
140}