use std::collections::BTreeMap;
use std::fmt::Formatter;
use std::time::Duration;
use mz_ore::str::{separated, Indent, IndentLike};
use mz_repr::explain::text::DisplayText;
use mz_repr::explain::ExplainError::LinearChainsPlusRecursive;
use mz_repr::explain::{
AnnotatedPlan, Explain, ExplainConfig, ExplainError, ExprHumanizer, ScalarOps,
UnsupportedFormat, UsedIndexes,
};
use mz_repr::optimize::OptimizerFeatures;
use mz_repr::GlobalId;
use crate::interpret::{Interpreter, MfpEval, Trace};
use crate::visit::Visit;
use crate::{
AccessStrategy, Id, LocalId, MapFilterProject, MirRelationExpr, MirScalarExpr, RowSetFinishing,
};
pub use crate::explain::text::{
fmt_text_constant_rows, HumanizedExplain, HumanizedExpr, HumanizedNotice, HumanizerMode,
};
mod json;
mod text;
#[derive(Debug)]
pub struct ExplainContext<'a> {
pub config: &'a ExplainConfig,
pub features: &'a OptimizerFeatures,
pub humanizer: &'a dyn ExprHumanizer,
pub cardinality_stats: BTreeMap<GlobalId, usize>,
pub used_indexes: UsedIndexes,
pub finishing: Option<RowSetFinishing>,
pub duration: Duration,
pub target_cluster: Option<&'a str>,
pub optimizer_notices: Vec<String>,
}
#[allow(missing_debug_implementations)]
pub struct ExplainSinglePlan<'a, T> {
pub context: &'a ExplainContext<'a>,
pub plan: AnnotatedPlan<'a, T>,
}
#[allow(missing_debug_implementations)]
pub struct PushdownInfo<'a> {
pub pushdown: Vec<&'a MirScalarExpr>,
}
impl<'a, C, M> DisplayText<C> for HumanizedExpr<'a, PushdownInfo<'a>, M>
where
C: AsMut<Indent>,
M: HumanizerMode,
{
fn fmt_text(&self, f: &mut Formatter<'_>, ctx: &mut C) -> std::fmt::Result {
let PushdownInfo { pushdown } = self.expr;
if !pushdown.is_empty() {
let pushdown = pushdown.iter().map(|e| self.mode.expr(*e, self.cols));
let pushdown = separated(" AND ", pushdown);
writeln!(f, "{}pushdown=({})", ctx.as_mut(), pushdown)?;
}
Ok(())
}
}
#[allow(missing_debug_implementations)]
pub struct ExplainSource<'a> {
pub id: GlobalId,
pub op: Option<&'a MapFilterProject>,
pub pushdown_info: Option<PushdownInfo<'a>>,
}
impl<'a> ExplainSource<'a> {
pub fn new(
id: GlobalId,
op: Option<&'a MapFilterProject>,
filter_pushdown: bool,
) -> ExplainSource<'a> {
let pushdown_info = if filter_pushdown {
op.map(|op| {
let mfp_mapped = MfpEval::new(&Trace, op.input_arity, &op.expressions);
let pushdown = op
.predicates
.iter()
.filter(|(_, e)| mfp_mapped.expr(e).pushdownable())
.map(|(_, e)| e)
.collect();
PushdownInfo { pushdown }
})
} else {
None
};
ExplainSource {
id,
op,
pushdown_info,
}
}
#[inline]
pub fn is_identity(&self) -> bool {
match self.op {
Some(op) => op.is_identity(),
None => false,
}
}
}
impl<'a, 'h, C, M> DisplayText<C> for HumanizedExpr<'a, ExplainSource<'a>, M>
where
C: AsMut<Indent> + AsRef<&'h dyn ExprHumanizer>,
M: HumanizerMode,
{
fn fmt_text(&self, f: &mut std::fmt::Formatter<'_>, ctx: &mut C) -> std::fmt::Result {
let id = ctx
.as_ref()
.humanize_id(self.expr.id)
.unwrap_or_else(|| self.expr.id.to_string());
writeln!(f, "{}Source {}", ctx.as_mut(), id)?;
ctx.indented(|ctx| {
if let Some(op) = self.expr.op {
self.child(op).fmt_text(f, ctx)?;
}
if let Some(pushdown_info) = &self.expr.pushdown_info {
self.child(pushdown_info).fmt_text(f, ctx)?;
}
Ok(())
})
}
}
#[allow(missing_debug_implementations)]
pub struct ExplainMultiPlan<'a, T> {
pub context: &'a ExplainContext<'a>,
pub sources: Vec<ExplainSource<'a>>,
pub plans: Vec<(String, AnnotatedPlan<'a, T>)>,
}
impl<'a> Explain<'a> for MirRelationExpr {
type Context = ExplainContext<'a>;
type Text = ExplainSinglePlan<'a, MirRelationExpr>;
type Json = ExplainSinglePlan<'a, MirRelationExpr>;
type Dot = UnsupportedFormat;
fn explain_text(&'a mut self, context: &'a Self::Context) -> Result<Self::Text, ExplainError> {
self.as_explain_single_plan(context)
}
fn explain_json(&'a mut self, context: &'a Self::Context) -> Result<Self::Json, ExplainError> {
self.as_explain_single_plan(context)
}
}
impl<'a> MirRelationExpr {
fn as_explain_single_plan(
&'a mut self,
context: &'a ExplainContext<'a>,
) -> Result<ExplainSinglePlan<'a, MirRelationExpr>, ExplainError> {
if context.config.linear_chains {
enforce_linear_chains(self)?;
};
let plan = AnnotatedPlan {
plan: self,
annotations: BTreeMap::default(),
};
Ok(ExplainSinglePlan { context, plan })
}
}
pub fn enforce_linear_chains(expr: &mut MirRelationExpr) -> Result<(), ExplainError> {
use MirRelationExpr::{Constant, Get, Join, Union};
if expr.is_recursive() {
return Err(LinearChainsPlusRecursive);
}
let mut id_gen = id_gen(expr).peekable();
let mut wrap_in_let = |input: &mut MirRelationExpr| {
match input {
Constant { .. } | Get { .. } => (),
input => {
let id = id_gen.next().unwrap();
let value = input.take_safely();
let mut binding = MirRelationExpr::Let {
id,
value: Box::new(value),
body: Box::new(Get {
id: Id::Local(id.clone()),
typ: input.typ(),
access_strategy: AccessStrategy::UnknownOrLocal,
}),
};
std::mem::swap(input, &mut binding);
}
}
};
expr.try_visit_mut_post(&mut |expr: &mut MirRelationExpr| {
match expr {
Join { inputs, .. } => {
for input in inputs {
wrap_in_let(input);
}
}
Union { base, inputs } => {
wrap_in_let(base);
for input in inputs {
wrap_in_let(input);
}
}
_ => (),
}
Ok(())
})
}
fn id_gen(expr: &MirRelationExpr) -> impl Iterator<Item = LocalId> {
let mut max_id = 0_u64;
expr.visit_pre(|expr| {
match expr {
MirRelationExpr::Let { id, .. } => max_id = std::cmp::max(max_id, id.into()),
_ => (),
};
});
(max_id + 1..).map(LocalId::new)
}
impl ScalarOps for MirScalarExpr {
fn match_col_ref(&self) -> Option<usize> {
match self {
MirScalarExpr::Column(c) => Some(*c),
_ => None,
}
}
fn references(&self, column: usize) -> bool {
match self {
MirScalarExpr::Column(c) => *c == column,
_ => false,
}
}
}