mz_adapter/catalog/builtin_table_updates/
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
10use std::sync::Arc;
11
12use mz_catalog::builtin::notice::MZ_OPTIMIZER_NOTICES;
13use mz_repr::{Datum, Diff, GlobalId, Row};
14use mz_transform::dataflow::DataflowMetainfo;
15use mz_transform::notice::{
16    Action, ActionKind, OptimizerNotice, OptimizerNoticeApi, OptimizerNoticeKind,
17    RawOptimizerNotice,
18};
19
20use crate::catalog::{BuiltinTableUpdate, Catalog, CatalogState};
21
22impl Catalog {
23    /// Transform the [`DataflowMetainfo`] by rendering an [`OptimizerNotice`]
24    /// for each [`RawOptimizerNotice`].
25    pub fn render_notices(
26        &self,
27        df_meta: DataflowMetainfo<RawOptimizerNotice>,
28        notice_ids: Vec<GlobalId>,
29        item_id: Option<GlobalId>,
30    ) -> DataflowMetainfo<Arc<OptimizerNotice>> {
31        // The caller should supply a pre-allocated GlobalId for each notice.
32        assert_eq!(notice_ids.len(), df_meta.optimizer_notices.len());
33
34        // Helper for rendering redacted fields.
35        fn some_if_neq<T: Eq>(x: T, y: &T) -> Option<T> {
36            if &x != y { Some(x) } else { None }
37        }
38
39        // These notices will be persisted in a system table, so should not be
40        // relative to any user's session.
41        let conn_catalog = self.for_system_session();
42
43        let optimizer_notices = std::iter::zip(df_meta.optimizer_notices, notice_ids)
44            .map(|(notice, id)| {
45                // Render non-redacted fields.
46                let message = notice.message(&conn_catalog, false).to_string();
47                let hint = notice.hint(&conn_catalog, false).to_string();
48                let action = match notice.action_kind(&conn_catalog) {
49                    ActionKind::SqlStatements => {
50                        Action::SqlStatements(notice.action(&conn_catalog, false).to_string())
51                    }
52                    ActionKind::PlainText => {
53                        Action::PlainText(notice.action(&conn_catalog, false).to_string())
54                    }
55                    ActionKind::None => {
56                        Action::None // No concrete action.
57                    }
58                };
59                // Render redacted fields.
60                let message_redacted = notice.message(&conn_catalog, true).to_string();
61                let hint_redacted = notice.hint(&conn_catalog, true).to_string();
62                let action_redacted = match notice.action_kind(&conn_catalog) {
63                    ActionKind::SqlStatements => {
64                        Action::SqlStatements(notice.action(&conn_catalog, true).to_string())
65                    }
66                    ActionKind::PlainText => {
67                        Action::PlainText(notice.action(&conn_catalog, true).to_string())
68                    }
69                    ActionKind::None => {
70                        Action::None // No concrete action.
71                    }
72                };
73                // Assemble the rendered notice.
74                OptimizerNotice {
75                    id,
76                    kind: OptimizerNoticeKind::from(&notice),
77                    item_id,
78                    dependencies: notice.dependencies(),
79                    message_redacted: some_if_neq(message_redacted, &message),
80                    hint_redacted: some_if_neq(hint_redacted, &hint),
81                    action_redacted: some_if_neq(action_redacted, &action),
82                    message,
83                    hint,
84                    action,
85                    created_at: (self.config().now)(),
86                }
87            })
88            .map(From::from) // Wrap each notice into an `Arc`.
89            .collect();
90
91        DataflowMetainfo {
92            optimizer_notices,
93            index_usage_types: df_meta.index_usage_types,
94        }
95    }
96}
97
98impl CatalogState {
99    /// Pack a [`BuiltinTableUpdate`] with the given `diff` for each
100    /// [`OptimizerNotice`] in `notices` into `updates`.
101    pub(crate) fn pack_optimizer_notices<'a>(
102        &self,
103        updates: &mut Vec<BuiltinTableUpdate>,
104        notices: impl Iterator<Item = &'a Arc<OptimizerNotice>>,
105        diff: Diff,
106    ) {
107        let mut row = Row::default();
108
109        for notice in notices {
110            let mut packer = row.packer();
111
112            // Pre-convert some fields into a type that can be wrapped into a
113            // Datum.
114            let id = notice.id.to_string();
115            let item_id = notice.item_id.as_ref().map(ToString::to_string);
116            let deps = notice
117                .dependencies
118                .iter()
119                .map(ToString::to_string)
120                .collect::<Vec<_>>();
121            let created_at = mz_ore::now::to_datetime(notice.created_at)
122                .try_into()
123                .expect("must fit");
124
125            // push `id` column
126            packer.push(Datum::String(id.as_str()));
127            // push `notice_type` column (TODO: encode as int?)
128            packer.push(Datum::String(notice.kind.as_str()));
129            // push `message` column
130            packer.push(Datum::String(&notice.message));
131            // push `hint` column
132            packer.push(Datum::String(&notice.hint));
133            // push `action` column
134            packer.push(match &notice.action {
135                Action::None => Datum::Null,
136                Action::PlainText(text) => Datum::String(text),
137                Action::SqlStatements(text) => Datum::String(text),
138            });
139            // push `message_redacted` column
140            packer.push(match notice.message_redacted.as_deref() {
141                Some(message_redacted) => Datum::String(message_redacted),
142                None => Datum::Null,
143            });
144            // push `hint_redacted` column
145            packer.push(match notice.hint_redacted.as_deref() {
146                Some(hint_redacted) => Datum::String(hint_redacted),
147                None => Datum::Null,
148            });
149            // push `action_redacted` column
150            packer.push(match notice.action_redacted.as_ref() {
151                Some(action_redacted) => match action_redacted {
152                    Action::None => Datum::Null,
153                    Action::PlainText(text) => Datum::String(text),
154                    Action::SqlStatements(text) => Datum::String(text),
155                },
156                None => Datum::Null,
157            });
158            // push `action_type` column (TODO: encode as int?)
159            packer.push(match &notice.action {
160                Action::None => Datum::Null,
161                action => Datum::String(action.kind().as_str()),
162            });
163            // push `object_id` column
164            packer.push(match item_id.as_ref() {
165                Some(item_id) => Datum::String(item_id),
166                None => Datum::Null,
167            });
168            // push `dependency_ids` column
169            packer.push_list(deps.iter().map(|d| Datum::String(d)));
170            // push `created_at` column
171            packer.push(Datum::TimestampTz(created_at));
172
173            updates.push(BuiltinTableUpdate::row(
174                self.resolve_builtin_table(&MZ_OPTIMIZER_NOTICES),
175                row.clone(),
176                diff,
177            ));
178        }
179    }
180}