use std::collections::BTreeSet;
use std::fmt;
use itertools::zip_eq;
use mz_expr::explain::{HumanizedNotice, HumanizerMode};
use mz_expr::MirScalarExpr;
use mz_ore::str::separated;
use mz_repr::explain::ExprHumanizer;
use mz_repr::GlobalId;
use mz_repr::Row;
use crate::notice::{ActionKind, OptimizerNoticeApi};
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct IndexTooWideForLiteralConstraints {
pub index_id: GlobalId,
pub index_key: Vec<MirScalarExpr>,
pub usable_subset: Vec<MirScalarExpr>,
pub literal_values: Vec<Row>,
pub index_on_id: GlobalId,
pub recommended_key: Vec<MirScalarExpr>,
}
impl OptimizerNoticeApi for IndexTooWideForLiteralConstraints {
fn dependencies(&self) -> BTreeSet<GlobalId> {
BTreeSet::from([self.index_id, self.index_on_id])
}
fn fmt_message(
&self,
f: &mut fmt::Formatter<'_>,
humanizer: &dyn ExprHumanizer,
redacted: bool,
) -> fmt::Result {
let col_names = humanizer.column_names_for_id(self.index_on_id);
let col_names = col_names.as_ref();
let index_name = humanizer
.humanize_id(self.index_id)
.unwrap_or_else(|| self.index_id.to_string());
let index_on_id_name = humanizer
.humanize_id_unqualified(self.index_on_id)
.unwrap_or_else(|| self.index_on_id.to_string());
let mode = HumanizedNotice::new(redacted);
let index_key = separated(", ", mode.seq(&self.index_key, col_names));
write!(
f,
"Index {index_name} on {index_on_id_name}({index_key}) \
is too wide to use for literal equalities "
)?;
write!(f, "`")?;
{
if self.usable_subset.len() == 1 {
let exprs = mode.expr(&self.usable_subset[0], col_names);
let lits = self
.literal_values
.iter()
.map(|l| l.unpack_first())
.collect::<Vec<_>>();
let mut lits = mode.seq(&lits, col_names);
if self.literal_values.len() == 1 {
write!(f, "{} = {}", exprs, lits.next().unwrap())?;
} else {
write!(f, "{} IN ({})", exprs, separated(", ", lits))?;
}
} else {
if self.literal_values.len() == 1 {
let exprs = mode.seq(&self.usable_subset, col_names);
let lits = self.literal_values[0].unpack();
let lits = mode.seq(&lits, col_names);
let eqs = zip_eq(exprs, lits).map(|(expr, lit)| format!("{} = {}", expr, lit));
write!(f, "{}", separated(" AND ", eqs))?;
} else {
let exprs = mode.seq(&self.usable_subset, col_names);
let lits = mode.seq(&self.literal_values, col_names);
write!(
f,
"({}) IN ({})",
separated(", ", exprs),
separated(", ", lits)
)?;
}
};
}
write!(f, "`.")
}
fn fmt_hint(
&self,
f: &mut fmt::Formatter<'_>,
humanizer: &dyn ExprHumanizer,
redacted: bool,
) -> fmt::Result {
let col_names = humanizer.column_names_for_id(self.index_on_id);
let mode = HumanizedNotice::new(redacted);
let recommended_key = mode.seq(&self.recommended_key, col_names.as_ref());
let recommended_key = separated(", ", recommended_key);
write!(
f,
"If your literal equalities filter out many rows, \
create an index whose key exactly matches your literal equalities: \
({recommended_key})."
)
}
fn fmt_action(
&self,
f: &mut fmt::Formatter<'_>,
humanizer: &dyn ExprHumanizer,
redacted: bool,
) -> fmt::Result {
let Some(index_on_id_name) = humanizer.humanize_id_unqualified(self.index_on_id) else {
return Ok(());
};
let mode = HumanizedNotice::new(redacted);
let col_names = humanizer.column_names_for_id(self.index_on_id);
let recommended_key = mode.seq(&self.recommended_key, col_names.as_ref());
let recommended_key = separated(", ", recommended_key);
write!(f, "CREATE INDEX ON {index_on_id_name}({recommended_key});")
}
fn action_kind(&self, humanizer: &dyn ExprHumanizer) -> ActionKind {
match humanizer.humanize_id_unqualified(self.index_on_id) {
Some(_) => ActionKind::SqlStatements,
None => ActionKind::None,
}
}
}