immigrant_generator_postgres/
lib.rs

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			// Technically groupable, yet postgres bails with error: column "column" of relation "tests" does not exist,
144			// if appears with the same statement as ADD COLUMN.
145			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	/// Column renaming doesn't support grouping multiple renames in one ALTER TABLE.
155	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			// assert!(
210			// 	v.initialize_as().is_none(),
211			// 	"@initialize_as may only appear when field is added, not when the table is created"
212			// );
213			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		// Add comments
233		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		// #[allow(clippy::match_same_arms)]
354		// match (self.old.initialize_as(), self.new.initialize_as()) {
355		// 	(Some(_), Some(_)) => panic!("@initialize_as may not be left on migration"),
356		// 	(None, Some(_)) => {
357		// 		// TODO: Disallow when generating up migration.
358		// 		//panic!("@initialize_as may only be dropped")
359		// 	}
360		// 	(Some(_) | None, None) => {}
361		// }
362	}
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		// Rename/moveaway columns
386		{
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		// Drop fks
424		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		// Drop constraints
456		for constraint in constraint_changes.dropped {
457			constraint.drop_alter_non_fk(&mut out, rn);
458		}
459		// Rename constraints
460		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					// All moveaways were dropped
478				}
479			}
480		}
481
482		// Drop old indexes
483		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		// Create new columns
498		for ele in &column_changes.created {
499			Pg(*ele).create_alter(&mut out, rn, report_new);
500		}
501
502		// Update columns
503		for ele in &column_changes.updated {
504			Pg(*ele).print_alter(&mut out, rn, report_old, report_new);
505		}
506
507		// Update/create constraints except for foreign keys
508		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		// Create/update indexes
520		for added in index_changes.created {
521			added.create(sql, rn);
522		}
523		for updated in index_changes.updated {
524			// There is nothing updateable in indexes except for names, `old` should be equal to `new`
525			// Indexes should be always dropped and recreated
526			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		// Drop old columns
547		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		// Reconcile comments on table items
556		// Note that comments on table creation are not handled here, see `Pg::<SchemaTable>::create`
557		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		// TODO: Is it allowed to change column types, if there is an active index exists?
630		// If not - then type equality should also be checked
631		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		// We can't update unique flag, so they are not compatible and need to be recreated
645		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		// TODO: missing set_db?
692	}
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		// Rename/moveaway everything
706		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		// Enums: print_renamed_added
725		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			// If item is updated - it must be already initialized
740			initialized_tys.insert(Ident::unchecked_cast(ele.new.id()));
741		}
742
743		// Inlined/external scalars are always initialized
744		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		// Create new types, in toposorted order
755		{
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		// Update scalars
792		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		// Create new tables
806		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		// Update tables
814		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		// Drop old views, in toposorted order
838		{
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		// Create new views, in toposorted order
915		{
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		// Create new foreign keys
968		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		// Drop foreign keys, both ends of which reference dropped tables
983		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		// Drop old tables
997		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		// Drop old types, in toposorted order
1005		{
1006			let mut remaining_types = changelist
1007				.dropped
1008				.iter()
1009				.filter_map(SchemaItem::as_type)
1010				.filter(|v| match v {
1011					// Do not drop external scalars
1012					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		// Reconcile comments on schema items
1054		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			// TODO: Escape as literal
1111			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		// TODO: Escape both as literals
1138		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			// TODO: Escape literal
1182			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				// Postgres enums have no way to associate comment with value, unfortunately.
1449				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				// Postgres composites have no way to associate comment with value, unfortunately.
1464				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		// It is possible to fully implement precedence climbing here, however I have tried, and it resulted in
1505		// not very good looking code in some cases (Mix&matching LIKE, AND & IN); So I will only add variants, which should never conflict with what user sees.
1506		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				// String comparison is not looking good, but it works...
1573				(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				// There is only one pk per table, its just makes sense
1713				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				// It makes little sense to have multiple unique constraints with the same set of columns, except if it used as implicit index.
1721				// Anyway, lets try to recreate/rename that.
1722				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}
1768// Constraints are not updateable, except for foreign keys... But they are kinda special.
1769impl 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				// There is no DB engine, which supports removing enum variants, so removals are incompatible, and the
2076				// enum should be recreated.
2077				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				// There is no DB engine, which supports updating structs
2090				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										// If view is rebuilt, current view can't be the same.
2149										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		// matches!(
2167		// 	(self, new),
2168		// 	(Self::Table(_), Self::Table(_))
2169		// 		| (Self::Enum(a), Self::Enum(_))
2170		// 		| (Self::Scalar(_), Self::Scalar(_))
2171		// )
2172	}
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		// Init defaults
2270		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		// Drop defaults
2279		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						// for e in &e {
2301						// 	match e {
2302						// 		schema::parser::ParsingError::Peg(e) => {
2303						// 			eprintln!(
2304						// 				"buffer start: {}",
2305						// 				&example.schema.as_str()[e.location.offset..]
2306						// 			);
2307						// 		}
2308						// 	}
2309						// }
2310						// panic!("failed to parse schema:\n{}\n\n{e:#?}", example.schema);
2311					}
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}