1use std::collections::{BTreeSet, HashMap, HashSet};
2
3use thiserror::Error;
4
5use crate::dialects::Dialect;
6use crate::opaque::{
7 opaque_option_sources_equal, opaque_sources_equal, table_option_sources_equal,
8};
9use crate::operations::Operation;
10use crate::states::{
11 Column, Constraint, EnumDef, ExtensionDef, FunctionDef, Schema, Table, TriggerDef, ViewDef,
12};
13
14#[derive(Debug, Error)]
15pub enum DiffError {
16 #[error("dependency cycle detected among operations — this is a bug in the diff engine")]
17 DependencyCycle,
18 #[error(
19 "primary key changes on existing table '{0}' are not generated automatically; use an explicit SQL statement migration"
20 )]
21 PrimaryKeyMutation(String),
22}
23
24pub fn generate_diff(current: &Schema, previous: &Schema) -> Vec<Operation> {
28 let mut ops: Vec<Operation> = Vec::new();
29
30 diff_map_with_eq(
31 ¤t.extensions,
32 &previous.extensions,
33 &mut ops,
34 extension_equal,
35 diff_extension,
36 );
37 diff_map(¤t.enums, &previous.enums, &mut ops, diff_enum);
38 diff_map_with_eq(
39 ¤t.functions,
40 &previous.functions,
41 &mut ops,
42 function_equal,
43 diff_function,
44 );
45 diff_map_with_eq(
46 ¤t.views,
47 &previous.views,
48 &mut ops,
49 view_equal,
50 diff_view,
51 );
52 diff_map(
53 ¤t.tables,
54 &previous.tables,
55 &mut ops,
56 |curr, prev, ops| diff_table(curr, prev, ops, None),
57 );
58
59 ops
60}
61
62fn diff_map<T: PartialEq>(
63 current: &std::collections::BTreeMap<String, T>,
64 previous: &std::collections::BTreeMap<String, T>,
65 ops: &mut Vec<Operation>,
66 handler: fn(Option<&T>, Option<&T>, &mut Vec<Operation>),
67) {
68 for (key, curr_val) in current {
69 match previous.get(key) {
70 None => handler(Some(curr_val), None, ops),
71 Some(prev_val) if prev_val != curr_val => handler(Some(curr_val), Some(prev_val), ops),
72 _ => {}
73 }
74 }
75 for (key, prev_val) in previous {
76 if !current.contains_key(key) {
77 handler(None, Some(prev_val), ops);
78 }
79 }
80}
81
82fn diff_map_with_eq<T>(
83 current: &std::collections::BTreeMap<String, T>,
84 previous: &std::collections::BTreeMap<String, T>,
85 ops: &mut Vec<Operation>,
86 equal: fn(&T, &T) -> bool,
87 handler: fn(Option<&T>, Option<&T>, &mut Vec<Operation>),
88) {
89 for (key, curr_val) in current {
90 match previous.get(key) {
91 None => handler(Some(curr_val), None, ops),
92 Some(prev_val) if !equal(curr_val, prev_val) => {
93 handler(Some(curr_val), Some(prev_val), ops)
94 }
95 _ => {}
96 }
97 }
98 for (key, prev_val) in previous {
99 if !current.contains_key(key) {
100 handler(None, Some(prev_val), ops);
101 }
102 }
103}
104
105fn diff_extension(
106 curr: Option<&ExtensionDef>,
107 prev: Option<&ExtensionDef>,
108 ops: &mut Vec<Operation>,
109) {
110 match (curr, prev) {
111 (Some(c), None) => ops.push(Operation::CreateExtension {
112 extension: c.clone(),
113 }),
114 (None, Some(p)) => ops.push(Operation::DropExtension {
115 extension: p.clone(),
116 }),
117 (Some(c), Some(p)) => {
118 ops.push(Operation::DropExtension {
119 extension: p.clone(),
120 });
121 ops.push(Operation::CreateExtension {
122 extension: c.clone(),
123 });
124 }
125 _ => {}
126 }
127}
128
129fn diff_enum(curr: Option<&EnumDef>, prev: Option<&EnumDef>, ops: &mut Vec<Operation>) {
130 match (curr, prev) {
131 (Some(c), None) => ops.push(Operation::CreateEnum {
132 enum_def: c.clone(),
133 }),
134 (None, Some(p)) => ops.push(Operation::DropEnum {
135 enum_def: p.clone(),
136 }),
137 (Some(c), Some(p)) => {
138 let old_set: HashSet<&str> = p.values.iter().map(|v| v.as_str()).collect();
139 let new_set: HashSet<&str> = c.values.iter().map(|v| v.as_str()).collect();
140 if old_set.is_subset(&new_set) && values_are_subsequence(&p.values, &c.values) {
141 ops.push(Operation::AlterEnum {
142 old: p.clone(),
143 new: c.clone(),
144 });
145 } else {
146 ops.push(Operation::DropEnum {
147 enum_def: p.clone(),
148 });
149 ops.push(Operation::CreateEnum {
150 enum_def: c.clone(),
151 });
152 }
153 }
154 _ => {}
155 }
156}
157
158fn values_are_subsequence(old: &[String], new: &[String]) -> bool {
159 let mut new_iter = new.iter();
160 old.iter()
161 .all(|old_value| new_iter.by_ref().any(|new_value| new_value == old_value))
162}
163
164fn diff_function(curr: Option<&FunctionDef>, prev: Option<&FunctionDef>, ops: &mut Vec<Operation>) {
165 match (curr, prev) {
166 (Some(c), None) => ops.push(Operation::CreateFunction {
167 function: c.clone(),
168 }),
169 (None, Some(p)) => ops.push(Operation::DropFunction {
170 function: p.clone(),
171 }),
172 (Some(c), Some(p)) => {
173 ops.push(Operation::AlterFunction {
174 old: p.clone(),
175 new: c.clone(),
176 });
177 }
178 _ => {}
179 }
180}
181
182fn diff_view(curr: Option<&ViewDef>, prev: Option<&ViewDef>, ops: &mut Vec<Operation>) {
183 match (curr, prev) {
184 (Some(c), None) => ops.push(Operation::CreateView { view: c.clone() }),
185 (None, Some(p)) => ops.push(Operation::DropView { view: p.clone() }),
186 (Some(c), Some(p)) => {
187 ops.push(Operation::ReplaceView {
188 old: p.clone(),
189 new: c.clone(),
190 });
191 }
192 _ => {}
193 }
194}
195
196fn diff_table(
197 curr: Option<&Table>,
198 prev: Option<&Table>,
199 ops: &mut Vec<Operation>,
200 dialect: Option<&Dialect>,
201) {
202 match (curr, prev) {
203 (Some(c), None) => ops.push(Operation::CreateTable { table: c.clone() }),
204 (None, Some(p)) => ops.push(Operation::DropTable { table: p.clone() }),
205 (Some(c), Some(p)) => diff_table_children(p, c, ops, dialect),
206 _ => {}
207 }
208}
209
210fn diff_table_children(
211 prev: &Table,
212 curr: &Table,
213 ops: &mut Vec<Operation>,
214 dialect: Option<&Dialect>,
215) {
216 let table_name = curr.qualified_name();
217 if !table_option_sources_equal(
218 &prev.options.header_raw,
219 &prev.options.tail_raw,
220 &curr.options.header_raw,
221 &curr.options.tail_raw,
222 ) {
223 ops.push(Operation::AcknowledgeTableOptions {
224 table_name: table_name.clone(),
225 old: prev.options.clone(),
226 new: curr.options.clone(),
227 });
228 }
229
230 for change in diff_by_name(
231 &prev.columns,
232 &curr.columns,
233 |c| c.name.as_str(),
234 |left, right| column_equal(left, right, dialect),
235 ) {
236 match change {
237 SubChange::Removed(col) => ops.push(Operation::DropColumn {
238 table_name: table_name.clone(),
239 column: col.clone(),
240 cascade: false,
241 }),
242 SubChange::Added(col) => ops.push(Operation::AddColumn {
243 table_name: table_name.clone(),
244 column: col.clone(),
245 }),
246 SubChange::Modified(old, new) => ops.push(Operation::AlterColumn {
247 table_name: table_name.clone(),
248 old: old.clone(),
249 new: new.clone(),
250 cast_expr: None,
251 }),
252 }
253 }
254
255 for change in diff_by_name(
256 &prev.foreign_keys,
257 &curr.foreign_keys,
258 |fk| fk.name.as_str(),
259 PartialEq::eq,
260 ) {
261 match change {
262 SubChange::Removed(fk) => ops.push(Operation::DropForeignKey {
263 table_name: table_name.clone(),
264 foreign_key: fk.clone(),
265 cascade: false,
266 }),
267 SubChange::Added(fk) => ops.push(Operation::AddForeignKey {
268 table_name: table_name.clone(),
269 foreign_key: fk.clone(),
270 }),
271 SubChange::Modified(old, new) => {
272 ops.push(Operation::DropForeignKey {
273 table_name: table_name.clone(),
274 foreign_key: old.clone(),
275 cascade: false,
276 });
277 ops.push(Operation::AddForeignKey {
278 table_name: table_name.clone(),
279 foreign_key: new.clone(),
280 });
281 }
282 }
283 }
284
285 for change in diff_by_name(
286 &prev.indexes,
287 &curr.indexes,
288 |i| i.name.as_str(),
289 index_equal,
290 ) {
291 match change {
292 SubChange::Removed(idx) => ops.push(Operation::DropIndex {
293 table_name: table_name.clone(),
294 index: idx.clone(),
295 concurrent: false,
296 }),
297 SubChange::Added(idx) => ops.push(Operation::AddIndex {
298 table_name: table_name.clone(),
299 index: idx.clone(),
300 concurrent: false,
301 }),
302 SubChange::Modified(old, new) => {
303 ops.push(Operation::DropIndex {
304 table_name: table_name.clone(),
305 index: old.clone(),
306 concurrent: false,
307 });
308 ops.push(Operation::AddIndex {
309 table_name: table_name.clone(),
310 index: new.clone(),
311 concurrent: false,
312 });
313 }
314 }
315 }
316
317 for change in diff_by_name(
318 &prev.constraints,
319 &curr.constraints,
320 |c| c.name(),
321 constraint_equal,
322 ) {
323 match change {
324 SubChange::Removed(con) => ops.push(Operation::DropConstraint {
325 table_name: table_name.clone(),
326 constraint: con.clone(),
327 }),
328 SubChange::Added(con) => ops.push(Operation::AddConstraint {
329 table_name: table_name.clone(),
330 constraint: con.clone(),
331 }),
332 SubChange::Modified(old, new) => {
333 ops.push(Operation::DropConstraint {
334 table_name: table_name.clone(),
335 constraint: old.clone(),
336 });
337 ops.push(Operation::AddConstraint {
338 table_name: table_name.clone(),
339 constraint: new.clone(),
340 });
341 }
342 }
343 }
344
345 for change in diff_by_name(
346 &prev.triggers,
347 &curr.triggers,
348 |t| t.name.as_deref().unwrap_or(""),
349 trigger_equal,
350 ) {
351 match change {
352 SubChange::Removed(trg) => ops.push(Operation::DropTrigger {
353 table_name: table_name.clone(),
354 trigger: trg.clone(),
355 }),
356 SubChange::Added(trg) => ops.push(Operation::CreateTrigger {
357 table_name: table_name.clone(),
358 trigger: trg.clone(),
359 }),
360 SubChange::Modified(old, new) => ops.push(Operation::AlterTrigger {
361 table_name: table_name.clone(),
362 old: old.clone(),
363 new: new.clone(),
364 }),
365 }
366 }
367}
368
369fn column_equal(left: &Column, right: &Column, dialect: Option<&Dialect>) -> bool {
370 let type_equal = dialect.map_or_else(
371 || left.col_type == right.col_type,
372 |dialect| {
373 dialect.type_comparison_key(&left.col_type)
374 == dialect.type_comparison_key(&right.col_type)
375 },
376 );
377 type_equal
378 && left.name == right.name
379 && left.nullable == right.nullable
380 && dialect.map_or_else(
381 || left.default == right.default,
382 |dialect| match (&left.default, &right.default) {
383 (Some(left), Some(right)) => dialect.default_expressions_equal(left, right),
384 (None, None) => true,
385 _ => false,
386 },
387 )
388 && left.primary_key == right.primary_key
389 && left.references == right.references
390 && left.check == right.check
391 && left.generated == right.generated
392}
393
394fn function_equal(left: &FunctionDef, right: &FunctionDef) -> bool {
395 if left.is_opaque() || right.is_opaque() {
396 return left.qualified_name() == right.qualified_name()
397 && opaque_option_sources_equal(&left.opaque.raw, &right.opaque.raw);
398 }
399 left.name == right.name
400 && left.schema == right.schema
401 && left.arguments == right.arguments
402 && left.returns == right.returns
403 && left.language == right.language
404 && left.volatility == right.volatility
405 && left.security_definer == right.security_definer
406 && opaque_sources_equal(&left.body, &right.body)
407}
408
409fn view_equal(left: &ViewDef, right: &ViewDef) -> bool {
410 if left.is_opaque() || right.is_opaque() {
411 return left.qualified_name() == right.qualified_name()
412 && opaque_option_sources_equal(&left.opaque.raw, &right.opaque.raw);
413 }
414 left.name == right.name
415 && left.schema == right.schema
416 && opaque_sources_equal(&left.definition, &right.definition)
417}
418
419fn trigger_equal(left: &TriggerDef, right: &TriggerDef) -> bool {
420 if left.is_opaque() || right.is_opaque() {
421 return left.name == right.name
422 && opaque_option_sources_equal(&left.opaque.raw, &right.opaque.raw);
423 }
424 left.name == right.name
425 && left.timing == right.timing
426 && left.events == right.events
427 && left.scope == right.scope
428 && left.function_name == right.function_name
429 && opaque_option_sources_equal(&left.when, &right.when)
430 && opaque_option_sources_equal(&left.query, &right.query)
431 && left.language == right.language
432}
433
434fn constraint_equal(left: &Constraint, right: &Constraint) -> bool {
435 if left.is_opaque() || right.is_opaque() {
436 let left_raw = left.raw_sql().map(ToOwned::to_owned);
437 let right_raw = right.raw_sql().map(ToOwned::to_owned);
438 return left.name() == right.name() && opaque_option_sources_equal(&left_raw, &right_raw);
439 }
440 left == right
441}
442
443fn extension_equal(left: &ExtensionDef, right: &ExtensionDef) -> bool {
444 if left.is_opaque() || right.is_opaque() {
445 return left.qualified_name() == right.qualified_name()
446 && opaque_option_sources_equal(&left.opaque.raw, &right.opaque.raw);
447 }
448 left == right
449}
450
451fn index_equal(left: &crate::states::Index, right: &crate::states::Index) -> bool {
452 if left.is_opaque() || right.is_opaque() {
453 return left.name == right.name
454 && opaque_option_sources_equal(&left.opaque.raw, &right.opaque.raw);
455 }
456 left == right
457}
458
459enum SubChange<'a, T> {
460 Added(&'a T),
461 Removed(&'a T),
462 Modified(&'a T, &'a T),
463}
464
465fn diff_by_name<'a, T>(
466 prev: &'a [T],
467 curr: &'a [T],
468 name_fn: impl Fn(&T) -> &str,
469 equal: impl Fn(&T, &T) -> bool,
470) -> Vec<SubChange<'a, T>> {
471 let prev_map: HashMap<&str, &T> = prev.iter().map(|v| (name_fn(v), v)).collect();
472 let curr_map: HashMap<&str, &T> = curr.iter().map(|v| (name_fn(v), v)).collect();
473 let mut changes = Vec::new();
474
475 for (&name, &prev_val) in &prev_map {
476 if !curr_map.contains_key(name) {
477 changes.push(SubChange::Removed(prev_val));
478 }
479 }
480 for (&name, &curr_val) in &curr_map {
481 match prev_map.get(name) {
482 None => changes.push(SubChange::Added(curr_val)),
483 Some(&prev_val) if !equal(prev_val, curr_val) => {
484 changes.push(SubChange::Modified(prev_val, curr_val))
485 }
486 _ => {}
487 }
488 }
489 changes
490}
491
492fn inject_orphan_triggers(ops: Vec<Operation>, previous: &Schema) -> Vec<Operation> {
496 let dropped_fns: HashSet<String> = ops
497 .iter()
498 .filter_map(|op| match op {
499 Operation::DropFunction { function } => Some(function.qualified_name()),
500 _ => None,
501 })
502 .collect();
503
504 if dropped_fns.is_empty() {
505 return ops;
506 }
507
508 let dropped_tables: HashSet<String> = ops
509 .iter()
510 .filter_map(|op| match op {
511 Operation::DropTable { table } => Some(table.qualified_name()),
512 _ => None,
513 })
514 .collect();
515
516 let existing_drop_keys: HashSet<String> = ops
517 .iter()
518 .filter_map(|op| match op {
519 Operation::DropTrigger {
520 table_name,
521 trigger,
522 } => trigger
523 .name
524 .as_deref()
525 .map(|n| format!("{}:{}", table_name, n)),
526 _ => None,
527 })
528 .collect();
529
530 let mut injected: Vec<Operation> = Vec::new();
531 for (table_name, table) in &previous.tables {
532 if dropped_tables.contains(table_name) {
533 continue;
534 }
535 for trg in &table.triggers {
536 let references_dropped = trg
537 .function_name
538 .as_deref()
539 .is_some_and(|f| dropped_fns.contains(f));
540 if !references_dropped {
541 continue;
542 }
543 let key = trg
544 .name
545 .as_deref()
546 .map(|n| format!("{}:{}", table_name, n))
547 .unwrap_or_default();
548 if !existing_drop_keys.contains(&key) {
549 injected.push(Operation::DropTrigger {
550 table_name: table_name.clone(),
551 trigger: trg.clone(),
552 });
553 }
554 }
555 }
556
557 let mut result = ops;
558 result.extend(injected);
559 result
560}
561
562fn inject_enum_column_casts(ops: Vec<Operation>, previous: &Schema) -> Vec<Operation> {
568 let dropped_enums: HashSet<&str> = ops
569 .iter()
570 .filter_map(|op| match op {
571 Operation::DropEnum { enum_def } => Some(enum_def.name.as_str()),
572 _ => None,
573 })
574 .collect();
575 let created_enums: HashSet<&str> = ops
576 .iter()
577 .filter_map(|op| match op {
578 Operation::CreateEnum { enum_def } => Some(enum_def.name.as_str()),
579 _ => None,
580 })
581 .collect();
582
583 let recreated: HashSet<&str> = dropped_enums
584 .intersection(&created_enums)
585 .copied()
586 .collect();
587 if recreated.is_empty() {
588 return ops;
589 }
590
591 let dropped_tables: HashSet<String> = ops
592 .iter()
593 .filter_map(|op| match op {
594 Operation::DropTable { table } => Some(table.qualified_name()),
595 _ => None,
596 })
597 .collect();
598
599 let touched_columns: HashSet<(&str, &str)> = ops
600 .iter()
601 .filter_map(|op| match op {
602 Operation::DropColumn {
603 table_name, column, ..
604 } => Some((table_name.as_str(), column.name.as_str())),
605 Operation::AlterColumn {
606 table_name, old, ..
607 } => Some((table_name.as_str(), old.name.as_str())),
608 _ => None,
609 })
610 .collect();
611
612 let mut casts: Vec<Operation> = Vec::new();
613
614 for (table_name, table) in &previous.tables {
615 if dropped_tables.contains(table_name) {
616 continue;
617 }
618 for col in &table.columns {
619 if !recreated.contains(col.col_type.as_str()) {
620 continue;
621 }
622 if touched_columns.contains(&(table_name.as_str(), col.name.as_str())) {
623 continue;
624 }
625 let text_col = Column {
626 col_type: "text".to_string(),
627 ..col.clone()
628 };
629 casts.push(Operation::AlterColumn {
630 table_name: table_name.clone(),
631 old: col.clone(),
632 new: text_col.clone(),
633 cast_expr: Some(format!("{}::text", col.name)),
634 });
635 casts.push(Operation::AlterColumn {
636 table_name: table_name.clone(),
637 old: text_col,
638 new: col.clone(),
639 cast_expr: Some(format!("{}::{}", col.name, col.col_type)),
640 });
641 }
642 }
643
644 if casts.is_empty() {
645 return ops;
646 }
647
648 let mut result = Vec::with_capacity(ops.len() + casts.len());
649 result.extend(ops);
650 result.extend(casts);
651 result
652}
653
654fn decompose(ops: Vec<Operation>) -> Vec<Operation> {
659 let mut result = Vec::with_capacity(ops.len() * 2);
660 for op in ops {
661 match op {
662 Operation::CreateTable { mut table } => {
663 let fks = std::mem::take(&mut table.foreign_keys);
664 let indexes = std::mem::take(&mut table.indexes);
665 let constraints = std::mem::take(&mut table.constraints);
666 let triggers = std::mem::take(&mut table.triggers);
667 let tname = table.name.clone();
668 result.push(Operation::CreateTable { table });
669 for fk in fks {
670 result.push(Operation::AddForeignKey {
671 table_name: tname.clone(),
672 foreign_key: fk,
673 });
674 }
675 for idx in indexes {
676 result.push(Operation::AddIndex {
677 table_name: tname.clone(),
678 index: idx,
679 concurrent: false,
680 });
681 }
682 for con in constraints {
683 result.push(Operation::AddConstraint {
684 table_name: tname.clone(),
685 constraint: con,
686 });
687 }
688 for trg in triggers {
689 result.push(Operation::CreateTrigger {
690 table_name: tname.clone(),
691 trigger: trg,
692 });
693 }
694 }
695 Operation::DropTable { mut table } => {
696 let fks = std::mem::take(&mut table.foreign_keys);
697 let indexes = std::mem::take(&mut table.indexes);
698 let constraints = std::mem::take(&mut table.constraints);
699 let triggers = std::mem::take(&mut table.triggers);
700 let tname = table.name.clone();
701 for trg in triggers {
702 result.push(Operation::DropTrigger {
703 table_name: tname.clone(),
704 trigger: trg,
705 });
706 }
707 for con in constraints {
708 result.push(Operation::DropConstraint {
709 table_name: tname.clone(),
710 constraint: con,
711 });
712 }
713 for idx in indexes {
714 result.push(Operation::DropIndex {
715 table_name: tname.clone(),
716 index: idx,
717 concurrent: false,
718 });
719 }
720 for fk in fks {
721 result.push(Operation::DropForeignKey {
722 table_name: tname.clone(),
723 foreign_key: fk,
724 cascade: false,
725 });
726 }
727 result.push(Operation::DropTable { table });
728 }
729 other => result.push(other),
730 }
731 }
732 result
733}
734
735fn fk_can_be_inlined(
742 to_table: &str,
743 owner_table: &str,
744 tables_being_created: &HashSet<String>,
745) -> bool {
746 to_table == owner_table || !tables_being_created.contains(to_table)
747}
748
749fn merge_operations(ops: Vec<Operation>, dialect: &Dialect) -> Vec<Operation> {
750 let tables_being_created: HashSet<String> = ops
751 .iter()
752 .filter_map(|op| match op {
753 Operation::CreateTable { table } => Some(table.qualified_name()),
754 _ => None,
755 })
756 .collect();
757
758 let mut result: Vec<Operation> = Vec::with_capacity(ops.len());
759 let mut created_tables: HashMap<String, usize> = HashMap::new();
760 for op in ops {
761 let merged = match &op {
762 Operation::AddForeignKey {
763 table_name,
764 foreign_key,
765 } if created_tables.contains_key(table_name)
766 && dialect.should_merge(table_name, &op)
767 && fk_can_be_inlined(&foreign_key.to_table, table_name, &tables_being_created) =>
768 {
769 let idx = created_tables[table_name];
770 if let Operation::CreateTable { ref mut table } = result[idx] {
771 table.foreign_keys.push(foreign_key.clone());
772 }
773 true
774 }
775 Operation::AddIndex {
776 table_name, index, ..
777 } if created_tables.contains_key(table_name)
778 && dialect.should_merge(table_name, &op) =>
779 {
780 let idx = created_tables[table_name];
781 if let Operation::CreateTable { ref mut table } = result[idx] {
782 table.indexes.push(index.clone());
783 }
784 true
785 }
786 Operation::AddConstraint {
787 table_name,
788 constraint,
789 } if created_tables.contains_key(table_name)
790 && dialect.should_merge(table_name, &op) =>
791 {
792 let idx = created_tables[table_name];
793 if let Operation::CreateTable { ref mut table } = result[idx] {
794 table.constraints.push(constraint.clone());
795 }
796 true
797 }
798 Operation::CreateTrigger {
799 table_name,
800 trigger,
801 } if created_tables.contains_key(table_name)
802 && dialect.should_merge(table_name, &op) =>
803 {
804 let idx = created_tables[table_name];
805 if let Operation::CreateTable { ref mut table } = result[idx] {
806 table.triggers.push(trigger.clone());
807 }
808 true
809 }
810 _ => false,
811 };
812 if !merged {
813 if let Operation::CreateTable { ref table } = op {
814 created_tables.insert(table.qualified_name(), result.len());
815 }
816 result.push(op);
817 }
818 }
819 result
820}
821
822pub fn sort_operations(ops: Vec<Operation>) -> Result<Vec<Operation>, DiffError> {
826 let n = ops.len();
827 if n == 0 {
828 return Ok(ops);
829 }
830
831 let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
832 let mut in_deg: Vec<usize> = vec![0; n];
833
834 build_dependency_edges(&ops, &mut adj, &mut in_deg);
835
836 let mut queue: BTreeSet<(u8, u8, String, usize)> = BTreeSet::new();
838 for i in 0..n {
839 if in_deg[i] == 0 {
840 let (tp, sp) = tiebreak_priority(&ops[i]);
841 queue.insert((tp, sp, ops[i].entity_name().to_string(), i));
842 }
843 }
844
845 let mut sorted_indices: Vec<usize> = Vec::with_capacity(n);
846 while let Some(entry) = queue.iter().next().cloned() {
847 queue.remove(&entry);
848 let idx = entry.3;
849 sorted_indices.push(idx);
850 for &dep in &adj[idx] {
851 in_deg[dep] -= 1;
852 if in_deg[dep] == 0 {
853 let (tp, sp) = tiebreak_priority(&ops[dep]);
854 queue.insert((tp, sp, ops[dep].entity_name().to_string(), dep));
855 }
856 }
857 }
858
859 if sorted_indices.len() != n {
860 return Err(DiffError::DependencyCycle);
861 }
862
863 let mut slots: Vec<Option<Operation>> = ops.into_iter().map(Some).collect();
864 let result = sorted_indices
865 .into_iter()
866 .map(|i| slots[i].take().expect("index used twice"))
867 .collect();
868 Ok(result)
869}
870
871fn tiebreak_priority(op: &Operation) -> (u8, u8) {
872 match op {
873 Operation::CreateExtension { .. } => (0, 0),
874 Operation::CreateEnum { .. }
875 | Operation::RenameEnumValue { .. }
876 | Operation::AlterEnum { .. } => (1, 0),
877 Operation::DropView { .. } => (2, 0),
878 Operation::ReplaceView { .. } => (11, 0),
879 Operation::DropTable { .. } => (4, 0),
880 Operation::CreateFunction { .. } | Operation::AlterFunction { .. } => (5, 0),
881 Operation::CreateTable { .. } => (6, 0),
882 Operation::DropTrigger { .. } => (8, 0),
883 Operation::DropConstraint { .. } => (8, 1),
884 Operation::DropIndex { .. } => (8, 2),
885 Operation::DropForeignKey { .. } => (8, 3),
886 Operation::DropColumn { .. } => (8, 4),
887 Operation::AlterColumn { .. } => (8, 5),
888 Operation::AddColumn { .. } => (8, 6),
889 Operation::AddForeignKey { .. } => (8, 7),
890 Operation::AddIndex { .. } => (8, 8),
891 Operation::AddConstraint { .. } => (8, 9),
892 Operation::CreateTrigger { .. } | Operation::AlterTrigger { .. } => (8, 10),
893 Operation::DropFunction { .. } => (10, 0),
894 Operation::CreateView { .. } => (11, 0),
895 Operation::DropEnum { .. } => (12, 0),
896 Operation::DropExtension { .. } => (13, 0),
897 Operation::Statement { .. }
898 | Operation::AcknowledgeTableOptions { .. }
899 | Operation::RenameTable { .. }
900 | Operation::RenameColumn { .. } => (8, 5),
901 }
902}
903
904use crate::states::types::{Dep, EntityKind};
905
906fn build_dependency_edges(ops: &[Operation], adj: &mut [Vec<usize>], in_deg: &mut [usize]) {
907 let mut seen: HashSet<(usize, usize)> = HashSet::new();
908
909 let entity_names: Vec<std::borrow::Cow<str>> = ops.iter().map(|op| op.entity_name()).collect();
910 let mut create_idx: HashMap<(EntityKind, &str), Vec<usize>> = HashMap::new();
911 let mut drop_idx: HashMap<(EntityKind, &str), Vec<usize>> = HashMap::new();
912
913 for (i, op) in ops.iter().enumerate() {
914 if let Some(kind) = op.entity_kind() {
915 let name: &str = &entity_names[i];
916 if op.is_drop() {
917 drop_idx.entry((kind, name)).or_default().push(i);
918 } else {
919 create_idx.entry((kind, name)).or_default().push(i);
920 }
921 }
922 }
923
924 let mut add_edge = |from: usize, to: usize| {
925 if from != to && seen.insert((from, to)) {
926 adj[from].push(to);
927 in_deg[to] += 1;
928 }
929 };
930
931 let resolve_create = |dep: &Dep| -> Vec<usize> {
932 match &dep.name {
933 Some(name) => create_idx
934 .get(&(dep.kind, name.as_str()))
935 .cloned()
936 .unwrap_or_default(),
937 None => create_idx
938 .iter()
939 .filter(|((k, _), _)| *k == dep.kind)
940 .flat_map(|(_, v)| v.iter().copied())
941 .collect(),
942 }
943 };
944
945 let resolve_drop = |dep: &Dep| -> Vec<usize> {
946 match &dep.name {
947 Some(name) => drop_idx
948 .get(&(dep.kind, name.as_str()))
949 .cloned()
950 .unwrap_or_default(),
951 None => drop_idx
952 .iter()
953 .filter(|((k, _), _)| *k == dep.kind)
954 .flat_map(|(_, v)| v.iter().copied())
955 .collect(),
956 }
957 };
958
959 for (i, op) in ops.iter().enumerate() {
960 if !op.is_drop() {
961 for dep in op.forward_deps() {
962 for j in resolve_create(&dep) {
963 add_edge(j, i);
964 }
965 }
966 }
967 if !op.is_create() {
968 for dep in op.backward_deps() {
969 for k in resolve_drop(&dep) {
970 add_edge(i, k);
971 }
972 }
973 }
974 }
975
976 let mut by_table: HashMap<&str, Vec<usize>> = HashMap::new();
977 for (i, op) in ops.iter().enumerate() {
978 if let Some(tn) = op.table_name() {
979 let (tp, _) = tiebreak_priority(op);
980 if tp == 8 {
981 by_table.entry(tn).or_default().push(i);
982 }
983 }
984 }
985 for indices in by_table.values() {
986 for &a in indices {
987 for &b in indices {
988 if a == b {
989 continue;
990 }
991 let (_, spa) = tiebreak_priority(&ops[a]);
992 let (_, spb) = tiebreak_priority(&ops[b]);
993 if spa < spb {
994 add_edge(a, b);
995 }
996 }
997 }
998 }
999}
1000
1001pub struct DiffEngine;
1004
1005impl DiffEngine {
1006 pub fn new() -> Self {
1007 Self
1008 }
1009
1010 pub fn diff(
1011 &self,
1012 current: &Schema,
1013 previous: &Schema,
1014 dialect: &Dialect,
1015 ) -> Result<Vec<Operation>, DiffError> {
1016 let mut current = current.clone();
1017 let mut previous = previous.clone();
1018 current.canonicalize(dialect);
1019 previous.canonicalize(dialect);
1020 reject_primary_key_mutations(¤t, &previous)?;
1021
1022 let raw_ops = generate_diff_for_dialect(¤t, &previous, dialect);
1023 let ops = inject_orphan_triggers(raw_ops, &previous);
1024 let ops = inject_enum_column_casts(ops, &previous);
1025 let ops = decompose(ops);
1026 let ops = sort_operations(ops)?;
1027 Ok(merge_operations(ops, dialect))
1028 }
1029}
1030
1031fn generate_diff_for_dialect(
1032 current: &Schema,
1033 previous: &Schema,
1034 dialect: &Dialect,
1035) -> Vec<Operation> {
1036 let mut current_without_tables = current.clone();
1037 current_without_tables.tables.clear();
1038 let mut previous_without_tables = previous.clone();
1039 previous_without_tables.tables.clear();
1040 let mut ops = generate_diff(¤t_without_tables, &previous_without_tables);
1041
1042 for (key, current_table) in ¤t.tables {
1043 match previous.tables.get(key) {
1044 None => diff_table(Some(current_table), None, &mut ops, Some(dialect)),
1045 Some(previous_table) => {
1046 diff_table(
1047 Some(current_table),
1048 Some(previous_table),
1049 &mut ops,
1050 Some(dialect),
1051 );
1052 }
1053 }
1054 }
1055 for (key, previous_table) in &previous.tables {
1056 if !current.tables.contains_key(key) {
1057 diff_table(None, Some(previous_table), &mut ops, Some(dialect));
1058 }
1059 }
1060 ops
1061}
1062
1063fn reject_primary_key_mutations(current: &Schema, previous: &Schema) -> Result<(), DiffError> {
1064 for (name, current_table) in ¤t.tables {
1065 let Some(previous_table) = previous.tables.get(name) else {
1066 continue;
1067 };
1068 if current_table.primary_key != previous_table.primary_key {
1069 return Err(DiffError::PrimaryKeyMutation(name.clone()));
1070 }
1071 }
1072 Ok(())
1073}
1074
1075impl Default for DiffEngine {
1076 fn default() -> Self {
1077 Self::new()
1078 }
1079}
1080
1081#[cfg(test)]
1082mod test_diff;