Skip to main content

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//!    - applies an [`ExprPrep`].
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::typecheck::{SharedTypecheckingContext, empty_typechecking_context};
30
31use crate::optimize::dataflows::{ExprPrep, ExprPrepNoop};
32use crate::optimize::{
33    Optimize, OptimizerConfig, OptimizerError, optimize_mir_constant, optimize_mir_local,
34    trace_plan,
35};
36
37pub struct Optimizer<S> {
38    /// A representation typechecking context to use throughout the optimizer pipeline.
39    typecheck_ctx: SharedTypecheckingContext,
40    /// Optimizer config.
41    config: OptimizerConfig,
42    /// Optimizer metrics.
43    ///
44    /// Allowed to be `None` for cases where view optimization is invoked outside the
45    /// coordinator context, and the metrics are not available.
46    metrics: Option<OptimizerMetrics>,
47    /// Expression preparation style to use. Can be `NoopExprPrepStyle` to skip expression
48    /// preparation.
49    expr_prep_style: S,
50    /// Whether to call `FoldConstants` with a size limit, or try to fold constants of any size.
51    fold_constants_limit: bool,
52}
53
54impl Optimizer<ExprPrepNoop> {
55    /// Creates an optimizer instance that does not perform any expression
56    /// preparation.
57    pub fn new(config: OptimizerConfig, metrics: Option<OptimizerMetrics>) -> Self {
58        Self::new_with_prep(config, metrics, ExprPrepNoop)
59    }
60}
61
62impl<S> Optimizer<S> {
63    /// Creates an optimizer instance that takes an [`ExprPrep`] to handle
64    /// unmaterializable functions.
65    ///
66    /// Constant folding runs with the usual size limit, see
67    /// [`Self::without_fold_constants_limit`].
68    pub fn new_with_prep(
69        config: OptimizerConfig,
70        metrics: Option<OptimizerMetrics>,
71        expr_prep_style: S,
72    ) -> Optimizer<S> {
73        Self {
74            typecheck_ctx: empty_typechecking_context(),
75            config,
76            metrics,
77            expr_prep_style,
78            fold_constants_limit: true,
79        }
80    }
81
82    /// Folds constants of any size, instead of giving up above the configured
83    /// limit.
84    pub fn without_fold_constants_limit(mut self) -> Self {
85        self.fold_constants_limit = false;
86        self
87    }
88}
89
90impl<S: ExprPrep> Optimize<HirRelationExpr> for Optimizer<S> {
91    type To = OptimizedMirRelationExpr;
92
93    fn optimize(&mut self, expr: HirRelationExpr) -> Result<Self::To, OptimizerError> {
94        let time = Instant::now();
95
96        // Trace the pipeline input under `optimize/raw`.
97        trace_plan!(at: "raw", &expr);
98
99        // HIR ⇒ MIR lowering and decorrelation
100        let mut expr = expr.lower(&self.config, self.metrics.as_ref())?;
101
102        let mut df_meta = DataflowMetainfo::default();
103        let mut transform_ctx = TransformCtx::local(
104            &self.config.features,
105            &self.typecheck_ctx,
106            &mut df_meta,
107            self.metrics.as_mut(),
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 `expr_prep_style`).
123            let mut opt_expr = OptimizedMirRelationExpr(expr);
124            self.expr_prep_style.prep_relation_expr(&mut opt_expr)?;
125            expr = opt_expr.into_inner();
126            optimize_mir_local(expr, &mut transform_ctx)?
127        };
128
129        if let Some(metrics) = &self.metrics {
130            metrics.observe_e2e_optimization_time("view", time.elapsed());
131        }
132
133        // TODO: Handle the `optimizer_notices` in `df_meta`.
134        // https://linear.app/materializeinc/issue/SQL-444
135
136        // Return the resulting OptimizedMirRelationExpr.
137        Ok(expr)
138    }
139}