1use super::collection::{DiffType, EntityDiff, PostgresDDL};
4use super::ddl::{
5 CheckConstraint, Column, Enum, ForeignKey, Index, Policy, PostgresEntity, PrimaryKey, Role,
6 Schema, Sequence, Table, TableSql, UniqueConstraint, View,
7};
8use crate::traits::EntityKind;
9use serde::Serialize;
10use std::collections::{HashMap, HashSet};
11use std::fmt::Write;
12
13pub const BREAKPOINT: &str = "--> statement-breakpoint";
14
15#[derive(Debug, Clone)]
16struct CreateTableOrder {
17 ordered: Vec<String>,
18 cycle_tables: HashSet<String>,
19}
20
21#[derive(Serialize, Debug, Clone)]
26#[serde(tag = "type", rename_all = "snake_case")]
27pub enum JsonStatement {
28 CreateTable {
29 table: RichTable,
30 },
31 DropTable {
32 table: Table,
33 #[serde(rename = "key")]
34 table_key: String,
35 },
36 RenameTable {
37 schema: String,
38 from: String,
39 to: String,
40 },
41 AddColumn {
42 column: Box<Column>,
43 #[serde(rename = "isPK")]
44 is_pk: bool,
45 #[serde(rename = "isCompositePK")]
46 is_composite_pk: bool,
47 },
48 DropColumn {
49 column: Box<Column>,
50 },
51 AlterColumn {
52 to: Box<Column>,
53 #[serde(rename = "wasEnum")]
54 was_enum: bool,
55 #[serde(rename = "isEnum")]
56 is_enum: bool,
57 diff: HashMap<String, serde_json::Value>, },
59 RenameColumn {
60 from: Box<Column>,
61 to: Box<Column>,
62 },
63 CreateIndex {
64 index: Index,
65 },
66 DropIndex {
67 index: Index,
68 },
69 CreateFk {
70 fk: ForeignKey,
71 },
72 DropFk {
73 fk: ForeignKey,
74 },
75 AddPk {
76 pk: PrimaryKey,
77 },
78 DropPk {
79 pk: PrimaryKey,
80 },
81 AddUnique {
82 unique: UniqueConstraint,
83 },
84 DropUnique {
85 unique: UniqueConstraint,
86 },
87 AddCheck {
88 check: CheckConstraint,
89 },
90 DropCheck {
91 check: CheckConstraint,
92 },
93 CreateSchema {
94 name: String,
95 },
96 DropSchema {
97 name: String,
98 },
99 RenameSchema {
100 from: Schema,
101 to: Schema,
102 },
103 CreateEnum {
104 #[serde(rename = "enum")]
105 enum_: Enum,
106 },
107 DropEnum {
108 #[serde(rename = "enum")]
109 enum_: Enum,
110 },
111 AlterEnum {
112 from: Enum,
113 to: Enum,
114 diff: Vec<EnumDiff>,
115 },
116 CreateSequence {
117 sequence: Sequence,
118 },
119 DropSequence {
120 sequence: Sequence,
121 },
122 CreateView {
123 view: View,
124 },
125 DropView {
126 view: View,
127 },
128 AlterView {
130 old_view: Box<View>,
131 new_view: Box<View>,
132 },
133 CreateRole {
134 role: Role,
135 },
136 DropRole {
137 role: Role,
138 },
139 CreatePolicy {
140 policy: Policy,
141 },
142 DropPolicy {
143 policy: Policy,
144 },
145 AlterTable {
146 old_table: Table,
147 new_table: Table,
148 },
149 RecreateFk {
150 old_fk: ForeignKey,
151 new_fk: ForeignKey,
152 },
153 RecreateUnique {
154 old_unique: UniqueConstraint,
155 new_unique: UniqueConstraint,
156 },
157 RecreateColumn {
159 old_column: Box<Column>,
160 new_column: Box<Column>,
161 },
162 RecreateIndex {
165 old_index: Box<Index>,
166 new_index: Box<Index>,
167 },
168 RecreatePk {
170 old_pk: PrimaryKey,
171 new_pk: PrimaryKey,
172 },
173 RecreateCheck {
175 old_check: CheckConstraint,
176 new_check: CheckConstraint,
177 },
178 RecreatePolicy {
180 old_policy: Box<Policy>,
181 new_policy: Box<Policy>,
182 },
183 AlterSequence {
185 old_sequence: Sequence,
186 new_sequence: Sequence,
187 },
188 AlterRole {
190 old_role: Role,
191 new_role: Role,
192 },
193 RecreateEnum {
198 old_enum: Enum,
199 new_enum: Enum,
200 columns: Vec<Column>,
202 },
203}
204
205#[derive(Serialize, Debug, Clone)]
206#[serde(rename_all = "camelCase")]
207pub struct EnumDiff {
208 pub r#type: String, pub value: String,
210 #[serde(skip_serializing_if = "Option::is_none")]
211 pub before_value: Option<String>,
212}
213
214#[derive(Serialize, Debug, Clone)]
217#[serde(rename_all = "camelCase")]
218pub struct RichTable {
219 pub name: String,
220 pub schema: String,
221 pub columns: Vec<Column>,
222 pub indexes: Vec<Index>,
223 pub foreign_keys: Vec<ForeignKey>,
224 pub pk: Option<PrimaryKey>,
225 pub uniques: Vec<UniqueConstraint>,
226 pub checks: Vec<CheckConstraint>,
227 pub policies: Vec<Policy>,
228 #[serde(skip_serializing_if = "Option::is_none")]
229 pub comment: Option<String>,
230 #[serde(skip_serializing_if = "Option::is_none")]
231 pub is_rls_enabled: Option<bool>,
232 #[serde(skip_serializing_if = "Option::is_none")]
233 pub is_unlogged: Option<bool>,
234 #[serde(skip_serializing_if = "Option::is_none")]
235 pub is_temporary: Option<bool>,
236 #[serde(skip_serializing_if = "Option::is_none")]
237 pub inherits: Option<String>,
238 #[serde(skip_serializing_if = "Option::is_none")]
239 pub tablespace: Option<String>,
240}
241
242#[derive(Default)]
243struct CreatedTableEntities<'a> {
244 columns: Vec<&'a Column>,
245 indexes: Vec<&'a Index>,
246 foreign_keys: Vec<&'a ForeignKey>,
247 primary_keys: Vec<&'a PrimaryKey>,
248 unique_constraints: Vec<&'a UniqueConstraint>,
249 check_constraints: Vec<&'a CheckConstraint>,
250 policies: Vec<&'a Policy>,
251}
252
253struct DiffIndex<'a> {
254 table_diffs: HashMap<&'a str, &'a EntityDiff>,
255 created_by_table: HashMap<String, CreatedTableEntities<'a>>,
256}
257
258impl<'a> DiffIndex<'a> {
259 fn new(diffs: &'a [EntityDiff]) -> Self {
260 let mut table_diffs = HashMap::new();
261 let mut created_by_table = HashMap::<String, CreatedTableEntities<'a>>::new();
262
263 for diff in diffs {
264 if diff.kind == EntityKind::Table {
265 table_diffs.insert(diff.name.as_str(), diff);
266 }
267 if diff.diff_type != DiffType::Create {
268 continue;
269 }
270
271 let Some(entity) = diff.right.as_ref() else {
272 continue;
273 };
274 let Some(table_key) = Generator::get_parent_table_key(diff) else {
275 continue;
276 };
277 let entries = created_by_table.entry(table_key).or_default();
278 match entity {
279 PostgresEntity::Column(value) => entries.columns.push(value),
280 PostgresEntity::Index(value) => entries.indexes.push(value),
281 PostgresEntity::ForeignKey(value) => entries.foreign_keys.push(value),
282 PostgresEntity::PrimaryKey(value) => entries.primary_keys.push(value),
283 PostgresEntity::UniqueConstraint(value) => {
284 entries.unique_constraints.push(value);
285 }
286 PostgresEntity::CheckConstraint(value) => entries.check_constraints.push(value),
287 PostgresEntity::Policy(value) => entries.policies.push(value),
288 _ => {}
289 }
290 }
291
292 Self {
293 table_diffs,
294 created_by_table,
295 }
296 }
297
298 fn table_diff(&self, table_key: &str) -> Option<&'a EntityDiff> {
299 self.table_diffs.get(table_key).copied()
300 }
301}
302
303fn rich_table_to_table(table: &RichTable) -> Table {
304 Table {
305 schema: table.schema.clone().into(),
306 name: table.name.clone().into(),
307 is_unlogged: table.is_unlogged,
308 is_temporary: table.is_temporary,
309 inherits: table.inherits.clone().map(Into::into),
310 tablespace: table.tablespace.clone().map(Into::into),
311 is_rls_enabled: table.is_rls_enabled,
312 comment: table.comment.clone().map(Into::into),
313 }
314}
315
316pub struct Generator {
321 pub breakpoints: bool,
322}
323
324impl Default for Generator {
325 fn default() -> Self {
326 Self::new()
327 }
328}
329
330impl Generator {
331 #[must_use]
332 pub const fn new() -> Self {
333 Self { breakpoints: true }
334 }
335
336 #[must_use]
337 pub const fn with_breakpoints(mut self, breakpoints: bool) -> Self {
338 self.breakpoints = breakpoints;
339 self
340 }
341
342 #[must_use]
350 pub fn generate(&self, diff: &[EntityDiff]) -> Vec<String> {
351 self.generate_with_ddl(diff, None)
352 }
353
354 #[must_use]
379 pub fn generate_with_ddl(
380 &self,
381 diff: &[EntityDiff],
382 cur_ddl: Option<&PostgresDDL>,
383 ) -> Vec<String> {
384 let mut sqls = Vec::new();
385 let diff_index = DiffIndex::new(diff);
386
387 let created_tables: Vec<String> = diff
389 .iter()
390 .filter(|d| d.diff_type == DiffType::Create && d.kind == EntityKind::Table)
391 .map(|d| d.name.clone())
392 .collect();
393 let dropped_tables: Vec<String> = diff
394 .iter()
395 .filter(|d| d.diff_type == DiffType::Drop && d.kind == EntityKind::Table)
396 .map(|d| d.name.clone())
397 .collect();
398
399 let push_diff = |sqls: &mut Vec<String>, d: &EntityDiff| {
400 if let Some(stmt) = Self::diff_to_statement_with_context(d, &diff_index, cur_ddl) {
401 sqls.extend(Self::statement_to_sqls(stmt));
402 }
403 };
404
405 for kind in [
407 EntityKind::Schema,
408 EntityKind::Enum,
409 EntityKind::Sequence,
410 EntityKind::Role,
411 ] {
412 for d in diff
413 .iter()
414 .filter(|d| d.kind == kind && d.diff_type == DiffType::Create)
415 {
416 push_diff(&mut sqls, d);
417 }
418 }
419
420 for d in diff
423 .iter()
424 .filter(|d| d.kind == EntityKind::View && d.diff_type == DiffType::Drop)
425 {
426 push_diff(&mut sqls, d);
427 }
428
429 for kind in [
434 EntityKind::ForeignKey,
435 EntityKind::Index,
436 EntityKind::PrimaryKey,
437 EntityKind::UniqueConstraint,
438 EntityKind::CheckConstraint,
439 EntityKind::Policy,
440 EntityKind::Column,
441 ] {
442 for d in diff
443 .iter()
444 .filter(|d| d.kind == kind && d.diff_type == DiffType::Drop)
445 {
446 if let Some(parent_table) = Self::get_parent_table_key(d)
447 && dropped_tables.contains(&parent_table)
448 {
449 continue; }
451 push_diff(&mut sqls, d);
452 }
453 }
454
455 let sorted_drops = topological_sort_tables_for_drop(&dropped_tables, diff);
458 for table_key in &sorted_drops {
459 if let Some(table_diff) = diff_index.table_diff(table_key)
460 && table_diff.diff_type == DiffType::Drop
461 {
462 push_diff(&mut sqls, table_diff);
463 }
464 }
465
466 let sorted_creates = topological_sort_tables_for_create(&created_tables, diff);
468 if sorted_creates.cycle_tables.is_empty() {
469 for table_key in &sorted_creates.ordered {
470 let table_diff = diff_index
471 .table_diff(table_key)
472 .expect("created table must have an indexed table diff");
473 if let Some(PostgresEntity::Table(table)) = &table_diff.right {
474 let rich_table = Self::build_rich_table(table, &diff_index);
475 sqls.push(Self::create_table_sql(&rich_table));
476 Self::push_created_table_extras(&mut sqls, &rich_table);
477 }
478 }
479 } else {
480 let mut deferred_fks = Vec::new();
481 let mut rich_tables = Vec::new();
482
483 for table_key in &sorted_creates.ordered {
484 let table_diff = diff_index
485 .table_diff(table_key)
486 .expect("created table must have an indexed table diff");
487 if let Some(PostgresEntity::Table(table)) = &table_diff.right {
488 let mut rich_table = Self::build_rich_table(table, &diff_index);
489 let (inline_fks, cycle_fks): (Vec<_>, Vec<_>) = rich_table
490 .foreign_keys
491 .into_iter()
492 .partition(|fk| !Self::is_cycle_fk(fk, &sorted_creates.cycle_tables));
493 rich_table.foreign_keys = inline_fks;
494 deferred_fks.extend(cycle_fks);
495 sqls.push(Self::create_table_sql(&rich_table));
496 rich_tables.push(rich_table);
497 }
498 }
499
500 for fk in &deferred_fks {
501 sqls.push(Self::add_fk_sql(fk));
502 }
503
504 for rich_table in &rich_tables {
505 Self::push_created_table_extras(&mut sqls, rich_table);
506 }
507 }
508
509 for kind in [
512 EntityKind::Column,
513 EntityKind::PrimaryKey,
514 EntityKind::UniqueConstraint,
515 EntityKind::CheckConstraint,
516 EntityKind::Index,
517 EntityKind::ForeignKey,
518 EntityKind::Policy,
519 ] {
520 for d in diff
521 .iter()
522 .filter(|d| d.kind == kind && d.diff_type == DiffType::Create)
523 {
524 if let Some(parent_table) = Self::get_parent_table_key(d)
525 && created_tables.contains(&parent_table)
526 {
527 continue; }
529 push_diff(&mut sqls, d);
530 }
531 }
532
533 for d in diff.iter().filter(|d| d.diff_type == DiffType::Alter) {
536 push_diff(&mut sqls, d);
537 }
538
539 for kind in [EntityKind::Enum, EntityKind::Sequence] {
544 for d in diff
545 .iter()
546 .filter(|d| d.kind == kind && d.diff_type == DiffType::Drop)
547 {
548 push_diff(&mut sqls, d);
549 }
550 }
551
552 for d in diff
554 .iter()
555 .filter(|d| d.kind == EntityKind::View && d.diff_type == DiffType::Create)
556 {
557 push_diff(&mut sqls, d);
558 }
559
560 for kind in [EntityKind::Role, EntityKind::Schema] {
563 for d in diff
564 .iter()
565 .filter(|d| d.kind == kind && d.diff_type == DiffType::Drop)
566 {
567 push_diff(&mut sqls, d);
568 }
569 }
570
571 sqls
572 }
573
574 fn get_parent_table_key(d: &EntityDiff) -> Option<String> {
575 match d.kind {
578 EntityKind::Column | EntityKind::Policy => {
579 let parts: Vec<&str> = d.name.split('.').collect();
581 if parts.len() >= 3 {
582 Some(format!("{}.{}", parts[0], parts[1]))
583 } else {
584 None
585 }
586 }
587 EntityKind::Index
588 | EntityKind::ForeignKey
589 | EntityKind::PrimaryKey
590 | EntityKind::UniqueConstraint
591 | EntityKind::CheckConstraint => {
592 let entity = d.right.as_ref().or(d.left.as_ref())?;
595 match entity {
596 PostgresEntity::Index(i) => Some(format!("{}.{}", i.schema, i.table)),
597 PostgresEntity::ForeignKey(f) => Some(format!("{}.{}", f.schema, f.table)),
598 PostgresEntity::PrimaryKey(p) => Some(format!("{}.{}", p.schema, p.table)),
599 PostgresEntity::UniqueConstraint(u) => {
600 Some(format!("{}.{}", u.schema, u.table))
601 }
602 PostgresEntity::CheckConstraint(c) => Some(format!("{}.{}", c.schema, c.table)),
603 _ => None,
604 }
605 }
606 _ => None,
607 }
608 }
609
610 fn table_key(schema: &str, table: &str) -> String {
611 format!("{schema}.{table}")
612 }
613
614 fn is_cycle_fk(fk: &ForeignKey, cycle_tables: &HashSet<String>) -> bool {
615 cycle_tables.contains(&Self::table_key(&fk.schema, &fk.table))
616 && cycle_tables.contains(&Self::table_key(&fk.schema_to, &fk.table_to))
617 }
618
619 fn push_created_table_extras(sqls: &mut Vec<String>, table: &RichTable) {
620 sqls.extend(Self::created_table_comments_sql(table));
621
622 for index in &table.indexes {
623 sqls.push(Self::create_index_sql(index));
624 }
625
626 if table.is_rls_enabled.unwrap_or(false) {
627 sqls.push(format!(
628 "ALTER TABLE {} ENABLE ROW LEVEL SECURITY;",
629 Self::qualified_name(&table.schema, &table.name)
630 ));
631 }
632
633 for policy in &table.policies {
634 sqls.push(Self::create_policy_sql(policy));
635 }
636 }
637
638 fn build_rich_table(table: &Table, diff_index: &DiffIndex<'_>) -> RichTable {
639 let table_key = format!("{}.{}", table.schema, table.name);
640 let entities = diff_index.created_by_table.get(&table_key);
641 let columns = entities
642 .map(|entries| {
643 entries
644 .columns
645 .iter()
646 .map(|value| (*value).clone())
647 .collect()
648 })
649 .unwrap_or_default();
650 let indexes = entities
651 .map(|entries| {
652 entries
653 .indexes
654 .iter()
655 .map(|value| (*value).clone())
656 .collect()
657 })
658 .unwrap_or_default();
659 let foreign_keys = entities
660 .map(|entries| {
661 entries
662 .foreign_keys
663 .iter()
664 .map(|value| (*value).clone())
665 .collect()
666 })
667 .unwrap_or_default();
668 let uniques = entities
669 .map(|entries| {
670 entries
671 .unique_constraints
672 .iter()
673 .map(|value| (*value).clone())
674 .collect()
675 })
676 .unwrap_or_default();
677 let checks = entities
678 .map(|entries| {
679 entries
680 .check_constraints
681 .iter()
682 .map(|value| (*value).clone())
683 .collect()
684 })
685 .unwrap_or_default();
686 let policies = entities
687 .map(|entries| {
688 entries
689 .policies
690 .iter()
691 .map(|value| (*value).clone())
692 .collect()
693 })
694 .unwrap_or_default();
695 let pk = entities
696 .and_then(|entries| entries.primary_keys.first())
697 .map(|value| (*value).clone());
698
699 RichTable {
700 name: table.name.to_string(),
701 schema: table.schema.to_string(),
702 is_rls_enabled: table.is_rls_enabled,
703 is_unlogged: table.is_unlogged,
704 is_temporary: table.is_temporary,
705 inherits: table.inherits.as_ref().map(ToString::to_string),
706 tablespace: table.tablespace.as_ref().map(ToString::to_string),
707 columns,
708 indexes,
709 foreign_keys,
710 pk,
711 uniques,
712 checks,
713 policies,
714 comment: table.comment.as_ref().map(ToString::to_string),
715 }
716 }
717
718 fn diff_to_statement_with_context(
721 d: &EntityDiff,
722 diff_index: &DiffIndex<'_>,
723 cur_ddl: Option<&PostgresDDL>,
724 ) -> Option<JsonStatement> {
725 match d.diff_type {
726 DiffType::Create => Self::create_diff_to_statement(d.right.as_ref()?, diff_index),
727 DiffType::Drop => Self::drop_diff_to_statement(d.left.as_ref()?),
728 DiffType::Alter => {
729 Self::alter_diff_to_statement(d.left.as_ref(), d.right.as_ref(), cur_ddl)
730 }
731 }
732 }
733
734 fn create_diff_to_statement(
735 right: &PostgresEntity,
736 diff_index: &DiffIndex<'_>,
737 ) -> Option<JsonStatement> {
738 match right {
739 PostgresEntity::Schema(s) => Some(JsonStatement::CreateSchema {
740 name: s.name.to_string(),
741 }),
742 PostgresEntity::Enum(e) => Some(JsonStatement::CreateEnum { enum_: e.clone() }),
743 PostgresEntity::Sequence(s) => Some(JsonStatement::CreateSequence {
744 sequence: s.clone(),
745 }),
746 PostgresEntity::Role(r) => Some(JsonStatement::CreateRole { role: r.clone() }),
747 PostgresEntity::View(v) => Some(JsonStatement::CreateView { view: v.clone() }),
748 PostgresEntity::Column(c) => {
749 let (is_pk, is_composite_pk) = Self::check_column_pk_status(c, diff_index);
750 Some(JsonStatement::AddColumn {
751 column: Box::new(c.clone()),
752 is_pk,
753 is_composite_pk,
754 })
755 }
756 PostgresEntity::Index(i) => Some(JsonStatement::CreateIndex { index: i.clone() }),
757 PostgresEntity::ForeignKey(f) => Some(JsonStatement::CreateFk { fk: f.clone() }),
758 PostgresEntity::PrimaryKey(p) => Some(JsonStatement::AddPk { pk: p.clone() }),
759 PostgresEntity::UniqueConstraint(u) => {
760 Some(JsonStatement::AddUnique { unique: u.clone() })
761 }
762 PostgresEntity::CheckConstraint(c) => {
763 Some(JsonStatement::AddCheck { check: c.clone() })
764 }
765 PostgresEntity::Policy(p) => Some(JsonStatement::CreatePolicy { policy: p.clone() }),
766 PostgresEntity::Table(_) | PostgresEntity::Privilege(_) => None,
768 }
769 }
770
771 fn drop_diff_to_statement(left: &PostgresEntity) -> Option<JsonStatement> {
772 match left {
773 PostgresEntity::Schema(s) => Some(JsonStatement::DropSchema {
774 name: s.name.to_string(),
775 }),
776 PostgresEntity::Enum(e) => Some(JsonStatement::DropEnum { enum_: e.clone() }),
777 PostgresEntity::Sequence(s) => Some(JsonStatement::DropSequence {
778 sequence: s.clone(),
779 }),
780 PostgresEntity::Role(r) => Some(JsonStatement::DropRole { role: r.clone() }),
781 PostgresEntity::View(v) => Some(JsonStatement::DropView { view: v.clone() }),
782 PostgresEntity::Table(t) => Some(JsonStatement::DropTable {
783 table: t.clone(),
784 table_key: format!("{}.{}", t.schema, t.name),
785 }),
786 PostgresEntity::Column(c) => Some(JsonStatement::DropColumn {
787 column: Box::new(c.clone()),
788 }),
789 PostgresEntity::Index(i) => Some(JsonStatement::DropIndex { index: i.clone() }),
790 PostgresEntity::ForeignKey(f) => Some(JsonStatement::DropFk { fk: f.clone() }),
791 PostgresEntity::PrimaryKey(p) => Some(JsonStatement::DropPk { pk: p.clone() }),
792 PostgresEntity::UniqueConstraint(u) => {
793 Some(JsonStatement::DropUnique { unique: u.clone() })
794 }
795 PostgresEntity::CheckConstraint(c) => {
796 Some(JsonStatement::DropCheck { check: c.clone() })
797 }
798 PostgresEntity::Policy(p) => Some(JsonStatement::DropPolicy { policy: p.clone() }),
799 PostgresEntity::Privilege(_) => None, }
801 }
802
803 fn enum_values_are_pure_additions(
807 old: &[std::borrow::Cow<'static, str>],
808 new: &[std::borrow::Cow<'static, str>],
809 ) -> bool {
810 let mut new_iter = new.iter();
811 old.iter()
812 .all(|old_value| new_iter.any(|new_value| new_value == old_value))
813 }
814
815 fn enum_dependent_columns(ddl: &PostgresDDL, enum_: &Enum) -> Vec<Column> {
817 ddl.columns
818 .list()
819 .iter()
820 .filter(|column| {
821 column.sql_type.as_ref() == enum_.name.as_ref()
822 && column.type_schema.as_deref().unwrap_or("public") == enum_.schema.as_ref()
823 })
824 .map(|column| (*column).clone())
825 .collect()
826 }
827
828 fn alter_diff_to_statement(
829 left: Option<&PostgresEntity>,
830 right: Option<&PostgresEntity>,
831 cur_ddl: Option<&PostgresDDL>,
832 ) -> Option<JsonStatement> {
833 match (left, right) {
834 (Some(PostgresEntity::Enum(old)), Some(PostgresEntity::Enum(new))) => {
835 if old.values == new.values {
836 return None;
837 }
838 if !Self::enum_values_are_pure_additions(&old.values, &new.values) {
839 let columns = cur_ddl
843 .map(|ddl| Self::enum_dependent_columns(ddl, new))
844 .unwrap_or_default();
845 return Some(JsonStatement::RecreateEnum {
846 old_enum: old.clone(),
847 new_enum: new.clone(),
848 columns,
849 });
850 }
851 let mut diffs = Vec::new();
852 for (idx, val) in new.values.iter().enumerate() {
853 if !old.values.iter().any(|v| v == val) {
854 let before_value = new
855 .values
856 .iter()
857 .skip(idx + 1)
858 .find(|candidate| {
859 old.values.iter().any(|old_value| old_value == *candidate)
860 })
861 .map(ToString::to_string);
862 diffs.push(EnumDiff {
863 r#type: "added".to_string(),
864 value: val.to_string(),
865 before_value,
866 });
867 }
868 }
869 if diffs.is_empty() {
870 None
871 } else {
872 Some(JsonStatement::AlterEnum {
873 from: old.clone(),
874 to: new.clone(),
875 diff: diffs,
876 })
877 }
878 }
879 (Some(PostgresEntity::Column(old)), Some(PostgresEntity::Column(new))) => {
880 let needs_recreate = old.generated.is_none() && new.generated.is_some();
882 if needs_recreate {
883 Some(JsonStatement::RecreateColumn {
884 old_column: Box::new(old.clone()),
885 new_column: Box::new(new.clone()),
886 })
887 } else {
888 let diff = Self::build_column_diff(old, new);
889 let was_enum = old.type_schema.is_some();
890 let is_enum = new.type_schema.is_some();
891 Some(JsonStatement::AlterColumn {
892 to: Box::new(new.clone()),
893 was_enum,
894 is_enum,
895 diff,
896 })
897 }
898 }
899 (Some(PostgresEntity::Table(old)), Some(PostgresEntity::Table(new))) => {
900 if Self::alter_table_sql(old, new).is_some() {
901 Some(JsonStatement::AlterTable {
902 old_table: old.clone(),
903 new_table: new.clone(),
904 })
905 } else {
906 None
907 }
908 }
909 (Some(PostgresEntity::ForeignKey(old)), Some(PostgresEntity::ForeignKey(new))) => {
910 Some(JsonStatement::RecreateFk {
911 old_fk: old.clone(),
912 new_fk: new.clone(),
913 })
914 }
915 (
916 Some(PostgresEntity::UniqueConstraint(old)),
917 Some(PostgresEntity::UniqueConstraint(new)),
918 ) => Some(JsonStatement::RecreateUnique {
919 old_unique: old.clone(),
920 new_unique: new.clone(),
921 }),
922 (Some(PostgresEntity::View(old)), Some(PostgresEntity::View(new))) => {
925 if old.is_existing || new.is_existing {
926 None
927 } else {
928 Some(JsonStatement::AlterView {
929 old_view: Box::new(old.clone()),
930 new_view: Box::new(new.clone()),
931 })
932 }
933 }
934 (Some(PostgresEntity::Index(old)), Some(PostgresEntity::Index(new))) => {
936 Some(JsonStatement::RecreateIndex {
937 old_index: Box::new(old.clone()),
938 new_index: Box::new(new.clone()),
939 })
940 }
941 (Some(PostgresEntity::PrimaryKey(old)), Some(PostgresEntity::PrimaryKey(new))) => {
942 Some(JsonStatement::RecreatePk {
943 old_pk: old.clone(),
944 new_pk: new.clone(),
945 })
946 }
947 (
948 Some(PostgresEntity::CheckConstraint(old)),
949 Some(PostgresEntity::CheckConstraint(new)),
950 ) => Some(JsonStatement::RecreateCheck {
951 old_check: old.clone(),
952 new_check: new.clone(),
953 }),
954 (Some(PostgresEntity::Policy(old)), Some(PostgresEntity::Policy(new))) => {
955 Some(JsonStatement::RecreatePolicy {
956 old_policy: Box::new(old.clone()),
957 new_policy: Box::new(new.clone()),
958 })
959 }
960 (Some(PostgresEntity::Sequence(old)), Some(PostgresEntity::Sequence(new))) => {
961 if Self::alter_sequence_sql(old, new).is_some() {
962 Some(JsonStatement::AlterSequence {
963 old_sequence: old.clone(),
964 new_sequence: new.clone(),
965 })
966 } else {
967 None
968 }
969 }
970 (Some(PostgresEntity::Role(old)), Some(PostgresEntity::Role(new))) => {
971 if Self::alter_role_sql(old, new).is_some() {
972 Some(JsonStatement::AlterRole {
973 old_role: old.clone(),
974 new_role: new.clone(),
975 })
976 } else {
977 None
978 }
979 }
980 _ => None,
981 }
982 }
983
984 fn check_column_pk_status(col: &Column, diff_index: &DiffIndex<'_>) -> (bool, bool) {
993 let table_key = format!("{}.{}", col.schema, col.table);
994 let table_created_this_diff = diff_index
995 .table_diff(&table_key)
996 .is_some_and(|table_diff| table_diff.diff_type == DiffType::Create);
997
998 if let Some(entries) = diff_index.created_by_table.get(&table_key) {
999 for pk in &entries.primary_keys {
1000 if pk.columns.contains(&col.name) {
1001 let is_composite = pk.columns.len() > 1;
1002 let default_pk_name = format!("{}_pkey", col.table);
1003 let is_single_pk = table_created_this_diff
1004 && pk.columns.len() == 1
1005 && pk.name == default_pk_name;
1006 return (is_single_pk, is_composite);
1007 }
1008 }
1009 }
1010
1011 (false, false)
1012 }
1013
1014 fn build_column_diff(old: &Column, new: &Column) -> HashMap<String, serde_json::Value> {
1017 let mut diff = HashMap::new();
1018
1019 if old.sql_type != new.sql_type
1021 || old.type_schema != new.type_schema
1022 || old.dimensions != new.dimensions
1023 {
1024 let mut type_diff = serde_json::Map::new();
1025 type_diff.insert("from".to_string(), serde_json::json!(old.sql_type));
1026 type_diff.insert("to".to_string(), serde_json::json!(new.sql_type));
1027 type_diff.insert(
1028 "fromDimensions".to_string(),
1029 serde_json::json!(old.dimensions),
1030 );
1031 type_diff.insert(
1032 "toDimensions".to_string(),
1033 serde_json::json!(new.dimensions),
1034 );
1035 diff.insert("type".to_string(), serde_json::Value::Object(type_diff));
1036
1037 if old.type_schema != new.type_schema {
1038 let mut ts_diff = serde_json::Map::new();
1039 ts_diff.insert("from".to_string(), serde_json::json!(old.type_schema));
1040 ts_diff.insert("to".to_string(), serde_json::json!(new.type_schema));
1041 diff.insert("typeSchema".to_string(), serde_json::Value::Object(ts_diff));
1042 }
1043 }
1044
1045 if old.default != new.default {
1047 let mut default_diff = serde_json::Map::new();
1048 default_diff.insert("from".to_string(), serde_json::json!(old.default));
1049 default_diff.insert("to".to_string(), serde_json::json!(new.default));
1050 diff.insert(
1051 "default".to_string(),
1052 serde_json::Value::Object(default_diff),
1053 );
1054 }
1055
1056 if old.not_null != new.not_null {
1058 let mut nn_diff = serde_json::Map::new();
1059 nn_diff.insert("from".to_string(), serde_json::json!(old.not_null));
1060 nn_diff.insert("to".to_string(), serde_json::json!(new.not_null));
1061 diff.insert("notNull".to_string(), serde_json::Value::Object(nn_diff));
1062 }
1063
1064 if old.generated != new.generated {
1066 let mut gen_diff = serde_json::Map::new();
1067 gen_diff.insert("from".to_string(), serde_json::json!(old.generated));
1068 gen_diff.insert("to".to_string(), serde_json::json!(new.generated));
1069 diff.insert("generated".to_string(), serde_json::Value::Object(gen_diff));
1070 }
1071
1072 if old.identity != new.identity {
1074 let mut id_diff = serde_json::Map::new();
1075 id_diff.insert("from".to_string(), serde_json::json!(old.identity));
1076 id_diff.insert("to".to_string(), serde_json::json!(new.identity));
1077 diff.insert("identity".to_string(), serde_json::Value::Object(id_diff));
1078 }
1079
1080 if old.comment != new.comment {
1081 let mut comment_diff = serde_json::Map::new();
1082 comment_diff.insert("from".to_string(), serde_json::json!(old.comment));
1083 comment_diff.insert("to".to_string(), serde_json::json!(new.comment));
1084 diff.insert(
1085 "comment".to_string(),
1086 serde_json::Value::Object(comment_diff),
1087 );
1088 }
1089
1090 diff
1091 }
1092
1093 fn schema_prefix(schema: &str) -> String {
1094 if schema == "public" {
1095 String::new()
1096 } else {
1097 format!("{}.", Self::quote_ident(schema))
1098 }
1099 }
1100
1101 fn quote_ident(ident: &str) -> String {
1102 format!("\"{}\"", ident.replace('"', "\"\""))
1103 }
1104
1105 fn quote_literal(value: &str) -> String {
1106 format!("'{}'", value.replace('\'', "''"))
1107 }
1108
1109 fn qualified_name(schema: &str, name: &str) -> String {
1110 format!("{}{}", Self::schema_prefix(schema), Self::quote_ident(name))
1111 }
1112
1113 fn comment_value_sql(comment: Option<&str>) -> String {
1114 comment.map_or_else(|| "NULL".to_string(), Self::quote_literal)
1115 }
1116
1117 fn comment_on_table_sql(schema: &str, table: &str, comment: Option<&str>) -> String {
1118 format!(
1119 "COMMENT ON TABLE {} IS {};",
1120 Self::qualified_name(schema, table),
1121 Self::comment_value_sql(comment)
1122 )
1123 }
1124
1125 fn comment_on_column_sql(
1126 schema: &str,
1127 table: &str,
1128 column: &str,
1129 comment: Option<&str>,
1130 ) -> String {
1131 format!(
1132 "COMMENT ON COLUMN {}.{} IS {};",
1133 Self::qualified_name(schema, table),
1134 Self::quote_ident(column),
1135 Self::comment_value_sql(comment)
1136 )
1137 }
1138
1139 fn created_table_comments_sql(table: &RichTable) -> Vec<String> {
1140 let ddl_table = rich_table_to_table(table);
1141 TableSql::new(&ddl_table)
1142 .columns(&table.columns)
1143 .create_comments_sql()
1144 }
1145
1146 fn column_type_sql(col: &Column) -> String {
1149 col.type_sql()
1150 }
1151
1152 fn identity_sql(col: &Column, id: &super::ddl::Identity) -> Option<String> {
1153 use super::ddl::IdentityType;
1154 use super::grammar::PgTypeCategory;
1155
1156 if PgTypeCategory::from_sql_type(&col.sql_type).is_serial() {
1157 return None;
1158 }
1159
1160 let type_str = match id.type_ {
1161 IdentityType::Always => "ALWAYS",
1162 IdentityType::ByDefault => "BY DEFAULT",
1163 };
1164
1165 let mut sql = format!(" GENERATED {type_str} AS IDENTITY");
1166 let mut options = Vec::new();
1167 if let Some(increment) = id.increment.as_ref() {
1168 options.push(format!("INCREMENT BY {increment}"));
1169 }
1170 if let Some(min) = id.min_value.as_ref() {
1171 options.push(format!("MINVALUE {min}"));
1172 }
1173 if let Some(max) = id.max_value.as_ref() {
1174 options.push(format!("MAXVALUE {max}"));
1175 }
1176 if let Some(start) = id.start_with.as_ref() {
1177 options.push(format!("START WITH {start}"));
1178 }
1179 if let Some(cache) = id.cache {
1180 options.push(format!("CACHE {cache}"));
1181 }
1182 if id.cycle.unwrap_or(false) {
1183 options.push("CYCLE".to_string());
1184 }
1185 if !options.is_empty() {
1186 let _ = write!(sql, " ({})", options.join(" "));
1187 }
1188 Some(sql)
1189 }
1190
1191 fn identity_set_options_sql(
1196 new: &super::ddl::Identity,
1197 old: &serde_json::Value,
1198 ) -> Option<String> {
1199 use super::ddl::IdentityType;
1200
1201 let old_str = |key: &str| old.get(key).and_then(|v| v.as_str());
1202 let mut pieces = Vec::new();
1203
1204 let new_type = match new.type_ {
1205 IdentityType::Always => "always",
1206 IdentityType::ByDefault => "byDefault",
1207 };
1208 if old_str("type").is_some_and(|t| t != new_type) {
1209 pieces.push(match new.type_ {
1210 IdentityType::Always => "SET GENERATED ALWAYS".to_string(),
1211 IdentityType::ByDefault => "SET GENERATED BY DEFAULT".to_string(),
1212 });
1213 }
1214
1215 if old_str("increment") != new.increment.as_deref() {
1216 let increment = new.increment.as_deref().unwrap_or("1");
1217 pieces.push(format!("SET INCREMENT BY {increment}"));
1218 }
1219 if old_str("minValue") != new.min_value.as_deref() {
1220 match new.min_value.as_deref() {
1221 Some(min) => pieces.push(format!("SET MINVALUE {min}")),
1222 None => pieces.push("SET NO MINVALUE".to_string()),
1223 }
1224 }
1225 if old_str("maxValue") != new.max_value.as_deref() {
1226 match new.max_value.as_deref() {
1227 Some(max) => pieces.push(format!("SET MAXVALUE {max}")),
1228 None => pieces.push("SET NO MAXVALUE".to_string()),
1229 }
1230 }
1231 if old_str("startWith") != new.start_with.as_deref()
1232 && let Some(start) = new.start_with.as_deref()
1233 {
1234 pieces.push(format!("SET START WITH {start}"));
1235 }
1236 let old_cache = old.get("cache").and_then(serde_json::Value::as_i64);
1237 if old_cache != new.cache.map(i64::from) {
1238 let cache = new.cache.unwrap_or(1);
1239 pieces.push(format!("SET CACHE {cache}"));
1240 }
1241 let old_cycle = old
1242 .get("cycle")
1243 .and_then(serde_json::Value::as_bool)
1244 .unwrap_or(false);
1245 let new_cycle = new.cycle.unwrap_or(false);
1246 if old_cycle != new_cycle {
1247 pieces.push(if new_cycle {
1248 "SET CYCLE".to_string()
1249 } else {
1250 "SET NO CYCLE".to_string()
1251 });
1252 }
1253
1254 if pieces.is_empty() {
1255 None
1256 } else {
1257 Some(pieces.join(" "))
1258 }
1259 }
1260
1261 fn create_sequence_sql(s: &super::ddl::Sequence) -> String {
1262 s.create_sequence_sql()
1263 }
1264
1265 fn create_table_sql(table: &RichTable) -> String {
1266 let ddl_table = rich_table_to_table(table);
1267 TableSql::new(&ddl_table)
1268 .columns(&table.columns)
1269 .primary_key(table.pk.as_ref())
1270 .foreign_keys(&table.foreign_keys)
1271 .unique_constraints(&table.uniques)
1272 .check_constraints(&table.checks)
1273 .create_table_sql()
1274 }
1275
1276 fn drop_constraint_sql(schema: &str, table: &str, name: &str) -> String {
1277 format!(
1278 "ALTER TABLE {} DROP CONSTRAINT {};",
1279 Self::qualified_name(schema, table),
1280 Self::quote_ident(name)
1281 )
1282 }
1283
1284 fn create_enum_sql(e: &super::ddl::Enum) -> String {
1285 e.create_enum_sql()
1286 }
1287
1288 fn alter_enum_sql(to: &super::ddl::Enum, diff: &[EnumDiff]) -> String {
1289 diff.iter()
1290 .map(|d| to.add_value_sql(&d.value, d.before_value.as_deref()))
1291 .collect::<Vec<_>>()
1292 .join("\n")
1293 }
1294
1295 fn add_pk_sql(pk: &super::ddl::PrimaryKey) -> String {
1296 pk.add_pk_sql()
1297 }
1298
1299 fn add_unique_sql(unique: &super::ddl::UniqueConstraint) -> String {
1300 unique.add_unique_sql()
1301 }
1302
1303 fn recreate_column_sql(old_column: &Column, new_column: &Column) -> String {
1304 let table_key = Self::qualified_name(&new_column.schema, &new_column.table);
1307 let drop_sql = format!(
1308 "ALTER TABLE {} DROP COLUMN {};",
1309 table_key,
1310 Self::quote_ident(&old_column.name)
1311 );
1312 let add_sql = format!(
1313 "ALTER TABLE {} ADD COLUMN {};",
1314 table_key,
1315 new_column.to_column_sql()
1316 );
1317 if new_column.comment.is_some() {
1318 format!(
1319 "{drop_sql}\n{add_sql}\n{}",
1320 Self::comment_on_column_sql(
1321 &new_column.schema,
1322 &new_column.table,
1323 &new_column.name,
1324 new_column.comment.as_deref(),
1325 )
1326 )
1327 } else {
1328 format!("{drop_sql}\n{add_sql}")
1329 }
1330 }
1331
1332 fn add_column_sql(column: &Column, is_pk: bool) -> String {
1333 let pk_clause = if is_pk { " PRIMARY KEY" } else { "" };
1334 let add_sql = format!(
1335 "ALTER TABLE {} ADD COLUMN {}{};",
1336 Self::qualified_name(&column.schema, &column.table),
1337 column.to_column_sql(),
1338 pk_clause
1339 );
1340 if column.comment.is_some() {
1341 format!(
1342 "{add_sql}\n{}",
1343 Self::comment_on_column_sql(
1344 &column.schema,
1345 &column.table,
1346 &column.name,
1347 column.comment.as_deref(),
1348 )
1349 )
1350 } else {
1351 add_sql
1352 }
1353 }
1354
1355 fn add_check_sql(check: &super::ddl::CheckConstraint) -> String {
1356 check.add_check_sql()
1357 }
1358
1359 fn drop_policy_sql(policy: &super::ddl::Policy) -> String {
1360 format!(
1361 "DROP POLICY {} ON {};",
1362 Self::quote_ident(&policy.name),
1363 Self::qualified_name(&policy.schema, &policy.table)
1364 )
1365 }
1366
1367 fn alter_view_sql(old_view: &View, new_view: &View) -> String {
1368 let drop_sql = Self::drop_view_sql(old_view);
1371 let create_sql = Self::create_view_sql(new_view);
1372 format!("{drop_sql}\n{create_sql}")
1373 }
1374
1375 fn recreate_index_sql(old_index: &Index, new_index: &Index) -> String {
1379 let concurrently = if old_index.concurrently {
1380 "CONCURRENTLY "
1381 } else {
1382 ""
1383 };
1384 let drop_sql = format!(
1385 "DROP INDEX {}{};",
1386 concurrently,
1387 Self::qualified_name(&old_index.schema, &old_index.name)
1388 );
1389 format!("{drop_sql}\n{}", Self::create_index_sql(new_index))
1390 }
1391
1392 fn alter_sequence_sql(old: &Sequence, new: &Sequence) -> Option<String> {
1395 let mut options = Vec::new();
1396
1397 if old.increment_by != new.increment_by {
1398 let increment = new.increment_by.as_deref().unwrap_or("1");
1399 options.push(format!("INCREMENT BY {increment}"));
1400 }
1401 if old.min_value != new.min_value {
1402 match new.min_value.as_deref() {
1403 Some(min) => options.push(format!("MINVALUE {min}")),
1404 None => options.push("NO MINVALUE".to_string()),
1405 }
1406 }
1407 if old.max_value != new.max_value {
1408 match new.max_value.as_deref() {
1409 Some(max) => options.push(format!("MAXVALUE {max}")),
1410 None => options.push("NO MAXVALUE".to_string()),
1411 }
1412 }
1413 if old.start_with != new.start_with
1414 && let Some(start) = new.start_with.as_deref()
1415 {
1416 options.push(format!("START WITH {start}"));
1417 }
1418 if old.cache_size != new.cache_size {
1419 let cache = new.cache_size.unwrap_or(1);
1420 options.push(format!("CACHE {cache}"));
1421 }
1422 if old.cycle.unwrap_or(false) != new.cycle.unwrap_or(false) {
1423 options.push(if new.cycle.unwrap_or(false) {
1424 "CYCLE".to_string()
1425 } else {
1426 "NO CYCLE".to_string()
1427 });
1428 }
1429
1430 if options.is_empty() {
1431 return None;
1432 }
1433 Some(format!(
1434 "ALTER SEQUENCE {} {};",
1435 Self::qualified_name(&new.schema, &new.name),
1436 options.join(" ")
1437 ))
1438 }
1439
1440 fn alter_role_sql(old: &Role, new: &Role) -> Option<String> {
1443 let mut options = Vec::new();
1444
1445 if old.create_db.unwrap_or(false) != new.create_db.unwrap_or(false) {
1446 options.push(if new.create_db.unwrap_or(false) {
1447 "CREATEDB"
1448 } else {
1449 "NOCREATEDB"
1450 });
1451 }
1452 if old.create_role.unwrap_or(false) != new.create_role.unwrap_or(false) {
1453 options.push(if new.create_role.unwrap_or(false) {
1454 "CREATEROLE"
1455 } else {
1456 "NOCREATEROLE"
1457 });
1458 }
1459 if old.inherit.unwrap_or(true) != new.inherit.unwrap_or(true) {
1460 options.push(if new.inherit.unwrap_or(true) {
1461 "INHERIT"
1462 } else {
1463 "NOINHERIT"
1464 });
1465 }
1466 if old.can_login.unwrap_or(false) != new.can_login.unwrap_or(false) {
1467 options.push(if new.can_login.unwrap_or(false) {
1468 "LOGIN"
1469 } else {
1470 "NOLOGIN"
1471 });
1472 }
1473 if old.bypass_rls.unwrap_or(false) != new.bypass_rls.unwrap_or(false) {
1474 options.push(if new.bypass_rls.unwrap_or(false) {
1475 "BYPASSRLS"
1476 } else {
1477 "NOBYPASSRLS"
1478 });
1479 }
1480
1481 let mut sql_options: Vec<String> = options.iter().map(ToString::to_string).collect();
1482 if old.conn_limit != new.conn_limit {
1483 sql_options.push(format!("CONNECTION LIMIT {}", new.conn_limit.unwrap_or(-1)));
1484 }
1485
1486 if sql_options.is_empty() {
1487 return None;
1488 }
1489 Some(format!(
1490 "ALTER ROLE {} WITH {};",
1491 Self::quote_ident(&new.name),
1492 sql_options.join(" ")
1493 ))
1494 }
1495
1496 fn recreate_enum_sql(new_enum: &Enum, columns: &[Column]) -> String {
1500 let type_name = Self::qualified_name(&new_enum.schema, &new_enum.name);
1501 let mut stmts = Vec::new();
1502
1503 for column in columns {
1504 if column.default.is_some() {
1505 stmts.push(format!(
1506 "ALTER TABLE {} ALTER COLUMN {} DROP DEFAULT;",
1507 Self::qualified_name(&column.schema, &column.table),
1508 Self::quote_ident(&column.name)
1509 ));
1510 }
1511 }
1512 for column in columns {
1513 stmts.push(format!(
1514 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE text USING {}::text;",
1515 Self::qualified_name(&column.schema, &column.table),
1516 Self::quote_ident(&column.name),
1517 Self::quote_ident(&column.name)
1518 ));
1519 }
1520 stmts.push(format!("DROP TYPE {type_name};"));
1521 stmts.push(Self::create_enum_sql(new_enum));
1522 for column in columns {
1523 stmts.push(format!(
1524 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {} USING {}::text::{};",
1525 Self::qualified_name(&column.schema, &column.table),
1526 Self::quote_ident(&column.name),
1527 type_name,
1528 Self::quote_ident(&column.name),
1529 type_name
1530 ));
1531 }
1532 for column in columns {
1533 if let Some(default) = column.default.as_deref() {
1534 stmts.push(format!(
1535 "ALTER TABLE {} ALTER COLUMN {} SET DEFAULT {};",
1536 Self::qualified_name(&column.schema, &column.table),
1537 Self::quote_ident(&column.name),
1538 default
1539 ));
1540 }
1541 }
1542
1543 stmts.join("\n")
1544 }
1545
1546 pub(crate) fn statement_to_sqls(stmt: JsonStatement) -> Vec<String> {
1555 Self::split_joined_commands(Self::statement_to_sql(stmt))
1556 }
1557
1558 fn split_joined_commands(sql: String) -> Vec<String> {
1564 if !sql.contains('\n') {
1565 return vec![sql];
1566 }
1567 let mut out = Vec::new();
1568 let mut current = String::new();
1569 for line in sql.lines() {
1570 if !current.is_empty() {
1571 current.push('\n');
1572 }
1573 current.push_str(line);
1574 if line.trim_end().ends_with(';') {
1575 out.push(std::mem::take(&mut current));
1576 }
1577 }
1578 if !current.trim().is_empty() {
1579 out.push(current);
1580 }
1581 out
1582 }
1583
1584 pub(crate) fn statement_to_sql(stmt: JsonStatement) -> String {
1585 match stmt {
1586 JsonStatement::CreateSchema { name } => {
1587 format!("CREATE SCHEMA {};", Self::quote_ident(&name))
1588 }
1589 JsonStatement::DropSchema { name } => {
1590 format!("DROP SCHEMA {};", Self::quote_ident(&name))
1591 }
1592 JsonStatement::RenameSchema { from, to } => {
1593 format!(
1594 "ALTER SCHEMA {} RENAME TO {};",
1595 Self::quote_ident(&from.name),
1596 Self::quote_ident(&to.name)
1597 )
1598 }
1599 JsonStatement::CreateEnum { enum_: e } => Self::create_enum_sql(&e),
1600 JsonStatement::DropEnum { enum_: e } => {
1601 format!("DROP TYPE {};", Self::qualified_name(&e.schema, &e.name))
1602 }
1603 JsonStatement::AlterEnum { from: _, to, diff } => Self::alter_enum_sql(&to, &diff),
1604 JsonStatement::CreateSequence { sequence: s } => Self::create_sequence_sql(&s),
1605 JsonStatement::DropSequence { sequence: s } => format!(
1606 "DROP SEQUENCE {};",
1607 Self::qualified_name(&s.schema, &s.name)
1608 ),
1609 JsonStatement::CreateTable { table } => Self::create_table_sql(&table),
1610 JsonStatement::DropTable { table, .. } => format!(
1611 "DROP TABLE {};",
1612 Self::qualified_name(&table.schema, &table.name)
1613 ),
1614 JsonStatement::RenameTable { schema, from, to } => format!(
1615 "ALTER TABLE {} RENAME TO {};",
1616 Self::qualified_name(&schema, &from),
1617 Self::quote_ident(&to)
1618 ),
1619 JsonStatement::AddColumn { column, is_pk, .. } => Self::add_column_sql(&column, is_pk),
1620 JsonStatement::DropColumn { column } => format!(
1621 "ALTER TABLE {} DROP COLUMN {};",
1622 Self::qualified_name(&column.schema, &column.table),
1623 Self::quote_ident(&column.name)
1624 ),
1625 JsonStatement::RenameColumn { from, to } => format!(
1626 "ALTER TABLE {} RENAME COLUMN {} TO {};",
1627 Self::qualified_name(&from.schema, &from.table),
1628 Self::quote_ident(&from.name),
1629 Self::quote_ident(&to.name)
1630 ),
1631 JsonStatement::AlterColumn {
1632 to,
1633 was_enum,
1634 is_enum,
1635 diff,
1636 } => Self::alter_column_sql(&to, was_enum, is_enum, &diff),
1637 JsonStatement::RecreateColumn {
1638 old_column,
1639 new_column,
1640 } => Self::recreate_column_sql(&old_column, &new_column),
1641 JsonStatement::CreateIndex { index } => Self::create_index_sql(&index),
1642 JsonStatement::DropIndex { index } => format!(
1643 "DROP INDEX {};",
1644 Self::qualified_name(&index.schema, &index.name)
1645 ),
1646 JsonStatement::CreateFk { fk } => Self::add_fk_sql(&fk),
1647 JsonStatement::DropFk { fk } => {
1648 Self::drop_constraint_sql(&fk.schema, &fk.table, &fk.name)
1649 }
1650 JsonStatement::CreateView { view } => Self::create_view_sql(&view),
1651 JsonStatement::DropView { view } => Self::drop_view_sql(&view),
1652 JsonStatement::AlterView { old_view, new_view } => {
1653 Self::alter_view_sql(&old_view, &new_view)
1654 }
1655 JsonStatement::AddPk { pk } => Self::add_pk_sql(&pk),
1656 JsonStatement::DropPk { pk } => {
1657 Self::drop_constraint_sql(&pk.schema, &pk.table, &pk.name)
1658 }
1659 JsonStatement::AddUnique { unique } => Self::add_unique_sql(&unique),
1660 JsonStatement::DropUnique { unique } => {
1661 Self::drop_constraint_sql(&unique.schema, &unique.table, &unique.name)
1662 }
1663 JsonStatement::AddCheck { check } => Self::add_check_sql(&check),
1664 JsonStatement::DropCheck { check } => {
1665 Self::drop_constraint_sql(&check.schema, &check.table, &check.name)
1666 }
1667 JsonStatement::CreateRole { role } => Self::create_role_sql(&role),
1668 JsonStatement::DropRole { role } => {
1669 format!("DROP ROLE {};", Self::quote_ident(&role.name))
1670 }
1671 JsonStatement::CreatePolicy { policy } => Self::create_policy_sql(&policy),
1672 JsonStatement::DropPolicy { policy } => Self::drop_policy_sql(&policy),
1673 JsonStatement::AlterTable {
1674 old_table,
1675 new_table,
1676 } => Self::alter_table_sql(&old_table, &new_table)
1677 .expect("alter table statement was prechecked"),
1678 JsonStatement::RecreateFk { old_fk, new_fk } => format!(
1679 "{}\n{}",
1680 Self::drop_constraint_sql(&old_fk.schema, &old_fk.table, &old_fk.name),
1681 Self::add_fk_sql(&new_fk)
1682 ),
1683 JsonStatement::RecreateUnique {
1684 old_unique,
1685 new_unique,
1686 } => format!(
1687 "{}\n{}",
1688 Self::drop_constraint_sql(&old_unique.schema, &old_unique.table, &old_unique.name),
1689 Self::add_unique_sql(&new_unique)
1690 ),
1691 JsonStatement::RecreateIndex {
1692 old_index,
1693 new_index,
1694 } => Self::recreate_index_sql(&old_index, &new_index),
1695 JsonStatement::RecreatePk { old_pk, new_pk } => format!(
1696 "{}\n{}",
1697 Self::drop_constraint_sql(&old_pk.schema, &old_pk.table, &old_pk.name),
1698 Self::add_pk_sql(&new_pk)
1699 ),
1700 JsonStatement::RecreateCheck {
1701 old_check,
1702 new_check,
1703 } => format!(
1704 "{}\n{}",
1705 Self::drop_constraint_sql(&old_check.schema, &old_check.table, &old_check.name),
1706 Self::add_check_sql(&new_check)
1707 ),
1708 JsonStatement::RecreatePolicy {
1709 old_policy,
1710 new_policy,
1711 } => format!(
1712 "{}\n{}",
1713 Self::drop_policy_sql(&old_policy),
1714 Self::create_policy_sql(&new_policy)
1715 ),
1716 JsonStatement::AlterSequence {
1717 old_sequence,
1718 new_sequence,
1719 } => Self::alter_sequence_sql(&old_sequence, &new_sequence)
1720 .expect("alter sequence statement was prechecked"),
1721 JsonStatement::AlterRole { old_role, new_role } => {
1722 Self::alter_role_sql(&old_role, &new_role)
1723 .expect("alter role statement was prechecked")
1724 }
1725 JsonStatement::RecreateEnum {
1726 new_enum, columns, ..
1727 } => Self::recreate_enum_sql(&new_enum, &columns),
1728 }
1729 }
1730
1731 fn create_index_sql(index: &Index) -> String {
1732 index.create_index_sql()
1733 }
1734
1735 fn create_view_sql(view: &View) -> String {
1736 view.create_view_sql()
1737 }
1738
1739 fn drop_view_sql(view: &View) -> String {
1740 let mat = if view.materialized {
1741 "MATERIALIZED "
1742 } else {
1743 ""
1744 };
1745 format!(
1746 "DROP {}VIEW {};",
1747 mat,
1748 Self::qualified_name(&view.schema, &view.name)
1749 )
1750 }
1751
1752 fn alter_column_sql(
1753 to: &Column,
1754 was_enum: bool,
1755 is_enum: bool,
1756 diff: &HashMap<String, serde_json::Value>,
1757 ) -> String {
1758 let table_key = Self::qualified_name(&to.schema, &to.table);
1759 let mut stmts = Vec::new();
1760
1761 if diff.contains_key("type") {
1762 let type_sql = Self::column_type_sql(to);
1763 let using_cast = if was_enum && is_enum {
1767 format!("{}::text::{type_sql}", Self::quote_ident(&to.name))
1768 } else {
1769 format!("{}::{type_sql}", Self::quote_ident(&to.name))
1770 };
1771 stmts.push(format!(
1772 "ALTER TABLE {} ALTER COLUMN {} SET DATA TYPE {} USING {};",
1773 table_key,
1774 Self::quote_ident(&to.name),
1775 type_sql,
1776 using_cast
1777 ));
1778 }
1779
1780 if diff.contains_key("notNull") {
1781 if to.not_null {
1782 stmts.push(format!(
1783 "ALTER TABLE {} ALTER COLUMN {} SET NOT NULL;",
1784 table_key,
1785 Self::quote_ident(&to.name)
1786 ));
1787 } else {
1788 stmts.push(format!(
1789 "ALTER TABLE {} ALTER COLUMN {} DROP NOT NULL;",
1790 table_key,
1791 Self::quote_ident(&to.name)
1792 ));
1793 }
1794 }
1795
1796 if diff.contains_key("default") {
1797 if let Some(default) = &to.default {
1798 stmts.push(format!(
1799 "ALTER TABLE {} ALTER COLUMN {} SET DEFAULT {};",
1800 table_key,
1801 Self::quote_ident(&to.name),
1802 default
1803 ));
1804 } else {
1805 stmts.push(format!(
1806 "ALTER TABLE {} ALTER COLUMN {} DROP DEFAULT;",
1807 table_key,
1808 Self::quote_ident(&to.name)
1809 ));
1810 }
1811 }
1812
1813 if diff.contains_key("generated") && to.generated.is_none() {
1814 stmts.push(format!(
1815 "ALTER TABLE {} ALTER COLUMN {} DROP EXPRESSION;",
1816 table_key,
1817 Self::quote_ident(&to.name)
1818 ));
1819 }
1820
1821 if diff.contains_key("identity")
1822 && !super::grammar::PgTypeCategory::from_sql_type(&to.sql_type).is_serial()
1823 {
1824 let old_identity = diff
1825 .get("identity")
1826 .and_then(|change| change.get("from"))
1827 .filter(|from| !from.is_null());
1828 match (&to.identity, old_identity) {
1829 (Some(id), None) => {
1831 if let Some(identity_sql) = Self::identity_sql(to, id) {
1832 stmts.push(format!(
1833 "ALTER TABLE {} ALTER COLUMN {} ADD{};",
1834 table_key,
1835 Self::quote_ident(&to.name),
1836 identity_sql
1837 ));
1838 }
1839 }
1840 (Some(id), Some(old)) => {
1843 if let Some(set_sql) = Self::identity_set_options_sql(id, old) {
1844 stmts.push(format!(
1845 "ALTER TABLE {} ALTER COLUMN {} {};",
1846 table_key,
1847 Self::quote_ident(&to.name),
1848 set_sql
1849 ));
1850 }
1851 }
1852 (None, _) => {
1854 stmts.push(format!(
1855 "ALTER TABLE {} ALTER COLUMN {} DROP IDENTITY;",
1856 table_key,
1857 Self::quote_ident(&to.name)
1858 ));
1859 }
1860 }
1861 }
1862
1863 if diff.contains_key("comment") {
1864 stmts.push(Self::comment_on_column_sql(
1865 &to.schema,
1866 &to.table,
1867 &to.name,
1868 to.comment.as_deref(),
1869 ));
1870 }
1871
1872 if stmts.is_empty() {
1873 format!("-- No column changes for {}.{}", to.table, to.name)
1874 } else {
1875 stmts.join("\n")
1876 }
1877 }
1878
1879 fn create_role_sql(role: &super::ddl::Role) -> String {
1880 let mut sql = format!("CREATE ROLE {}", Self::quote_ident(&role.name));
1881 if role.create_db.unwrap_or(false) {
1882 sql.push_str(" CREATEDB");
1883 }
1884 if role.create_role.unwrap_or(false) {
1885 sql.push_str(" CREATEROLE");
1886 }
1887 if role.inherit.unwrap_or(true) {
1888 sql.push_str(" INHERIT");
1889 } else {
1890 sql.push_str(" NOINHERIT");
1891 }
1892 if role.can_login.unwrap_or(false) {
1893 sql.push_str(" LOGIN");
1894 }
1895 if role.bypass_rls.unwrap_or(false) {
1896 sql.push_str(" BYPASSRLS");
1897 }
1898 if let Some(conn_limit) = role.conn_limit {
1899 let _ = write!(sql, " CONNECTION LIMIT {conn_limit}");
1900 }
1901 sql.push(';');
1902 sql
1903 }
1904
1905 fn create_policy_sql(policy: &super::ddl::Policy) -> String {
1906 policy.create_policy_sql()
1907 }
1908
1909 fn add_fk_sql(fk: &ForeignKey) -> String {
1910 fk.add_fk_sql()
1911 }
1912
1913 fn alter_table_sql(old: &Table, new: &Table) -> Option<String> {
1914 let mut stmts = Vec::new();
1915 let table_name = Self::qualified_name(&new.schema, &new.name);
1916 let old_unlogged = old.is_unlogged.unwrap_or(false);
1917 let new_unlogged = new.is_unlogged.unwrap_or(false);
1918 if old_unlogged != new_unlogged {
1919 let logged = if new_unlogged { "UNLOGGED" } else { "LOGGED" };
1920 stmts.push(format!("ALTER TABLE {table_name} SET {logged};"));
1921 }
1922
1923 if old.tablespace.as_deref() != new.tablespace.as_deref() {
1924 let tablespace = new.tablespace.as_deref().unwrap_or("pg_default");
1925 stmts.push(format!(
1926 "ALTER TABLE {table_name} SET TABLESPACE {};",
1927 Self::quote_ident(tablespace)
1928 ));
1929 }
1930
1931 if old.comment.as_deref() != new.comment.as_deref() {
1932 stmts.push(Self::comment_on_table_sql(
1933 &new.schema,
1934 &new.name,
1935 new.comment.as_deref(),
1936 ));
1937 }
1938
1939 if stmts.is_empty() {
1940 None
1941 } else {
1942 Some(stmts.join("\n"))
1943 }
1944 }
1945}
1946
1947fn topological_sort_tables_for_create(
1953 table_keys: &[String],
1954 diff: &[EntityDiff],
1955) -> CreateTableOrder {
1956 if table_keys.len() <= 1 {
1957 return CreateTableOrder {
1958 ordered: table_keys.to_vec(),
1959 cycle_tables: HashSet::new(),
1960 };
1961 }
1962
1963 let table_set: HashSet<&String> = table_keys.iter().collect();
1965
1966 let mut dependencies: HashMap<String, HashSet<String>> = HashMap::new();
1968 for table_key in table_keys {
1969 dependencies.insert(table_key.clone(), HashSet::new());
1970 }
1971
1972 for d in diff
1974 .iter()
1975 .filter(|d| d.kind == EntityKind::ForeignKey && d.diff_type == DiffType::Create)
1976 {
1977 if let Some(PostgresEntity::ForeignKey(fk)) = &d.right {
1978 let from_table = format!("{}.{}", fk.schema, fk.table);
1979 let to_table = format!("{}.{}", fk.schema_to, fk.table_to);
1980
1981 if table_set.contains(&from_table)
1983 && table_set.contains(&to_table)
1984 && let Some(deps) = dependencies.get_mut(&from_table)
1985 {
1986 deps.insert(to_table);
1987 }
1988 }
1989 }
1990
1991 let mut result = Vec::new();
1993 let mut remaining: HashSet<String> = table_keys.iter().cloned().collect();
1994 let mut satisfied: HashSet<String> = HashSet::new();
1995 let mut cycle_tables = HashSet::new();
1996
1997 while !remaining.is_empty() {
1998 let ready: Vec<String> = remaining
2000 .iter()
2001 .filter(|t| {
2002 dependencies
2003 .get(*t)
2004 .is_none_or(|deps| deps.iter().all(|d| satisfied.contains(d)))
2005 })
2006 .cloned()
2007 .collect();
2008
2009 if ready.is_empty() {
2010 cycle_tables = remaining.clone();
2013 result.extend(remaining);
2014 break;
2015 }
2016
2017 for t in ready {
2018 remaining.remove(&t);
2019 satisfied.insert(t.clone());
2020 result.push(t);
2021 }
2022 }
2023
2024 CreateTableOrder {
2025 ordered: result,
2026 cycle_tables,
2027 }
2028}
2029
2030fn topological_sort_tables_for_drop(table_keys: &[String], diff: &[EntityDiff]) -> Vec<String> {
2032 let create_order = topological_sort_tables_for_create(table_keys, diff);
2034 create_order.ordered.into_iter().rev().collect()
2035}