1use itertools::Itertools;
34use proptest_derive::Arbitrary;
35use serde::{Deserialize, Serialize};
36use std::borrow::Cow;
37use std::collections::{BTreeMap, BTreeSet};
38use std::fmt;
39use std::fmt::{Display, Formatter};
40
41use mz_ore::stack::RecursionLimitError;
42use mz_ore::str::{Indent, bracketed, separated};
43
44use crate::explain::dot::{DisplayDot, dot_string};
45use crate::explain::json::{DisplayJson, json_string};
46use crate::explain::text::{DisplayText, text_string};
47use crate::optimize::OptimizerFeatureOverrides;
48use crate::{ColumnType, GlobalId, ScalarType};
49
50pub mod dot;
51pub mod json;
52pub mod text;
53#[cfg(feature = "tracing")]
54pub mod tracing;
55
56#[cfg(feature = "tracing")]
57pub use crate::explain::tracing::trace_plan;
58
59#[derive(Debug, Clone, Copy, Eq, PartialEq)]
61pub enum ExplainFormat {
62 Text,
63 VerboseText,
64 Json,
65 Dot,
66}
67
68impl fmt::Display for ExplainFormat {
69 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70 match self {
71 ExplainFormat::Text => f.write_str("TEXT"),
72 ExplainFormat::VerboseText => f.write_str("VERBOSE TEXT"),
73 ExplainFormat::Json => f.write_str("JSON"),
74 ExplainFormat::Dot => f.write_str("DOT"),
75 }
76 }
77}
78
79#[allow(missing_debug_implementations)]
83pub enum UnsupportedFormat {}
84
85#[derive(Debug)]
88pub enum ExplainError {
89 UnsupportedFormat(ExplainFormat),
90 FormatError(fmt::Error),
91 AnyhowError(anyhow::Error),
92 RecursionLimitError(RecursionLimitError),
93 SerdeJsonError(serde_json::Error),
94 LinearChainsPlusRecursive,
95 UnknownError(String),
96}
97
98impl fmt::Display for ExplainError {
99 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
100 write!(f, "error while rendering explain output: ")?;
101 match self {
102 ExplainError::UnsupportedFormat(format) => {
103 write!(f, "{} format is not supported", format)
104 }
105 ExplainError::FormatError(error) => {
106 write!(f, "{}", error)
107 }
108 ExplainError::AnyhowError(error) => {
109 write!(f, "{}", error)
110 }
111 ExplainError::RecursionLimitError(error) => {
112 write!(f, "{}", error)
113 }
114 ExplainError::SerdeJsonError(error) => {
115 write!(f, "{}", error)
116 }
117 ExplainError::LinearChainsPlusRecursive => {
118 write!(
119 f,
120 "The linear_chains option is not supported with WITH MUTUALLY RECURSIVE."
121 )
122 }
123 ExplainError::UnknownError(error) => {
124 write!(f, "{}", error)
125 }
126 }
127 }
128}
129
130impl From<fmt::Error> for ExplainError {
131 fn from(error: fmt::Error) -> Self {
132 ExplainError::FormatError(error)
133 }
134}
135
136impl From<anyhow::Error> for ExplainError {
137 fn from(error: anyhow::Error) -> Self {
138 ExplainError::AnyhowError(error)
139 }
140}
141
142impl From<RecursionLimitError> for ExplainError {
143 fn from(error: RecursionLimitError) -> Self {
144 ExplainError::RecursionLimitError(error)
145 }
146}
147
148impl From<serde_json::Error> for ExplainError {
149 fn from(error: serde_json::Error) -> Self {
150 ExplainError::SerdeJsonError(error)
151 }
152}
153
154#[derive(Clone, Debug)]
156pub struct ExplainConfig {
157 pub subtree_size: bool,
161 pub arity: bool,
163 pub types: bool,
165 pub keys: bool,
167 pub non_negative: bool,
169 pub cardinality: bool,
171 pub column_names: bool,
173 pub equivalences: bool,
175 pub join_impls: bool,
181 pub humanized_exprs: bool,
183 pub linear_chains: bool,
185 pub no_fast_path: bool,
188 pub no_notices: bool,
190 pub node_ids: bool,
192 pub raw_plans: bool,
194 pub raw_syntax: bool,
196 pub redacted: bool,
198 pub timing: bool,
200 pub filter_pushdown: bool,
202
203 pub features: OptimizerFeatureOverrides,
205}
206
207impl Default for ExplainConfig {
208 fn default() -> Self {
209 Self {
210 redacted: !mz_ore::assert::soft_assertions_enabled(),
212 arity: false,
213 cardinality: false,
214 column_names: false,
215 filter_pushdown: false,
216 humanized_exprs: false,
217 join_impls: true,
218 keys: false,
219 linear_chains: false,
220 no_fast_path: true,
221 no_notices: false,
222 node_ids: false,
223 non_negative: false,
224 raw_plans: true,
225 raw_syntax: false,
226 subtree_size: false,
227 timing: false,
228 types: false,
229 equivalences: false,
230 features: Default::default(),
231 }
232 }
233}
234
235impl ExplainConfig {
236 pub fn requires_analyses(&self) -> bool {
237 self.subtree_size
238 || self.non_negative
239 || self.arity
240 || self.types
241 || self.keys
242 || self.cardinality
243 || self.column_names
244 || self.equivalences
245 }
246}
247
248#[derive(Clone, Debug)]
250pub enum Explainee {
251 MaterializedView(GlobalId),
253 Index(GlobalId),
255 Dataflow(GlobalId),
259 Select,
262}
263
264pub trait Explain<'a>: 'a {
270 type Context;
273
274 type Text: DisplayText;
277
278 type VerboseText: DisplayText;
281
282 type Json: DisplayJson;
285
286 type Dot: DisplayDot;
289
290 fn explain(
305 &'a mut self,
306 format: &'a ExplainFormat,
307 context: &'a Self::Context,
308 ) -> Result<String, ExplainError> {
309 match format {
310 ExplainFormat::Text => self.explain_text(context).map(|e| text_string(&e)),
311 ExplainFormat::VerboseText => {
312 self.explain_verbose_text(context).map(|e| text_string(&e))
313 }
314 ExplainFormat::Json => self.explain_json(context).map(|e| json_string(&e)),
315 ExplainFormat::Dot => self.explain_dot(context).map(|e| dot_string(&e)),
316 }
317 }
318
319 #[allow(unused_variables)]
331 fn explain_text(&'a mut self, context: &'a Self::Context) -> Result<Self::Text, ExplainError> {
332 Err(ExplainError::UnsupportedFormat(ExplainFormat::Text))
333 }
334
335 #[allow(unused_variables)]
347 fn explain_verbose_text(
348 &'a mut self,
349 context: &'a Self::Context,
350 ) -> Result<Self::VerboseText, ExplainError> {
351 Err(ExplainError::UnsupportedFormat(ExplainFormat::VerboseText))
352 }
353
354 #[allow(unused_variables)]
366 fn explain_json(&'a mut self, context: &'a Self::Context) -> Result<Self::Json, ExplainError> {
367 Err(ExplainError::UnsupportedFormat(ExplainFormat::Json))
368 }
369
370 #[allow(unused_variables)]
382 fn explain_dot(&'a mut self, context: &'a Self::Context) -> Result<Self::Dot, ExplainError> {
383 Err(ExplainError::UnsupportedFormat(ExplainFormat::Dot))
384 }
385}
386
387#[derive(Debug)]
391pub struct RenderingContext<'a> {
392 pub indent: Indent,
393 pub humanizer: &'a dyn ExprHumanizer,
394}
395
396impl<'a> RenderingContext<'a> {
397 pub fn new(indent: Indent, humanizer: &'a dyn ExprHumanizer) -> RenderingContext<'a> {
398 RenderingContext { indent, humanizer }
399 }
400}
401
402impl<'a> AsMut<Indent> for RenderingContext<'a> {
403 fn as_mut(&mut self) -> &mut Indent {
404 &mut self.indent
405 }
406}
407
408impl<'a> AsRef<&'a dyn ExprHumanizer> for RenderingContext<'a> {
409 fn as_ref(&self) -> &&'a dyn ExprHumanizer {
410 &self.humanizer
411 }
412}
413
414#[allow(missing_debug_implementations)]
415pub struct PlanRenderingContext<'a, T> {
416 pub indent: Indent,
417 pub humanizer: &'a dyn ExprHumanizer,
418 pub annotations: BTreeMap<&'a T, Analyses>,
419 pub config: &'a ExplainConfig,
420}
421
422impl<'a, T> PlanRenderingContext<'a, T> {
423 pub fn new(
424 indent: Indent,
425 humanizer: &'a dyn ExprHumanizer,
426 annotations: BTreeMap<&'a T, Analyses>,
427 config: &'a ExplainConfig,
428 ) -> PlanRenderingContext<'a, T> {
429 PlanRenderingContext {
430 indent,
431 humanizer,
432 annotations,
433 config,
434 }
435 }
436}
437
438impl<'a, T> AsMut<Indent> for PlanRenderingContext<'a, T> {
439 fn as_mut(&mut self) -> &mut Indent {
440 &mut self.indent
441 }
442}
443
444impl<'a, T> AsRef<&'a dyn ExprHumanizer> for PlanRenderingContext<'a, T> {
445 fn as_ref(&self) -> &&'a dyn ExprHumanizer {
446 &self.humanizer
447 }
448}
449
450pub trait ExprHumanizer: fmt::Debug {
455 fn humanize_id(&self, id: GlobalId) -> Option<String>;
458
459 fn humanize_id_unqualified(&self, id: GlobalId) -> Option<String>;
461
462 fn humanize_id_parts(&self, id: GlobalId) -> Option<Vec<String>>;
465
466 fn humanize_scalar_type(&self, ty: &ScalarType, postgres_compat: bool) -> String;
471
472 fn humanize_column_type(&self, typ: &ColumnType, postgres_compat: bool) -> String {
477 format!(
478 "{}{}",
479 self.humanize_scalar_type(&typ.scalar_type, postgres_compat),
480 if typ.nullable { "?" } else { "" }
481 )
482 }
483
484 fn column_names_for_id(&self, id: GlobalId) -> Option<Vec<String>>;
486
487 fn humanize_column(&self, id: GlobalId, column: usize) -> Option<String>;
489
490 fn id_exists(&self, id: GlobalId) -> bool;
492}
493
494#[derive(Debug)]
497pub struct ExprHumanizerExt<'a> {
498 items: BTreeMap<GlobalId, TransientItem>,
501 inner: &'a dyn ExprHumanizer,
504}
505
506impl<'a> ExprHumanizerExt<'a> {
507 pub fn new(items: BTreeMap<GlobalId, TransientItem>, inner: &'a dyn ExprHumanizer) -> Self {
508 Self { items, inner }
509 }
510}
511
512impl<'a> ExprHumanizer for ExprHumanizerExt<'a> {
513 fn humanize_id(&self, id: GlobalId) -> Option<String> {
514 match self.items.get(&id) {
515 Some(item) => item
516 .humanized_id_parts
517 .as_ref()
518 .map(|parts| parts.join(".")),
519 None => self.inner.humanize_id(id),
520 }
521 }
522
523 fn humanize_id_unqualified(&self, id: GlobalId) -> Option<String> {
524 match self.items.get(&id) {
525 Some(item) => item
526 .humanized_id_parts
527 .as_ref()
528 .and_then(|parts| parts.last().cloned()),
529 None => self.inner.humanize_id_unqualified(id),
530 }
531 }
532
533 fn humanize_id_parts(&self, id: GlobalId) -> Option<Vec<String>> {
534 match self.items.get(&id) {
535 Some(item) => item.humanized_id_parts.clone(),
536 None => self.inner.humanize_id_parts(id),
537 }
538 }
539
540 fn humanize_scalar_type(&self, ty: &ScalarType, postgres_compat: bool) -> String {
541 self.inner.humanize_scalar_type(ty, postgres_compat)
542 }
543
544 fn column_names_for_id(&self, id: GlobalId) -> Option<Vec<String>> {
545 match self.items.get(&id) {
546 Some(item) => item.column_names.clone(),
547 None => self.inner.column_names_for_id(id),
548 }
549 }
550
551 fn humanize_column(&self, id: GlobalId, column: usize) -> Option<String> {
552 match self.items.get(&id) {
553 Some(item) => match &item.column_names {
554 Some(column_names) => Some(column_names[column].clone()),
555 None => None,
556 },
557 None => self.inner.humanize_column(id, column),
558 }
559 }
560
561 fn id_exists(&self, id: GlobalId) -> bool {
562 self.items.contains_key(&id) || self.inner.id_exists(id)
563 }
564}
565
566#[derive(Debug)]
570pub struct TransientItem {
571 humanized_id_parts: Option<Vec<String>>,
572 column_names: Option<Vec<String>>,
573}
574
575impl TransientItem {
576 pub fn new(humanized_id_parts: Option<Vec<String>>, column_names: Option<Vec<String>>) -> Self {
577 Self {
578 humanized_id_parts,
579 column_names,
580 }
581 }
582}
583
584#[derive(Debug)]
590pub struct DummyHumanizer;
591
592impl ExprHumanizer for DummyHumanizer {
593 fn humanize_id(&self, _: GlobalId) -> Option<String> {
594 None
597 }
598
599 fn humanize_id_unqualified(&self, _id: GlobalId) -> Option<String> {
600 None
601 }
602
603 fn humanize_id_parts(&self, _id: GlobalId) -> Option<Vec<String>> {
604 None
605 }
606
607 fn humanize_scalar_type(&self, ty: &ScalarType, _postgres_compat: bool) -> String {
608 format!("{:?}", ty)
610 }
611
612 fn column_names_for_id(&self, _id: GlobalId) -> Option<Vec<String>> {
613 None
614 }
615
616 fn humanize_column(&self, _id: GlobalId, _column: usize) -> Option<String> {
617 None
618 }
619
620 fn id_exists(&self, _id: GlobalId) -> bool {
621 false
622 }
623}
624
625#[derive(Debug)]
627pub struct Indices<'a>(pub &'a [usize]);
628
629#[derive(Debug)]
634pub struct CompactScalarSeq<'a, T: ScalarOps>(pub &'a [T]); #[derive(Debug)]
641pub struct CompactScalars<T, I>(pub I)
642where
643 T: ScalarOps,
644 I: Iterator<Item = T> + Clone;
645
646pub trait ScalarOps {
647 fn match_col_ref(&self) -> Option<usize>;
648
649 fn references(&self, col_ref: usize) -> bool;
650}
651
652#[allow(missing_debug_implementations)]
655pub struct AnnotatedPlan<'a, T> {
656 pub plan: &'a T,
657 pub annotations: BTreeMap<&'a T, Analyses>,
658}
659
660#[derive(Clone, Default, Debug)]
662pub struct Analyses {
663 pub non_negative: Option<bool>,
664 pub subtree_size: Option<usize>,
665 pub arity: Option<usize>,
666 pub types: Option<Option<Vec<ColumnType>>>,
667 pub keys: Option<Vec<Vec<usize>>>,
668 pub cardinality: Option<String>,
669 pub column_names: Option<Vec<String>>,
670 pub equivalences: Option<String>,
671}
672
673#[derive(Debug, Clone)]
674pub struct HumanizedAnalyses<'a> {
675 analyses: &'a Analyses,
676 humanizer: &'a dyn ExprHumanizer,
677 config: &'a ExplainConfig,
678}
679
680impl<'a> HumanizedAnalyses<'a> {
681 pub fn new<T>(analyses: &'a Analyses, ctx: &PlanRenderingContext<'a, T>) -> Self {
682 Self {
683 analyses,
684 humanizer: ctx.humanizer,
685 config: ctx.config,
686 }
687 }
688}
689
690impl<'a> Display for HumanizedAnalyses<'a> {
691 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
696 let mut builder = f.debug_struct("//");
697
698 if self.config.subtree_size {
699 let subtree_size = self.analyses.subtree_size.expect("subtree_size");
700 builder.field("subtree_size", &subtree_size);
701 }
702
703 if self.config.non_negative {
704 let non_negative = self.analyses.non_negative.expect("non_negative");
705 builder.field("non_negative", &non_negative);
706 }
707
708 if self.config.arity {
709 let arity = self.analyses.arity.expect("arity");
710 builder.field("arity", &arity);
711 }
712
713 if self.config.types {
714 let types = match self.analyses.types.as_ref().expect("types") {
715 Some(types) => {
716 let types = types
717 .into_iter()
718 .map(|c| self.humanizer.humanize_column_type(c, false))
719 .collect::<Vec<_>>();
720
721 bracketed("(", ")", separated(", ", types)).to_string()
722 }
723 None => "(<error>)".to_string(),
724 };
725 builder.field("types", &types);
726 }
727
728 if self.config.keys {
729 let keys = self
730 .analyses
731 .keys
732 .as_ref()
733 .expect("keys")
734 .into_iter()
735 .map(|key| bracketed("[", "]", separated(", ", key)).to_string());
736 let keys = bracketed("(", ")", separated(", ", keys)).to_string();
737 builder.field("keys", &keys);
738 }
739
740 if self.config.cardinality {
741 let cardinality = self.analyses.cardinality.as_ref().expect("cardinality");
742 builder.field("cardinality", cardinality);
743 }
744
745 if self.config.column_names {
746 let column_names = self.analyses.column_names.as_ref().expect("column_names");
747 let column_names = column_names.into_iter().enumerate().map(|(i, c)| {
748 if c.is_empty() {
749 Cow::Owned(format!("#{i}"))
750 } else {
751 Cow::Borrowed(c)
752 }
753 });
754 let column_names = bracketed("(", ")", separated(", ", column_names)).to_string();
755 builder.field("column_names", &column_names);
756 }
757
758 if self.config.equivalences {
759 let equivs = self.analyses.equivalences.as_ref().expect("equivalences");
760 builder.field("equivs", equivs);
761 }
762
763 builder.finish()
764 }
765}
766
767#[derive(Clone, Debug, Default)]
776pub struct UsedIndexes(BTreeSet<(GlobalId, Vec<IndexUsageType>)>);
777
778impl UsedIndexes {
779 pub fn new(values: BTreeSet<(GlobalId, Vec<IndexUsageType>)>) -> UsedIndexes {
780 UsedIndexes(values)
781 }
782
783 pub fn is_empty(&self) -> bool {
784 self.0.is_empty()
785 }
786}
787
788#[derive(Debug, Clone, Arbitrary, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash)]
789pub enum IndexUsageType {
790 FullScan,
792 DifferentialJoin,
794 DeltaJoin(DeltaJoinIndexUsageType),
796 Lookup(GlobalId),
801 PlanRootNoArrangement,
807 SinkExport,
810 IndexExport,
813 FastPathLimit,
818 DanglingArrangeBy,
824 Unknown,
827}
828
829#[derive(Debug, Clone, Arbitrary, Serialize, Deserialize, Eq, PartialEq, Ord, PartialOrd, Hash)]
833pub enum DeltaJoinIndexUsageType {
834 Unknown,
835 Lookup,
836 FirstInputFullScan,
837}
838
839impl std::fmt::Display for IndexUsageType {
840 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
841 write!(
842 f,
843 "{}",
844 match self {
845 IndexUsageType::FullScan => "*** full scan ***",
846 IndexUsageType::Lookup(_idx_id) => "lookup",
847 IndexUsageType::DifferentialJoin => "differential join",
848 IndexUsageType::DeltaJoin(DeltaJoinIndexUsageType::FirstInputFullScan) =>
849 "delta join 1st input (full scan)",
850 IndexUsageType::DeltaJoin(DeltaJoinIndexUsageType::Lookup) => "delta join lookup",
857 IndexUsageType::DeltaJoin(DeltaJoinIndexUsageType::Unknown) =>
858 "*** INTERNAL ERROR (unknown delta join usage) ***",
859 IndexUsageType::PlanRootNoArrangement => "plan root (no new arrangement)",
860 IndexUsageType::SinkExport => "sink export",
861 IndexUsageType::IndexExport => "index export",
862 IndexUsageType::FastPathLimit => "fast path limit",
863 IndexUsageType::DanglingArrangeBy => "*** INTERNAL ERROR (dangling ArrangeBy) ***",
864 IndexUsageType::Unknown => "*** INTERNAL ERROR (unknown usage) ***",
865 }
866 )
867 }
868}
869
870impl IndexUsageType {
871 pub fn display_vec<'a, I>(usage_types: I) -> impl Display + Sized + 'a
872 where
873 I: IntoIterator<Item = &'a IndexUsageType>,
874 {
875 separated(", ", usage_types.into_iter().sorted().dedup())
876 }
877}
878
879#[cfg(test)]
880mod tests {
881 use mz_ore::assert_ok;
882
883 use super::*;
884
885 struct Environment {
886 name: String,
887 }
888
889 impl Default for Environment {
890 fn default() -> Self {
891 Environment {
892 name: "test env".to_string(),
893 }
894 }
895 }
896
897 struct Frontiers<T> {
898 since: T,
899 upper: T,
900 }
901
902 impl<T> Frontiers<T> {
903 fn new(since: T, upper: T) -> Self {
904 Self { since, upper }
905 }
906 }
907
908 struct ExplainContext<'a> {
909 env: &'a mut Environment,
910 config: &'a ExplainConfig,
911 frontiers: Frontiers<u64>,
912 }
913
914 struct TestExpr {
916 lhs: i32,
917 rhs: i32,
918 }
919
920 struct TestExplanation<'a> {
921 expr: &'a TestExpr,
922 context: &'a ExplainContext<'a>,
923 }
924
925 impl<'a> DisplayText for TestExplanation<'a> {
926 fn fmt_text(&self, f: &mut fmt::Formatter<'_>, _ctx: &mut ()) -> fmt::Result {
927 let lhs = &self.expr.lhs;
928 let rhs = &self.expr.rhs;
929 writeln!(f, "expr = {lhs} + {rhs}")?;
930
931 if self.context.config.timing {
932 let since = &self.context.frontiers.since;
933 let upper = &self.context.frontiers.upper;
934 writeln!(f, "at t ∊ [{since}, {upper})")?;
935 }
936
937 let name = &self.context.env.name;
938 writeln!(f, "env = {name}")?;
939
940 Ok(())
941 }
942 }
943
944 impl<'a> Explain<'a> for TestExpr {
945 type Context = ExplainContext<'a>;
946 type Text = UnsupportedFormat;
947 type VerboseText = TestExplanation<'a>;
948 type Json = UnsupportedFormat;
949 type Dot = UnsupportedFormat;
950
951 fn explain_verbose_text(
952 &'a mut self,
953 context: &'a Self::Context,
954 ) -> Result<Self::VerboseText, ExplainError> {
955 Ok(TestExplanation {
956 expr: self,
957 context,
958 })
959 }
960 }
961
962 fn do_explain(
963 env: &mut Environment,
964 frontiers: Frontiers<u64>,
965 ) -> Result<String, ExplainError> {
966 let mut expr = TestExpr { lhs: 1, rhs: 2 };
967
968 let format = ExplainFormat::VerboseText;
969 let config = &ExplainConfig {
970 redacted: false,
971 arity: false,
972 cardinality: false,
973 column_names: false,
974 filter_pushdown: false,
975 humanized_exprs: false,
976 join_impls: false,
977 keys: false,
978 linear_chains: false,
979 no_fast_path: false,
980 no_notices: false,
981 node_ids: false,
982 non_negative: false,
983 raw_plans: false,
984 raw_syntax: false,
985 subtree_size: false,
986 equivalences: false,
987 timing: true,
988 types: false,
989 features: Default::default(),
990 };
991 let context = ExplainContext {
992 env,
993 config,
994 frontiers,
995 };
996
997 expr.explain(&format, &context)
998 }
999
1000 #[mz_ore::test]
1001 fn test_mutable_context() {
1002 let mut env = Environment::default();
1003 let frontiers = Frontiers::<u64>::new(3, 7);
1004
1005 let act = do_explain(&mut env, frontiers);
1006 let exp = "expr = 1 + 2\nat t ∊ [3, 7)\nenv = test env\n".to_string();
1007
1008 assert_ok!(act);
1009 assert_eq!(act.unwrap(), exp);
1010 }
1011}