mz_sql/plan/
notice.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10// Copyright Materialize, Inc. and contributors. All rights reserved.
11//
12// Use of this software is governed by the Business Source License
13// included in the LICENSE file.
14//
15// As of the Change Date specified in that file, in accordance with
16// the Business Source License, use of this software will be governed
17// by the Apache License, Version 2.0.
18
19use std::fmt;
20
21use mz_ore::str::{StrExt, separated};
22use mz_repr::ColumnName;
23
24use crate::catalog::ObjectType;
25
26/// Notices that can occur in the adapter layer.
27///
28/// These are diagnostic warnings or informational messages that are not
29/// severe enough to warrant failing a query entirely.
30#[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    /// Reports additional details about the notice, if any are available.
49    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    /// Reports a hint for the user about how the notice could be addressed.
67    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}