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