1mod equals_null;
32mod index_already_exists;
33mod index_key_empty;
34mod index_too_wide_for_literal_constraints;
35
36pub use equals_null::EqualsNull;
37pub use index_already_exists::IndexAlreadyExists;
38pub use index_key_empty::IndexKeyEmpty;
39pub use index_too_wide_for_literal_constraints::IndexTooWideForLiteralConstraints;
40
41use std::collections::BTreeSet;
42use std::fmt::{self, Error, Formatter, Write};
43use std::sync::Arc;
44use std::{concat, stringify};
45
46use enum_kinds::EnumKind;
47use mz_repr::GlobalId;
48use mz_repr::explain::ExprHumanizer;
49use proptest_derive::Arbitrary;
50use serde::{Deserialize, Serialize};
51
52#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Arbitrary)]
53pub struct OptimizerNotice {
56 pub id: GlobalId,
58 pub kind: OptimizerNoticeKind,
60 pub item_id: Option<GlobalId>,
64 pub dependencies: BTreeSet<GlobalId>,
68 pub message: String,
74 pub hint: String,
76 pub action: Action,
78 pub message_redacted: Option<String>,
80 pub hint_redacted: Option<String>,
82 pub action_redacted: Option<Action>,
84 pub created_at: u64,
86}
87
88impl OptimizerNotice {
89 pub fn explain(
94 notices: &Vec<Arc<Self>>,
95 humanizer: &dyn ExprHumanizer,
96 redacted: bool,
97 ) -> Result<Vec<String>, Error> {
98 let mut notice_strings = Vec::new();
99 for notice in notices {
100 if notice.is_valid(humanizer) {
101 let mut s = String::new();
102 let message = match notice.message_redacted.as_deref() {
103 Some(message_redacted) if redacted => message_redacted,
104 _ => notice.message.as_str(),
105 };
106 let hint = match notice.hint_redacted.as_deref() {
107 Some(hint_redacted) if redacted => hint_redacted,
108 _ => notice.hint.as_str(),
109 };
110 write!(s, " - Notice: {}\n", message)?;
111 write!(s, " Hint: {}", hint)?;
112 notice_strings.push(s);
113 }
114 }
115 Ok(notice_strings)
116 }
117
118 fn is_valid(&self, humanizer: &dyn ExprHumanizer) -> bool {
123 self.dependencies.iter().all(|id| humanizer.id_exists(*id))
125 }
126}
127
128#[derive(
129 EnumKind, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Arbitrary,
130)]
131#[enum_kind(ActionKind)]
132pub enum Action {
134 None,
136 PlainText(String),
138 SqlStatements(String),
144}
145
146impl Action {
147 pub fn kind(&self) -> ActionKind {
149 ActionKind::from(self)
150 }
151}
152
153impl ActionKind {
154 pub fn as_str(&self) -> &'static str {
156 match self {
157 Self::None => "",
158 Self::PlainText => "plain_text",
159 Self::SqlStatements => "sql_statements",
160 }
161 }
162}
163
164pub trait OptimizerNoticeApi: Sized {
166 fn dependencies(&self) -> BTreeSet<GlobalId>;
168
169 fn fmt_message(
172 &self,
173 f: &mut Formatter<'_>,
174 humanizer: &dyn ExprHumanizer,
175 redacted: bool,
176 ) -> fmt::Result;
177
178 fn fmt_hint(
181 &self,
182 f: &mut Formatter<'_>,
183 humanizer: &dyn ExprHumanizer,
184 redacted: bool,
185 ) -> fmt::Result;
186
187 fn fmt_action(
190 &self,
191 f: &mut Formatter<'_>,
192 humanizer: &dyn ExprHumanizer,
193 redacted: bool,
194 ) -> fmt::Result;
195
196 fn action_kind(&self, humanizer: &dyn ExprHumanizer) -> ActionKind;
198
199 fn message<'a>(
202 &'a self,
203 humanizer: &'a dyn ExprHumanizer,
204 redacted: bool,
205 ) -> HumanizedMessage<'a, Self> {
206 HumanizedMessage {
207 notice: self,
208 humanizer,
209 redacted,
210 }
211 }
212
213 fn hint<'a>(
217 &'a self,
218 humanizer: &'a dyn ExprHumanizer,
219 redacted: bool,
220 ) -> HumanizedHint<'a, Self> {
221 HumanizedHint {
222 notice: self,
223 humanizer,
224 redacted,
225 }
226 }
227
228 fn action<'a>(
231 &'a self,
232 humanizer: &'a dyn ExprHumanizer,
233 redacted: bool,
234 ) -> HumanizedAction<'a, Self> {
235 HumanizedAction {
236 notice: self,
237 humanizer,
238 redacted,
239 }
240 }
241}
242
243#[allow(missing_debug_implementations)]
246pub struct HumanizedMessage<'a, T> {
247 notice: &'a T,
248 humanizer: &'a dyn ExprHumanizer,
249 redacted: bool,
250}
251impl<'a, T: OptimizerNoticeApi> fmt::Display for HumanizedMessage<'a, T> {
252 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
253 self.notice.fmt_message(f, self.humanizer, self.redacted)
254 }
255}
256
257#[allow(missing_debug_implementations)]
259pub struct HumanizedHint<'a, T> {
260 notice: &'a T,
261 humanizer: &'a dyn ExprHumanizer,
262 redacted: bool,
263}
264
265impl<'a, T: OptimizerNoticeApi> fmt::Display for HumanizedHint<'a, T> {
266 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
267 self.notice.fmt_hint(f, self.humanizer, self.redacted)
268 }
269}
270
271#[allow(missing_debug_implementations)]
274pub struct HumanizedAction<'a, T> {
275 notice: &'a T,
276 humanizer: &'a dyn ExprHumanizer,
277 redacted: bool,
278}
279
280impl<'a, T: OptimizerNoticeApi> fmt::Display for HumanizedAction<'a, T> {
281 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
282 self.notice.fmt_action(f, self.humanizer, self.redacted)
283 }
284}
285
286macro_rules! raw_optimizer_notices {
287 ($($ty:ident => $name:literal,)+) => {
288 paste::paste!{
289 #[derive(EnumKind, Clone, Debug, Eq, PartialEq, Hash)]
291 #[enum_kind(OptimizerNoticeKind, derive(PartialOrd, Ord, Hash, Serialize, Deserialize,Arbitrary))]
292 pub enum RawOptimizerNotice {
293 $(
294 #[doc = concat!("See [`", stringify!($ty), "`].")]
295 $ty($ty),
296 )+
297 }
298
299 impl OptimizerNoticeApi for RawOptimizerNotice {
300 fn dependencies(&self) -> BTreeSet<GlobalId> {
301 match self {
302 $(Self::$ty(notice) => notice.dependencies(),)+
303 }
304 }
305
306 fn fmt_message(&self, f: &mut Formatter<'_>, humanizer: &dyn ExprHumanizer, redacted: bool) -> fmt::Result {
307 match self {
308 $(Self::$ty(notice) => notice.fmt_message(f, humanizer, redacted),)+
309 }
310 }
311
312 fn fmt_hint(&self, f: &mut Formatter<'_>, humanizer: &dyn ExprHumanizer, redacted: bool) -> fmt::Result {
313 match self {
314 $(Self::$ty(notice) => notice.fmt_hint(f, humanizer, redacted),)+
315 }
316 }
317
318 fn fmt_action(&self, f: &mut Formatter<'_>, humanizer: &dyn ExprHumanizer, redacted: bool) -> fmt::Result {
319 match self {
320 $(Self::$ty(notice) => notice.fmt_action(f, humanizer, redacted),)+
321 }
322 }
323
324 fn action_kind(&self, humanizer: &dyn ExprHumanizer) -> ActionKind {
325 match self {
326 $(Self::$ty(notice) => notice.action_kind(humanizer),)+
327 }
328 }
329 }
330
331 impl OptimizerNoticeKind {
332 pub fn as_str(&self) -> &'static str {
335 match self {
336 $(Self::$ty => $name,)+
337 }
338 }
339
340 pub fn metric_label(&self) -> &str {
343 match self {
344 $(Self::$ty => stringify!($ty),)+
345 }
346 }
347 }
348
349 $(
350 impl From<$ty> for RawOptimizerNotice {
351 fn from(value: $ty) -> Self {
352 RawOptimizerNotice::$ty(value)
353 }
354 }
355 )+
356 }
357 };
358}
359
360raw_optimizer_notices![
361 EqualsNull => "Comparison with NULL",
362 IndexAlreadyExists => "An identical index already exists",
363 IndexTooWideForLiteralConstraints => "Index too wide for literal constraints",
364 IndexKeyEmpty => "Empty index key",
365];
366
367impl RawOptimizerNotice {
368 pub fn explain(
373 notices: &Vec<RawOptimizerNotice>,
374 humanizer: &dyn ExprHumanizer,
375 redacted: bool,
376 ) -> Result<Vec<String>, Error> {
377 let mut notice_strings = Vec::new();
378 for notice in notices {
379 if notice.is_valid(humanizer) {
380 let mut s = String::new();
381 write!(s, " - Notice: {}\n", notice.message(humanizer, redacted))?;
382 write!(s, " Hint: {}", notice.hint(humanizer, redacted))?;
383 notice_strings.push(s);
384 }
385 }
386 Ok(notice_strings)
387 }
388
389 fn is_valid(&self, humanizer: &dyn ExprHumanizer) -> bool {
393 self.dependencies()
394 .iter()
395 .all(|id| humanizer.id_exists(*id))
396 }
397
398 pub fn metric_label(&self) -> &str {
401 OptimizerNoticeKind::from(self).as_str()
402 }
403}