Skip to main content

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