mz_adapter/optimize/
view.rs

1// Copyright Materialize, Inc. and contributors. All rights reserved.
2//
3// Use of this software is governed by the Business Source License
4// included in the LICENSE file.
5//
6// As of the Change Date specified in that file, in accordance with
7// the Business Source License, use of this software will be governed
8// by the Apache License, Version 2.0.
9
10//! An Optimizer that
11//! 1. Optimistically calls `optimize_mir_constant`.
12//! 2. Then, if we haven't arrived at a constant, it does real optimization:
13//!    - calls `prep_relation_expr` an `ExprPrepStyle` was given.
14//!    - calls `optimize_mir_local`, i.e., the logical optimizer.
15//!
16//! This is used for `CREATE VIEW` statements and in various other situations where no physical
17//! optimization is needed, such as for `INSERT` statements.
18//!
19//! TODO: We should split this into an optimizer that is just for views, and another optimizer
20//! for various other ad hoc things, such as `INSERT`, `COPY FROM`, etc.
21
22use std::time::Instant;
23
24use mz_expr::OptimizedMirRelationExpr;
25use mz_sql::optimizer_metrics::OptimizerMetrics;
26use mz_sql::plan::HirRelationExpr;
27use mz_transform::TransformCtx;
28use mz_transform::dataflow::DataflowMetainfo;
29use mz_transform::reprtypecheck::{
30    SharedContext as ReprTypecheckContext, empty_context as empty_repr_context,
31};
32use mz_transform::typecheck::{SharedContext as TypecheckContext, empty_context};
33
34use crate::optimize::dataflows::{ExprPrepStyle, prep_relation_expr};
35use crate::optimize::{
36    Optimize, OptimizerConfig, OptimizerError, optimize_mir_constant, optimize_mir_local,
37    trace_plan,
38};
39
40pub struct Optimizer<'a> {
41    /// A typechecking context to use throughout the optimizer pipeline.
42    typecheck_ctx: TypecheckContext,
43    /// A representation typechecking context to use throughout the optimizer pipeline.
44    repr_typecheck_ctx: ReprTypecheckContext,
45    /// Optimizer config.
46    config: OptimizerConfig,
47    /// Optimizer metrics.
48    ///
49    /// Allowed to be `None` for cases where view optimization is invoked outside of the
50    /// coordinator context and the metrics are not available.
51    metrics: Option<OptimizerMetrics>,
52    /// If present, the optimizer will call `prep_relation_expr` using the given `ExprPrepStyle`.
53    expr_prep_style: Option<ExprPrepStyle<'a>>,
54    /// Whether to call `FoldConstants` with a size limit, or try to fold constants of any size.
55    fold_constants_limit: bool,
56}
57
58impl<'a> Optimizer<'a> {
59    pub fn new(config: OptimizerConfig, metrics: Option<OptimizerMetrics>) -> Self {
60        Self {
61            typecheck_ctx: empty_context(),
62            repr_typecheck_ctx: empty_repr_context(),
63            config,
64            metrics,
65            expr_prep_style: None,
66            fold_constants_limit: true,
67        }
68    }
69
70    /// Creates an optimizer instance that also calls `prep_relation_expr` with the given
71    /// `ExprPrepStyle`, so that unmaterializable functions are resolved.
72    /// Additionally, this instance calls constant folding without a size limit.
73    pub fn new_with_prep_no_limit(
74        config: OptimizerConfig,
75        metrics: Option<OptimizerMetrics>,
76        expr_prep_style: ExprPrepStyle<'a>,
77    ) -> Optimizer<'a> {
78        Self {
79            typecheck_ctx: empty_context(),
80            repr_typecheck_ctx: empty_repr_context(),
81            config,
82            metrics,
83            expr_prep_style: Some(expr_prep_style),
84            fold_constants_limit: false,
85        }
86    }
87}
88
89impl Optimize<HirRelationExpr> for Optimizer<'_> {
90    type To = OptimizedMirRelationExpr;
91
92    fn optimize(&mut self, expr: HirRelationExpr) -> Result<Self::To, OptimizerError> {
93        let time = Instant::now();
94
95        // Trace the pipeline input under `optimize/raw`.
96        trace_plan!(at: "raw", &expr);
97
98        // HIR ⇒ MIR lowering and decorrelation
99        let mut expr = expr.lower(&self.config, self.metrics.as_ref())?;
100
101        let mut df_meta = DataflowMetainfo::default();
102        let mut transform_ctx = TransformCtx::local(
103            &self.config.features,
104            &self.typecheck_ctx,
105            &self.repr_typecheck_ctx,
106            &mut df_meta,
107            self.metrics.as_ref(),
108            None,
109        );
110
111        // First, we run a very simple optimizer pipeline, which only folds constants. This takes
112        // care of constant INSERTs. (This optimizer is also used for INSERTs, not just VIEWs.)
113        expr = optimize_mir_constant(expr, &mut transform_ctx, self.fold_constants_limit)?;
114
115        // MIR ⇒ MIR optimization (local)
116        let expr = if expr.as_const().is_some() {
117            // No need to optimize further, because we already have a constant.
118            // But trace this at "local", so that `EXPLAIN LOCALLY OPTIMIZED PLAN` can pick it up.
119            trace_plan!(at: "local", &expr);
120            OptimizedMirRelationExpr(expr)
121        } else {
122            // Do the real optimization (starting with `prep_relation_expr` if needed).
123            if let Some(expr_prep_style) = &self.expr_prep_style {
124                let mut opt_expr = OptimizedMirRelationExpr(expr);
125                prep_relation_expr(&mut opt_expr, expr_prep_style.clone())?;
126                expr = opt_expr.into_inner();
127            }
128            optimize_mir_local(expr, &mut transform_ctx)?
129        };
130
131        if let Some(metrics) = &self.metrics {
132            metrics.observe_e2e_optimization_time("view", time.elapsed());
133        }
134
135        // Return the resulting OptimizedMirRelationExpr.
136        Ok(expr)
137    }
138}