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