mz_transform/notice/
index_too_wide_for_literal_constraints.rs1use std::collections::BTreeSet;
13use std::fmt;
14
15use itertools::zip_eq;
16use mz_expr::MirScalarExpr;
17use mz_expr::explain::{HumanizedNotice, HumanizerMode};
18use mz_ore::str::separated;
19use mz_repr::GlobalId;
20use mz_repr::Row;
21use mz_repr::explain::ExprHumanizer;
22
23use crate::notice::{ActionKind, OptimizerNoticeApi};
24
25#[derive(Clone, Debug, Eq, PartialEq, Hash)]
28pub struct IndexTooWideForLiteralConstraints {
29 pub index_id: GlobalId,
31 pub index_key: Vec<MirScalarExpr>,
33 pub usable_subset: Vec<MirScalarExpr>,
36 pub literal_values: Vec<Row>,
38 pub index_on_id: GlobalId,
40 pub recommended_key: Vec<MirScalarExpr>,
43}
44
45impl OptimizerNoticeApi for IndexTooWideForLiteralConstraints {
46 fn dependencies(&self) -> BTreeSet<GlobalId> {
47 BTreeSet::from([self.index_id, self.index_on_id])
48 }
49
50 fn fmt_message(
51 &self,
52 f: &mut fmt::Formatter<'_>,
53 humanizer: &dyn ExprHumanizer,
54 redacted: bool,
55 ) -> fmt::Result {
56 let col_names = humanizer.column_names_for_id(self.index_on_id);
57 let col_names = col_names.as_ref();
58
59 let index_name = humanizer
60 .humanize_id(self.index_id)
61 .unwrap_or_else(|| self.index_id.to_string());
62 let index_on_id_name = humanizer
63 .humanize_id_unqualified(self.index_on_id)
64 .unwrap_or_else(|| self.index_on_id.to_string());
65
66 let mode = HumanizedNotice::new(redacted);
67
68 let index_key = separated(", ", mode.seq(&self.index_key, col_names));
69 write!(
70 f,
71 "Index {index_name} on {index_on_id_name}({index_key}) \
72 is too wide to use for literal equalities "
73 )?;
74
75 write!(f, "`")?;
76 {
77 if self.usable_subset.len() == 1 {
78 let exprs = mode.expr(&self.usable_subset[0], col_names);
79 let lits = self
80 .literal_values
81 .iter()
82 .map(|l| l.unpack_first())
83 .collect::<Vec<_>>();
84 let mut lits = mode.seq(&lits, col_names);
85 if self.literal_values.len() == 1 {
86 write!(f, "{} = {}", exprs, lits.next().unwrap())?;
87 } else {
88 write!(f, "{} IN ({})", exprs, separated(", ", lits))?;
89 }
90 } else {
91 if self.literal_values.len() == 1 {
92 let exprs = mode.seq(&self.usable_subset, col_names);
93 let lits = self.literal_values[0].unpack();
94 let lits = mode.seq(&lits, col_names);
95 let eqs = zip_eq(exprs, lits).map(|(expr, lit)| format!("{} = {}", expr, lit));
96 write!(f, "{}", separated(" AND ", eqs))?;
97 } else {
98 let exprs = mode.seq(&self.usable_subset, col_names);
99 let lits = mode.seq(&self.literal_values, col_names);
100 write!(
101 f,
102 "({}) IN ({})",
103 separated(", ", exprs),
104 separated(", ", lits)
105 )?;
106 }
107 };
108 }
109 write!(f, "`.")
110 }
111
112 fn fmt_hint(
113 &self,
114 f: &mut fmt::Formatter<'_>,
115 humanizer: &dyn ExprHumanizer,
116 redacted: bool,
117 ) -> fmt::Result {
118 let col_names = humanizer.column_names_for_id(self.index_on_id);
119
120 let mode = HumanizedNotice::new(redacted);
121
122 let recommended_key = mode.seq(&self.recommended_key, col_names.as_ref());
123 let recommended_key = separated(", ", recommended_key);
124
125 write!(
128 f,
129 "If your literal equalities filter out many rows, \
130 create an index whose key exactly matches your literal equalities: \
131 ({recommended_key})."
132 )
133 }
134
135 fn fmt_action(
136 &self,
137 f: &mut fmt::Formatter<'_>,
138 humanizer: &dyn ExprHumanizer,
139 redacted: bool,
140 ) -> fmt::Result {
141 let Some(index_on_id_name) = humanizer.humanize_id_unqualified(self.index_on_id) else {
142 return Ok(());
143 };
144
145 let mode = HumanizedNotice::new(redacted);
146 let col_names = humanizer.column_names_for_id(self.index_on_id);
147
148 let recommended_key = mode.seq(&self.recommended_key, col_names.as_ref());
149 let recommended_key = separated(", ", recommended_key);
150
151 write!(f, "CREATE INDEX ON {index_on_id_name}({recommended_key});")
152 }
153
154 fn action_kind(&self, humanizer: &dyn ExprHumanizer) -> ActionKind {
155 match humanizer.humanize_id_unqualified(self.index_on_id) {
156 Some(_) => ActionKind::SqlStatements,
157 None => ActionKind::None,
158 }
159 }
160}