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::{Equiv, field_difference, prefix_differences};
12
13#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
15pub struct Constraint {
16 pub qname: QualifiedName,
18 pub kind: ConstraintKind,
20 pub deferrable: Deferrable,
22 pub comment: Option<String>,
24}
25
26impl Equiv for Constraint {
27 fn differences(&self, other: &Self) -> Vec<Difference> {
28 let Self {
29 qname: _,
30 kind: _,
31 deferrable: _,
32 comment: _,
33 } = self;
34 let mut out = Vec::new();
35 out.extend(field_difference("qname", &self.qname, &other.qname));
36 out.extend(prefix_differences(
37 "kind",
38 Equiv::differences(&self.kind, &other.kind),
39 ));
40 out.extend(field_difference(
41 "deferrable",
42 &format!("{:?}", self.deferrable),
43 &format!("{:?}", other.deferrable),
44 ));
45 out.extend(field_difference(
46 "comment",
47 &format!("{:?}", self.comment),
48 &format!("{:?}", other.comment),
49 ));
50 out
51 }
52}
53
54#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
56#[serde(tag = "kind", rename_all = "snake_case")]
57pub enum ConstraintKind {
58 PrimaryKey {
60 columns: Vec<Identifier>,
62 include: Vec<Identifier>,
64 },
65 Unique {
67 columns: Vec<Identifier>,
69 include: Vec<Identifier>,
71 nulls_distinct: bool,
74 },
75 ForeignKey(ForeignKey),
77 Check {
79 expression: NormalizedExpr,
81 no_inherit: bool,
83 },
84}
85
86#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88pub struct ForeignKey {
89 pub columns: Vec<Identifier>,
91 pub referenced_table: QualifiedName,
93 pub referenced_columns: Vec<Identifier>,
95 pub on_update: ReferentialAction,
97 pub on_delete: ReferentialAction,
99 pub match_type: FkMatchType,
101}
102
103impl Equiv for ForeignKey {
104 fn differences(&self, other: &Self) -> Vec<Difference> {
105 let Self {
106 columns: _,
107 referenced_table: _,
108 referenced_columns: _,
109 on_update: _,
110 on_delete: _,
111 match_type: _,
112 } = self;
113 let mut out = Vec::new();
114 out.extend(field_difference(
115 "columns",
116 &format!("{:?}", self.columns),
117 &format!("{:?}", other.columns),
118 ));
119 out.extend(field_difference(
120 "referenced_table",
121 &self.referenced_table,
122 &other.referenced_table,
123 ));
124 out.extend(field_difference(
125 "referenced_columns",
126 &format!("{:?}", self.referenced_columns),
127 &format!("{:?}", other.referenced_columns),
128 ));
129 out.extend(field_difference(
130 "on_update",
131 &format!("{:?}", self.on_update),
132 &format!("{:?}", other.on_update),
133 ));
134 out.extend(field_difference(
135 "on_delete",
136 &format!("{:?}", self.on_delete),
137 &format!("{:?}", other.on_delete),
138 ));
139 out.extend(field_difference(
140 "match_type",
141 &format!("{:?}", self.match_type),
142 &format!("{:?}", other.match_type),
143 ));
144 out
145 }
146}
147
148#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
150#[serde(rename_all = "snake_case")]
151pub enum ReferentialAction {
152 NoAction,
154 Restrict,
156 Cascade,
158 SetNull(Vec<Identifier>),
160 SetDefault(Vec<Identifier>),
162}
163
164#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
166#[serde(rename_all = "snake_case")]
167pub enum FkMatchType {
168 Simple,
170 Full,
172}
173
174#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
176#[serde(tag = "kind", rename_all = "snake_case")]
177pub enum Deferrable {
178 NotDeferrable,
180 Deferrable {
182 initially_deferred: bool,
184 },
185}
186
187fn render_idents(v: &[Identifier]) -> String {
188 let mut s = String::from("[");
189 for (i, id) in v.iter().enumerate() {
190 if i > 0 {
191 s.push(',');
192 }
193 s.push_str(id.as_str());
194 }
195 s.push(']');
196 s
197}
198
199impl Equiv for ConstraintKind {
200 fn differences(&self, other: &Self) -> Vec<Difference> {
201 let mut out = Vec::new();
202 match (self, other) {
203 (
204 Self::PrimaryKey {
205 columns: c1,
206 include: i1,
207 },
208 Self::PrimaryKey {
209 columns: c2,
210 include: i2,
211 },
212 ) => {
213 out.extend(field_difference(
214 "columns",
215 &render_idents(c1),
216 &render_idents(c2),
217 ));
218 out.extend(field_difference(
219 "include",
220 &render_idents(i1),
221 &render_idents(i2),
222 ));
223 }
224 (
225 Self::Unique {
226 columns: c1,
227 include: i1,
228 nulls_distinct: n1,
229 },
230 Self::Unique {
231 columns: c2,
232 include: i2,
233 nulls_distinct: n2,
234 },
235 ) => {
236 out.extend(field_difference(
237 "columns",
238 &render_idents(c1),
239 &render_idents(c2),
240 ));
241 out.extend(field_difference(
242 "include",
243 &render_idents(i1),
244 &render_idents(i2),
245 ));
246 out.extend(field_difference("nulls_distinct", n1, n2));
247 }
248 (Self::ForeignKey(a), Self::ForeignKey(b)) => {
249 out.extend(prefix_differences("fk", a.differences(b)));
250 }
251 (
252 Self::Check {
253 expression: e1,
254 no_inherit: n1,
255 },
256 Self::Check {
257 expression: e2,
258 no_inherit: n2,
259 },
260 ) => {
261 out.extend(field_difference(
262 "expression",
263 &e1.canonical_text,
264 &e2.canonical_text,
265 ));
266 out.extend(field_difference("no_inherit", n1, n2));
267 }
268 _ => {
269 out.push(Difference::new(
270 "",
271 format!("{self:?}"),
272 format!("{other:?}"),
273 ));
274 }
275 }
276 out
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 fn id(s: &str) -> Identifier {
285 Identifier::from_unquoted(s).unwrap()
286 }
287
288 fn qn(schema: &str, name: &str) -> QualifiedName {
289 QualifiedName::new(id(schema), id(name))
290 }
291
292 fn pk_constraint(cols: &[&str]) -> Constraint {
293 Constraint {
294 qname: qn("app", "users_pkey"),
295 kind: ConstraintKind::PrimaryKey {
296 columns: cols.iter().map(|c| id(c)).collect(),
297 include: vec![],
298 },
299 deferrable: Deferrable::NotDeferrable,
300 comment: None,
301 }
302 }
303
304 #[test]
305 fn equal_pks_have_no_diff() {
306 assert!(pk_constraint(&["id"]).canonical_eq(&pk_constraint(&["id"])));
307 }
308
309 #[test]
310 fn pk_column_list_change_diffs() {
311 let a = pk_constraint(&["id"]);
312 let b = pk_constraint(&["id", "tenant_id"]);
313 let d = a.differences(&b);
314 assert!(d.iter().any(|x| x.path == "kind.columns"));
315 }
316
317 #[test]
318 fn pk_column_order_matters() {
319 let a = pk_constraint(&["a", "b"]);
320 let b = pk_constraint(&["b", "a"]);
321 assert!(!a.canonical_eq(&b));
322 }
323
324 #[test]
325 fn fk_on_delete_change_diffs() {
326 let mk = |on_delete| Constraint {
327 qname: qn("app", "users_org_fkey"),
328 kind: ConstraintKind::ForeignKey(ForeignKey {
329 columns: vec![id("org_id")],
330 referenced_table: qn("app", "orgs"),
331 referenced_columns: vec![id("id")],
332 on_update: ReferentialAction::NoAction,
333 on_delete,
334 match_type: FkMatchType::Simple,
335 }),
336 deferrable: Deferrable::NotDeferrable,
337 comment: None,
338 };
339 let a = mk(ReferentialAction::NoAction);
340 let b = mk(ReferentialAction::Cascade);
341 let d = a.differences(&b);
342 assert!(d.iter().any(|x| x.path == "kind.fk.on_delete"));
343 }
344
345 #[test]
346 fn pk_vs_unique_kind_change() {
347 let a = pk_constraint(&["id"]);
348 let b = Constraint {
349 kind: ConstraintKind::Unique {
350 columns: vec![id("id")],
351 include: vec![],
352 nulls_distinct: true,
353 },
354 ..pk_constraint(&["id"])
355 };
356 let d = a.differences(&b);
357 assert!(d.iter().any(|x| x.path == "kind"));
358 }
359
360 #[test]
361 fn deferrable_change_diffs() {
362 let mut b = pk_constraint(&["id"]);
363 b.deferrable = Deferrable::Deferrable {
364 initially_deferred: true,
365 };
366 let d = pk_constraint(&["id"]).differences(&b);
367 assert!(d.iter().any(|x| x.path == "deferrable"));
368 }
369}