mz_source_schema_change/
lib.rs1use mz_ore::str::StrExt;
13use postgres_protocol::escape;
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
23pub enum SchemaChange {
24 #[error("table was dropped and recreated upstream")]
25 TableDropped,
26 #[error("table was renamed or moved upstream")]
27 TableRenamed,
28 #[error("column {} was dropped or renamed upstream", .column.quoted())]
29 ColumnDropped { column: String },
30 #[error(
31 "column {} changed position upstream (the column or table was likely dropped and \
32 recreated)",
33 .column.quoted()
34 )]
35 ColumnMoved { column: String },
36 #[error("the type of column {} changed upstream", .column.quoted())]
37 ColumnTypeChanged { column: String },
38 #[error("the NOT NULL constraint on column {} was dropped upstream", .column.quoted())]
39 NotNullDropped { column: String },
40 #[error("{key} was dropped upstream")]
41 KeyDropped { key: KeyRef },
42 #[error("{key} was renamed or recreated upstream")]
43 KeyAltered { key: KeyRef },
44 #[error("{0}")]
45 DescriptionFailed(String),
46}
47
48#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
50pub struct KeyRef {
51 pub name: String,
52 pub is_primary: bool,
53 pub columns: Vec<String>,
54}
55
56impl std::fmt::Display for KeyRef {
57 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
58 let kind = if self.is_primary {
59 "PRIMARY KEY"
60 } else {
61 "UNIQUE"
62 };
63 write!(
64 f,
65 "{kind} constraint {} ({})",
66 self.name.quoted(),
67 self.columns.join(", ")
68 )
69 }
70}
71
72impl SchemaChange {
73 pub fn hint(&self, schema: &str, name: &str) -> Option<String> {
76 let recreate = |with_clause: Option<&str>| {
77 let mut hint = format!(
78 "To keep ingesting without this constraint, recreate the table in a new \
79 versioned schema, then swap your views to the new table:\n CREATE SCHEMA v2;\n \
80 CREATE TABLE v2.{}\n FROM SOURCE <source> (REFERENCE {}.{})",
81 escape::escape_identifier(name),
82 escape::escape_identifier(schema),
83 escape::escape_identifier(name),
84 );
85 if let Some(with_clause) = with_clause {
86 hint.push_str(&format!("\n WITH ({with_clause})"));
87 }
88 hint.push(';');
89 hint
90 };
91 match self {
92 SchemaChange::KeyDropped { key } | SchemaChange::KeyAltered { key } => Some(format!(
93 "{}\nTo make a planned constraint drop a non-event, create the table with \
94 WITH (EXCLUDE CONSTRAINTS ({})) before the upstream drop.",
95 recreate(None),
96 escape::escape_literal(&key.name),
97 )),
98 SchemaChange::NotNullDropped { .. } => Some(recreate(Some("EXCLUDE ALL CONSTRAINTS"))),
99 SchemaChange::TableDropped
100 | SchemaChange::TableRenamed
101 | SchemaChange::ColumnDropped { .. }
102 | SchemaChange::ColumnMoved { .. }
103 | SchemaChange::ColumnTypeChanged { .. }
104 | SchemaChange::DescriptionFailed(_) => None,
105 }
106 }
107}