1use serde::{Deserialize, Serialize};
7
8use crate::identifier::{Identifier, QualifiedName};
9use crate::ir::default_expr::NormalizedExpr;
10use crate::ir::difference::Difference;
11use crate::ir::eq::{Diff, DiffMacro, diff_field, prefix_diffs};
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DiffMacro)]
15pub struct Constraint {
16 pub qname: QualifiedName,
18 #[diff(nested)]
20 pub kind: ConstraintKind,
21 #[diff(via_debug)]
23 pub deferrable: Deferrable,
24 #[diff(via_debug)]
26 pub comment: Option<String>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
31#[serde(tag = "kind", rename_all = "snake_case")]
32pub enum ConstraintKind {
33 PrimaryKey {
35 columns: Vec<Identifier>,
37 include: Vec<Identifier>,
39 },
40 Unique {
42 columns: Vec<Identifier>,
44 include: Vec<Identifier>,
46 nulls_distinct: bool,
49 },
50 ForeignKey(ForeignKey),
52 Check {
54 expression: NormalizedExpr,
56 no_inherit: bool,
58 },
59}
60
61#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, DiffMacro)]
63pub struct ForeignKey {
64 #[diff(via_debug)]
66 pub columns: Vec<Identifier>,
67 pub referenced_table: QualifiedName,
69 #[diff(via_debug)]
71 pub referenced_columns: Vec<Identifier>,
72 #[diff(via_debug)]
74 pub on_update: ReferentialAction,
75 #[diff(via_debug)]
77 pub on_delete: ReferentialAction,
78 #[diff(via_debug)]
80 pub match_type: FkMatchType,
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
85#[serde(rename_all = "snake_case")]
86pub enum ReferentialAction {
87 NoAction,
89 Restrict,
91 Cascade,
93 SetNull(Vec<Identifier>),
95 SetDefault(Vec<Identifier>),
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(rename_all = "snake_case")]
102pub enum FkMatchType {
103 Simple,
105 Full,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
111#[serde(tag = "kind", rename_all = "snake_case")]
112pub enum Deferrable {
113 NotDeferrable,
115 Deferrable {
117 initially_deferred: bool,
119 },
120}
121
122fn render_idents(v: &[Identifier]) -> String {
123 let mut s = String::from("[");
124 for (i, id) in v.iter().enumerate() {
125 if i > 0 {
126 s.push(',');
127 }
128 s.push_str(id.as_str());
129 }
130 s.push(']');
131 s
132}
133
134impl Diff for ConstraintKind {
135 fn diff(&self, other: &Self) -> Vec<Difference> {
136 let mut out = Vec::new();
137 match (self, other) {
138 (
139 Self::PrimaryKey {
140 columns: c1,
141 include: i1,
142 },
143 Self::PrimaryKey {
144 columns: c2,
145 include: i2,
146 },
147 ) => {
148 out.extend(diff_field(
149 "columns",
150 &render_idents(c1),
151 &render_idents(c2),
152 ));
153 out.extend(diff_field(
154 "include",
155 &render_idents(i1),
156 &render_idents(i2),
157 ));
158 }
159 (
160 Self::Unique {
161 columns: c1,
162 include: i1,
163 nulls_distinct: n1,
164 },
165 Self::Unique {
166 columns: c2,
167 include: i2,
168 nulls_distinct: n2,
169 },
170 ) => {
171 out.extend(diff_field(
172 "columns",
173 &render_idents(c1),
174 &render_idents(c2),
175 ));
176 out.extend(diff_field(
177 "include",
178 &render_idents(i1),
179 &render_idents(i2),
180 ));
181 out.extend(diff_field("nulls_distinct", n1, n2));
182 }
183 (Self::ForeignKey(a), Self::ForeignKey(b)) => {
184 out.extend(prefix_diffs("fk", a.diff(b)));
185 }
186 (
187 Self::Check {
188 expression: e1,
189 no_inherit: n1,
190 },
191 Self::Check {
192 expression: e2,
193 no_inherit: n2,
194 },
195 ) => {
196 out.extend(diff_field(
197 "expression",
198 &e1.canonical_text,
199 &e2.canonical_text,
200 ));
201 out.extend(diff_field("no_inherit", n1, n2));
202 }
203 _ => {
204 out.push(Difference::new(
205 "",
206 format!("{self:?}"),
207 format!("{other:?}"),
208 ));
209 }
210 }
211 out
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218
219 fn id(s: &str) -> Identifier {
220 Identifier::from_unquoted(s).unwrap()
221 }
222
223 fn qn(schema: &str, name: &str) -> QualifiedName {
224 QualifiedName::new(id(schema), id(name))
225 }
226
227 fn pk_constraint(cols: &[&str]) -> Constraint {
228 Constraint {
229 qname: qn("app", "users_pkey"),
230 kind: ConstraintKind::PrimaryKey {
231 columns: cols.iter().map(|c| id(c)).collect(),
232 include: vec![],
233 },
234 deferrable: Deferrable::NotDeferrable,
235 comment: None,
236 }
237 }
238
239 #[test]
240 fn equal_pks_have_no_diff() {
241 assert!(pk_constraint(&["id"]).canonical_eq(&pk_constraint(&["id"])));
242 }
243
244 #[test]
245 fn pk_column_list_change_diffs() {
246 let a = pk_constraint(&["id"]);
247 let b = pk_constraint(&["id", "tenant_id"]);
248 let d = a.diff(&b);
249 assert!(d.iter().any(|x| x.path == "kind.columns"));
250 }
251
252 #[test]
253 fn pk_column_order_matters() {
254 let a = pk_constraint(&["a", "b"]);
255 let b = pk_constraint(&["b", "a"]);
256 assert!(!a.canonical_eq(&b));
257 }
258
259 #[test]
260 fn fk_on_delete_change_diffs() {
261 let mk = |on_delete| Constraint {
262 qname: qn("app", "users_org_fkey"),
263 kind: ConstraintKind::ForeignKey(ForeignKey {
264 columns: vec![id("org_id")],
265 referenced_table: qn("app", "orgs"),
266 referenced_columns: vec![id("id")],
267 on_update: ReferentialAction::NoAction,
268 on_delete,
269 match_type: FkMatchType::Simple,
270 }),
271 deferrable: Deferrable::NotDeferrable,
272 comment: None,
273 };
274 let a = mk(ReferentialAction::NoAction);
275 let b = mk(ReferentialAction::Cascade);
276 let d = a.diff(&b);
277 assert!(d.iter().any(|x| x.path == "kind.fk.on_delete"));
278 }
279
280 #[test]
281 fn pk_vs_unique_kind_change() {
282 let a = pk_constraint(&["id"]);
283 let b = Constraint {
284 kind: ConstraintKind::Unique {
285 columns: vec![id("id")],
286 include: vec![],
287 nulls_distinct: true,
288 },
289 ..pk_constraint(&["id"])
290 };
291 let d = a.diff(&b);
292 assert!(d.iter().any(|x| x.path == "kind"));
293 }
294
295 #[test]
296 fn deferrable_change_diffs() {
297 let mut b = pk_constraint(&["id"]);
298 b.deferrable = Deferrable::Deferrable {
299 initially_deferred: true,
300 };
301 let d = pk_constraint(&["id"]).diff(&b);
302 assert!(d.iter().any(|x| x.path == "deferrable"));
303 }
304}