1use std::fmt;
20
21use mz_ore::str::{StrExt, separated};
22use mz_repr::ColumnName;
23
24use crate::catalog::ObjectType;
25
26#[derive(Clone, Debug, Eq, PartialEq)]
31pub enum PlanNotice {
32 ObjectDoesNotExist {
33 name: String,
34 object_type: ObjectType,
35 },
36 ColumnAlreadyExists {
37 column_name: String,
38 object_name: String,
39 },
40 UpsertSinkKeyNotEnforced {
41 key: Vec<ColumnName>,
42 name: String,
43 },
44 ReplicaDiskOptionDeprecated,
45}
46
47impl PlanNotice {
48 pub fn detail(&self) -> Option<String> {
50 match self {
51 PlanNotice::UpsertSinkKeyNotEnforced { key, name } => {
52 let details = format!(
53 "Materialize did not validate that the specified upsert envelope key ({}) \
54 was a unique key of the underlying relation {}. If this key is not unique, \
55 the sink might produce multiple updates for the same key at the same time, \
56 which may confuse downstream consumers.",
57 separated(", ", key.iter().map(|c| c.quoted())),
58 name.quoted()
59 );
60 Some(details)
61 }
62 _ => None,
63 }
64 }
65
66 pub fn hint(&self) -> Option<String> {
68 match self {
69 PlanNotice::UpsertSinkKeyNotEnforced { .. } => {
70 Some("See: https://materialize.com/s/sink-key-selection".into())
71 }
72 _ => None,
73 }
74 }
75}
76
77impl fmt::Display for PlanNotice {
78 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 match self {
80 PlanNotice::ObjectDoesNotExist { name, object_type } => {
81 write!(
82 f,
83 "{} {} does not exist, skipping",
84 object_type,
85 name.quoted()
86 )
87 }
88 PlanNotice::ColumnAlreadyExists {
89 column_name,
90 object_name,
91 } => {
92 write!(
93 f,
94 "column {} of relation {} already exists, skipping",
95 column_name.quoted(),
96 object_name.quoted()
97 )
98 }
99 PlanNotice::UpsertSinkKeyNotEnforced { .. } => {
100 write!(f, "upsert key not validated to be unique")
101 }
102 PlanNotice::ReplicaDiskOptionDeprecated => {
103 write!(f, "the DISK option is deprecated and has no effect")
104 }
105 }
106 }
107}