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 columns;
19mod indices;
20mod schema;
21mod tables;
22mod types;
23
24pub use columns::{Columns, ColumnsItem};
25pub use indices::{Indices, IndicesItem};
26pub use schema::Schema;
27pub use tables::{Tables, TablesItem};
28pub use types::{Types, TypesItem};
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/// assert_eq!(hints.get_table(TableId(0)), Some(TableId(1)));
48/// assert_eq!(hints.get_table(TableId(2)), None);
49/// ```
50#[derive(Default)]
51pub struct RenameHints {
52    tables: HashMap<TableId, TableId>,
53    columns: HashMap<ColumnId, ColumnId>,
54    indices: HashMap<IndexId, IndexId>,
55}
56
57impl RenameHints {
58    /// Creates an empty set of rename hints.
59    pub fn new() -> Self {
60        Self::default()
61    }
62
63    /// Records that the table previously identified by `from` is now identified by `to`.
64    pub fn add_table_hint(&mut self, from: TableId, to: TableId) {
65        self.tables.insert(from, to);
66    }
67
68    /// Records that the column previously identified by `from` is now identified by `to`.
69    pub fn add_column_hint(&mut self, from: ColumnId, to: ColumnId) {
70        self.columns.insert(from, to);
71    }
72
73    /// Records that the index previously identified by `from` is now identified by `to`.
74    pub fn add_index_hint(&mut self, from: IndexId, to: IndexId) {
75        self.indices.insert(from, to);
76    }
77
78    /// Returns the new [`TableId`] if a rename hint exists for `from`.
79    pub fn get_table(&self, from: TableId) -> Option<TableId> {
80        self.tables.get(&from).copied()
81    }
82
83    /// Returns the new [`ColumnId`] if a rename hint exists for `from`.
84    pub fn get_column(&self, from: ColumnId) -> Option<ColumnId> {
85        self.columns.get(&from).copied()
86    }
87
88    /// Returns the new [`IndexId`] if a rename hint exists for `from`.
89    pub fn get_index(&self, from: IndexId) -> Option<IndexId> {
90        self.indices.get(&from).copied()
91    }
92}
93
94/// Shared context passed to all diff computations.
95///
96/// Holds references to both the previous and next [`db::Schema`](super::db::Schema)
97/// versions and the [`RenameHints`] that guide rename detection.
98///
99/// # Examples
100///
101/// ```ignore
102/// use toasty_core::schema::{db, diff};
103///
104/// let previous = db::Schema::default();
105/// let next = db::Schema::default();
106/// let hints = diff::RenameHints::new();
107/// let cx = diff::Context::new(&previous, &next, &hints);
108/// assert!(cx.previous().tables.is_empty());
109/// ```
110pub struct Context<'a> {
111    previous: &'a DbSchema,
112    next: &'a DbSchema,
113
114    rename_hints: &'a RenameHints,
115}
116
117impl<'a> Context<'a> {
118    /// Creates a new diff context from the previous schema, the next schema,
119    /// and the rename hints that map old IDs to new IDs.
120    pub fn new(previous: &'a DbSchema, next: &'a DbSchema, rename_hints: &'a RenameHints) -> Self {
121        Self {
122            previous,
123            next,
124            rename_hints,
125        }
126    }
127
128    /// Returns the rename hints for this diff.
129    pub fn rename_hints(&self) -> &'a RenameHints {
130        self.rename_hints
131    }
132
133    /// Returns the schema before the change.
134    pub fn previous(&self) -> &'a DbSchema {
135        self.previous
136    }
137
138    /// Returns the schema after the change.
139    pub fn next(&self) -> &'a DbSchema {
140        self.next
141    }
142}