1use std::collections::{BTreeMap, BTreeSet};
4
5use crate::column::ColumnDef;
6use crate::error::DbCoreError;
7use crate::index::IndexDef;
8use crate::rename::{NoRenames, RenameResolver};
9use crate::schema::SchemaRegistry;
10use crate::snapshot::Snapshot;
11use crate::table::TableDef;
12
13use super::column::compute_column_changes;
14use super::enums::diff_enums;
15use super::fk::{diff_check_constraints_inner, diff_foreign_keys_inner};
16use super::operation::Operation;
17use super::virtual_tables::{check_new_virtual_table, check_virtual_pair};
18
19pub fn diff(
24 old_snapshot: &Snapshot,
25 new_schema: &SchemaRegistry,
26) -> Result<Vec<Operation>, DbCoreError> {
27 diff_with_resolver(old_snapshot, new_schema, &NoRenames)
28}
29
30pub fn diff_with_resolver(
35 old_snapshot: &Snapshot,
36 new_schema: &SchemaRegistry,
37 resolver: &impl RenameResolver,
38) -> Result<Vec<Operation>, DbCoreError> {
39 let new_snap = Snapshot::from_registry(new_schema);
40 let mut ops = diff_enums(old_snapshot, &new_snap);
41 ops.extend(diff_tables(old_snapshot, &new_snap, new_schema, resolver)?);
42 Ok(ops)
43}
44
45fn diff_tables(
46 old_snapshot: &Snapshot,
47 new_snap: &Snapshot,
48 new_schema: &SchemaRegistry,
49 resolver: &impl RenameResolver,
50) -> Result<Vec<Operation>, DbCoreError> {
51 let mut ops = Vec::new();
52 let old_names: BTreeSet<String> = old_snapshot.tables.keys().cloned().collect();
53 let new_names: BTreeSet<String> = new_snap.tables.keys().cloned().collect();
54
55 let mut added: Vec<String> = new_names.difference(&old_names).cloned().collect();
56 let mut removed: Vec<String> = old_names.difference(&new_names).cloned().collect();
57
58 let renames = resolver.resolve_tables(&added, &removed);
59 for (old_n, new_n) in &renames {
60 added.retain(|x| x != new_n);
61 removed.retain(|x| x != old_n);
62 ops.push(Operation::RenameTable {
63 old: old_n.clone(),
64 new: new_n.clone(),
65 });
66 }
67
68 for name in &removed {
69 ops.push(Operation::DropTable { name: name.clone() });
70 }
71
72 let mut new_to_old: BTreeMap<String, String> = BTreeMap::new();
73 for t in new_schema.tables() {
74 new_to_old.insert(t.name.clone(), t.name.clone());
75 }
76 for (o, n) in &renames {
77 new_to_old.insert(n.clone(), o.clone());
78 }
79
80 let pure_added: BTreeSet<String> = added.iter().cloned().collect();
81
82 for table in new_schema.tables() {
83 if pure_added.contains(&table.name) {
84 check_new_virtual_table(table)?;
85 ops.push(Operation::CreateTable {
86 table: table.clone(),
87 });
88 for idx in &table.indexes {
89 ops.push(Operation::CreateIndex {
90 table: table.name.clone(),
91 index: idx.clone(),
92 });
93 }
94 continue;
95 }
96
97 let old_name = new_to_old
98 .get(&table.name)
99 .cloned()
100 .unwrap_or_else(|| table.name.clone());
101 let Some(old_st) = old_snapshot.tables.get(&old_name) else {
102 continue;
103 };
104 let Some(new_st) = new_snap.tables.get(&table.name) else {
105 continue;
106 };
107 if check_virtual_pair(&table.name, old_st, new_st)? {
108 continue;
109 }
110
111 diff_columns_for_table(
112 &mut ops,
113 &table.name,
114 &old_st.columns,
115 &new_st.columns,
116 table,
117 resolver,
118 );
119 diff_indexes_inner(&mut ops, &table.name, &old_st.indexes, &new_st.indexes);
120 diff_foreign_keys_inner(
121 &mut ops,
122 &table.name,
123 &old_st.foreign_keys,
124 &new_st.foreign_keys,
125 );
126 diff_check_constraints_inner(
127 &mut ops,
128 &table.name,
129 &old_st.check_constraints,
130 &new_st.check_constraints,
131 );
132 }
133
134 Ok(ops)
135}
136
137fn diff_columns_for_table(
138 ops: &mut Vec<Operation>,
139 table_name: &str,
140 old_cols: &BTreeMap<String, ColumnDef>,
141 new_cols: &BTreeMap<String, ColumnDef>,
142 new_table_def: &TableDef,
143 resolver: &impl RenameResolver,
144) {
145 let added: Vec<String> = new_cols
146 .keys()
147 .filter(|k| !old_cols.contains_key(*k))
148 .cloned()
149 .collect();
150 let removed: Vec<String> = old_cols
151 .keys()
152 .filter(|k| !new_cols.contains_key(*k))
153 .cloned()
154 .collect();
155
156 let renames = resolver.resolve_columns(table_name, &added, &removed);
157 let renamed_old: Vec<&str> = renames.iter().map(|(o, _)| o.as_str()).collect();
158 let renamed_new: Vec<&str> = renames.iter().map(|(_, n)| n.as_str()).collect();
159
160 for (old_name, new_name) in &renames {
161 ops.push(Operation::RenameColumn {
162 table: table_name.to_owned(),
163 old: old_name.clone(),
164 new: new_name.clone(),
165 });
166 }
167
168 for name in &removed {
169 if !renamed_old.contains(&name.as_str()) {
170 ops.push(Operation::DropColumn {
171 table: table_name.to_owned(),
172 column: name.clone(),
173 });
174 }
175 }
176
177 for name in &added {
178 if !renamed_new.contains(&name.as_str()) {
179 if let Some(col) = new_cols.get(name) {
180 ops.push(Operation::AddColumn {
181 table: table_name.to_owned(),
182 column: col.clone(),
183 });
184 }
185 }
186 }
187
188 for name in new_cols.keys() {
189 let Some(old_col) = old_cols.get(name) else {
190 continue;
191 };
192 let new_col = &new_cols[name];
193 let changes = compute_column_changes(name, old_col, new_col);
194 if !changes.is_empty() {
195 ops.push(Operation::AlterColumn {
196 table: table_name.to_owned(),
197 changes,
198 table_def: new_table_def.clone(),
199 });
200 }
201 }
202}
203
204fn diff_indexes_inner(
205 ops: &mut Vec<Operation>,
206 table_name: &str,
207 old_indexes: &BTreeMap<String, IndexDef>,
208 new_indexes: &BTreeMap<String, IndexDef>,
209) {
210 for (name, old_idx) in old_indexes {
211 match new_indexes.get(name) {
212 None => {
213 ops.push(Operation::DropIndex { name: name.clone() });
214 },
215 Some(new_idx) if new_idx != old_idx => {
216 ops.push(Operation::DropIndex { name: name.clone() });
217 ops.push(Operation::CreateIndex {
218 table: table_name.to_owned(),
219 index: new_idx.clone(),
220 });
221 },
222 _ => {},
223 }
224 }
225 for (name, new_idx) in new_indexes {
226 if !old_indexes.contains_key(name) {
227 ops.push(Operation::CreateIndex {
228 table: table_name.to_owned(),
229 index: new_idx.clone(),
230 });
231 }
232 }
233}