pgevolve_core/diff/
triggers.rs1use std::collections::BTreeMap;
8
9use crate::diff::change::{Change, TriggerChange};
10use crate::diff::changeset::ChangeSet;
11use crate::diff::destructiveness::Destructiveness;
12use crate::ir::trigger::Trigger;
13
14pub fn diff_triggers(target: &[Trigger], source: &[Trigger], out: &mut ChangeSet) {
20 let target_by: BTreeMap<_, _> = target.iter().map(|t| (t.qname.clone(), t)).collect();
21 let source_by: BTreeMap<_, _> = source.iter().map(|t| (t.qname.clone(), t)).collect();
22
23 for (qname, t) in &target_by {
24 if !source_by.contains_key(qname) {
25 out.push(
26 Change::Trigger(TriggerChange::Drop {
27 qname: qname.clone(),
28 table: t.table.clone(),
29 }),
30 Destructiveness::Safe,
31 );
32 }
33 }
34
35 for (qname, s) in &source_by {
36 match target_by.get(qname) {
37 None => out.push(
38 Change::Trigger(TriggerChange::Create((*s).clone())),
39 Destructiveness::Safe,
40 ),
41 Some(t) => {
42 let structurally_equal = same_structure(s, t);
43 let comment_changed = s.comment != t.comment;
44 if structurally_equal && comment_changed {
45 out.push(
46 Change::Trigger(TriggerChange::CommentOn {
47 qname: qname.clone(),
48 table: t.table.clone(),
49 comment: s.comment.clone(),
50 }),
51 Destructiveness::Safe,
52 );
53 } else if !structurally_equal {
54 out.push(
55 Change::Trigger(TriggerChange::Replace((*s).clone())),
56 Destructiveness::Safe,
57 );
58 }
59 }
60 }
61 }
62}
63
64fn same_structure(a: &Trigger, b: &Trigger) -> bool {
66 a.table == b.table
67 && a.timing == b.timing
68 && a.events == b.events
69 && a.level == b.level
70 && a.when_clause == b.when_clause
71 && a.transition_tables == b.transition_tables
72 && a.function_qname == b.function_qname
73 && a.function_args == b.function_args
74 && a.is_constraint == b.is_constraint
75 && a.deferrable == b.deferrable
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::identifier::{Identifier, QualifiedName};
82 use crate::ir::constraint::Deferrable;
83 use crate::ir::trigger::{TriggerEvent, TriggerLevel, TriggerTiming};
84
85 fn id(s: &str) -> Identifier {
86 Identifier::from_unquoted(s).unwrap()
87 }
88 fn qn(schema: &str, name: &str) -> QualifiedName {
89 QualifiedName::new(id(schema), id(name))
90 }
91 fn trg(name: &str) -> Trigger {
92 Trigger {
93 qname: qn("app", name),
94 table: qn("app", "users"),
95 timing: TriggerTiming::Before,
96 events: vec![TriggerEvent::Insert],
97 level: TriggerLevel::Row,
98 when_clause: None,
99 transition_tables: vec![],
100 function_qname: qn("app", "f"),
101 function_args: vec![],
102 is_constraint: false,
103 deferrable: Deferrable::NotDeferrable,
104 comment: None,
105 }
106 }
107
108 #[test]
109 fn create_when_only_in_source() {
110 let mut cs = ChangeSet::new();
111 diff_triggers(&[], &[trg("t1")], &mut cs);
112 assert!(matches!(
113 cs.iter().next().map(|e| &e.change),
114 Some(Change::Trigger(TriggerChange::Create(_)))
115 ));
116 }
117
118 #[test]
119 fn drop_when_only_in_target() {
120 let mut cs = ChangeSet::new();
121 diff_triggers(&[trg("t1")], &[], &mut cs);
122 let first = cs.iter().next().expect("one change");
123 assert!(matches!(
124 &first.change,
125 Change::Trigger(TriggerChange::Drop { .. })
126 ));
127 assert!(matches!(&first.destructiveness, Destructiveness::Safe));
128 }
129
130 #[test]
131 fn timing_change_emits_replace() {
132 let t = trg("t1");
133 let mut s = trg("t1");
134 s.timing = TriggerTiming::After;
135 let mut cs = ChangeSet::new();
136 diff_triggers(&[t], &[s], &mut cs);
137 let first = cs.iter().next().expect("one change");
138 assert!(matches!(
139 &first.change,
140 Change::Trigger(TriggerChange::Replace(_))
141 ));
142 }
143
144 #[test]
145 fn comment_only_change_emits_comment_on() {
146 let t = trg("t1");
147 let mut s = trg("t1");
148 s.comment = Some("docs".into());
149 let mut cs = ChangeSet::new();
150 diff_triggers(&[t], &[s], &mut cs);
151 let first = cs.iter().next().expect("one change");
152 assert!(matches!(
153 &first.change,
154 Change::Trigger(TriggerChange::CommentOn { .. })
155 ));
156 }
157
158 #[test]
159 fn identical_triggers_emit_no_change() {
160 let t = trg("t1");
161 let s = trg("t1");
162 let mut cs = ChangeSet::new();
163 diff_triggers(&[t], &[s], &mut cs);
164 assert!(cs.iter().next().is_none());
165 }
166}