1use std::{
2 collections::{HashMap, HashSet},
3 fmt::{self, Display},
4 ops::{Deref, DerefMut},
5};
6
7use itertools::Itertools;
8use schema::{
9 diagnostics::Report,
10 ids::{DbIdent, Ident},
11 index::Check,
12 mk_change_list,
13 names::{
14 ConstraintKind, DbColumn, DbConstraint, DbEnumItem, DbIndex, DbItem, DbTable, DbType,
15 DbView,
16 },
17 renamelist::RenameOp,
18 root::Schema,
19 scalar::{EnumItemHandle, ScalarAnnotation},
20 sql::{Sql, SqlOp},
21 uid::{RenameExt, RenameMap},
22 view::DefinitionPart,
23 w, wl, ChangeList, ColumnDiff, Diff, EnumDiff, HasDefaultDbName, HasIdent, HasUid,
24 IsCompatible, IsIsomorph, SchemaComposite, SchemaDiff, SchemaEnum, SchemaItem, SchemaScalar,
25 SchemaSql, SchemaTable, SchemaTableOrView, SchemaType, SchemaView, TableCheck, TableColumn,
26 TableDiff, TableForeignKey, TableIndex, TablePrimaryKey, TableSql, TableUniqueConstraint,
27};
28
29use crate::escape::escape_identifier;
30
31mod escape;
32pub mod validate;
33
34#[derive(Clone, Copy, Debug)]
35pub struct Pg<T>(pub T);
36impl<T> Deref for Pg<T> {
37 type Target = T;
38
39 fn deref(&self) -> &Self::Target {
40 &self.0
41 }
42}
43impl<T> DerefMut for Pg<T> {
44 fn deref_mut(&mut self) -> &mut Self::Target {
45 &mut self.0
46 }
47}
48impl<T> HasUid for Pg<T>
49where
50 T: HasUid,
51{
52 fn uid(&self) -> schema::uid::Uid {
53 self.0.uid()
54 }
55}
56impl<T> HasDefaultDbName for Pg<T>
57where
58 T: HasDefaultDbName,
59{
60 type Kind = T::Kind;
61
62 fn default_db(&self) -> Option<DbIdent<Self::Kind>> {
63 self.0.default_db()
64 }
65}
66
67#[derive(Debug)]
68pub struct Alternation {
69 groupable_up: bool,
70 groupable_down: bool,
71 alt: String,
72}
73macro_rules! alt_group {
74 ($($tt:tt)*) => {
75 Alternation {
76 groupable_up: true,
77 groupable_down: true,
78 alt: format!($($tt)*),
79 }
80 };
81}
82macro_rules! alt_ungroup {
83 ($($tt:tt)*) => {
84 Alternation {
85 groupable_up: false,
86 groupable_down: false,
87 alt: format!($($tt)*),
88 }
89 };
90}
91macro_rules! alt_ungroup_up {
92 ($($tt:tt)*) => {
93 Alternation {
94 groupable_up: false,
95 groupable_down: true,
96 alt: format!($($tt)*),
97 }
98 };
99}
100
101impl Pg<TableColumn<'_>> {
102 pub fn create_inline(&self, sql: &mut String, rn: &RenameMap, report: &mut Report) {
103 self.create_inline_inner(sql, rn, false, report);
104 }
105 fn create_inline_inner(
106 &self,
107 sql: &mut String,
108 rn: &RenameMap,
109 force_nullable: bool,
110 report: &mut Report,
111 ) {
112 let name = Id(self.db(rn));
113 let db_type = self.db_type(rn, report);
114 let nullability = if self.nullable || force_nullable {
115 ""
116 } else {
117 " NOT NULL"
118 };
119 let defaultability = self.default().map_or_else(String::new, |default| {
120 let sql = Pg(self.table).format_sql(default, rn, report);
121 format!(" DEFAULT ({sql})")
122 });
123 w!(sql, "{name} {}{nullability}{defaultability}", db_type.raw());
124 }
125 pub fn drop_alter(&self, out: &mut Vec<Alternation>, rn: &RenameMap) {
126 let name = Id(self.db(rn));
127 out.push(alt_group!("DROP COLUMN {name}"));
128 }
129 pub fn create_alter(&self, out: &mut Vec<Alternation>, rn: &RenameMap, report: &mut Report) {
130 let mut inl = String::new();
131 self.create_inline_inner(&mut inl, rn, self.initialize_as().is_some(), report);
132 out.push(alt_group!("ADD COLUMN {inl}"));
133 if let Some(initialize_as) = self.initialize_as() {
134 let name = Id(self.db(rn));
135 let db_type = self.db_type(rn, report);
136 let sql = format_sql(
137 initialize_as,
138 self.table.schema,
139 SchemaItem::Table(self.table),
140 rn,
141 report,
142 );
143 out.push(alt_ungroup_up!(
146 "ALTER COLUMN {name} SET DATA TYPE {} USING {sql}",
147 db_type.raw(),
148 ));
149 if !self.nullable {
150 out.push(alt_group!("ALTER COLUMN {name} SET NOT NULL"));
151 }
152 }
153 }
154 pub fn rename_alter(&self, to: DbColumn, out: &mut Vec<Alternation>, rn: &mut RenameMap) {
156 let name = Id(self.db(rn));
157 out.push(alt_ungroup!("RENAME COLUMN {name} TO {}", Id(&to)));
158 self.set_db(rn, to);
159 }
160}
161impl Pg<TableSql<'_>> {
162 pub fn print(&self, sql: &mut String, rn: &RenameMap, report: &mut Report) {
163 let o = format_sql(
164 &self.0,
165 self.table.schema,
166 SchemaItem::Table(self.0.table),
167 rn,
168 report,
169 );
170 sql.push_str(&o);
171 }
172}
173
174fn pg_constraints<'v>(t: &'v SchemaTable) -> Vec<PgTableConstraint<'v>> {
175 let mut out = vec![];
176 if let Some(pk) = t.primary_key() {
177 out.push(PgTableConstraint::PrimaryKey(pk));
178 }
179 for check in t.checks() {
180 out.push(PgTableConstraint::Check(check));
181 }
182 for unique in t.unique_constraints() {
183 out.push(PgTableConstraint::Unique(unique));
184 }
185 for fk in t.foreign_keys() {
186 out.push(PgTableConstraint::ForeignKey(fk));
187 }
188 out
189}
190
191impl Pg<SchemaTable<'_>> {
192 fn format_sql(&self, sql: &Sql, rn: &RenameMap, report: &mut Report) -> String {
193 let mut out = String::new();
194 Pg(self.sql(sql)).print(&mut out, rn, report);
195 out
196 }
197 pub fn create(&self, sql: &mut String, rn: &RenameMap, report: &mut Report) {
198 let table_name = Id(self.db(rn));
199 wl!(sql, "CREATE TABLE {table_name} (");
200 let mut had = false;
201 for v in self.columns() {
202 if had {
203 w!(sql, ",");
204 } else {
205 had = true;
206 };
207 w!(sql, "\t");
208 Pg(v).create_inline(sql, rn, report);
209 wl!(sql, "");
214 }
215 let constraints = pg_constraints(self);
216 for constraint in constraints {
217 let Some(inline) = constraint.create_inline_non_fk(rn, report) else {
218 continue;
219 };
220 if had {
221 w!(sql, ",");
222 } else {
223 had = true;
224 };
225 wl!(sql, "\t{inline}");
226 }
227 wl!(sql, ");");
228 for idx in self.indexes() {
229 Pg(idx).create(sql, rn);
230 }
231
232 for column in self.columns() {
234 let column_name = Id(column.db(rn));
235 let new_c = docs_to_string(column.docs.clone());
236
237 if !new_c.is_empty() {
238 let doc = new_c.replace('\'', "''");
239 wl!(
240 sql,
241 "COMMENT ON COLUMN {table_name}.{column_name} IS '{doc}';"
242 );
243 }
244 }
245 }
246 pub fn comment_id(&self, rn: &RenameMap) -> String {
247 let table_name = Id(self.db(rn));
248 format!("TABLE {table_name}")
249 }
250 pub fn drop(&self, sql: &mut String, rn: &RenameMap) {
251 let table_name = Id(self.db(rn));
252 wl!(sql, "DROP TABLE {table_name};");
253 }
254 pub fn rename(&self, to: DbTable, sql: &mut String, rn: &mut RenameMap, external: bool) {
255 if !external {
256 self.print_alternations(&[alt_ungroup!("RENAME TO {}", Id(&to))], sql, rn);
257 }
258 self.set_db(rn, to);
259 }
260
261 pub fn print_alternations(&self, mut out: &[Alternation], sql: &mut String, rn: &RenameMap) {
262 fn print_group(name: &DbTable, list: &[Alternation], sql: &mut String) {
263 let name = Id(name);
264 if list.len() > 1 {
265 w!(sql, "ALTER TABLE {name}\n");
266 for (i, alt) in list.iter().enumerate() {
267 if i != 0 {
268 w!(sql, ",");
269 };
270 wl!(sql, "\t{}", alt.alt);
271 }
272 wl!(sql, ";");
273 } else {
274 let alt = &list[0];
275 w!(sql, "ALTER TABLE {name} {};\n", alt.alt);
276 }
277 }
278 let name = &self.db(rn);
279 while !out.is_empty() {
280 let mut count = 1;
281 loop {
282 if !out[count - 1].groupable_down || out.len() == count {
283 break;
284 }
285 if !out[count].groupable_up {
286 break;
287 }
288 count += 1;
289 }
290 print_group(name, &out[..count], sql);
291 out = &out[count..];
292 }
293 }
294}
295
296impl Pg<ColumnDiff<'_>> {
297 pub fn print_alter(
298 &self,
299 out: &mut Vec<Alternation>,
300 rn: &RenameMap,
301 report_old: &mut Report,
302 report_new: &mut Report,
303 ) {
304 let name = Id(self.new.db(rn));
305 let new_ty = self.new.db_type(rn, report_new);
306 if self.old.db_type(rn, report_old) != new_ty {
307 if let Some(initialize_as) = self.new.initialize_as() {
308 let sql = format_sql(
309 initialize_as,
310 self.new.table.schema,
311 SchemaItem::Table(self.new.table),
312 rn,
313 report_new,
314 );
315 out.push(alt_group!(
316 "ALTER COLUMN {name} SET DATA TYPE {} USING {sql}",
317 new_ty.raw()
318 ));
319 } else {
320 out.push(alt_group!(
321 "ALTER COLUMN {name} SET DATA TYPE {}",
322 new_ty.raw()
323 ));
324 }
325 }
326 let new_nullable = self.new.nullable;
327 if self.old.nullable != new_nullable {
328 if new_nullable {
329 out.push(alt_group!("ALTER COLUMN {name} DROP NOT NULL"));
330 } else {
331 out.push(alt_group!("ALTER COLUMN {name} SET NOT NULL"));
332 }
333 }
334
335 let old_default = self.old.default();
336 let new_default = self.new.default();
337 match (old_default, new_default) {
338 (None, Some(new_default)) => out.push(alt_group!(
339 "ALTER COLUMN {name} SET DEFAULT {}",
340 Pg(self.new.table).format_sql(new_default, rn, report_new)
341 )),
342 (Some(_), None) => out.push(alt_group!("ALTER COLUMN {name} DROP DEFAULT")),
343 (Some(old_default), Some(new_default)) => {
344 let old_default = Pg(self.old.table).format_sql(old_default, rn, report_old);
345 let new_default = Pg(self.new.table).format_sql(new_default, rn, report_new);
346 if new_default != old_default {
347 out.push(alt_group!("ALTER COLUMN {name} SET DEFAULT {new_default}",));
348 }
349 }
350 (None, None) => {}
351 }
352
353 }
363}
364impl Pg<TableDiff<'_>> {
365 pub fn print_stage1(
366 &self,
367 sql: &mut String,
368 rn: &mut RenameMap,
369 external: bool,
370 report_old: &mut Report,
371 report_new: &mut Report,
372 ) -> ChangeList<TableColumn<'_>> {
373 let mut out = Vec::new();
374
375 let old_columns = self.old.columns().collect::<Vec<_>>();
376 let new_columns = self.new.columns().collect::<Vec<_>>();
377 let column_changes = mk_change_list(
378 rn,
379 &old_columns,
380 &new_columns,
381 |v| v,
382 report_old,
383 report_new,
384 );
385 {
387 let mut updated = HashMap::new();
388 for ele in &column_changes.renamed {
389 match ele {
390 RenameOp::Rename(i, r, _) => {
391 Pg(*i).rename_alter(DbIdent::unchecked_from(r.clone()), &mut out, rn);
392 }
393 RenameOp::Store(i, t) => {
394 Pg(*i).rename_alter(t.db(), &mut out, rn);
395 updated.insert(t, i);
396 }
397 RenameOp::Moveaway(i, t) => {
398 Pg(*i).rename_alter(t.db(), &mut out, rn);
399 }
400 RenameOp::Restore(t, n, _) => {
401 let table = updated.remove(&t).expect("stored");
402 Pg(*table).rename_alter(DbIdent::unchecked_from(n.clone()), &mut out, rn);
403 }
404 };
405 }
406 }
407
408 if !external {
409 Pg(self.new).print_alternations(&out, sql, rn);
410 }
411 column_changes
412 }
413
414 pub fn print_stage1_5(
415 &self,
416 sql: &mut String,
417 rn: &RenameMap,
418 external: bool,
419 report_old: &mut Report,
420 report_new: &mut Report,
421 ) -> ChangeList<PgTableConstraint<'_>> {
422 let mut out = vec![];
423 let old_constraints = pg_constraints(&self.old);
425 let new_constraints = pg_constraints(&self.new);
426 let constraint_changes = mk_change_list(
427 rn,
428 &old_constraints,
429 &new_constraints,
430 |v| v,
431 report_old,
432 report_new,
433 );
434 for constraint in &constraint_changes.dropped {
435 constraint.drop_alter_fk(&mut out, rn);
436 }
437 if !external {
438 Pg(self.new).print_alternations(&out, sql, rn);
439 }
440 constraint_changes
441 }
442
443 pub fn print_stage2(
444 &self,
445 sql: &mut String,
446 rn: &mut RenameMap,
447 column_changes: &ChangeList<TableColumn<'_>>,
448 constraint_changes: ChangeList<PgTableConstraint<'_>>,
449 external: bool,
450 report_old: &mut Report,
451 report_new: &mut Report,
452 ) -> Vec<Alternation> {
453 let mut out = Vec::new();
454
455 for constraint in constraint_changes.dropped {
457 constraint.drop_alter_non_fk(&mut out, rn);
458 }
459 for ele in constraint_changes.renamed {
461 let mut stored = HashMap::new();
462 match ele {
463 RenameOp::Rename(a, b, _) => {
464 a.rename_alter(b, &mut out, rn);
465 }
466 RenameOp::Store(a, b) => {
467 a.rename_alter(b.db(), &mut out, rn);
468 stored.insert(b, a);
469 }
470 RenameOp::Restore(r, t, _) => {
471 stored
472 .remove(&r)
473 .expect("stored")
474 .rename_alter(t, &mut out, rn);
475 }
476 RenameOp::Moveaway(_, _) => {
477 }
479 }
480 }
481
482 let old_indexes = self.old.indexes().map(Pg).collect_vec();
484 let new_indexes = self.new.indexes().map(Pg).collect_vec();
485 let index_changes = mk_change_list(
486 rn,
487 &old_indexes,
488 &new_indexes,
489 |v| v,
490 report_old,
491 report_new,
492 );
493 for index in index_changes.dropped {
494 index.drop(sql, rn);
495 }
496
497 for ele in &column_changes.created {
499 Pg(*ele).create_alter(&mut out, rn, report_new);
500 }
501
502 for ele in &column_changes.updated {
504 Pg(*ele).print_alter(&mut out, rn, report_old, report_new);
505 }
506
507 let mut fks = vec![];
509 for constraint in constraint_changes.created {
510 constraint.create_alter_non_fk(&mut out, rn, report_new);
511 constraint.create_alter_fk(&mut fks, rn);
512 }
513
514 if !external {
515 Pg(self.new).print_alternations(&out, sql, rn);
516 out = vec![];
517 }
518
519 for added in index_changes.created {
521 added.create(sql, rn);
522 }
523 for updated in index_changes.updated {
524 let _old_name = updated.old.db(rn);
527 let new_name = updated.new.db(rn);
528 updated.old.rename(new_name, sql, rn);
529 }
530
531 if !external {
532 Pg(self.new).print_alternations(&out, sql, rn);
533 }
534
535 fks
536 }
537 pub fn print_stage3(
538 &self,
539 sql: &mut String,
540 rn: &RenameMap,
541 column_changes: ChangeList<TableColumn<'_>>,
542 external: bool,
543 ) {
544 let mut out = Vec::new();
545
546 for column in column_changes.dropped {
548 Pg(column).drop_alter(&mut out, rn);
549 }
550
551 if !external {
552 Pg(self.new).print_alternations(&out, sql, rn);
553 }
554
555 for ele in column_changes.created {
558 let table_name = Id(self.old.db(rn));
559 let column_name = Id(ele.db(rn));
560 let new_c = docs_to_string(ele.docs.clone());
561
562 if !new_c.is_empty() {
563 let doc = new_c.replace('\'', "''");
564 wl!(
565 sql,
566 "COMMENT ON COLUMN {table_name}.{column_name} IS '{doc}';"
567 );
568 }
569 }
570 for ele in column_changes.updated {
571 let table_name = Id(self.old.db(rn));
572 let column_name = Id(ele.old.db(rn));
573 let old_c = docs_to_string(ele.old.docs.clone());
574 let new_c = docs_to_string(ele.new.docs.clone());
575
576 if old_c == new_c {
577 continue;
578 }
579 if new_c.is_empty() {
580 wl!(sql, "COMMENT ON COLUMN {table_name}.{column_name} IS NULL;");
581 } else {
582 let doc = new_c.replace('\'', "''");
583 wl!(
584 sql,
585 "COMMENT ON COLUMN {table_name}.{column_name} IS '{doc}';"
586 );
587 }
588 }
589 }
590}
591
592impl Pg<TableForeignKey<'_>> {
593 pub fn create_alter(&self, out: &mut Vec<Alternation>, rn: &RenameMap) {
594 let mut alt = String::new();
595
596 let name = Id(self.db(rn));
597 w!(alt, "ADD CONSTRAINT {name} FOREIGN KEY(");
598 let source_columns = self.source_columns();
599 self.table
600 .print_column_list(&mut alt, source_columns.into_iter(), rn);
601 w!(alt, ") REFERENCES ");
602 let target_table = self.target_table();
603 let target_columns = self.target_columns();
604 let target_table_name = Id(target_table.db(rn));
605 w!(alt, "{target_table_name}(");
606 target_table.print_column_list(&mut alt, target_columns.into_iter(), rn);
607 w!(alt, ")");
608 if let Some(on_delete) = self.on_delete.sql() {
609 w!(alt, " ON DELETE {on_delete}");
610 }
611
612 out.push(Alternation {
613 groupable_up: true,
614 groupable_down: true,
615 alt,
616 });
617 }
618}
619impl IsIsomorph for Pg<TableIndex<'_>> {
620 fn is_isomorph(
621 &self,
622 other: &Self,
623 rn: &RenameMap,
624 report_self: &mut Report,
625 report_other: &mut Report,
626 ) -> bool {
627 let old_fields = self.db_columns(rn).collect_vec();
628 let new_fields = other.db_columns(rn).collect_vec();
629 old_fields == new_fields
632 }
633}
634impl IsCompatible for Pg<TableIndex<'_>> {
635 fn is_compatible(
636 &self,
637 new: &Self,
638 rn: &RenameMap,
639 report_self: &mut Report,
640 report_other: &mut Report,
641 ) -> bool {
642 let old_column_opclass = self.db_columns_opclass(rn).collect_vec();
643 let new_column_opclass = new.db_columns_opclass(rn).collect_vec();
644 self.unique == new.unique
646 && self.using == new.using
647 && old_column_opclass == new_column_opclass
648 }
649}
650impl Pg<TableIndex<'_>> {
651 pub fn create(&self, sql: &mut String, rn: &RenameMap) {
652 let name = Id(self.db(rn));
653 let table_name = Id(self.table.db(rn));
654
655 w!(sql, "CREATE ");
656 if self.unique {
657 w!(sql, "UNIQUE ");
658 }
659 w!(sql, "INDEX {name} ON {table_name}");
660 if let Some(using) = &self.using {
661 w!(sql, " USING {}", Id(using.0.as_str()));
662 }
663 w!(sql, "(\n");
664 for (i, (c, opclass)) in self.db_columns_opclass(rn).enumerate() {
665 if i != 0 {
666 w!(sql, ",");
667 }
668 w!(sql, "\t{}", Id(c));
669 if let Some(opclass) = opclass {
670 w!(sql, " {}", Id(opclass.0.as_str()));
671 }
672 w!(sql, "\n");
673 }
674 w!(sql, ")");
675 if let Some(with) = &self.with {
676 w!(sql, " WITH ({})", with.0);
677 }
678 wl!(sql, ";");
679 }
680 pub fn drop(&self, sql: &mut String, rn: &RenameMap) {
681 let name = Id(self.db(rn));
682 w!(sql, "DROP INDEX {name};\n");
683 }
684 pub fn rename(&self, new_name: DbIndex, sql: &mut String, rn: &RenameMap) {
685 let name = Id(self.db(rn));
686 let new_name = Id(new_name);
687 if name == new_name {
688 return;
689 }
690 w!(sql, "ALTER INDEX {name} RENAME TO {new_name};\n");
691 }
693}
694impl Pg<SchemaDiff<'_>> {
695 #[allow(clippy::too_many_lines)]
696 pub(crate) fn print(
697 &self,
698 sql: &mut String,
699 rn: &mut RenameMap,
700 report_old: &mut Report,
701 report_new: &mut Report,
702 ) {
703 let changelist = self.changelist(rn, report_old, report_new);
704
705 for ele in changelist.renamed {
707 let mut stored = HashMap::new();
708 match ele {
709 RenameOp::Store(i, t) => {
710 stored.insert(t, i);
711 Pg(i).rename(t.db(), sql, rn, i.is_external());
712 }
713 RenameOp::Moveaway(i, t) => {
714 Pg(i).rename(t.db(), sql, rn, i.is_external());
715 }
716 RenameOp::Restore(t, n, r) => {
717 let item = stored.remove(&t).expect("stored");
718 Pg(item).rename(n, sql, rn, r.is_external());
719 }
720 RenameOp::Rename(v, n, r) => Pg(v).rename(n, sql, rn, r.is_external()),
721 }
722 }
723
724 for ele in changelist
726 .updated
727 .iter()
728 .filter(|d| matches!(d.old, SchemaItem::Enum(_)))
729 .map(|ele| {
730 let old = ele.old.as_enum().expect("enum");
731 let new = ele.new.as_enum().expect("enum");
732 Diff { old, new }
733 }) {
734 Pg(ele).print_renamed_added(sql, rn, report_old, report_new);
735 }
736
737 let mut initialized_tys = HashSet::new();
738 for ele in &changelist.updated {
739 initialized_tys.insert(Ident::unchecked_cast(ele.new.id()));
741 }
742
743 for ele in self
745 .new
746 .items()
747 .iter()
748 .filter_map(SchemaItem::as_scalar)
749 .filter(|s| s.inlined() || s.is_external())
750 {
751 initialized_tys.insert(ele.id());
752 }
753
754 {
756 let mut remaining_types = changelist
757 .created
758 .iter()
759 .filter_map(SchemaItem::as_type)
760 .filter(|t| !initialized_tys.contains(&t.ident()))
761 .collect_vec();
762 if !remaining_types.is_empty() {
763 loop {
764 let (ready, pending) = remaining_types
765 .iter()
766 .partition::<Vec<SchemaType<'_>>, _>(|c| {
767 let mut deps = Vec::new();
768 c.type_dependencies(&mut deps);
769 deps.iter().all(|f| initialized_tys.contains(f))
770 });
771 if ready.is_empty() {
772 let mut e =
773 report_new.error("circular dependency while creating new types");
774 for ele in pending {
775 e.annotate("cycle part", ele.ident().span());
776 }
777 break;
778 }
779 for ele in ready {
780 Pg(ele).create(sql, rn, report_new);
781 initialized_tys.insert(ele.ident());
782 }
783 if pending.is_empty() {
784 break;
785 }
786 remaining_types = pending;
787 }
788 }
789 }
790
791 for ele in changelist
793 .updated
794 .iter()
795 .filter(|d| matches!(d.old, SchemaItem::Scalar(_)))
796 .map(|ele| {
797 let old = ele.old.as_scalar().expect("scalar");
798 let new = ele.new.as_scalar().expect("scalar");
799 Pg(Diff { old, new })
800 }) {
801 let is_external = ele.new.is_external();
802 Pg(ele).print(sql, rn, is_external, report_old, report_new);
803 }
804
805 for ele in changelist.created.iter().filter_map(SchemaItem::as_table) {
807 if ele.is_external() {
808 continue;
809 }
810 Pg(ele).create(sql, rn, report_new);
811 }
812
813 let diffs = changelist
815 .updated
816 .iter()
817 .filter(|d| matches!(d.old, SchemaItem::Table(_)))
818 .map(|ele| {
819 let old = ele.old.as_table().expect("table");
820 let new = ele.new.as_table().expect("table");
821 Pg(Diff { old, new })
822 })
823 .collect_vec();
824 let mut changes = vec![];
825 for diff in &diffs {
826 let changelist =
827 diff.print_stage1(sql, rn, diff.new.is_external(), report_old, report_new);
828 changes.push(changelist);
829 }
830 let mut fksd = vec![];
831 for diff in &diffs {
832 let changelist =
833 diff.print_stage1_5(sql, rn, diff.new.is_external(), report_old, report_new);
834 fksd.push(changelist);
835 }
836
837 {
839 let mut remaining_views = changelist
840 .dropped
841 .iter()
842 .filter_map(SchemaItem::as_view)
843 .collect_vec();
844 if !remaining_views.is_empty() {
845 let mut sorted = vec![];
846 let mut dependencies = remaining_views
847 .iter()
848 .map(|c| c.id())
849 .collect::<HashSet<_>>();
850 loop {
851 let (ready, pending) = remaining_views
852 .iter()
853 .partition::<Vec<SchemaView<'_>>, _>(|c| {
854 for def in &c.definition.0 {
855 match def {
856 DefinitionPart::Raw(_) => {}
857 DefinitionPart::TableRef(t) => {
858 if dependencies.contains(&Ident::unchecked_cast(*t)) {
859 return false;
860 }
861 }
862 DefinitionPart::ColumnRef(t, _) => {
863 if dependencies.contains(&Ident::unchecked_cast(*t)) {
864 return false;
865 }
866 }
867 }
868 }
869 true
870 });
871 if ready.is_empty() {
872 let mut e =
873 report_new.error("circular dependency while dropping old views");
874 for ele in pending {
875 e.annotate("cycle part", ele.id().span());
876 }
877 break;
878 }
879 for ele in ready.into_iter().rev() {
880 sorted.push(ele);
881 dependencies.remove(&ele.id());
882 }
883 if pending.is_empty() {
884 break;
885 }
886 remaining_views = pending;
887 }
888 for e in sorted.into_iter().rev() {
889 Pg(e).drop(sql, rn);
890 }
891 }
892 }
893
894 let mut fkss = vec![];
895 for ((diff, column_changes), constraint_changes) in
896 diffs.iter().zip(changes.iter()).zip(fksd)
897 {
898 let fks = Pg(diff).print_stage2(
899 sql,
900 rn,
901 column_changes,
902 constraint_changes,
903 diff.new.is_external(),
904 report_old,
905 report_new,
906 );
907 fkss.push(fks);
908 }
909
910 for (diff, column_changes) in diffs.iter().zip(changes) {
911 Pg(diff).print_stage3(sql, rn, column_changes, diff.new.is_external());
912 }
913
914 {
916 let mut remaining_views = changelist
917 .created
918 .iter()
919 .filter_map(SchemaItem::as_view)
920 .collect_vec();
921 if !remaining_views.is_empty() {
922 let mut dependencies = remaining_views
923 .iter()
924 .map(|c| c.id())
925 .collect::<HashSet<_>>();
926 loop {
927 let (ready, pending) = remaining_views
928 .iter()
929 .partition::<Vec<SchemaView<'_>>, _>(|c| {
930 for def in &c.definition.0 {
931 match def {
932 DefinitionPart::Raw(_) => {}
933 DefinitionPart::TableRef(t) => {
934 if dependencies.contains(&Ident::unchecked_cast(*t)) {
935 return false;
936 }
937 }
938 DefinitionPart::ColumnRef(t, _) => {
939 if dependencies.contains(&Ident::unchecked_cast(*t)) {
940 return false;
941 }
942 }
943 }
944 }
945 true
946 });
947 if ready.is_empty() {
948 let mut e =
949 report_new.error("circular dependency while creating new views");
950 for ele in pending {
951 e.annotate("cycle part", ele.id().span());
952 }
953 break;
954 }
955 for ele in ready {
956 Pg(ele).create(sql, rn, report_new);
957 dependencies.remove(&ele.id());
958 }
959 if pending.is_empty() {
960 break;
961 }
962 remaining_views = pending;
963 }
964 }
965 }
966
967 assert_eq!(diffs.len(), fkss.len());
969 for (diff, fks) in diffs.iter().zip(fkss) {
970 Pg(diff.new).print_alternations(&fks, sql, rn);
971 }
972 for ele in changelist.created.iter().filter_map(SchemaItem::as_table) {
973 let mut out = Vec::new();
974 let ele = Pg(ele);
975 let constraints = pg_constraints(&ele);
976 for constraint in constraints {
977 constraint.create_alter_fk(&mut out, rn);
978 }
979 ele.print_alternations(&out, sql, rn);
980 }
981
982 for a in changelist.dropped.iter().filter_map(SchemaItem::as_table) {
984 let mut out = Vec::new();
985 'fk: for fk in a.foreign_keys() {
986 for b in changelist.dropped.iter().filter_map(SchemaItem::as_table) {
987 if fk.target == b.id() {
988 Pg(PgTableConstraint::ForeignKey(fk)).drop_alter_fk(&mut out, rn);
989 continue 'fk;
990 }
991 }
992 }
993 Pg(a).print_alternations(&out, sql, rn);
994 }
995
996 for ele in changelist.dropped.iter().filter_map(SchemaItem::as_table) {
998 if ele.is_external() {
999 continue;
1000 }
1001 Pg(ele).drop(sql, rn);
1002 }
1003
1004 {
1006 let mut remaining_types = changelist
1007 .dropped
1008 .iter()
1009 .filter_map(SchemaItem::as_type)
1010 .filter(|v| match v {
1011 SchemaType::Scalar(s) => !s.is_external(),
1013 _ => true,
1014 })
1015 .collect_vec();
1016 if !remaining_types.is_empty() {
1017 let mut sorted = vec![];
1018 let mut dependencies = remaining_types
1019 .iter()
1020 .map(|c| c.ident())
1021 .collect::<HashSet<_>>();
1022 loop {
1023 let (ready, pending) = remaining_types
1024 .iter()
1025 .partition::<Vec<SchemaType<'_>>, _>(|c| {
1026 let mut deps = Vec::new();
1027 c.type_dependencies(&mut deps);
1028 deps.iter().all(|f| !dependencies.contains(f))
1029 });
1030 if ready.is_empty() {
1031 let mut e =
1032 report_new.error("circular dependency while dropping old types");
1033 for ele in pending {
1034 e.annotate("cycle part", ele.ident().span());
1035 }
1036 break;
1037 }
1038 for ele in ready.into_iter().rev() {
1039 sorted.push(ele);
1040 dependencies.remove(&ele.ident());
1041 }
1042 if pending.is_empty() {
1043 break;
1044 }
1045 remaining_types = pending;
1046 }
1047 for e in sorted.into_iter().rev() {
1048 Pg(e).drop(sql, rn);
1049 }
1050 }
1051 };
1052
1053 for ele in changelist.created {
1055 let id = Pg(ele).comment_id(rn);
1056 let new_c = Pg(ele).docs();
1057
1058 if !new_c.is_empty() {
1059 let doc = new_c.replace('\'', "''");
1060 wl!(sql, "COMMENT ON {id} IS '{doc}';");
1061 }
1062 }
1063 for ele in changelist.updated {
1064 let id = Pg(ele.old).comment_id(rn);
1065 let old_c = Pg(ele.old).docs();
1066 let new_c = Pg(ele.new).docs();
1067
1068 if old_c == new_c {
1069 continue;
1070 }
1071 if new_c.is_empty() {
1072 wl!(sql, "COMMENT ON {id} IS NULL;");
1073 } else {
1074 let doc = new_c.replace('\'', "''");
1075 wl!(sql, "COMMENT ON {id} IS '{doc}';");
1076 }
1077 }
1078 }
1079}
1080impl Pg<&Schema> {
1081 pub fn diff(
1082 &self,
1083 old: &Self,
1084 sql: &mut String,
1085 rn: &mut RenameMap,
1086 report_old: &mut Report,
1087 report_new: &mut Report,
1088 ) {
1089 Pg(SchemaDiff { old, new: self }).print(sql, rn, report_old, report_new);
1090 }
1091 pub fn create(&self, sql: &mut String, rn: &mut RenameMap, report: &mut Report) {
1092 self.diff(&Pg(&Schema::default()), sql, rn, &mut Report::new(), report);
1093 }
1094 pub fn drop(&self, sql: &mut String, rn: &mut RenameMap, report: &mut Report) {
1095 Pg(&Schema::default()).diff(self, sql, rn, report, &mut Report::new());
1096 }
1097}
1098impl Pg<SchemaEnum<'_>> {
1099 pub fn rename(&self, db: DbType, sql: &mut String, rn: &mut RenameMap) {
1100 self.print_alternations(&[format!("RENAME TO {}", Id(&db))], sql, rn);
1101 self.set_db(rn, db);
1102 }
1103 pub fn create(&self, sql: &mut String, rn: &RenameMap) {
1104 let db_name = Id(self.db(rn));
1105 w!(sql, "CREATE TYPE {db_name} AS ENUM (\n");
1106 for (i, v) in self.items.iter().enumerate() {
1107 if i != 0 {
1108 w!(sql, ",");
1109 }
1110 w!(sql, "\t'{}'\n", v.db(rn).raw());
1112 }
1113 wl!(sql, ");");
1114 wl!(sql,);
1115 }
1116 pub fn drop(&self, sql: &mut String, rn: &RenameMap) {
1117 let db_name = Id(self.db(rn));
1118 w!(sql, "DROP TYPE {db_name};\n");
1119 }
1120 pub fn print_alternations(&self, out: &[String], sql: &mut String, rn: &RenameMap) {
1121 if out.is_empty() {
1122 return;
1123 }
1124 let name = Id(self.db(rn));
1125 w!(sql, "ALTER TYPE {name}\n");
1126 for (i, alt) in out.iter().enumerate() {
1127 if i != 0 {
1128 w!(sql, ",");
1129 }
1130 wl!(sql, "\t{alt}");
1131 }
1132 wl!(sql, ";");
1133 }
1134}
1135impl Pg<EnumItemHandle<'_>> {
1136 pub fn rename_alter(&self, to: DbEnumItem, rn: &mut RenameMap) -> String {
1137 let out = format!("RENAME VALUE '{}' TO {}", self.db(rn).raw(), to.raw());
1139 self.set_db(rn, to);
1140 out
1141 }
1142}
1143impl Pg<EnumDiff<'_>> {
1144 pub fn print_renamed_added(
1145 &self,
1146 sql: &mut String,
1147 rn: &mut RenameMap,
1148 report_old: &mut Report,
1149 report_new: &mut Report,
1150 ) {
1151 let changelist = schema::mk_change_list(
1152 rn,
1153 &self.0.old.items().collect_vec(),
1154 &self.0.new.items().collect_vec(),
1155 |v| v,
1156 report_old,
1157 report_new,
1158 );
1159 let mut changes = vec![];
1160 for el in changelist.renamed.iter().cloned() {
1161 let mut stored = HashMap::new();
1162 match el {
1163 RenameOp::Store(i, t) => {
1164 stored.insert(t, i);
1165 changes.push(Pg(i).rename_alter(t.db(), rn));
1166 }
1167 RenameOp::Moveaway(i, t) => {
1168 changes.push(Pg(i).rename_alter(t.db(), rn));
1169 }
1170 RenameOp::Rename(v, n, _) => {
1171 changes.push(Pg(v).rename_alter(n, rn));
1172 }
1173 RenameOp::Restore(r, n, _) => {
1174 let stored = stored.remove(&r).expect("was not stored");
1175 Pg(stored).rename_alter(n, rn);
1176 }
1177 }
1178 }
1179
1180 for added in &changelist.created {
1181 changes.push(format!("ADD VALUE '{}'", added.db(rn).raw()));
1183 }
1184
1185 Pg(self.old).print_alternations(&changes, sql, rn);
1186 assert!(
1187 changelist.dropped.is_empty(),
1188 "enums with dropped elements are not compatible"
1189 );
1190 }
1191}
1192impl Pg<SchemaComposite<'_>> {
1193 pub fn rename(&self, db: DbType, sql: &mut String, rn: &mut RenameMap) {
1194 self.print_alternations(&[format!("RENAME TO {}", Id(&db))], sql, rn);
1195 self.set_db(rn, db);
1196 }
1197 pub fn create(&self, sql: &mut String, rn: &RenameMap, report: &mut Report) {
1198 let db_name = &self.db(rn);
1199 w!(sql, "CREATE TYPE {} AS (\n", Id(db_name));
1200 for (i, v) in self.fields().enumerate() {
1201 if i != 0 {
1202 w!(sql, ",");
1203 }
1204 let db_name = Id(v.db(rn));
1205 let db_type = v.db_type(rn, report);
1206 w!(sql, "\t{db_name} {}\n", db_type.raw());
1207 }
1208 wl!(sql, ");");
1209 }
1210 pub fn drop(&self, sql: &mut String, rn: &RenameMap) {
1211 let db_name = Id(self.db(rn));
1212 w!(sql, "DROP TYPE {db_name};\n");
1213 }
1214 pub fn print_alternations(&self, out: &[String], sql: &mut String, rn: &RenameMap) {
1215 if out.is_empty() {
1216 return;
1217 }
1218 let name = Id(self.db(rn));
1219 w!(sql, "ALTER TYPE {name}\n");
1220 for (i, alt) in out.iter().enumerate() {
1221 if i != 0 {
1222 w!(sql, ",");
1223 }
1224 wl!(sql, "\t{alt}");
1225 }
1226 wl!(sql, ";");
1227 }
1228}
1229impl Pg<SchemaScalar<'_>> {
1230 pub fn rename(&self, to: DbType, sql: &mut String, rn: &mut RenameMap, external: bool) {
1231 if !external {
1232 self.print_alternations(&[format!("RENAME TO {}", Id(&to))], sql, rn);
1233 }
1234 self.scalar.set_db(rn, to);
1235 }
1236 pub fn print_alternations(&self, out: &[String], sql: &mut String, rn: &RenameMap) {
1237 if out.is_empty() {
1238 return;
1239 }
1240 let name = Id(self.db(rn));
1241 w!(sql, "ALTER DOMAIN {name}\n");
1242 for (i, alt) in out.iter().enumerate() {
1243 if i != 0 {
1244 w!(sql, ",");
1245 }
1246 wl!(sql, "\t{alt}");
1247 }
1248 wl!(sql, ";");
1249 }
1250 pub fn create(&self, sql: &mut String, rn: &RenameMap, report: &mut Report) {
1251 let name = Id(self.db(rn));
1252 let ty = self.inner_type(rn, report);
1253 w!(sql, "CREATE DOMAIN {name} AS {}", ty.raw());
1254 for ele in &self.annotations {
1255 match ele {
1256 ScalarAnnotation::Default(d) => {
1257 w!(sql, "\n\tDEFAULT ");
1258 let formatted =
1259 Pg(self.schema).format_sql(d, SchemaItem::Scalar(self.0), rn, report);
1260 w!(sql, "{formatted}");
1261 }
1262 ScalarAnnotation::Check(check) => {
1263 let name = Id(check.db(rn));
1264 w!(sql, "\n\tCONSTRAINT {name} CHECK (");
1265 let formatted = Pg(self.schema).format_sql(
1266 &check.check,
1267 SchemaItem::Scalar(self.0),
1268 rn,
1269 report,
1270 );
1271 w!(sql, "{formatted})");
1272 }
1273 ScalarAnnotation::Inline | ScalarAnnotation::External => {
1274 unreachable!("non-material scalars are not created")
1275 }
1276 ScalarAnnotation::Index(_)
1277 | ScalarAnnotation::Unique(_)
1278 | ScalarAnnotation::PrimaryKey(_) => {
1279 unreachable!("only check constrains are allowed on domain scalars")
1280 }
1281 }
1282 }
1283 wl!(sql, ";");
1284 }
1285 pub fn drop(&self, sql: &mut String, rn: &RenameMap) {
1286 assert!(!self.is_external(), "should not drop external scalars");
1287 let name = Id(self.db(rn));
1288 wl!(sql, "DROP DOMAIN {name};");
1289 }
1290}
1291impl Pg<SchemaType<'_>> {
1292 fn create(self, sql: &mut String, rn: &RenameMap, report: &mut Report) {
1293 match self.0 {
1294 SchemaType::Enum(e) => Pg(e).create(sql, rn),
1295 SchemaType::Scalar(s) => Pg(s).create(sql, rn, report),
1296 SchemaType::Composite(c) => Pg(c).create(sql, rn, report),
1297 }
1298 }
1299 fn drop(self, sql: &mut String, rn: &RenameMap) {
1300 match self.0 {
1301 SchemaType::Enum(e) => Pg(e).drop(sql, rn),
1302 SchemaType::Scalar(s) => Pg(s).drop(sql, rn),
1303 SchemaType::Composite(c) => Pg(c).drop(sql, rn),
1304 }
1305 }
1306}
1307
1308impl Pg<&Check> {
1309 fn rename_alter(&self, to: DbConstraint, out: &mut Vec<String>, rn: &mut RenameMap) {
1310 let db = Id(self.db(rn));
1311 let to = Id(to);
1312 if db == to {
1313 return;
1314 }
1315 out.push(format!("ALTER CONSTRAINT {db} RENAME TO {to}"));
1316 self.set_db(rn, to.0);
1317 }
1318}
1319impl Pg<Diff<SchemaScalar<'_>>> {
1320 pub fn print(
1321 &self,
1322 sql: &mut String,
1323 rn: &mut RenameMap,
1324 external: bool,
1325 report_old: &mut Report,
1326 report_new: &mut Report,
1327 ) {
1328 let mut new = self
1329 .new
1330 .annotations
1331 .iter()
1332 .filter_map(ScalarAnnotation::as_check)
1333 .map(|c| {
1334 let mut sql = String::new();
1335 Pg(self.new.schema.sql(&c.check)).print(
1336 &mut sql,
1337 SchemaItem::Scalar(self.new),
1338 rn,
1339 report_new,
1340 );
1341 (c, sql)
1342 })
1343 .collect::<Vec<_>>();
1344 let old = self
1345 .old
1346 .annotations
1347 .iter()
1348 .filter_map(ScalarAnnotation::as_check)
1349 .collect::<Vec<_>>();
1350 let mut out = Vec::new();
1351 for ann in &old {
1352 let mut sql = String::new();
1353 Pg(self.old.schema.sql(&ann.check)).print(
1354 &mut sql,
1355 SchemaItem::Scalar(self.old),
1356 rn,
1357 report_old,
1358 );
1359
1360 if let Some((i, (c, _))) = new.iter().find_position(|(_, nsql)| nsql == &sql) {
1361 Pg(*ann).rename_alter(c.db(rn), &mut out, rn);
1362 new.remove(i);
1363 } else {
1364 let db = Id(ann.db(rn));
1365 out.push(format!("DROP CONSTRAINT {db}"));
1366 }
1367 }
1368
1369 for (check, _) in &new {
1370 let db = Id(check.db(rn));
1371 let mut sql = format!("ADD CONSTRAINT {db} CHECK (");
1372 Pg(self.new.schema.sql(&check.check)).print(
1373 &mut sql,
1374 SchemaItem::Scalar(self.new),
1375 rn,
1376 report_new,
1377 );
1378 w!(sql, ")");
1379 out.push(sql);
1380 }
1381
1382 if !external {
1383 Pg(self.old).print_alternations(&out, sql, rn);
1384 }
1385 }
1386}
1387
1388fn cleanup_docs(mut docs: Vec<String>) -> Vec<String> {
1389 if docs.iter().all(|v| v.starts_with(' ')) {
1390 for ele in docs.iter_mut() {
1391 ele.remove(0);
1392 *ele = ele.trim_end().to_owned();
1393 }
1394 }
1395 while matches!(docs.first(), Some(v) if v.is_empty()) {
1396 docs.remove(0);
1397 }
1398 while matches!(docs.last(), Some(v) if v.is_empty()) {
1399 docs.pop();
1400 }
1401 docs
1402}
1403fn wrap_docs(mut docs: Vec<String>, header: String) -> Vec<String> {
1404 for ele in docs.iter_mut() {
1405 ele.insert_str(0, " ");
1406 }
1407 docs.insert(0, header);
1408 docs
1409}
1410fn docs_to_string(mut docs: Vec<String>) -> String {
1411 docs = cleanup_docs(docs);
1412 docs.join("\n")
1413}
1414
1415impl Pg<SchemaItem<'_>> {
1416 pub fn rename(&self, to: DbItem, sql: &mut String, rn: &mut RenameMap, external: bool) {
1417 match self.0 {
1418 SchemaItem::Table(t) => Pg(t).rename(DbTable::unchecked_from(to), sql, rn, external),
1419 SchemaItem::Enum(e) => Pg(e).rename(DbType::unchecked_from(to), sql, rn),
1420 SchemaItem::Scalar(s) => Pg(s).rename(DbType::unchecked_from(to), sql, rn, external),
1421 SchemaItem::Composite(c) => Pg(c).rename(DbType::unchecked_from(to), sql, rn),
1422 SchemaItem::View(v) => Pg(v).rename(DbView::unchecked_from(to), sql, rn),
1423 }
1424 }
1425 pub fn create(&self, sql: &mut String, rn: &RenameMap, report: &mut Report) {
1426 match self.0 {
1427 SchemaItem::Table(t) => Pg(t).create(sql, rn, report),
1428 SchemaItem::Enum(e) => Pg(e).create(sql, rn),
1429 SchemaItem::Scalar(s) => Pg(s).create(sql, rn, report),
1430 SchemaItem::Composite(c) => Pg(c).create(sql, rn, report),
1431 SchemaItem::View(_) => todo!(),
1432 }
1433 }
1434 pub fn drop(&self, sql: &mut String, rn: &RenameMap) {
1435 match self.0 {
1436 SchemaItem::Table(t) => Pg(t).drop(sql, rn),
1437 SchemaItem::Enum(e) => Pg(e).drop(sql, rn),
1438 SchemaItem::Scalar(s) => Pg(s).drop(sql, rn),
1439 SchemaItem::Composite(c) => Pg(c).drop(sql, rn),
1440 SchemaItem::View(c) => Pg(c).drop(sql, rn),
1441 }
1442 }
1443 pub fn docs(&self) -> String {
1444 let docs = match self.0 {
1445 SchemaItem::Table(t) => t.docs.clone(),
1446 SchemaItem::Enum(e) => {
1447 let mut docs = cleanup_docs(e.docs.clone());
1448 for ele in e.items() {
1450 let edocs = cleanup_docs(ele.docs.clone());
1451 if !edocs.is_empty() {
1452 let name = ele.id().name();
1453 let edocs = wrap_docs(edocs, format!("Value {name}:"));
1454 docs.push(String::new());
1455 docs.extend(edocs);
1456 }
1457 }
1458 docs
1459 }
1460 SchemaItem::Scalar(s) => s.docs.clone(),
1461 SchemaItem::Composite(c) => {
1462 let mut docs = cleanup_docs(c.docs.clone());
1463 for ele in c.fields() {
1465 let edocs = cleanup_docs(ele.docs.clone());
1466 if !edocs.is_empty() {
1467 let name = ele.id().name();
1468 let edocs = wrap_docs(edocs, format!("Field {name}:"));
1469 docs.push(String::new());
1470 docs.extend(edocs);
1471 }
1472 }
1473 docs
1474 }
1475 SchemaItem::View(v) => v.docs.clone(),
1476 };
1477 docs_to_string(docs)
1478 }
1479 pub fn comment_id(&self, rn: &RenameMap) -> String {
1480 let name = Id(self.db(rn));
1481 match self.0 {
1482 SchemaItem::Table(_) => format!("TABLE {name}"),
1483 SchemaItem::Scalar(_) => format!("DOMAIN {name}"),
1484 SchemaItem::Enum(_) | SchemaItem::Composite(_) => format!("TYPE {name}"),
1485 SchemaItem::View(_) => format!("VIEW {name}"),
1486 }
1487 }
1488}
1489
1490fn sql_needs_parens(sql: &Sql, parent_binop: Option<SqlOp>) -> bool {
1491 match sql {
1492 Sql::Cast(_, _)
1493 | Sql::Call(_, _)
1494 | Sql::String(_)
1495 | Sql::Number(_)
1496 | Sql::Ident(_)
1497 | Sql::Parened(_)
1498 | Sql::Boolean(_)
1499 | Sql::GetField(_, _)
1500 | Sql::Placeholder
1501 | Sql::Tuple(_)
1502 | Sql::Null => false,
1503
1504 Sql::BinOp(_, a @ (SqlOp::And | SqlOp::Or), _) if Some(*a) == parent_binop => false,
1507 Sql::BinOp(_, SqlOp::And, _) if matches!(parent_binop, Some(SqlOp::Or)) => false,
1508 Sql::BinOp(
1509 _,
1510 SqlOp::Lt | SqlOp::Gt | SqlOp::Le | SqlOp::Ge | SqlOp::Ne | SqlOp::SEq | SqlOp::SNe,
1511 _,
1512 ) if matches!(parent_binop, Some(SqlOp::And | SqlOp::Or)) => false,
1513
1514 Sql::UnOp(_, _) | Sql::BinOp(_, _, _) | Sql::If(_, _, _) => true,
1515 }
1516}
1517fn format_sql(
1518 sql: &Sql,
1519 schema: &Schema,
1520 context: SchemaItem<'_>,
1521 rn: &RenameMap,
1522 report: &mut Report,
1523) -> String {
1524 let mut out = String::new();
1525 match sql {
1526 Sql::Cast(expr, ty) => {
1527 let expr = format_sql(expr, schema, context, rn, report);
1528 let native_ty = Id(schema.native_type(ty, rn, report));
1529 w!(out, "({expr})::{native_ty}");
1530 }
1531 Sql::Call(proc, args) => {
1532 let proc = Id(proc);
1533 w!(out, "{proc}(");
1534 for (i, arg) in args.iter().enumerate() {
1535 if i != 0 {
1536 w!(out, ", ");
1537 }
1538 let arg = format_sql(arg, schema, context, rn, report);
1539 w!(out, "{arg}");
1540 }
1541 w!(out, ")");
1542 }
1543 Sql::String(s) => {
1544 w!(out, "'{s}'");
1545 }
1546 Sql::Number(n) => {
1547 w!(out, "{n}");
1548 }
1549 Sql::Ident(_) => {
1550 if let Some(native_name) = sql.ident_name(&context, rn, report) {
1551 let native_name = Id(native_name);
1552 w!(out, "{native_name}");
1553 } else {
1554 w!(out, "ERROR")
1555 }
1556 }
1557 Sql::UnOp(op, expr) => {
1558 let op = op.format();
1559 let expr = format_sql(expr, schema, context, rn, report);
1560 w!(out, "{op}({expr})");
1561 }
1562 Sql::BinOp(a, op, b) => {
1563 let sop = op.format();
1564 let va = format_sql(a, schema, context, rn, report);
1565 let vb = format_sql(b, schema, context, rn, report);
1566 if sql_needs_parens(a, Some(*op)) {
1567 w!(out, "({va})");
1568 } else {
1569 w!(out, "{va}");
1570 }
1571 match (op, vb.as_str()) {
1572 (SqlOp::SEq, "NULL") => w!(out, " IS NULL"),
1574 (SqlOp::SNe, "NULL") => w!(out, " IS NOT NULL"),
1575 _ => {
1576 w!(out, " {sop} ");
1577 if sql_needs_parens(b, Some(*op)) {
1578 w!(out, "({vb})");
1579 } else {
1580 w!(out, "{vb}");
1581 }
1582 }
1583 }
1584 }
1585 Sql::Parened(a) => {
1586 let va = format_sql(a, schema, context, rn, report);
1587 if sql_needs_parens(a, None) {
1588 w!(out, "({va})");
1589 } else {
1590 w!(out, "{va}");
1591 }
1592 }
1593 Sql::Boolean(b) => {
1594 if *b {
1595 w!(out, "TRUE");
1596 } else {
1597 w!(out, "FALSE");
1598 }
1599 }
1600 Sql::Placeholder => {
1601 match context {
1602 SchemaItem::Table(_) | SchemaItem::View(_) => {
1603 unreachable!("placeholder should be replaced on this point")
1604 }
1605 SchemaItem::Enum(_) => panic!("enums have no sql items"),
1606 SchemaItem::Scalar(_) => w!(out, "VALUE"),
1607 SchemaItem::Composite(_) => panic!("composite checks should be inlined"),
1608 };
1609 }
1610 Sql::Null => w!(out, "NULL"),
1611 Sql::GetField(f, c) => {
1612 let va = format_sql(f, schema, context, rn, report);
1613 if let Some(id) = f.field_name(&context, *c, rn, report) {
1614 let name = Id(id);
1615 if sql_needs_parens(f, None) {
1616 w!(out, "({va})")
1617 } else {
1618 w!(out, "{va}");
1619 }
1620 w!(out, ".{name}");
1621 } else {
1622 assert!(report.is_error());
1623 w!(out, "{va}(ERROR)")
1624 }
1625 }
1626 Sql::Tuple(t) => {
1627 w!(out, "ROW(");
1628 for (i, v) in t.iter().enumerate() {
1629 if i != 0 {
1630 w!(out, ", ");
1631 }
1632 let va = format_sql(v, schema, context, rn, report);
1633 w!(out, "{va}");
1634 }
1635 w!(out, ")");
1636 }
1637 Sql::If(cond, then, else_) => {
1638 let cond = format_sql(cond, schema, context, rn, report);
1639 let then = format_sql(then, schema, context, rn, report);
1640 let else_ = format_sql(else_, schema, context, rn, report);
1641 w!(out, "CASE WHEN ({cond}) THEN ({then}) ELSE ({else_}) END");
1642 }
1643 }
1644 out
1645}
1646
1647impl Pg<SchemaSql<'_>> {
1648 pub fn print(
1649 &self,
1650 sql: &mut String,
1651 context: SchemaItem<'_>,
1652 rn: &RenameMap,
1653 report: &mut Report,
1654 ) {
1655 let o = format_sql(&self.0, self.schema, context, rn, report);
1656 sql.push_str(&o);
1657 }
1658}
1659impl Pg<&Schema> {
1660 pub fn format_sql(
1661 &self,
1662 sql: &Sql,
1663 context: SchemaItem<'_>,
1664 rn: &RenameMap,
1665 report: &mut Report,
1666 ) -> String {
1667 let mut out = String::new();
1668 Pg(self.sql(sql)).print(&mut out, context, rn, report);
1669 out
1670 }
1671}
1672
1673#[derive(Clone, Copy, Debug)]
1674pub enum PgTableConstraint<'s> {
1675 PrimaryKey(TablePrimaryKey<'s>),
1676 Unique(TableUniqueConstraint<'s>),
1677 Check(TableCheck<'s>),
1678 ForeignKey(TableForeignKey<'s>),
1679}
1680impl HasUid for PgTableConstraint<'_> {
1681 fn uid(&self) -> schema::uid::Uid {
1682 match self {
1683 PgTableConstraint::PrimaryKey(p) => p.uid(),
1684 PgTableConstraint::Unique(u) => u.uid(),
1685 PgTableConstraint::Check(c) => c.uid(),
1686 PgTableConstraint::ForeignKey(f) => f.uid(),
1687 }
1688 }
1689}
1690impl HasDefaultDbName for PgTableConstraint<'_> {
1691 type Kind = ConstraintKind;
1692
1693 fn default_db(&self) -> Option<DbIdent<Self::Kind>> {
1694 match self {
1695 PgTableConstraint::PrimaryKey(p) => p.default_db(),
1696 PgTableConstraint::Unique(u) => u.default_db(),
1697 PgTableConstraint::Check(c) => c.default_db(),
1698 PgTableConstraint::ForeignKey(f) => f.default_db().map(DbIdent::unchecked_from),
1699 }
1700 }
1701}
1702impl IsIsomorph for PgTableConstraint<'_> {
1703 fn is_isomorph(
1704 &self,
1705 other: &Self,
1706 rn: &RenameMap,
1707 report_self: &mut Report,
1708 report_other: &mut Report,
1709 ) -> bool {
1710 match (self, other) {
1711 (PgTableConstraint::PrimaryKey(_), PgTableConstraint::PrimaryKey(_)) => {
1712 true
1714 }
1715 (PgTableConstraint::Unique(a), PgTableConstraint::Unique(b)) => {
1716 let mut a_columns = a.table.db_names(a.columns.clone(), rn);
1717 a_columns.sort();
1718 let mut b_columns = b.table.db_names(b.columns.clone(), rn);
1719 b_columns.sort();
1720 a.db(rn) == b.db(rn) || a_columns == b_columns
1723 }
1724 (PgTableConstraint::Check(a), PgTableConstraint::Check(b)) => {
1725 let mut sql_a = String::new();
1726 Pg(a.table.sql(&a.check)).print(&mut sql_a, rn, report_self);
1727 let mut sql_b = String::new();
1728 Pg(b.table.sql(&b.check)).print(&mut sql_b, rn, report_other);
1729 a.db(rn) == b.db(rn) || sql_a == sql_b
1730 }
1731 (PgTableConstraint::ForeignKey(a), PgTableConstraint::ForeignKey(b)) => {
1732 if a.db(rn) == b.db(rn) {
1733 return true;
1734 }
1735 if a.target_table().db(rn) != b.target_table().db(rn) {
1736 return false;
1737 }
1738 let mut a_source_columns = a.source_db_columns(rn);
1739 let mut a_target_columns = a.target_db_columns(rn);
1740 let mut b_source_columns = b.source_db_columns(rn);
1741 let mut b_target_columns = b.target_db_columns(rn);
1742 let mut a_perm_by_source = permutation::sort(&a_source_columns);
1743 a_perm_by_source.apply_slice_in_place(&mut a_source_columns);
1744 a_perm_by_source.apply_slice_in_place(&mut a_target_columns);
1745 let mut b_perm_by_source = permutation::sort(&b_source_columns);
1746 b_perm_by_source.apply_slice_in_place(&mut b_source_columns);
1747 b_perm_by_source.apply_slice_in_place(&mut b_target_columns);
1748 if a_source_columns == b_source_columns && a_target_columns == b_target_columns {
1749 return true;
1750 }
1751
1752 let mut a_perm_by_target = permutation::sort(&a_target_columns);
1753 a_perm_by_target.apply_slice_in_place(&mut a_source_columns);
1754 a_perm_by_target.apply_slice_in_place(&mut a_target_columns);
1755 let mut b_perm_by_target = permutation::sort(&b_target_columns);
1756 b_perm_by_target.apply_slice_in_place(&mut b_source_columns);
1757 b_perm_by_target.apply_slice_in_place(&mut b_target_columns);
1758 if a_source_columns == b_source_columns && a_target_columns == b_target_columns {
1759 return true;
1760 }
1761
1762 false
1763 }
1764 _ => false,
1765 }
1766 }
1767}
1768impl IsCompatible for PgTableConstraint<'_> {
1770 fn is_compatible(
1771 &self,
1772 new: &Self,
1773 rn: &RenameMap,
1774 report_self: &mut Report,
1775 report_new: &mut Report,
1776 ) -> bool {
1777 match (self, new) {
1778 (PgTableConstraint::PrimaryKey(a), PgTableConstraint::PrimaryKey(b)) => {
1779 let a_columns = a.table.db_names(a.columns.clone(), rn);
1780 let b_columns = b.table.db_names(b.columns.clone(), rn);
1781 a_columns == b_columns
1782 }
1783 (PgTableConstraint::Unique(a), PgTableConstraint::Unique(b)) => {
1784 let a_columns = a.table.db_names(a.columns.clone(), rn);
1785 let b_columns = b.table.db_names(b.columns.clone(), rn);
1786 a_columns == b_columns
1787 }
1788 (PgTableConstraint::Check(a), PgTableConstraint::Check(b)) => {
1789 let mut sql_a = String::new();
1790 Pg(a.table.sql(&a.check)).print(&mut sql_a, rn, report_self);
1791 let mut sql_b = String::new();
1792 Pg(b.table.sql(&b.check)).print(&mut sql_b, rn, report_new);
1793 sql_a == sql_b
1794 }
1795 (PgTableConstraint::ForeignKey(a), PgTableConstraint::ForeignKey(b)) => {
1796 assert_eq!(
1797 a.target_table().db(rn),
1798 b.target_table().db(rn),
1799 "rejected by isomorp test"
1800 );
1801 if a.on_delete != b.on_delete {
1802 return false;
1803 }
1804 let a_source_columns = a.source_db_columns(rn);
1805 let b_source_columns = b.source_db_columns(rn);
1806 let a_target_columns = a.target_db_columns(rn);
1807 let b_target_columns = b.target_db_columns(rn);
1808 a_source_columns == b_source_columns && a_target_columns == b_target_columns
1809 }
1810 _ => unreachable!("non-isomorphs are rejected"),
1811 }
1812 }
1813}
1814impl PgTableConstraint<'_> {
1815 fn is_fk(&self) -> bool {
1816 matches!(self, Self::ForeignKey(_))
1817 }
1818 fn drop_alter_non_fk(&self, out: &mut Vec<Alternation>, rn: &RenameMap) {
1819 if self.is_fk() {
1820 return;
1821 }
1822 let name = Id(self.db(rn));
1823 out.push(alt_group!("DROP CONSTRAINT {name}"));
1824 }
1825 fn drop_alter_fk(&self, out: &mut Vec<Alternation>, rn: &RenameMap) {
1826 if !self.is_fk() {
1827 return;
1828 }
1829 let name = Id(self.db(rn));
1830 out.push(alt_group!("DROP CONSTRAINT {name}"));
1831 }
1832 pub fn rename_alter(
1833 &self,
1834 new_name: DbConstraint,
1835 out: &mut Vec<Alternation>,
1836 rn: &mut RenameMap,
1837 ) {
1838 let name = Id(self.db(rn));
1839 let new_name = Id(new_name);
1840 if name == new_name {
1841 return;
1842 }
1843 out.push(alt_ungroup!("RENAME CONSTRAINT {name} TO {new_name}"));
1844 self.set_db(rn, new_name.0);
1845 }
1846 pub fn create_alter_non_fk(
1847 &self,
1848 out: &mut Vec<Alternation>,
1849 rn: &RenameMap,
1850 report: &mut Report,
1851 ) {
1852 let text = match self {
1853 PgTableConstraint::PrimaryKey(p) => Pg(*p).create_inline(rn),
1854 PgTableConstraint::Unique(u) => Pg(*u).create_inline(rn),
1855 PgTableConstraint::Check(c) => Pg(*c).create_inline(rn, report),
1856 PgTableConstraint::ForeignKey(_) => return,
1857 };
1858 out.push(alt_group!("ADD {text}"));
1859 }
1860 pub fn create_alter_fk(&self, out: &mut Vec<Alternation>, rn: &RenameMap) {
1861 if let PgTableConstraint::ForeignKey(f) = self {
1862 Pg(*f).create_alter(out, rn);
1863 };
1864 }
1865 pub fn create_inline_non_fk(&self, rn: &RenameMap, report: &mut Report) -> Option<String> {
1866 Some(match self {
1867 PgTableConstraint::PrimaryKey(pk) => Pg(*pk).create_inline(rn),
1868 PgTableConstraint::Unique(u) => Pg(*u).create_inline(rn),
1869 PgTableConstraint::Check(c) => Pg(*c).create_inline(rn, report),
1870 PgTableConstraint::ForeignKey(_) => return None,
1871 })
1872 }
1873}
1874
1875impl Pg<TablePrimaryKey<'_>> {
1876 pub fn create_inline(&self, rn: &RenameMap) -> String {
1877 let mut sql = String::new();
1878 w!(sql, "CONSTRAINT {} PRIMARY KEY(", Id(self.db(rn)));
1879 self.table
1880 .print_column_list(&mut sql, self.columns.iter().copied(), rn);
1881 w!(sql, ")");
1882 sql
1883 }
1884 pub fn drop_alter(&self, out: &mut Vec<String>, rn: &RenameMap) {
1885 out.push(format!("DROP CONSTRAINT {}", Id(self.db(rn))));
1886 }
1887 pub fn rename_alter(&self, new_name: DbConstraint, out: &mut Vec<String>, rn: &mut RenameMap) {
1888 out.push(format!(
1889 "RENAME CONSTRAINT {} TO {}",
1890 Id(self.db(rn)),
1891 Id(&new_name)
1892 ));
1893 self.set_db(rn, new_name);
1894 }
1895}
1896impl Pg<TableUniqueConstraint<'_>> {
1897 pub fn create_inline(&self, rn: &RenameMap) -> String {
1898 let mut sql = String::new();
1899 w!(sql, "CONSTRAINT {} UNIQUE(", Id(self.db(rn)));
1900 self.table
1901 .print_column_list(&mut sql, self.columns.iter().copied(), rn);
1902 wl!(sql, ")");
1903 sql
1904 }
1905 pub fn drop_alter(&self, out: &mut Vec<String>, rn: &RenameMap) {
1906 out.push(format!("DROP CONSTRAINT {}", Id(self.db(rn))));
1907 }
1908}
1909impl Pg<TableCheck<'_>> {
1910 pub fn create_inline(&self, rn: &RenameMap, report: &mut Report) -> String {
1911 let mut sql = String::new();
1912 w!(sql, "CONSTRAINT {} CHECK (", Id(self.db(rn)));
1913 Pg(self.table.sql(&self.check)).print(&mut sql, rn, report);
1914 w!(sql, ")");
1915 sql
1916 }
1917 pub fn drop_alter(&self, out: &mut Vec<String>, rn: &RenameMap) {
1918 out.push(format!("DROP CONSTRAINT {}", Id(self.db(rn))));
1919 }
1920}
1921
1922impl Pg<SchemaView<'_>> {
1923 pub fn create(&self, sql: &mut String, rn: &RenameMap, report: &mut Report) {
1924 let table_name = Id(self.db(rn));
1925 w!(sql, "CREATE");
1926 if self.materialized {
1927 w!(sql, " MATERIALIZED");
1928 }
1929 w!(sql, " VIEW {table_name} AS");
1930 for ele in &self.0.definition.0 {
1931 match ele {
1932 DefinitionPart::Raw(r) => {
1933 w!(sql, "{r}")
1934 }
1935 DefinitionPart::TableRef(t) => {
1936 let Some(table) = self.schema.schema_table_or_view(t) else {
1937 panic!("referenced table not found: {t:?}");
1938 };
1939 match table {
1940 SchemaTableOrView::Table(t) => {
1941 w!(sql, "{}", Id(t.db(rn)))
1942 }
1943 SchemaTableOrView::View(v) => {
1944 w!(sql, "{}", Id(v.db(rn)))
1945 }
1946 }
1947 }
1948 DefinitionPart::ColumnRef(t, c) => {
1949 let table = self.schema.schema_table(t).expect("referenced");
1950 if let Some(db) =
1951 Sql::context_ident_name(&SchemaItem::Table(table), *c, rn, report)
1952 {
1953 let db = Id(db);
1954 w!(sql, "{db}")
1955 } else {
1956 w!(sql, "ERROR")
1957 }
1958 }
1959 }
1960 }
1961 wl!(sql, ";");
1962 }
1963 pub fn rename(&self, to: DbView, sql: &mut String, rn: &mut RenameMap) {
1964 self.print_alternations(&[alt_ungroup!("RENAME TO {}", Id(&to))], sql, rn);
1965 self.set_db(rn, to);
1966 }
1967 pub fn drop(&self, sql: &mut String, rn: &RenameMap) {
1968 let name = Id(self.db(rn));
1969 w!(sql, "DROP");
1970 if self.materialized {
1971 w!(sql, " MATERIALIZED");
1972 }
1973 wl!(sql, " VIEW {name};");
1974 }
1975
1976 pub fn print_alternations(&self, mut out: &[Alternation], sql: &mut String, rn: &RenameMap) {
1977 fn print_group(materialized: bool, name: &DbView, list: &[Alternation], sql: &mut String) {
1978 let name = Id(name);
1979 w!(sql, "ALTER");
1980 if materialized {
1981 w!(sql, " MATERIALIZED");
1982 }
1983 if list.len() > 1 {
1984 w!(sql, " VIEW {name}\n");
1985 for (i, alt) in list.iter().enumerate() {
1986 if i != 0 {
1987 w!(sql, ",");
1988 };
1989 wl!(sql, "\t{}", alt.alt);
1990 }
1991 wl!(sql, ";");
1992 } else {
1993 let alt = &list[0];
1994 w!(sql, " VIEW {name} {};\n", alt.alt);
1995 }
1996 }
1997 let name = &self.db(rn);
1998 while !out.is_empty() {
1999 let mut count = 1;
2000 loop {
2001 if !out[count - 1].groupable_down || out.len() == count {
2002 break;
2003 }
2004 if !out[count].groupable_up {
2005 break;
2006 }
2007 count += 1;
2008 }
2009 print_group(self.materialized, name, &out[..count], sql);
2010 out = &out[count..];
2011 }
2012 }
2013}
2014
2015#[derive(PartialEq, Clone, Copy)]
2016struct Id<T>(T);
2017impl<T> Display for Id<DbIdent<T>> {
2018 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2019 let id = escape_identifier(self.0.raw());
2020 write!(f, "{id}")
2021 }
2022}
2023impl<T> Display for Id<&DbIdent<T>> {
2024 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2025 let id = escape_identifier(self.0.raw());
2026 write!(f, "{id}")
2027 }
2028}
2029impl Display for Id<&str> {
2030 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2031 let id = escape_identifier(self.0);
2032 write!(f, "{id}")
2033 }
2034}
2035
2036#[derive(PartialEq, Eq)]
2037struct FingerprintBuilder(Vec<u8>);
2038impl FingerprintBuilder {
2039 fn new() -> Self {
2040 let mut value = FingerprintBuilder(Vec::new());
2041 value.section("start");
2042 value
2043 }
2044 fn section(&mut self, name: impl AsRef<[u8]>) {
2045 self.raw(0, name);
2046 }
2047 fn fragment(&mut self, fragment: impl AsRef<[u8]>) {
2048 self.raw(1, fragment);
2049 }
2050 fn raw(&mut self, kind: u8, raw: impl AsRef<[u8]>) {
2051 self.0.push(kind);
2052 let raw = raw.as_ref();
2053 let encoded = u32::to_be_bytes(raw.len() as u32);
2054 self.0.extend(&encoded);
2055 self.0.extend(raw);
2056 }
2057 fn sub(&mut self, name: impl AsRef<[u8]>, sub: FingerprintBuilder) {
2058 self.section(name);
2059 self.0.extend(sub.0);
2060 self.section("[END]");
2061 }
2062}
2063
2064impl IsCompatible for Pg<SchemaItem<'_>> {
2065 fn is_compatible(
2066 &self,
2067 new: &Self,
2068 rn: &RenameMap,
2069 report_self: &mut Report,
2070 report_new: &mut Report,
2071 ) -> bool {
2072 match (self.0, new.0) {
2073 (SchemaItem::Table(_), SchemaItem::Table(_)) => true,
2074 (SchemaItem::Enum(a), SchemaItem::Enum(b)) => {
2075 mk_change_list(
2078 rn,
2079 &a.items().collect_vec(),
2080 &b.items().collect_vec(),
2081 |v| v,
2082 report_self,
2083 report_new,
2084 )
2085 .dropped
2086 .is_empty()
2087 }
2088 (SchemaItem::Composite(a), SchemaItem::Composite(b)) => {
2089 let changes = mk_change_list(
2091 rn,
2092 &a.fields().collect_vec(),
2093 &b.fields().collect_vec(),
2094 |v| v,
2095 report_self,
2096 report_new,
2097 );
2098 changes.dropped.is_empty() && changes.created.is_empty()
2099 }
2100 (SchemaItem::Scalar(a), SchemaItem::Scalar(b)) => {
2101 if a.is_external() || b.is_external() {
2102 return true;
2103 }
2104 a.inner_type(rn, report_self) == b.inner_type(rn, report_new)
2105 }
2106 (SchemaItem::View(a), SchemaItem::View(b)) => {
2107 fn fingerprint(
2108 view: SchemaView<'_>,
2109 rn: &RenameMap,
2110 report: &mut Report,
2111 ) -> FingerprintBuilder {
2112 let mut fp = FingerprintBuilder::new();
2113 for part in &view.definition.0 {
2114 match part {
2115 DefinitionPart::Raw(r) => {
2116 fp.section("raw");
2117 fp.fragment(r);
2118 }
2119 DefinitionPart::TableRef(t) => {
2120 let t = view.schema.schema_table_or_view(t).expect("exists");
2121 match t {
2122 SchemaTableOrView::Table(t) => {
2123 fp.section("subtable");
2124 for ele in t.columns() {
2125 fp.section("name");
2126 fp.fragment(ele.db(rn).raw());
2127 fp.section("ty");
2128 fp.fragment(ele.db_type(rn, report).raw());
2129 }
2130 }
2131 SchemaTableOrView::View(v) => {
2132 fp.sub("reqview", fingerprint(v, rn, report));
2133 }
2134 }
2135 }
2136 DefinitionPart::ColumnRef(t, c) => {
2137 let t = view.schema.schema_table_or_view(t).expect("exists");
2138 match t {
2139 SchemaTableOrView::Table(t) => {
2140 fp.section("subtable");
2141 let c = t.schema_column(*c);
2142 fp.section("name");
2143 fp.fragment(c.db(rn).raw());
2144 fp.section("ty");
2145 fp.fragment(c.db_type(rn, report).raw());
2146 }
2147 SchemaTableOrView::View(v) => {
2148 fp.sub("reqview", fingerprint(v, rn, report));
2150 }
2151 }
2152 }
2153 }
2154 }
2155 fp
2156 }
2157 if a.materialized != b.materialized {
2158 return false;
2159 }
2160 let fpa = fingerprint(a, rn, report_self);
2161 let fpb = fingerprint(b, rn, report_new);
2162 fpa == fpb
2163 }
2164 _ => false,
2165 }
2166 }
2173}
2174impl IsIsomorph for Pg<SchemaItem<'_>> {
2175 fn is_isomorph(
2176 &self,
2177 other: &Self,
2178 rn: &RenameMap,
2179 report_self: &mut Report,
2180 report_other: &mut Report,
2181 ) -> bool {
2182 self.0.is_isomorph(&other.0, rn, report_self, report_other)
2183 }
2184}
2185impl Pg<SchemaDiff<'_>> {
2186 pub fn changelist(
2187 &self,
2188 rn: &RenameMap,
2189 report_old: &mut Report,
2190 report_new: &mut Report,
2191 ) -> ChangeList<SchemaItem<'_>> {
2192 let old = self.old.material_items();
2193 let new = self.new.material_items();
2194 mk_change_list(
2195 rn,
2196 old.as_slice(),
2197 new.as_slice(),
2198 Pg,
2199 report_old,
2200 report_new,
2201 )
2202 }
2203}
2204
2205#[cfg(test)]
2206mod tests {
2207 use std::{fs, io::Write, path::PathBuf};
2208
2209 use schema::diagnostics::Report;
2210 use schema::{
2211 parser::parse, process::NamingConvention, root::SchemaProcessOptions, uid::RenameMap, wl,
2212 Diff,
2213 };
2214 use tempfile::NamedTempFile;
2215 use tracing_test::traced_test;
2216
2217 use crate::Pg;
2218
2219 pub fn default_options() -> SchemaProcessOptions {
2220 SchemaProcessOptions {
2221 generator_supports_domain: true,
2222 naming_convention: NamingConvention::Postgres,
2223 }
2224 }
2225
2226 fn test_example(name: &str) {
2227 #[derive(Debug)]
2228 struct Update {
2229 description: String,
2230 schema: String,
2231 }
2232
2233 let mut data = fs::read_to_string(name).expect("example read");
2234 let result_offset = data.find("\n!!!RESULT\n").unwrap_or(data.len());
2235 data.truncate(result_offset);
2236 let (defaults, parts) = data.split_once("!!!TEST").unwrap_or(("", &data));
2237
2238 let defaults = defaults.strip_prefix("!!!SETUP\n").unwrap_or(defaults);
2239
2240 let mut examples = parts
2241 .split("\n!!!UPDATE")
2242 .map(|s| {
2243 let (description, text) = s.split_once('\n').unwrap_or((s, ""));
2244 Update {
2245 description: description.trim().to_string(),
2246 schema: text.to_string(),
2247 }
2248 })
2249 .collect::<Vec<_>>();
2250 examples.push(Update {
2251 description: "cleanup schema changes".to_owned(),
2252 schema: String::new(),
2253 });
2254 if !defaults.is_empty() {
2255 for example in &mut examples {
2256 if !example.schema.is_empty() {
2257 example.schema.insert(0, '\n');
2258 }
2259 example.schema.insert_str(0, defaults);
2260 }
2261 examples.insert(
2262 0,
2263 Update {
2264 description: "setup".to_owned(),
2265 schema: defaults.to_owned(),
2266 },
2267 );
2268 }
2269 examples.insert(
2271 0,
2272 Update {
2273 description: "in the beginning there was nothing (doesn't exist in output)"
2274 .to_owned(),
2275 schema: String::new(),
2276 },
2277 );
2278 if !defaults.is_empty() {
2280 examples.push(Update {
2281 description: "cleanup setup".to_owned(),
2282 schema: String::new(),
2283 });
2284 }
2285 let mut rn = RenameMap::default();
2286 let examples = examples
2287 .iter()
2288 .map(|example| {
2289 let mut report = Report::new();
2290 let schema = match parse(
2291 example.schema.as_str(),
2292 false,
2293 &default_options(),
2294 &mut rn,
2295 &mut report,
2296 ) {
2297 Ok(s) => s,
2298 Err(e) => {
2299 panic!()
2300 }
2312 };
2313 if report.is_error() {
2314 panic!("error found in report");
2315 }
2316 (example.description.clone(), schema)
2317 })
2318 .collect::<Vec<_>>();
2319 let mut out = String::new();
2320 for ele in examples.windows(2) {
2321 let description = &ele[1].0;
2322 if description.is_empty() {
2323 wl!(out, "\n-- updated --");
2324 } else {
2325 wl!(out, "\n-- updated: {description} --");
2326 }
2327 let mut out_tmp = String::new();
2328 Pg(Diff {
2329 old: &ele[0].1,
2330 new: &ele[1].1,
2331 })
2332 .print(
2333 &mut out_tmp,
2334 &mut rn,
2335 &mut Report::new(),
2336 &mut Report::new(),
2337 );
2338 out.push_str(out_tmp.trim());
2339 }
2340 let output = format!("{}\n!!!RESULT\n{}\n", data.trim(), out.trim());
2341 let mut named_file =
2342 NamedTempFile::new_in(PathBuf::from(name).parent().expect("parent")).expect("new temp");
2343 named_file.write_all(output.as_bytes()).expect("write");
2344 named_file.persist(name).expect("persist");
2345 }
2346 include!(concat!(env!("OUT_DIR"), "/example_tests.rs"));
2347}