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;
49#[cfg(any(test, feature = "proptest"))]
50use proptest_derive::Arbitrary;
51use serde::{Deserialize, Serialize};
52
53#[derive(
54 Clone,
55 Debug,
56 PartialEq,
57 Eq,
58 PartialOrd,
59 Ord,
60 Hash,
61 Serialize,
62 Deserialize
63)]
64#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
65pub struct OptimizerNotice {
68 pub id: GlobalId,
70 pub kind: OptimizerNoticeKind,
72 pub item_id: Option<GlobalId>,
76 pub dependencies: BTreeSet<GlobalId>,
80 pub message: String,
86 pub hint: String,
88 pub action: Action,
90 pub message_redacted: Option<String>,
92 pub hint_redacted: Option<String>,
94 pub action_redacted: Option<Action>,
96 pub created_at: u64,
98}
99
100impl OptimizerNotice {
101 pub fn explain(
106 notices: &Vec<Arc<Self>>,
107 humanizer: &dyn ExprHumanizer,
108 redacted: bool,
109 ) -> Result<Vec<String>, Error> {
110 let mut notice_strings = Vec::new();
111 for notice in notices {
112 if notice.is_valid(humanizer) {
113 let mut s = String::new();
114 let message = match notice.message_redacted.as_deref() {
115 Some(message_redacted) if redacted => message_redacted,
116 _ => notice.message.as_str(),
117 };
118 let hint = match notice.hint_redacted.as_deref() {
119 Some(hint_redacted) if redacted => hint_redacted,
120 _ => notice.hint.as_str(),
121 };
122 write!(s, " - Notice: {}\n", message)?;
123 write!(s, " Hint: {}", hint)?;
124 notice_strings.push(s);
125 }
126 }
127 Ok(notice_strings)
128 }
129
130 fn is_valid(&self, humanizer: &dyn ExprHumanizer) -> bool {
135 self.dependencies.iter().all(|id| humanizer.id_exists(*id))
137 }
138}
139
140#[derive(
141 EnumKind,
142 Clone,
143 Debug,
144 PartialEq,
145 Eq,
146 PartialOrd,
147 Ord,
148 Hash,
149 Serialize,
150 Deserialize
151)]
152#[cfg_attr(any(test, feature = "proptest"), derive(Arbitrary))]
153#[enum_kind(ActionKind)]
154pub enum Action {
156 None,
158 PlainText(String),
160 SqlStatements(String),
166}
167
168impl Action {
169 pub fn kind(&self) -> ActionKind {
171 ActionKind::from(self)
172 }
173}
174
175impl ActionKind {
176 pub fn as_str(&self) -> &'static str {
178 match self {
179 Self::None => "",
180 Self::PlainText => "plain_text",
181 Self::SqlStatements => "sql_statements",
182 }
183 }
184}
185
186pub trait OptimizerNoticeApi: Sized {
188 fn dependencies(&self) -> BTreeSet<GlobalId>;
190
191 fn fmt_message(
194 &self,
195 f: &mut Formatter<'_>,
196 humanizer: &dyn ExprHumanizer,
197 redacted: bool,
198 ) -> fmt::Result;
199
200 fn fmt_hint(
203 &self,
204 f: &mut Formatter<'_>,
205 humanizer: &dyn ExprHumanizer,
206 redacted: bool,
207 ) -> fmt::Result;
208
209 fn fmt_action(
212 &self,
213 f: &mut Formatter<'_>,
214 humanizer: &dyn ExprHumanizer,
215 redacted: bool,
216 ) -> fmt::Result;
217
218 fn action_kind(&self, humanizer: &dyn ExprHumanizer) -> ActionKind;
220
221 fn message<'a>(
224 &'a self,
225 humanizer: &'a dyn ExprHumanizer,
226 redacted: bool,
227 ) -> HumanizedMessage<'a, Self> {
228 HumanizedMessage {
229 notice: self,
230 humanizer,
231 redacted,
232 }
233 }
234
235 fn hint<'a>(
239 &'a self,
240 humanizer: &'a dyn ExprHumanizer,
241 redacted: bool,
242 ) -> HumanizedHint<'a, Self> {
243 HumanizedHint {
244 notice: self,
245 humanizer,
246 redacted,
247 }
248 }
249
250 fn action<'a>(
253 &'a self,
254 humanizer: &'a dyn ExprHumanizer,
255 redacted: bool,
256 ) -> HumanizedAction<'a, Self> {
257 HumanizedAction {
258 notice: self,
259 humanizer,
260 redacted,
261 }
262 }
263}
264
265#[allow(missing_debug_implementations)]
268pub struct HumanizedMessage<'a, T> {
269 notice: &'a T,
270 humanizer: &'a dyn ExprHumanizer,
271 redacted: bool,
272}
273impl<'a, T: OptimizerNoticeApi> fmt::Display for HumanizedMessage<'a, T> {
274 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
275 self.notice.fmt_message(f, self.humanizer, self.redacted)
276 }
277}
278
279#[allow(missing_debug_implementations)]
281pub struct HumanizedHint<'a, T> {
282 notice: &'a T,
283 humanizer: &'a dyn ExprHumanizer,
284 redacted: bool,
285}
286
287impl<'a, T: OptimizerNoticeApi> fmt::Display for HumanizedHint<'a, T> {
288 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
289 self.notice.fmt_hint(f, self.humanizer, self.redacted)
290 }
291}
292
293#[allow(missing_debug_implementations)]
296pub struct HumanizedAction<'a, T> {
297 notice: &'a T,
298 humanizer: &'a dyn ExprHumanizer,
299 redacted: bool,
300}
301
302impl<'a, T: OptimizerNoticeApi> fmt::Display for HumanizedAction<'a, T> {
303 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
304 self.notice.fmt_action(f, self.humanizer, self.redacted)
305 }
306}
307
308macro_rules! raw_optimizer_notices {
309 ($($ty:ident => $name:literal,)+) => {
310 paste::paste!{
311 #[derive(EnumKind, Clone, Debug, Eq, PartialEq, Hash)]
313 #[cfg_attr(
314 any(test, feature = "proptest"),
315 enum_kind(
316 OptimizerNoticeKind,
317 derive(PartialOrd, Ord, Hash, Serialize, Deserialize, Arbitrary)
318 )
319 )]
320 #[cfg_attr(
321 not(any(test, feature = "proptest")),
322 enum_kind(
323 OptimizerNoticeKind,
324 derive(PartialOrd, Ord, Hash, Serialize, Deserialize)
325 )
326 )]
327 pub enum RawOptimizerNotice {
328 $(
329 #[doc = concat!("See [`", stringify!($ty), "`].")]
330 $ty($ty),
331 )+
332 }
333
334 impl OptimizerNoticeApi for RawOptimizerNotice {
335 fn dependencies(&self) -> BTreeSet<GlobalId> {
336 match self {
337 $(Self::$ty(notice) => notice.dependencies(),)+
338 }
339 }
340
341 fn fmt_message(
342 &self,
343 f: &mut Formatter<'_>,
344 humanizer: &dyn ExprHumanizer,
345 redacted: bool,
346 ) -> fmt::Result {
347 match self {
348 $(Self::$ty(notice) => notice.fmt_message(f, humanizer, redacted),)+
349 }
350 }
351
352 fn fmt_hint(
353 &self,
354 f: &mut Formatter<'_>,
355 humanizer: &dyn ExprHumanizer,
356 redacted: bool,
357 ) -> fmt::Result {
358 match self {
359 $(Self::$ty(notice) => notice.fmt_hint(f, humanizer, redacted),)+
360 }
361 }
362
363 fn fmt_action(
364 &self,
365 f: &mut Formatter<'_>,
366 humanizer: &dyn ExprHumanizer,
367 redacted: bool,
368 ) -> fmt::Result {
369 match self {
370 $(Self::$ty(notice) => notice.fmt_action(f, humanizer, redacted),)+
371 }
372 }
373
374 fn action_kind(&self, humanizer: &dyn ExprHumanizer) -> ActionKind {
375 match self {
376 $(Self::$ty(notice) => notice.action_kind(humanizer),)+
377 }
378 }
379 }
380
381 impl OptimizerNoticeKind {
382 pub fn as_str(&self) -> &'static str {
385 match self {
386 $(Self::$ty => $name,)+
387 }
388 }
389
390 pub fn metric_label(&self) -> &str {
393 match self {
394 $(Self::$ty => stringify!($ty),)+
395 }
396 }
397 }
398
399 $(
400 impl From<$ty> for RawOptimizerNotice {
401 fn from(value: $ty) -> Self {
402 RawOptimizerNotice::$ty(value)
403 }
404 }
405 )+
406 }
407 };
408}
409
410raw_optimizer_notices![
411 EqualsNull => "Comparison with NULL",
412 IndexAlreadyExists => "An identical index already exists",
413 IndexTooWideForLiteralConstraints => "Index too wide for literal constraints",
414 IndexKeyEmpty => "Empty index key",
415];
416
417impl RawOptimizerNotice {
418 pub fn explain(
423 notices: &Vec<RawOptimizerNotice>,
424 humanizer: &dyn ExprHumanizer,
425 redacted: bool,
426 ) -> Result<Vec<String>, Error> {
427 let mut notice_strings = Vec::new();
428 for notice in notices {
429 if notice.is_valid(humanizer) {
430 let mut s = String::new();
431 write!(s, " - Notice: {}\n", notice.message(humanizer, redacted))?;
432 write!(s, " Hint: {}", notice.hint(humanizer, redacted))?;
433 notice_strings.push(s);
434 }
435 }
436 Ok(notice_strings)
437 }
438
439 fn is_valid(&self, humanizer: &dyn ExprHumanizer) -> bool {
443 self.dependencies()
444 .iter()
445 .all(|id| humanizer.id_exists(*id))
446 }
447
448 pub fn metric_label(&self) -> &str {
451 OptimizerNoticeKind::from(self).as_str()
452 }
453}