use std::fmt::{Display, Formatter, Result};
use egg::Id;
use super::{Expr, RecExpr};
use crate::catalog::RootCatalog;
pub struct Explain<'a> {
expr: &'a RecExpr,
costs: Option<&'a [f32]>,
catalog: Option<&'a RootCatalog>,
id: Id,
depth: u8,
}
impl<'a> Explain<'a> {
pub fn of(expr: &'a RecExpr) -> Self {
Self {
expr,
costs: None,
catalog: None,
id: Id::from(expr.as_ref().len() - 1),
depth: 0,
}
}
pub fn with_costs(mut self, costs: &'a [f32]) -> Self {
self.costs = Some(costs);
self
}
pub fn with_catalog(mut self, catalog: &'a RootCatalog) -> Self {
self.catalog = Some(catalog);
self
}
#[inline]
const fn expr(&self, id: &Id) -> Self {
Explain {
expr: self.expr,
costs: self.costs,
catalog: self.catalog,
id: *id,
depth: self.depth,
}
}
#[inline]
const fn child(&self, id: &Id) -> Self {
Explain {
expr: self.expr,
costs: self.costs,
catalog: self.catalog,
id: *id,
depth: self.depth + 1,
}
}
#[inline]
const fn tab(&self) -> impl Display {
struct Tab(u8);
impl Display for Tab {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
for _ in 0..self.0 {
write!(f, " ")?;
}
Ok(())
}
}
Tab(self.depth)
}
#[inline]
fn cost(&self) -> impl Display {
struct Cost(Option<f32>);
impl Display for Cost {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
match self.0 {
Some(c) => write!(f, " (cost={c})"),
None => Ok(()),
}
}
}
Cost(self.costs.map(|cs| cs[usize::from(self.id)]))
}
#[inline]
fn is_true(&self, id: &Id) -> bool {
self.expr[*id] == Expr::true_()
}
}
impl Display for Explain<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> Result {
use Expr::*;
let enode = &self.expr[self.id];
let tab = self.tab();
let cost = self.cost();
match enode {
Constant(v) => write!(f, "{v}"),
Type(t) => write!(f, "{t}"),
Table(i) => {
if let Some(catalog) = self.catalog {
write!(f, "{}", catalog.get_table(i).expect("no table").name())
} else {
write!(f, "{i}")
}
}
Column(i) => {
if let Some(catalog) = self.catalog {
write!(f, "{}", catalog.get_column(i).expect("no column").name())
} else {
write!(f, "{i}")
}
}
ColumnIndex(i) => write!(f, "{i}"),
ExtSource(src) => write!(f, "path={:?}, format={}", src.path, src.format),
Symbol(s) => write!(f, "{s}"),
Nested(e) => write!(f, "{}", self.expr(e)),
List(list) => {
write!(f, "[")?;
for (i, v) in list.iter().enumerate() {
if i != 0 {
write!(f, ", ")?;
}
write!(f, "{}", self.expr(v))?;
}
write!(f, "]")
}
Add([a, b]) | Sub([a, b]) | Mul([a, b]) | Div([a, b]) | Mod([a, b])
| StringConcat([a, b]) | Gt([a, b]) | Lt([a, b]) | GtEq([a, b]) | LtEq([a, b])
| Eq([a, b]) | NotEq([a, b]) | And([a, b]) | Or([a, b]) | Xor([a, b])
| Like([a, b]) => write!(f, "({} {} {})", self.expr(a), enode, self.expr(b)),
Neg(a) | Not(a) | IsNull(a) => write!(f, "({} {})", enode, self.expr(a)),
If([cond, then, else_]) => write!(
f,
"(if {} {} {})",
self.expr(cond),
self.expr(then),
self.expr(else_)
),
RowCount => write!(f, "rowcount"),
Max(a) | Min(a) | Sum(a) | Avg(a) | Count(a) | First(a) | Last(a) => {
write!(f, "{}({})", enode, self.expr(a))
}
Exists(a) => write!(f, "exists({})", self.expr(a)),
In([a, b]) => write!(f, "({} in {})", self.expr(a), self.expr(b)),
Cast([a, b]) => write!(f, "({} :: {})", self.expr(a), self.expr(b)),
Scan([table, list]) => writeln!(
f,
"{tab}Scan: {}{}{cost}",
self.expr(table),
self.expr(list)
),
Values(rows) => writeln!(f, "{tab}Values: {} rows{cost}", rows.len()),
Proj([exprs, child]) => write!(
f,
"{tab}Projection: {}{cost}\n{}",
self.expr(exprs),
self.child(child)
),
Filter([cond, child]) => {
write!(
f,
"{tab}Filter: {}{cost}\n{}",
self.expr(cond),
self.child(child)
)
}
Order([orderby, child]) => {
write!(
f,
"{tab}Order: {}{cost}\n{}",
self.expr(orderby),
self.child(child)
)
}
Asc(a) | Desc(a) => write!(f, "{} {}", self.expr(a), enode),
Limit([limit, offset, child]) => write!(
f,
"{tab}Limit: limit={}, offset={}{cost}\n{}",
self.expr(limit),
self.expr(offset),
self.child(child)
),
TopN([limit, offset, orderby, child]) => write!(
f,
"{tab}TopN: limit={}, offset={}, orderby={}{cost}\n{}",
self.expr(limit),
self.expr(offset),
self.expr(orderby),
self.child(child)
),
Join([ty, cond, left, right]) => {
write!(f, "{tab}Join: {}", self.expr(ty))?;
if !self.is_true(cond) {
write!(f, ", on={}", self.expr(cond))?;
}
write!(f, "{cost}\n{}{}", self.child(left), self.child(right))
}
HashJoin([ty, lkeys, rkeys, left, right]) => write!(
f,
"{tab}HashJoin: {}, on=({} = {}){cost}\n{}{}",
self.expr(ty),
self.expr(lkeys),
self.expr(rkeys),
self.child(left),
self.child(right)
),
Inner | LeftOuter | RightOuter | FullOuter => write!(f, "{}", enode),
Agg([aggs, group_keys, child]) => write!(
f,
"{tab}Aggregate: {}, groupby={}{cost}\n{}",
self.expr(aggs),
self.expr(group_keys),
self.child(child)
),
CreateTable(t) => writeln!(f, "{tab}CreateTable: name={:?}, ...{cost}", t.table_name),
Drop(t) => writeln!(f, "{tab}Drop: {}, ...{cost}", t.object),
Insert([table, cols, child]) => write!(
f,
"{tab}Insert: {}{}{cost}\n{}",
self.expr(table),
self.expr(cols),
self.child(child)
),
Delete([table, child]) => write!(
f,
"{tab}Delete: from={}{cost}\n{}",
self.expr(table),
self.child(child)
),
CopyFrom([src, _]) => writeln!(f, "{tab}CopyFrom: {}{cost}", self.expr(src)),
CopyTo([dst, child]) => write!(
f,
"{tab}CopyTo: {}{cost}\n{}",
self.expr(dst),
self.child(child)
),
Explain(child) => write!(f, "{tab}Explain:{cost}\n{}", self.child(child)),
Empty(_) => writeln!(f, "{tab}Empty:{cost}"),
Prune(_) => panic!("cannot explain Prune"),
}
}
}