Skip to main content

mz_sql/plan/
lowering.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//! Lowering is the process of transforming a `HirRelationExpr`
11//! into a `MirRelationExpr`.
12//!
13//! The most crucial part of lowering is decorrelation; i.e.: rewriting a
14//! `HirScalarExpr` that may contain subqueries (e.g. `SELECT` or `EXISTS`)
15//! with instances of `MirScalarExpr` that contain none of these.
16//!
17//! Informally, a subquery should be viewed as a query that is executed in
18//! the context of some outer relation, for each row of that relation. The
19//! subqueries often contain references to the columns of the outer
20//! relation.
21//!
22//! The transformation we perform maintains an `outer` relation and then
23//! traverses the relation expression that may contain references to those
24//! outer columns. As subqueries are discovered, the current relation
25//! expression is recast as the outer expression until such a point as the
26//! scalar expression's evaluation can be determined and appended to each
27//! row of the previously outer relation.
28//!
29//! It is important that the outer columns (the initial columns) act as keys
30//! for all nested computation. When counts or other aggregations are
31//! performed, they should include not only the indicated keys but also all
32//! of the outer columns.
33//!
34//! The decorrelation transformation is initialized with an empty outer
35//! relation, but it seems entirely appropriate to decorrelate queries that
36//! contain "holes" from prepared statements, as if the query was a subquery
37//! against a relation containing the assignments of values to those holes.
38
39use std::collections::{BTreeMap, BTreeSet};
40use std::iter::repeat;
41
42use itertools::Itertools;
43use mz_expr::func::variadic;
44use mz_expr::visit::Visit;
45use mz_expr::{AccessStrategy, AggregateFunc, Columns, MirRelationExpr, MirScalarExpr, func};
46use mz_ore::collections::CollectionExt;
47use mz_ore::stack::maybe_grow;
48use mz_repr::*;
49
50use crate::optimizer_metrics::OptimizerMetrics;
51use crate::plan::hir::{
52    AggregateExpr, ColumnOrder, ColumnRef, HirRelationExpr, HirScalarExpr, JoinKind, WindowExprType,
53};
54use crate::plan::{PlanError, transform_hir};
55use crate::session::vars::SystemVars;
56
57mod variadic_left;
58
59/// Maps a leveled column reference to a specific column.
60///
61/// Leveled column references are nested, so that larger levels are
62/// found early in a record and level zero is found at the end.
63///
64/// The column map only stores references for levels greater than zero,
65/// and column references at level zero simply start at the first column
66/// after all prior references.
67#[derive(Debug, Clone)]
68struct ColumnMap {
69    inner: BTreeMap<ColumnRef, usize>,
70}
71
72impl ColumnMap {
73    fn empty() -> ColumnMap {
74        Self::new(BTreeMap::new())
75    }
76
77    fn new(inner: BTreeMap<ColumnRef, usize>) -> ColumnMap {
78        ColumnMap { inner }
79    }
80
81    fn get(&self, col_ref: &ColumnRef) -> usize {
82        if col_ref.level == 0 {
83            self.inner.len() + col_ref.column
84        } else {
85            self.inner[col_ref]
86        }
87    }
88
89    fn len(&self) -> usize {
90        self.inner.len()
91    }
92
93    /// Updates references in the `ColumnMap` for use in a nested scope. The
94    /// provided `arity` must specify the arity of the current scope.
95    fn enter_scope(&self, arity: usize) -> ColumnMap {
96        // From the perspective of the nested scope, all existing column
97        // references will be one level greater.
98        let existing = self
99            .inner
100            .clone()
101            .into_iter()
102            .update(|(col, _i)| col.level += 1);
103
104        // All columns in the current scope become explicit entries in the
105        // immediate parent scope.
106        let new = (0..arity).map(|i| {
107            (
108                ColumnRef {
109                    level: 1,
110                    column: i,
111                },
112                self.len() + i,
113            )
114        });
115
116        ColumnMap::new(existing.chain(new).collect())
117    }
118}
119
120/// Map with the CTEs currently in scope.
121type CteMap = BTreeMap<mz_expr::LocalId, CteDesc>;
122
123/// Information about needed when finding a reference to a CTE in scope.
124#[derive(Clone)]
125struct CteDesc {
126    /// The new ID assigned to the lowered version of the CTE, which may not match
127    /// the ID of the input CTE.
128    new_id: mz_expr::LocalId,
129    /// The relation type of the CTE including the columns from the outer
130    /// context at the beginning.
131    relation_type: ReprRelationType,
132    /// The outer relation the CTE was applied to.
133    outer_relation: MirRelationExpr,
134}
135
136#[derive(Debug, Clone, Copy)]
137pub struct Config {
138    /// Enable outer join lowering implemented in database-issues#6747.
139    pub enable_new_outer_join_lowering: bool,
140    /// Enable outer join lowering implemented in database-issues#7561.
141    pub enable_variadic_left_join_lowering: bool,
142    pub enable_cast_elimination: bool,
143    pub enable_simplify_quantified_comparisons: bool,
144    /// See the feature flag of the same name.
145    pub enable_fixed_correlated_cte_lowering: bool,
146    pub enable_simplify_from_less_existence: bool,
147}
148
149impl Default for Config {
150    fn default() -> Self {
151        Self {
152            enable_new_outer_join_lowering: false,
153            enable_variadic_left_join_lowering: false,
154            enable_cast_elimination: false,
155            enable_simplify_quantified_comparisons: false,
156            enable_fixed_correlated_cte_lowering: false,
157            enable_simplify_from_less_existence: false,
158        }
159    }
160}
161
162impl From<&SystemVars> for Config {
163    fn from(vars: &SystemVars) -> Self {
164        Self {
165            enable_new_outer_join_lowering: vars.enable_new_outer_join_lowering(),
166            enable_variadic_left_join_lowering: vars.enable_variadic_left_join_lowering(),
167            enable_cast_elimination: vars.enable_cast_elimination(),
168            enable_simplify_quantified_comparisons: vars.enable_simplify_quantified_comparisons(),
169            enable_fixed_correlated_cte_lowering: vars.enable_fixed_correlated_cte_lowering(),
170            enable_simplify_from_less_existence: vars.enable_simplify_from_less_existence(),
171        }
172    }
173}
174
175/// Context passed to the lowering. This is wired to most parts of the lowering.
176pub(crate) struct Context<'a> {
177    /// Feature flags affecting the behavior of lowering.
178    pub config: &'a Config,
179    /// Optional, because some callers don't have an `OptimizerMetrics` handy. When it's None, we
180    /// simply don't write metrics.
181    pub metrics: Option<&'a OptimizerMetrics>,
182}
183
184impl HirRelationExpr {
185    /// Rewrite `self` into a `MirRelationExpr`.
186    /// This requires rewriting all correlated subqueries (nested `HirRelationExpr`s) into flat queries
187    #[mz_ore::instrument(target = "optimizer", level = "trace", name = "hir_to_mir")]
188    pub fn lower<C: Into<Config>>(
189        self,
190        config: C,
191        metrics: Option<&OptimizerMetrics>,
192    ) -> Result<MirRelationExpr, PlanError> {
193        let context = Context {
194            config: &config.into(),
195            metrics,
196        };
197        let result = match self {
198            // We directly rewrite a Constant into the corresponding `MirRelationExpr::Constant`
199            // to ensure that the downstream optimizer can easily bypass most
200            // irrelevant optimizations (e.g. reduce folding) for this expression
201            // without having to re-learn the fact that it is just a constant,
202            // as it would if the constant were wrapped in a Let-Get pair.
203            HirRelationExpr::Constant { rows, typ } => {
204                let rows: Vec<_> = rows.into_iter().map(|row| (row, Diff::ONE)).collect();
205                MirRelationExpr::Constant {
206                    rows: Ok(rows),
207                    typ: ReprRelationType::from(&typ),
208                }
209            }
210            mut other => {
211                let mut id_gen = mz_ore::id_gen::IdGen::default();
212                transform_hir::split_subquery_predicates(&mut other)?;
213                transform_hir::try_simplify_quantified_comparisons(
214                    &mut other,
215                    context.config.enable_simplify_quantified_comparisons,
216                )?;
217                if context.config.enable_simplify_from_less_existence {
218                    transform_hir::simplify_from_less_existence_subqueries(&mut other)?;
219                }
220                transform_hir::fuse_window_functions(&mut other, &context)?;
221                MirRelationExpr::constant(vec![vec![]], ReprRelationType::new(vec![])).let_in(
222                    &mut id_gen,
223                    |id_gen, get_outer| {
224                        other.applied_to(
225                            id_gen,
226                            get_outer,
227                            &ColumnMap::empty(),
228                            &mut CteMap::new(),
229                            &context,
230                        )
231                    },
232                )?
233            }
234        };
235
236        mz_repr::explain::trace_plan(&result);
237
238        Ok(result)
239    }
240
241    /// Return a `MirRelationExpr` which evaluates `self` once for each row of `get_outer`.
242    ///
243    /// For uncorrelated `self`, this should be the cross-product between `get_outer` and `self`.
244    /// When `self` references columns of `get_outer`, much more work needs to occur.
245    ///
246    /// The `col_map` argument contains mappings to some of the columns of `get_outer`, though
247    /// perhaps not all of them. It should be used as the basis of resolving column references,
248    /// but care must be taken when adding new columns that `get_outer.arity()` is where they
249    /// will start, rather than any function of `col_map`.
250    ///
251    /// The `get_outer` expression should be a `Get` with no duplicate rows, describing the distinct
252    /// assignment of values to outer rows.
253    fn applied_to(
254        self,
255        id_gen: &mut mz_ore::id_gen::IdGen,
256        get_outer: MirRelationExpr,
257        col_map: &ColumnMap,
258        cte_map: &mut CteMap,
259        context: &Context,
260    ) -> Result<MirRelationExpr, PlanError> {
261        maybe_grow(|| {
262            use MirRelationExpr as SR;
263
264            use HirRelationExpr::*;
265
266            if let MirRelationExpr::Get { .. } = &get_outer {
267            } else {
268                panic!(
269                    "get_outer: expected a MirRelationExpr::Get, found\n{}",
270                    get_outer.pretty(),
271                );
272            }
273            assert_eq!(col_map.len(), get_outer.arity());
274            Ok(match self {
275                Constant { rows, typ } => {
276                    // Constant expressions are not correlated with `get_outer`, and should be cross-products.
277                    get_outer.product(SR::Constant {
278                        rows: Ok(rows.into_iter().map(|row| (row, Diff::ONE)).collect()),
279                        typ: ReprRelationType::from(&typ),
280                    })
281                }
282                Get { id, typ } => match id {
283                    mz_expr::Id::Local(local_id) => {
284                        let cte_desc = cte_map.get(&local_id).unwrap();
285                        let get_cte = SR::Get {
286                            id: mz_expr::Id::Local(cte_desc.new_id.clone()),
287                            typ: cte_desc.relation_type.clone(),
288                            access_strategy: AccessStrategy::UnknownOrLocal,
289                        };
290                        if get_outer == cte_desc.outer_relation {
291                            // If the CTE was applied to the same exact relation, we can safely
292                            // return a `Get` relation.
293                            get_cte
294                        } else {
295                            // Otherwise, the new outer relation may contain more columns from some
296                            // intermediate scope placed between the definition of the CTE and this
297                            // reference of the CTE and/or more operations applied on top of the
298                            // outer relation.
299                            //
300                            // An example of the latter is the following query:
301                            //
302                            // SELECT *
303                            // FROM x,
304                            //      LATERAL(WITH a(m) as (SELECT max(y.a) FROM y WHERE y.a < x.a)
305                            //              SELECT (SELECT m FROM a) FROM y) b;
306                            //
307                            // When the CTE is lowered, the outer relation is `Get x`. But then,
308                            // the reference of the CTE is applied to `Distinct(Join(Get x, Get y), x.*)`
309                            // which has the same cardinality as `Get x`.
310                            //
311                            // In any case, `get_outer` is guaranteed to contain the columns of the
312                            // outer relation the CTE was applied to at its prefix. Since, we must
313                            // return a relation containing `get_outer`'s column at the beginning,
314                            // we must build a join between `get_outer` and `get_cte` on their common
315                            // columns.
316                            let oa = get_outer.arity();
317                            let cte_outer_columns = cte_desc.relation_type.arity() - typ.arity();
318                            let equivalences = (0..cte_outer_columns)
319                                .map(|pos| {
320                                    vec![
321                                        MirScalarExpr::column(pos),
322                                        MirScalarExpr::column(pos + oa),
323                                    ]
324                                })
325                                .collect();
326
327                            // Project out the second copy of the common between `get_outer` and
328                            // `cte_desc.outer_relation`.
329                            let projection = (0..oa)
330                                .chain(oa + cte_outer_columns..oa + cte_outer_columns + typ.arity())
331                                .collect_vec();
332                            SR::join_scalars(vec![get_outer, get_cte], equivalences)
333                                .project(projection)
334                        }
335                    }
336                    mz_expr::Id::Global(_) => {
337                        // Get statements are only to external sources, and are not correlated with `get_outer`.
338                        get_outer.product(SR::Get {
339                            id,
340                            typ: ReprRelationType::from(&typ),
341                            access_strategy: AccessStrategy::UnknownOrLocal,
342                        })
343                    }
344                },
345                Let {
346                    name: _,
347                    id,
348                    value,
349                    body,
350                } => {
351                    let value =
352                        value.applied_to(id_gen, get_outer.clone(), col_map, cte_map, context)?;
353                    value.let_in(id_gen, |id_gen, get_value| {
354                        let (new_id, typ) = if let MirRelationExpr::Get {
355                            id: mz_expr::Id::Local(id),
356                            typ,
357                            ..
358                        } = get_value
359                        {
360                            (id, typ)
361                        } else {
362                            panic!(
363                                "get_value: expected a MirRelationExpr::Get with local Id, found\n{}",
364                                get_value.pretty(),
365                            );
366                        };
367                        // Add the information about the CTE to the map and remove it when
368                        // it goes out of scope.
369                        let old_value = cte_map.insert(
370                            id.clone(),
371                            CteDesc {
372                                new_id,
373                                relation_type: typ,
374                                outer_relation: get_outer.clone(),
375                            },
376                        );
377                        let body = body.applied_to(id_gen, get_outer, col_map, cte_map, context);
378                        if let Some(old_value) = old_value {
379                            cte_map.insert(id, old_value);
380                        } else {
381                            cte_map.remove(&id);
382                        }
383                        body
384                    })?
385                }
386                LetRec {
387                    limit,
388                    bindings,
389                    body,
390                } => {
391                    let num_bindings = bindings.len();
392
393                    // We use the outer type with the HIR types to form MIR CTE types.
394                    let outer_column_types = get_outer.typ().column_types;
395
396                    // Rename and introduce all bindings.
397                    let mut shadowed_bindings = Vec::with_capacity(num_bindings);
398                    let mut mir_ids = Vec::with_capacity(num_bindings);
399                    for (_name, id, _value, typ) in bindings.iter() {
400                        let mir_id = mz_expr::LocalId::new(id_gen.allocate_id());
401                        mir_ids.push(mir_id);
402                        let shadowed = cte_map.insert(
403                            id.clone(),
404                            CteDesc {
405                                new_id: mir_id,
406                                relation_type: ReprRelationType::new(
407                                    outer_column_types
408                                        .iter()
409                                        .cloned()
410                                        .chain(typ.column_types.iter().map(ReprColumnType::from))
411                                        .collect::<Vec<_>>(),
412                                ),
413                                outer_relation: get_outer.clone(),
414                            },
415                        );
416                        shadowed_bindings.push((*id, shadowed));
417                    }
418
419                    let mut mir_values = Vec::with_capacity(num_bindings);
420                    for (_name, _id, value, _typ) in bindings.into_iter() {
421                        mir_values.push(value.applied_to(
422                            id_gen,
423                            get_outer.clone(),
424                            col_map,
425                            cte_map,
426                            context,
427                        )?);
428                    }
429
430                    let mir_body = body.applied_to(id_gen, get_outer, col_map, cte_map, context)?;
431
432                    // Remove our bindings and reinstate any shadowed bindings.
433                    for (id, shadowed) in shadowed_bindings {
434                        if let Some(shadowed) = shadowed {
435                            cte_map.insert(id, shadowed);
436                        } else {
437                            cte_map.remove(&id);
438                        }
439                    }
440
441                    MirRelationExpr::LetRec {
442                        ids: mir_ids,
443                        values: mir_values,
444                        // Copy the limit to each binding.
445                        limits: repeat(limit).take(num_bindings).collect(),
446                        body: Box::new(mir_body),
447                    }
448                }
449                Project { input, outputs } => {
450                    // Projections should be applied to the decorrelated `inner`, and to its columns,
451                    // which means rebasing `outputs` to start `get_outer.arity()` columns later.
452                    let input =
453                        input.applied_to(id_gen, get_outer.clone(), col_map, cte_map, context)?;
454                    let outputs = (0..get_outer.arity())
455                        .chain(outputs.into_iter().map(|i| get_outer.arity() + i))
456                        .collect::<Vec<_>>();
457                    input.project(outputs)
458                }
459                Map { input, mut scalars } => {
460                    // Scalar expressions may contain correlated subqueries. We must be cautious!
461
462                    // We lower scalars in chunks, and must keep track of the
463                    // arity of the HIR fragments lowered so far.
464                    let mut lowered_arity = input.arity();
465
466                    let mut input =
467                        input.applied_to(id_gen, get_outer, col_map, cte_map, context)?;
468
469                    // Lower subqueries in maximally sized batches, such as no subquery in the current
470                    // batch depends on columns from the same batch.
471                    // Note that subqueries in this projection may reference columns added by this
472                    // Map operator, so we need to ensure these columns exist before lowering the
473                    // subquery.
474                    while !scalars.is_empty() {
475                        let end_idx = scalars
476                            .iter_mut()
477                            .position(|s| {
478                                let mut requires_nonexistent_column = false;
479                                #[allow(deprecated)]
480                                s.visit_columns(0, &mut |depth, col| {
481                                    if col.level == depth {
482                                        requires_nonexistent_column |= col.column >= lowered_arity
483                                    }
484                                });
485                                requires_nonexistent_column
486                            })
487                            .unwrap_or(scalars.len());
488                        assert!(
489                            end_idx > 0,
490                            "a Map expression references itself or a later column; lowered_arity: {}, expressions: {:?}",
491                            lowered_arity,
492                            scalars
493                        );
494
495                        lowered_arity = lowered_arity + end_idx;
496                        let scalars = scalars.drain(0..end_idx).collect_vec();
497
498                        let old_arity = input.arity();
499                        let (with_subqueries, subquery_map) = HirScalarExpr::lower_subqueries(
500                            &scalars, id_gen, col_map, cte_map, input, context,
501                        )?;
502                        input = with_subqueries;
503
504                        // We will proceed sequentially through the scalar expressions, for each transforming
505                        // the decorrelated `input` into a relation with potentially more columns capable of
506                        // addressing the needs of the scalar expression.
507                        // Having done so, we add the scalar value of interest and trim off any other newly
508                        // added columns.
509                        //
510                        // The sequential traversal is present as expressions are allowed to depend on the
511                        // values of prior expressions.
512                        let mut scalar_columns = Vec::new();
513                        for scalar in scalars {
514                            let scalar = scalar.applied_to(
515                                id_gen,
516                                col_map,
517                                cte_map,
518                                &mut input,
519                                &Some(&subquery_map),
520                                context,
521                            )?;
522                            input = input.map_one(scalar);
523                            scalar_columns.push(input.arity() - 1);
524                        }
525
526                        // Discard any new columns added by the lowering of the scalar expressions
527                        input = input.project((0..old_arity).chain(scalar_columns).collect());
528                    }
529
530                    input
531                }
532                CallTable { func, exprs } => {
533                    // FlatMap expressions may contain correlated subqueries. Unlike Map they are not
534                    // allowed to refer to the results of previous expressions, and we have a simpler
535                    // implementation that appends all relevant columns first, then applies the flatmap
536                    // operator to the result, then strips off any columns introduce by subqueries.
537
538                    let mut input = get_outer;
539                    let old_arity = input.arity();
540
541                    let exprs = exprs
542                        .into_iter()
543                        .map(|e| e.applied_to(id_gen, col_map, cte_map, &mut input, &None, context))
544                        .collect::<Result<Vec<_>, _>>()?;
545
546                    let new_arity = input.arity();
547                    let output_arity = func.output_arity();
548                    input = input.flat_map(func, exprs);
549                    if old_arity != new_arity {
550                        // this means we added some columns to handle subqueries, and now we need to get rid of them
551                        input = input.project(
552                            (0..old_arity)
553                                .chain(new_arity..new_arity + output_arity)
554                                .collect(),
555                        );
556                    }
557                    input
558                }
559                Filter { input, predicates } => {
560                    // Filter expressions may contain correlated subqueries.
561                    // We extend `get_outer` with sufficient values to determine the value of the predicate,
562                    // then filter the results, then strip off any columns that were added for this purpose.
563                    let mut input =
564                        input.applied_to(id_gen, get_outer, col_map, cte_map, context)?;
565                    for predicate in predicates {
566                        let old_arity = input.arity();
567                        let predicate = predicate
568                            .applied_to(id_gen, col_map, cte_map, &mut input, &None, context)?;
569                        let new_arity = input.arity();
570                        input = input.filter(vec![predicate]);
571                        if old_arity != new_arity {
572                            // this means we added some columns to handle subqueries, and now we need to get rid of them
573                            input = input.project((0..old_arity).collect());
574                        }
575                    }
576                    input
577                }
578                Join {
579                    left,
580                    right,
581                    on,
582                    kind,
583                } if right.is_correlated() => {
584                    // A correlated join is a join in which the right expression has
585                    // access to the columns in the left expression. It turns out
586                    // this is *exactly* our branch operator, plus some additional
587                    // null handling in the case of left joins. (Right and full
588                    // lateral joins are not permitted.)
589                    //
590                    // As with normal joins, the `on` predicate may be correlated,
591                    // and we treat it as a filter that follows the branch.
592
593                    assert!(kind.can_be_correlated());
594
595                    let left = left.applied_to(id_gen, get_outer, col_map, cte_map, context)?;
596                    left.let_in(id_gen, |id_gen, get_left| {
597                        let apply_requires_distinct_outer = false;
598                        let mut join = branch(
599                            id_gen,
600                            get_left.clone(),
601                            col_map,
602                            cte_map,
603                            *right,
604                            apply_requires_distinct_outer,
605                            context,
606                            |id_gen, right, get_left, col_map, cte_map, context| {
607                                right.applied_to(id_gen, get_left, col_map, cte_map, context)
608                            },
609                        )?;
610
611                        // Plan the `on` predicate.
612                        let old_arity = join.arity();
613                        let on =
614                            on.applied_to(id_gen, col_map, cte_map, &mut join, &None, context)?;
615                        join = join.filter(vec![on]);
616                        let new_arity = join.arity();
617                        if old_arity != new_arity {
618                            // This means we added some columns to handle
619                            // subqueries, and now we need to get rid of them.
620                            join = join.project((0..old_arity).collect());
621                        }
622
623                        // If a left join, reintroduce any rows from the left that
624                        // are missing, with nulls filled in for the right columns.
625                        if let JoinKind::LeftOuter { .. } = kind {
626                            let default = join
627                                .typ()
628                                .column_types
629                                .into_iter()
630                                .skip(get_left.arity())
631                                .map(|typ| (Datum::Null, typ.scalar_type))
632                                .collect();
633                            get_left.lookup(id_gen, join, default)
634                        } else {
635                            Ok::<_, PlanError>(join)
636                        }
637                    })?
638                }
639                Join {
640                    left,
641                    right,
642                    on,
643                    kind,
644                } => {
645                    if context.config.enable_variadic_left_join_lowering {
646                        // Attempt to extract a stack of left joins.
647                        if let JoinKind::LeftOuter = kind {
648                            let mut rights = vec![(&*right, &on)];
649                            let mut left_test = &left;
650                            while let Join {
651                                left,
652                                right,
653                                on,
654                                kind: JoinKind::LeftOuter,
655                            } = &**left_test
656                            {
657                                rights.push((&**right, on));
658                                left_test = left;
659                            }
660                            if rights.len() > 1 {
661                                // Defensively clone `cte_map` as it may be mutated.
662                                let cte_map_clone = cte_map.clone();
663                                if let Ok(Some(magic)) = variadic_left::attempt_left_join_magic(
664                                    left_test,
665                                    rights,
666                                    id_gen,
667                                    get_outer.clone(),
668                                    col_map,
669                                    cte_map,
670                                    context,
671                                ) {
672                                    return Ok(magic);
673                                } else {
674                                    cte_map.clone_from(&cte_map_clone);
675                                }
676                            }
677                        }
678                    }
679
680                    // Both join expressions should be decorrelated, and then joined by their
681                    // leading columns to form only those pairs corresponding to the same row
682                    // of `get_outer`.
683                    //
684                    // The `on` predicate may contain correlated subqueries, and we treat it
685                    // as though it was a filter, with the caveat that we also translate outer
686                    // joins in this step. The post-filtration results need to be considered
687                    // against the records present in the left and right (decorrelated) inputs,
688                    // depending on the type of join.
689                    let oa = get_outer.arity();
690                    let left =
691                        left.applied_to(id_gen, get_outer.clone(), col_map, cte_map, context)?;
692                    let lt = left.typ().column_types.into_iter().skip(oa).collect_vec();
693                    let la = lt.len();
694                    left.let_in(id_gen, |id_gen, get_left| {
695                        let right_col_map = col_map.enter_scope(0);
696                        let right = right.applied_to(
697                            id_gen,
698                            get_outer.clone(),
699                            &right_col_map,
700                            cte_map,
701                            context,
702                        )?;
703                        let rt = right.typ().column_types.into_iter().skip(oa).collect_vec();
704                        let ra = rt.len();
705                        right.let_in(id_gen, |id_gen, get_right| {
706                            let mut product = SR::join(
707                                vec![get_left.clone(), get_right.clone()],
708                                (0..oa).map(|i| vec![(0, i), (1, i)]).collect(),
709                            )
710                            // Project away the repeated copy of get_outer's columns.
711                            .project(
712                                (0..(oa + la))
713                                    .chain((oa + la + oa)..(oa + la + oa + ra))
714                                    .collect(),
715                            );
716
717                            // Decorrelate and lower the `on` clause.
718                            let on = on.applied_to(
719                                id_gen,
720                                col_map,
721                                cte_map,
722                                &mut product,
723                                &None,
724                                context,
725                            )?;
726                            // Collect the types of all subqueries appearing in
727                            // the `on` clause. The subquery results were
728                            // appended to `product` in the `on.applied_to(...)`
729                            // call above.
730                            let on_subquery_types = product
731                                .typ()
732                                .column_types
733                                .drain(oa + la + ra..)
734                                .collect_vec();
735                            // Remember if `on` had any subqueries.
736                            let on_has_subqueries = !on_subquery_types.is_empty();
737
738                            // Attempt an efficient equijoin implementation, in which outer joins are
739                            // more efficiently rendered than in general. This can return `None` if
740                            // such a plan is not possible, for example if `on` does not describe an
741                            // equijoin between columns of `left` and `right`.
742                            if kind != JoinKind::Inner {
743                                if let Some(joined) = attempt_outer_equijoin(
744                                    get_left.clone(),
745                                    get_right.clone(),
746                                    on.clone(),
747                                    on_subquery_types,
748                                    kind.clone(),
749                                    oa,
750                                    id_gen,
751                                    context,
752                                )? {
753                                    if let Some(metrics) = context.metrics {
754                                        metrics.inc_outer_join_lowering("equi");
755                                    }
756                                    return Ok(joined);
757                                }
758                            }
759
760                            // Otherwise, perform a more general join.
761                            if let Some(metrics) = context.metrics {
762                                metrics.inc_outer_join_lowering("general");
763                            }
764                            let mut join = product.filter(vec![on]);
765                            if on_has_subqueries {
766                                // This means that `on.applied_to(...)` appended
767                                // some columns to handle subqueries, and now we
768                                // need to get rid of them.
769                                join = join.project((0..oa + la + ra).collect());
770                            }
771                            join.let_in(id_gen, |id_gen, get_join| {
772                                let mut result = get_join.clone();
773                                if let JoinKind::LeftOuter { .. } | JoinKind::FullOuter { .. } =
774                                    kind
775                                {
776                                    let left_outer = get_left.clone().anti_lookup::<PlanError>(
777                                        id_gen,
778                                        get_join.clone(),
779                                        rt.into_iter()
780                                            .map(|typ| (Datum::Null, typ.scalar_type))
781                                            .collect(),
782                                    )?;
783                                    result = result.union(left_outer);
784                                }
785                                if let JoinKind::RightOuter | JoinKind::FullOuter = kind {
786                                    let right_outer = get_right
787                                        .clone()
788                                        .anti_lookup::<PlanError>(
789                                            id_gen,
790                                            get_join
791                                                // need to swap left and right to make the anti_lookup work
792                                                .project(
793                                                    (0..oa)
794                                                        .chain((oa + la)..(oa + la + ra))
795                                                        .chain((oa)..(oa + la))
796                                                        .collect(),
797                                                ),
798                                            lt.into_iter()
799                                                .map(|typ| (Datum::Null, typ.scalar_type))
800                                                .collect(),
801                                        )?
802                                        // swap left and right back again
803                                        .project(
804                                            (0..oa)
805                                                .chain((oa + ra)..(oa + ra + la))
806                                                .chain((oa)..(oa + ra))
807                                                .collect(),
808                                        );
809                                    result = result.union(right_outer);
810                                }
811                                Ok::<MirRelationExpr, PlanError>(result)
812                            })
813                        })
814                    })?
815                }
816                Union { base, inputs } => {
817                    // Union is uncomplicated.
818                    SR::Union {
819                        base: Box::new(base.applied_to(
820                            id_gen,
821                            get_outer.clone(),
822                            col_map,
823                            cte_map,
824                            context,
825                        )?),
826                        inputs: inputs
827                            .into_iter()
828                            .map(|input| {
829                                input.applied_to(
830                                    id_gen,
831                                    get_outer.clone(),
832                                    col_map,
833                                    cte_map,
834                                    context,
835                                )
836                            })
837                            .collect::<Result<Vec<_>, _>>()?,
838                    }
839                }
840                Reduce {
841                    input,
842                    group_key,
843                    aggregates,
844                    expected_group_size,
845                } => {
846                    // Reduce may contain expressions with correlated subqueries.
847                    // In addition, here an empty reduction key signifies that we need to supply default values
848                    // in the case that there are no results (as in a SQL aggregation without an explicit GROUP BY).
849                    let mut input =
850                        input.applied_to(id_gen, get_outer.clone(), col_map, cte_map, context)?;
851                    let applied_group_key = (0..get_outer.arity())
852                        .chain(group_key.iter().map(|i| get_outer.arity() + i))
853                        .collect();
854                    let applied_aggregates = aggregates
855                        .into_iter()
856                        .map(|aggregate| {
857                            aggregate.applied_to(id_gen, col_map, cte_map, &mut input, context)
858                        })
859                        .collect::<Result<Vec<_>, _>>()?;
860                    let input_type = input.typ();
861                    let default = applied_aggregates
862                        .iter()
863                        .map(|agg| {
864                            (
865                                agg.func.default(),
866                                agg.typ(&input_type.column_types).scalar_type,
867                            )
868                        })
869                        .collect();
870                    // NOTE we don't need to remove any extra columns from aggregate.applied_to above because the reduce will do that anyway
871                    let mut reduced =
872                        input.reduce(applied_group_key, applied_aggregates, expected_group_size);
873
874                    // Introduce default values in the case the group key is empty.
875                    if group_key.is_empty() {
876                        reduced = get_outer.lookup::<PlanError>(id_gen, reduced, default)?;
877                    }
878                    reduced
879                }
880                Distinct { input } => {
881                    // Distinct is uncomplicated.
882                    input
883                        .applied_to(id_gen, get_outer, col_map, cte_map, context)?
884                        .distinct()
885                }
886                TopK {
887                    input,
888                    group_key,
889                    order_key,
890                    limit,
891                    offset,
892                    expected_group_size,
893                } => {
894                    // TopK is uncomplicated, except that we must group by the columns of `get_outer` as well.
895                    let mut input =
896                        input.applied_to(id_gen, get_outer.clone(), col_map, cte_map, context)?;
897                    let mut applied_group_key: Vec<_> = (0..get_outer.arity())
898                        .chain(group_key.iter().map(|i| get_outer.arity() + i))
899                        .collect();
900                    let applied_order_key = order_key
901                        .iter()
902                        .map(|column_order| ColumnOrder {
903                            column: column_order.column + get_outer.arity(),
904                            desc: column_order.desc,
905                            nulls_last: column_order.nulls_last,
906                        })
907                        .collect();
908
909                    let old_arity = input.arity();
910
911                    // Lower `limit`, which may introduce new columns if is a correlated subquery.
912                    let mut limit_mir = None;
913                    if let Some(limit) = limit {
914                        limit_mir = Some(
915                            limit
916                                .applied_to(id_gen, col_map, cte_map, &mut input, &None, context)?,
917                        );
918                    }
919
920                    let new_arity = input.arity();
921                    // Extend the key to contain any new columns.
922                    applied_group_key.extend(old_arity..new_arity);
923
924                    let offset = offset
925                        .try_into_literal_int64()
926                        .expect("Should be a Literal by this time")
927                        .try_into()
928                        .expect("Should have checked non-negativity of OFFSET clause already");
929                    let mut result = input.top_k(
930                        applied_group_key,
931                        applied_order_key,
932                        limit_mir,
933                        offset,
934                        expected_group_size,
935                    );
936
937                    // If new columns were added for `limit` we must remove them.
938                    if old_arity != new_arity {
939                        result = result.project((0..old_arity).collect());
940                    }
941
942                    result
943                }
944                Negate { input } => {
945                    // Negate is uncomplicated.
946                    input
947                        .applied_to(id_gen, get_outer, col_map, cte_map, context)?
948                        .negate()
949                }
950                Threshold { input } => {
951                    // Threshold is uncomplicated.
952                    input
953                        .applied_to(id_gen, get_outer, col_map, cte_map, context)?
954                        .threshold()
955                }
956            })
957        })
958    }
959}
960
961impl HirScalarExpr {
962    /// Rewrite `self` into a `mz_expr::ScalarExpr` which can be applied to the modified `inner`.
963    ///
964    /// This method is responsible for decorrelating subqueries in `self` by introducing further columns
965    /// to `inner`, and rewriting `self` to refer to its physical columns (specified by `usize` positions).
966    /// The most complicated logic is for the scalar expressions that involve subqueries, each of which are
967    /// documented in more detail closer to their logic.
968    ///
969    /// This process presumes that `inner` is the result of decorrelation, meaning its first several columns
970    /// may be inherited from outer relations. The `col_map` column map should provide specific offsets where
971    /// each of these references can be found.
972    fn applied_to(
973        self,
974        id_gen: &mut mz_ore::id_gen::IdGen,
975        col_map: &ColumnMap,
976        cte_map: &mut CteMap,
977        inner: &mut MirRelationExpr,
978        subquery_map: &Option<&BTreeMap<HirScalarExpr, usize>>,
979        context: &Context,
980    ) -> Result<MirScalarExpr, PlanError> {
981        maybe_grow(|| {
982            use MirScalarExpr as SS;
983
984            use HirScalarExpr::*;
985
986            if let Some(subquery_map) = subquery_map {
987                if let Some(col) = subquery_map.get(&self) {
988                    return Ok(SS::column(*col));
989                }
990            }
991
992            Ok::<MirScalarExpr, PlanError>(match self {
993                Column(col_ref, name) => SS::Column(col_map.get(&col_ref), name),
994                Literal(row, typ, _name) => SS::Literal(Ok(row), ReprColumnType::from(&typ)),
995                Parameter(_, _name) => {
996                    panic!("cannot decorrelate expression with unbound parameters")
997                }
998                CallUnmaterializable(func, _name) => SS::CallUnmaterializable(func),
999                CallUnary {
1000                    func,
1001                    expr,
1002                    name: _,
1003                } => {
1004                    let inner =
1005                        expr.applied_to(id_gen, col_map, cte_map, inner, subquery_map, context)?;
1006                    if context.config.enable_cast_elimination && func.is_eliminable_cast() {
1007                        inner
1008                    } else {
1009                        SS::CallUnary {
1010                            func,
1011                            expr: Box::new(inner),
1012                        }
1013                    }
1014                }
1015                CallBinary {
1016                    func,
1017                    expr1,
1018                    expr2,
1019                    name: _,
1020                } => SS::CallBinary {
1021                    func,
1022                    expr1: Box::new(expr1.applied_to(
1023                        id_gen,
1024                        col_map,
1025                        cte_map,
1026                        inner,
1027                        subquery_map,
1028                        context,
1029                    )?),
1030                    expr2: Box::new(expr2.applied_to(
1031                        id_gen,
1032                        col_map,
1033                        cte_map,
1034                        inner,
1035                        subquery_map,
1036                        context,
1037                    )?),
1038                },
1039                CallVariadic {
1040                    func,
1041                    exprs,
1042                    name: _,
1043                } => SS::call_variadic(
1044                    func,
1045                    exprs
1046                        .into_iter()
1047                        .map(|expr| {
1048                            expr.applied_to(id_gen, col_map, cte_map, inner, subquery_map, context)
1049                        })
1050                        .collect::<Result<_, _>>()?,
1051                ),
1052                If {
1053                    cond,
1054                    then,
1055                    els,
1056                    name,
1057                } => {
1058                    // The `If` case is complicated by the fact that we do not want to
1059                    // apply the `then` or `else` logic to tuples that respectively do
1060                    // not or do pass the `cond` test. Our strategy is to independently
1061                    // decorrelate the `then` and `else` logic, and apply each to tuples
1062                    // that respectively pass and do not pass the `cond` logic (which is
1063                    // executed, and so decorrelated, for all tuples).
1064                    //
1065                    // Informally, we turn the `if` statement into:
1066                    //
1067                    //   let then_case = inner.filter(cond).map(then);
1068                    //   let else_case = inner.filter(!cond).map(else);
1069                    //   return then_case.concat(else_case);
1070                    //
1071                    // We only require this if either expression would result in any
1072                    // computation beyond the expr itself, which we will interpret as
1073                    // "introduces additional columns". In the absence of correlation,
1074                    // we should just retain a `ScalarExpr::If` expression; the inverse
1075                    // transformation as above is complicated to recover after the fact,
1076                    // and we would benefit from not introducing the complexity.
1077
1078                    let inner_arity = inner.arity();
1079                    let cond_expr =
1080                        cond.applied_to(id_gen, col_map, cte_map, inner, subquery_map, context)?;
1081
1082                    // Defensive copies, in case we mangle these in decorrelation.
1083                    let inner_clone = inner.clone();
1084                    let then_clone = then.clone();
1085                    let else_clone = els.clone();
1086
1087                    let cond_arity = inner.arity();
1088                    let then_expr =
1089                        then.applied_to(id_gen, col_map, cte_map, inner, subquery_map, context)?;
1090                    let else_expr =
1091                        els.applied_to(id_gen, col_map, cte_map, inner, subquery_map, context)?;
1092
1093                    if cond_arity == inner.arity() {
1094                        // If no additional columns were added, we simply return the
1095                        // `If` variant with the updated expressions.
1096                        SS::If {
1097                            cond: Box::new(cond_expr),
1098                            then: Box::new(then_expr),
1099                            els: Box::new(else_expr),
1100                        }
1101                    } else {
1102                        // If columns were added, we need a more careful approach, as
1103                        // described above. First, we need to de-correlate each of
1104                        // the two expressions independently, and apply their cases
1105                        // as `MirRelationExpr::Map` operations.
1106
1107                        *inner = inner_clone.let_in(id_gen, |id_gen, get_inner| {
1108                            // Restrict to records satisfying `cond_expr` and apply `then` as a map.
1109                            let mut then_inner = get_inner.clone().filter(vec![cond_expr.clone()]);
1110                            let then_expr = then_clone.applied_to(
1111                                id_gen,
1112                                col_map,
1113                                cte_map,
1114                                &mut then_inner,
1115                                subquery_map,
1116                                context,
1117                            )?;
1118                            let then_arity = then_inner.arity();
1119                            then_inner = then_inner
1120                                .map_one(then_expr)
1121                                .project((0..inner_arity).chain(Some(then_arity)).collect());
1122
1123                            // Restrict to records not satisfying `cond_expr` and apply `els` as a map.
1124                            let mut else_inner = get_inner.filter(vec![SS::call_variadic(
1125                                variadic::Or,
1126                                vec![
1127                                    cond_expr.clone().call_binary(SS::literal_false(), func::Eq),
1128                                    cond_expr.clone().call_is_null(),
1129                                ],
1130                            )]);
1131                            let else_expr = else_clone.applied_to(
1132                                id_gen,
1133                                col_map,
1134                                cte_map,
1135                                &mut else_inner,
1136                                subquery_map,
1137                                context,
1138                            )?;
1139                            let else_arity = else_inner.arity();
1140                            else_inner = else_inner
1141                                .map_one(else_expr)
1142                                .project((0..inner_arity).chain(Some(else_arity)).collect());
1143
1144                            // concatenate the two results.
1145                            Ok::<MirRelationExpr, PlanError>(then_inner.union(else_inner))
1146                        })?;
1147
1148                        SS::Column(inner_arity, name)
1149                    }
1150                }
1151
1152                // Subqueries!
1153                // These are surprisingly subtle. Things to be careful of:
1154
1155                // Anything in the subquery that cares about row counts (Reduce/Distinct/Negate/Threshold) must not:
1156                // * change the row counts of the outer query
1157                // * accidentally compute its own value using the row counts of the outer query
1158                // Use `branch` to calculate the subquery once for each __distinct__ key in the outer
1159                // query and then join the answers back on to the original rows of the outer query.
1160
1161                // When the subquery would return 0 rows for some row in the outer query, `subquery.applied_to(get_inner)` will not have any corresponding row.
1162                // Use `lookup` if you need to add default values for cases when the subquery returns 0 rows.
1163                Exists(expr, name) => {
1164                    let apply_requires_distinct_outer = true;
1165                    *inner = apply_existential_subquery(
1166                        id_gen,
1167                        inner.take_dangerous(),
1168                        col_map,
1169                        cte_map,
1170                        *expr,
1171                        apply_requires_distinct_outer,
1172                        context,
1173                    )?;
1174                    SS::Column(inner.arity() - 1, name)
1175                }
1176
1177                Select(expr, name) => {
1178                    let apply_requires_distinct_outer = true;
1179                    *inner = apply_scalar_subquery(
1180                        id_gen,
1181                        inner.take_dangerous(),
1182                        col_map,
1183                        cte_map,
1184                        *expr,
1185                        apply_requires_distinct_outer,
1186                        context,
1187                    )?;
1188                    SS::Column(inner.arity() - 1, name)
1189                }
1190                Windowing(expr, _name) => {
1191                    let partition_by = expr.partition_by;
1192                    let order_by = expr.order_by;
1193
1194                    // argument lowering for scalar window functions
1195                    // (We need to specify the & _ in the arguments because of this problem:
1196                    // https://users.rust-lang.org/t/the-implementation-of-fnonce-is-not-general-enough/72141/3 )
1197                    let scalar_lower_args =
1198                        |_id_gen: &mut _,
1199                         _col_map: &_,
1200                         _cte_map: &mut _,
1201                         _get_inner: &mut _,
1202                         _subquery_map: &Option<&_>,
1203                         order_by_mir: Vec<MirScalarExpr>,
1204                         original_row_record,
1205                         original_row_record_type: SqlScalarType| {
1206                            let agg_input = MirScalarExpr::call_variadic(
1207                                variadic::ListCreate {
1208                                    elem_type: original_row_record_type.clone(),
1209                                },
1210                                vec![original_row_record],
1211                            );
1212                            let mut agg_input = vec![agg_input];
1213                            agg_input.extend(order_by_mir.clone());
1214                            let agg_input = MirScalarExpr::call_variadic(
1215                                variadic::RecordCreate {
1216                                    field_names: (0..agg_input.len())
1217                                        .map(|_| ColumnName::from(UNKNOWN_COLUMN_NAME))
1218                                        .collect_vec(),
1219                                },
1220                                agg_input,
1221                            );
1222                            let list_type = SqlScalarType::List {
1223                                element_type: Box::new(original_row_record_type),
1224                                custom_id: None,
1225                            };
1226                            let agg_input_type = SqlScalarType::Record {
1227                                fields: std::iter::once(&list_type)
1228                                    .map(|t| {
1229                                        (
1230                                            ColumnName::from(UNKNOWN_COLUMN_NAME),
1231                                            t.clone().nullable(false),
1232                                        )
1233                                    })
1234                                    .collect(),
1235                                custom_id: None,
1236                            }
1237                            .nullable(false);
1238
1239                            Ok((agg_input, agg_input_type))
1240                        };
1241
1242                    // argument lowering for value window functions and aggregate window functions
1243                    let value_or_aggr_lower_args = |hir_encoded_args: Box<HirScalarExpr>| {
1244                        |id_gen: &mut _,
1245                         col_map: &_,
1246                         cte_map: &mut _,
1247                         get_inner: &mut _,
1248                         subquery_map: &Option<&_>,
1249                         order_by_mir: Vec<MirScalarExpr>,
1250                         original_row_record,
1251                         original_row_record_type| {
1252                            // Creates [((OriginalRow, EncodedArgs), OrderByExprs...)]
1253
1254                            // Compute the encoded args for all rows
1255                            let mir_encoded_args = hir_encoded_args.applied_to(
1256                                id_gen,
1257                                col_map,
1258                                cte_map,
1259                                get_inner,
1260                                subquery_map,
1261                                context,
1262                            )?;
1263                            let mir_encoded_args_type = mir_encoded_args
1264                                .sql_typ(&get_inner.sql_typ().column_types)
1265                                .scalar_type;
1266
1267                            // Build a new record that has two fields:
1268                            // 1. the original row in a record
1269                            // 2. the encoded args (which can be either a single value, or a record
1270                            //    if the window function has multiple arguments, such as `lag`)
1271                            let fn_input_record_fields: Box<[_]> =
1272                                [original_row_record_type, mir_encoded_args_type]
1273                                    .iter()
1274                                    .map(|t| {
1275                                        (
1276                                            ColumnName::from(UNKNOWN_COLUMN_NAME),
1277                                            t.clone().nullable(false),
1278                                        )
1279                                    })
1280                                    .collect();
1281                            let fn_input_record = MirScalarExpr::call_variadic(
1282                                variadic::RecordCreate {
1283                                    field_names: fn_input_record_fields
1284                                        .iter()
1285                                        .map(|(n, _)| n.clone())
1286                                        .collect_vec(),
1287                                },
1288                                vec![original_row_record, mir_encoded_args],
1289                            );
1290                            let fn_input_record_type = SqlScalarType::Record {
1291                                fields: fn_input_record_fields,
1292                                custom_id: None,
1293                            }
1294                            .nullable(false);
1295
1296                            // Build a new record with the record above + the ORDER BY exprs
1297                            // This follows the standard encoding of ORDER BY exprs used by aggregate functions
1298                            let mut agg_input = vec![fn_input_record];
1299                            agg_input.extend(order_by_mir.clone());
1300                            let agg_input = MirScalarExpr::call_variadic(
1301                                variadic::RecordCreate {
1302                                    field_names: (0..agg_input.len())
1303                                        .map(|_| ColumnName::from(UNKNOWN_COLUMN_NAME))
1304                                        .collect_vec(),
1305                                },
1306                                agg_input,
1307                            );
1308
1309                            let agg_input_type = SqlScalarType::Record {
1310                                fields: [(
1311                                    ColumnName::from(UNKNOWN_COLUMN_NAME),
1312                                    fn_input_record_type.nullable(false),
1313                                )]
1314                                .into(),
1315                                custom_id: None,
1316                            }
1317                            .nullable(false);
1318
1319                            Ok((agg_input, agg_input_type))
1320                        }
1321                    };
1322
1323                    match expr.func {
1324                        WindowExprType::Scalar(scalar_window_expr) => {
1325                            let mir_aggr_func = scalar_window_expr.into_expr();
1326                            Self::window_func_applied_to(
1327                                id_gen,
1328                                col_map,
1329                                cte_map,
1330                                inner,
1331                                subquery_map,
1332                                partition_by,
1333                                order_by,
1334                                mir_aggr_func,
1335                                scalar_lower_args,
1336                                context,
1337                            )?
1338                        }
1339                        WindowExprType::Value(value_window_expr) => {
1340                            let (hir_encoded_args, mir_aggr_func) = value_window_expr.into_expr();
1341
1342                            Self::window_func_applied_to(
1343                                id_gen,
1344                                col_map,
1345                                cte_map,
1346                                inner,
1347                                subquery_map,
1348                                partition_by,
1349                                order_by,
1350                                mir_aggr_func,
1351                                value_or_aggr_lower_args(hir_encoded_args),
1352                                context,
1353                            )?
1354                        }
1355                        WindowExprType::Aggregate(aggr_window_expr) => {
1356                            let (hir_encoded_args, mir_aggr_func) = aggr_window_expr.into_expr();
1357
1358                            Self::window_func_applied_to(
1359                                id_gen,
1360                                col_map,
1361                                cte_map,
1362                                inner,
1363                                subquery_map,
1364                                partition_by,
1365                                order_by,
1366                                mir_aggr_func,
1367                                value_or_aggr_lower_args(hir_encoded_args),
1368                                context,
1369                            )?
1370                        }
1371                    }
1372                }
1373            })
1374        })
1375    }
1376
1377    fn window_func_applied_to<F>(
1378        id_gen: &mut mz_ore::id_gen::IdGen,
1379        col_map: &ColumnMap,
1380        cte_map: &mut CteMap,
1381        inner: &mut MirRelationExpr,
1382        subquery_map: &Option<&BTreeMap<HirScalarExpr, usize>>,
1383        partition_by: Vec<HirScalarExpr>,
1384        order_by: Vec<HirScalarExpr>,
1385        mir_aggr_func: AggregateFunc,
1386        lower_args: F,
1387        context: &Context,
1388    ) -> Result<MirScalarExpr, PlanError>
1389    where
1390        F: FnOnce(
1391            &mut mz_ore::id_gen::IdGen,
1392            &ColumnMap,
1393            &mut CteMap,
1394            &mut MirRelationExpr,
1395            &Option<&BTreeMap<HirScalarExpr, usize>>,
1396            Vec<MirScalarExpr>,
1397            MirScalarExpr,
1398            SqlScalarType,
1399        ) -> Result<(MirScalarExpr, SqlColumnType), PlanError>,
1400    {
1401        // Example MIRs for a window function (specifically, a window aggregation):
1402        //
1403        // CREATE TABLE t7(x INT, y INT);
1404        //
1405        // explain decorrelated plan for select sum(x*y) over (partition by x+y order by x-y, x/y) from t7;
1406        //
1407        // Decorrelated Plan
1408        // Project (#3)
1409        //   Map (#2)
1410        //     Project (#3..=#5)
1411        //       Map (record_get[0](record_get[1](#2)), record_get[1](record_get[1](#2)), record_get[0](#2))
1412        //         FlatMap unnest_list(#1)
1413        //           Reduce group_by=[#2] aggregates=[window_agg[sum order_by=[#0 asc nulls_last, #1 asc nulls_last]](row(row(row(#0, #1), (#0 * #1)), (#0 - #1), (#0 / #1)))]
1414        //             Map ((#0 + #1))
1415        //               CrossJoin
1416        //                 Constant
1417        //                   - ()
1418        //                 Get materialize.public.t7
1419        //
1420        // The same query after optimizations:
1421        //
1422        // explain select sum(x*y) over (partition by x+y order by x-y, x/y) from t7;
1423        //
1424        // Optimized Plan
1425        // Explained Query:
1426        //   Project (#2)
1427        //     Map (record_get[0](#1))
1428        //       FlatMap unnest_list(#0)
1429        //         Project (#1)
1430        //           Reduce group_by=[(#0 + #1)] aggregates=[window_agg[sum order_by=[#0 asc nulls_last, #1 asc nulls_last]](row(row(row(#0, #1), (#0 * #1)), (#0 - #1), (#0 / #1)))]
1431        //             ReadStorage materialize.public.t7
1432        //
1433        // The `row(row(row(...), ...), ...)` stuff means the following:
1434        // `row(row(row(<original row>), <arguments to window function>), <order by values>...)`
1435        //   - The <arguments to window function> can be either a single value or itself a
1436        //     `row` if there are multiple arguments.
1437        //   - The <order by values> are _not_ wrapped in a `row`, even if there are more than one
1438        //     ORDER BY columns.
1439        //   - The <original row> currently always captures the entire original row. This should
1440        //     improve when we make `ProjectionPushdown` smarter, see
1441        //     https://github.com/MaterializeInc/database-issues/issues/5090
1442        //
1443        // TODO:
1444        // We should probably introduce some dedicated Datum constructor functions instead of `row`
1445        // to make MIR plans and MIR construction/manipulation code more readable. Additionally, we
1446        // might even introduce dedicated Datum enum variants, so that the rendering code also
1447        // becomes more readable (and possibly slightly more performant).
1448
1449        *inner = inner
1450            .take_dangerous()
1451            .let_in(id_gen, |id_gen, mut get_inner| {
1452                let order_by_mir = order_by
1453                    .into_iter()
1454                    .map(|o| {
1455                        o.applied_to(
1456                            id_gen,
1457                            col_map,
1458                            cte_map,
1459                            &mut get_inner,
1460                            subquery_map,
1461                            context,
1462                        )
1463                    })
1464                    .collect::<Result<Vec<_>, _>>()?;
1465
1466                // Record input arity here so that any group_keys that need to mutate get_inner
1467                // don't add those columns to the aggregate input.
1468                let input_type = get_inner.sql_typ();
1469                let input_arity = input_type.arity();
1470                // The reduction that computes the window function must be keyed on the columns
1471                // from the outer context, plus the expressions in the partition key. The current
1472                // subquery will be 'executed' for every distinct row from the outer context so
1473                // by putting the outer columns in the grouping key we isolate each re-execution.
1474                let mut group_key = col_map
1475                    .inner
1476                    .iter()
1477                    .map(|(_, outer_col)| *outer_col)
1478                    .sorted()
1479                    .collect_vec();
1480                for p in partition_by {
1481                    let key = p.applied_to(
1482                        id_gen,
1483                        col_map,
1484                        cte_map,
1485                        &mut get_inner,
1486                        subquery_map,
1487                        context,
1488                    )?;
1489                    if let MirScalarExpr::Column(c, _name) = key {
1490                        group_key.push(c);
1491                    } else {
1492                        get_inner = get_inner.map_one(key);
1493                        group_key.push(get_inner.arity() - 1);
1494                    }
1495                }
1496
1497                get_inner.let_in(id_gen, |id_gen, mut get_inner| {
1498                    // Original columns of the relation
1499                    let fields: Box<_> = input_type
1500                        .column_types
1501                        .iter()
1502                        .take(input_arity)
1503                        .map(|t| (ColumnName::from(UNKNOWN_COLUMN_NAME), t.clone()))
1504                        .collect();
1505
1506                    // Original row made into a record
1507                    let original_row_record = MirScalarExpr::call_variadic(
1508                        variadic::RecordCreate {
1509                            field_names: fields.iter().map(|(name, _)| name.clone()).collect_vec(),
1510                        },
1511                        (0..input_arity).map(MirScalarExpr::column).collect_vec(),
1512                    );
1513                    let original_row_record_type = SqlScalarType::Record {
1514                        fields,
1515                        custom_id: None,
1516                    };
1517
1518                    let (agg_input, agg_input_type) = lower_args(
1519                        id_gen,
1520                        col_map,
1521                        cte_map,
1522                        &mut get_inner,
1523                        subquery_map,
1524                        order_by_mir,
1525                        original_row_record,
1526                        original_row_record_type,
1527                    )?;
1528
1529                    let aggregate = mz_expr::AggregateExpr {
1530                        func: mir_aggr_func,
1531                        expr: agg_input,
1532                        distinct: false,
1533                    };
1534
1535                    // Actually call reduce with the window function
1536                    // The output of the aggregation function should be a list of tuples that has
1537                    // the result in the first position, and the original row in the second position
1538                    let mut reduce = get_inner
1539                        .reduce(group_key.clone(), vec![aggregate.clone()], None)
1540                        .flat_map(
1541                            mz_expr::TableFunc::UnnestList {
1542                                el_typ: aggregate
1543                                    .func
1544                                    .output_sql_type(agg_input_type)
1545                                    .scalar_type
1546                                    .unwrap_list_element_type()
1547                                    .clone(),
1548                            },
1549                            vec![MirScalarExpr::column(group_key.len())],
1550                        );
1551                    let record_col = reduce.arity() - 1;
1552
1553                    // Unpack the record output by the window function
1554                    for c in 0..input_arity {
1555                        reduce = reduce.take_dangerous().map_one(MirScalarExpr::CallUnary {
1556                            func: mz_expr::UnaryFunc::RecordGet(mz_expr::func::RecordGet(c)),
1557                            expr: Box::new(MirScalarExpr::CallUnary {
1558                                func: mz_expr::UnaryFunc::RecordGet(mz_expr::func::RecordGet(1)),
1559                                expr: Box::new(MirScalarExpr::column(record_col)),
1560                            }),
1561                        });
1562                    }
1563
1564                    // Append the column with the result of the window function.
1565                    reduce = reduce.take_dangerous().map_one(MirScalarExpr::CallUnary {
1566                        func: mz_expr::UnaryFunc::RecordGet(mz_expr::func::RecordGet(0)),
1567                        expr: Box::new(MirScalarExpr::column(record_col)),
1568                    });
1569
1570                    let agg_col = record_col + 1 + input_arity;
1571                    Ok::<_, PlanError>(reduce.project((record_col + 1..agg_col + 1).collect_vec()))
1572                })
1573            })?;
1574        Ok(MirScalarExpr::column(inner.arity() - 1))
1575    }
1576
1577    /// Applies the subqueries in the given list of scalar expressions to every distinct
1578    /// value of the given relation and returns a join of the given relation with all
1579    /// the subqueries found, and the mapping of scalar expressions with columns projected
1580    /// by the returned join that will hold their results.
1581    fn lower_subqueries(
1582        exprs: &[Self],
1583        id_gen: &mut mz_ore::id_gen::IdGen,
1584        col_map: &ColumnMap,
1585        cte_map: &mut CteMap,
1586        inner: MirRelationExpr,
1587        context: &Context,
1588    ) -> Result<(MirRelationExpr, BTreeMap<HirScalarExpr, usize>), PlanError> {
1589        let mut subquery_map = BTreeMap::new();
1590        let output = inner.let_in(id_gen, |id_gen, get_inner| {
1591            let mut subqueries = Vec::new();
1592            let distinct_inner = get_inner.clone().distinct();
1593            for expr in exprs.iter() {
1594                expr.visit_pre_post(
1595                    &mut |e| match e {
1596                        // For simplicity, subqueries within a conditional statement will be
1597                        // lowered when lowering the conditional expression.
1598                        HirScalarExpr::If { .. } => Some(vec![]),
1599                        _ => None,
1600                    },
1601                    &mut |e| match e {
1602                        HirScalarExpr::Select(expr, _name) => {
1603                            let apply_requires_distinct_outer = false;
1604                            let subquery = apply_scalar_subquery(
1605                                id_gen,
1606                                distinct_inner.clone(),
1607                                col_map,
1608                                cte_map,
1609                                (**expr).clone(),
1610                                apply_requires_distinct_outer,
1611                                context,
1612                            )
1613                            .unwrap();
1614
1615                            subqueries.push((e.clone(), subquery));
1616                        }
1617                        HirScalarExpr::Exists(expr, _name) => {
1618                            let apply_requires_distinct_outer = false;
1619                            let subquery = apply_existential_subquery(
1620                                id_gen,
1621                                distinct_inner.clone(),
1622                                col_map,
1623                                cte_map,
1624                                (**expr).clone(),
1625                                apply_requires_distinct_outer,
1626                                context,
1627                            )
1628                            .unwrap();
1629                            subqueries.push((e.clone(), subquery));
1630                        }
1631                        _ => {}
1632                    },
1633                );
1634            }
1635
1636            if subqueries.is_empty() {
1637                Ok::<MirRelationExpr, PlanError>(get_inner)
1638            } else {
1639                let inner_arity = get_inner.arity();
1640                let mut total_arity = inner_arity;
1641                let mut join_inputs = vec![get_inner];
1642                let mut join_input_arities = vec![inner_arity];
1643                for (expr, subquery) in subqueries.into_iter() {
1644                    // Avoid lowering duplicated subqueries
1645                    if !subquery_map.contains_key(&expr) {
1646                        let subquery_arity = subquery.arity();
1647                        assert_eq!(subquery_arity, inner_arity + 1);
1648                        join_inputs.push(subquery);
1649                        join_input_arities.push(subquery_arity);
1650                        total_arity += subquery_arity;
1651
1652                        // Column with the value of the subquery
1653                        subquery_map.insert(expr, total_arity - 1);
1654                    }
1655                }
1656                // Each subquery projects all the columns of the outer context (distinct_inner)
1657                // plus 1 column, containing the result of the subquery. Those columns must be
1658                // joined with the outer/main relation (get_inner).
1659                let input_mapper =
1660                    mz_expr::JoinInputMapper::new_from_input_arities(join_input_arities);
1661                let equivalences = (0..inner_arity)
1662                    .map(|col| {
1663                        join_inputs
1664                            .iter()
1665                            .enumerate()
1666                            .map(|(input, _)| {
1667                                MirScalarExpr::column(input_mapper.map_column_to_global(col, input))
1668                            })
1669                            .collect_vec()
1670                    })
1671                    .collect_vec();
1672                Ok(MirRelationExpr::join_scalars(join_inputs, equivalences))
1673            }
1674        })?;
1675        Ok((output, subquery_map))
1676    }
1677
1678    /// Rewrites `self` into a `mz_expr::ScalarExpr`.
1679    ///
1680    /// Returns an _internal_ error if the expression contains
1681    /// - a subquery
1682    /// - a column reference to an outer level
1683    /// - a parameter
1684    /// - a window function call
1685    ///
1686    /// Should succeed if [`HirScalarExpr::is_constant`] would return true on `self`.
1687    ///
1688    /// Set `enable_cast_elimination` to remove casts that are noops in MIR.
1689    pub fn lower_uncorrelated<C: Into<Config>>(
1690        self,
1691        config: C,
1692    ) -> Result<MirScalarExpr, PlanError> {
1693        let config = config.into();
1694
1695        use MirScalarExpr as SS;
1696
1697        use HirScalarExpr::*;
1698
1699        Ok(match self {
1700            Column(ColumnRef { level: 0, column }, name) => SS::Column(column, name),
1701            Literal(datum, typ, _name) => SS::Literal(Ok(datum), ReprColumnType::from(&typ)),
1702            CallUnmaterializable(func, _name) => SS::CallUnmaterializable(func),
1703            CallUnary {
1704                func,
1705                expr,
1706                name: _,
1707            } => {
1708                let inner = expr.lower_uncorrelated(config)?;
1709
1710                if config.enable_cast_elimination && func.is_eliminable_cast() {
1711                    inner
1712                } else {
1713                    SS::CallUnary {
1714                        func,
1715                        expr: Box::new(inner),
1716                    }
1717                }
1718            }
1719            CallBinary {
1720                func,
1721                expr1,
1722                expr2,
1723                name: _,
1724            } => SS::CallBinary {
1725                func,
1726                expr1: Box::new(expr1.lower_uncorrelated(config)?),
1727                expr2: Box::new(expr2.lower_uncorrelated(config)?),
1728            },
1729            CallVariadic {
1730                func,
1731                exprs,
1732                name: _,
1733            } => SS::call_variadic(
1734                func,
1735                exprs
1736                    .into_iter()
1737                    .map(|expr| expr.lower_uncorrelated(config))
1738                    .collect::<Result<_, _>>()?,
1739            ),
1740            If {
1741                cond,
1742                then,
1743                els,
1744                name: _,
1745            } => SS::If {
1746                cond: Box::new(cond.lower_uncorrelated(config)?),
1747                then: Box::new(then.lower_uncorrelated(config)?),
1748                els: Box::new(els.lower_uncorrelated(config)?),
1749            },
1750            Select { .. } | Exists { .. } | Parameter(..) | Column(..) | Windowing(..) => {
1751                sql_bail!(
1752                    "Internal error: unexpected HirScalarExpr in lower_uncorrelated: {:?}",
1753                    self
1754                );
1755            }
1756        })
1757    }
1758}
1759
1760/// Prepare to apply `inner` to `outer`. Note that `inner` is a correlated (SQL)
1761/// expression, while `outer` is a non-correlated (dataflow) expression. `inner`
1762/// will, in effect, be executed once for every distinct row in `outer`, and the
1763/// results will be joined with `outer`. Note that columns in `outer` that are
1764/// not depended upon by `inner` are thrown away before the distinct, so that we
1765/// don't perform needless computation of `inner`.
1766///
1767/// `branch` will inspect the contents of `inner` to determine whether `inner`
1768/// is not multiplicity sensitive (roughly, contains only maps, filters,
1769/// projections, and calls to table functions). If it is not multiplicity
1770/// sensitive, `branch` will *not* distinctify outer. If this is problematic,
1771/// e.g. because the `apply` callback itself introduces multiplicity-sensitive
1772/// operations that were not present in `inner`, then set
1773/// `apply_requires_distinct_outer` to ensure that `branch` chooses the plan
1774/// that distinctifies `outer`.
1775///
1776/// The caller must supply the `apply` function that applies the rewritten
1777/// `inner` to `outer`.
1778fn branch<F>(
1779    id_gen: &mut mz_ore::id_gen::IdGen,
1780    outer: MirRelationExpr,
1781    col_map: &ColumnMap,
1782    cte_map: &mut CteMap,
1783    inner: HirRelationExpr,
1784    apply_requires_distinct_outer: bool,
1785    context: &Context,
1786    apply: F,
1787) -> Result<MirRelationExpr, PlanError>
1788where
1789    F: FnOnce(
1790        &mut mz_ore::id_gen::IdGen,
1791        HirRelationExpr,
1792        MirRelationExpr,
1793        &ColumnMap,
1794        &mut CteMap,
1795        &Context,
1796    ) -> Result<MirRelationExpr, PlanError>,
1797{
1798    // TODO: It would be nice to have a version of this code w/o optimizations,
1799    // at the least for purposes of understanding. It was difficult for one reader
1800    // to understand the required properties of `outer` and `col_map`.
1801
1802    // If the inner expression is sufficiently simple, it is safe to apply it
1803    // *directly* to outer, rather than applying it to the distinctified key
1804    // (see below).
1805    //
1806    // As an example, consider the following two queries:
1807    //
1808    //     CREATE TABLE t (a int, b int);
1809    //     SELECT a, series FROM t, generate_series(1, t.b) series;
1810    //
1811    // The "simple" path for the `SELECT` yields
1812    //
1813    //     %0 =
1814    //     | Get t
1815    //     | FlatMap generate_series(1, #1)
1816    //
1817    // while the non-simple path yields:
1818    //
1819    //    %0 =
1820    //    | Get t
1821    //
1822    //    %1 =
1823    //    | Get t
1824    //    | Distinct group=(#1)
1825    //    | FlatMap generate_series(1, #0)
1826    //
1827    //    %2 =
1828    //    | LeftJoin %1 %2 (= #1 #2)
1829    //
1830    // There is a tradeoff here: the simple plan is stateless, but the non-
1831    // simple plan may do (much) less computation if there are only a few
1832    // distinct values of `t.b`.
1833    //
1834    // We apply a very simple heuristic here and take the simple path if `inner`
1835    // contains only maps, filters, projections, and calls to table functions.
1836    // The intuition is that straightforward usage of table functions should
1837    // take the simple path, while everything else should not. (In theory we
1838    // think this transformation is valid as long as `inner` does not contain a
1839    // Reduce, Distinct, or TopK node, but it is not always an optimization in
1840    // the general case.)
1841    //
1842    // TODO(benesch): this should all be handled by a proper optimizer, but
1843    // detecting the moment of decorrelation in the optimizer right now is too
1844    // hard.
1845    let mut is_simple = true;
1846    #[allow(deprecated)]
1847    inner.visit(0, &mut |expr, _| match expr {
1848        HirRelationExpr::Constant { .. }
1849        | HirRelationExpr::Project { .. }
1850        | HirRelationExpr::Map { .. }
1851        | HirRelationExpr::Filter { .. }
1852        | HirRelationExpr::CallTable { .. } => (),
1853        _ => is_simple = false,
1854    });
1855    if is_simple && !apply_requires_distinct_outer {
1856        let new_col_map = col_map.enter_scope(outer.arity() - col_map.len());
1857        return outer.let_in(id_gen, |id_gen, get_outer| {
1858            apply(id_gen, inner, get_outer, &new_col_map, cte_map, context)
1859        });
1860    }
1861
1862    // The key consists of the columns from the outer expression upon which the
1863    // inner relation depends. We discover these dependencies by walking the
1864    // inner relation expression and looking for column references whose level
1865    // escapes inner.
1866    //
1867    // At the end of this process, `key` contains the decorrelated position of
1868    // each outer column, according to the passed-in `col_map`, and
1869    // `new_col_map` maps each outer column to its new ordinal position in key.
1870    let mut outer_cols = BTreeSet::new();
1871    #[allow(deprecated)]
1872    inner.visit_columns(0, &mut |depth, col| {
1873        // Test if the column reference escapes the subquery.
1874        if col.level > depth {
1875            outer_cols.insert(ColumnRef {
1876                level: col.level - depth,
1877                column: col.column,
1878            });
1879        }
1880    });
1881    // Collect all the outer columns referenced by any CTE referenced by
1882    // the inner relation. The `Get` arm of `applied_to` reconciles a CTE
1883    // reference with the relation the CTE was applied to by equating the
1884    // first `cte_outer_arity` columns of both sides, so it relies on the
1885    // outer columns of every referenced CTE sitting at the beginning of the
1886    // branch key, in order. We track the maximum such arity to arrange for
1887    // that below.
1888    let mut max_cte_outer_arity = 0;
1889    {
1890        let mut visit = |e: &HirRelationExpr| match e {
1891            HirRelationExpr::Get {
1892                id: mz_expr::Id::Local(id),
1893                ..
1894            } => {
1895                if let Some(cte_desc) = cte_map.get(id) {
1896                    let cte_outer_arity = cte_desc.outer_relation.arity();
1897                    max_cte_outer_arity = max_cte_outer_arity.max(cte_outer_arity);
1898                    outer_cols.extend(
1899                        col_map
1900                            .inner
1901                            .iter()
1902                            .filter(|(_, position)| **position < cte_outer_arity)
1903                            .map(|(c, _)| {
1904                                // `col_map` maps column references to column positions in
1905                                // `outer`'s projection.
1906                                // `outer_cols` is meant to contain the external column
1907                                // references in `inner`.
1908                                // Since `inner` defines a new scope, any column reference
1909                                // in `col_map` is one level deeper when seen from within
1910                                // `inner`, hence the +1.
1911                                ColumnRef {
1912                                    level: c.level + 1,
1913                                    column: c.column,
1914                                }
1915                            }),
1916                    );
1917                }
1918            }
1919            HirRelationExpr::Let { id, .. } => {
1920                // Note: if ID uniqueness is not guaranteed, we can't use `visit` since
1921                // we would need to remove the old CTE with the same ID temporarily while
1922                // traversing the definition of the new CTE under the same ID.
1923                assert!(!cte_map.contains_key(id));
1924            }
1925            _ => {}
1926        };
1927        if context.config.enable_fixed_correlated_cte_lowering {
1928            // `visit_post` descends into scalar subqueries, so this finds CTE
1929            // references at any depth inside `inner`. Full depth matters: a
1930            // nested `branch()` inside `inner` relies on the key computed
1931            // here already carrying its referenced CTEs' outer columns as a
1932            // prefix.
1933            inner.visit_post(&mut visit);
1934        } else {
1935            // The deprecated `visit` does not descend into scalar subqueries,
1936            // so CTE references inside them are missed and the branch key can
1937            // lack outer columns that the CTE reference's reconciliation join
1938            // needs, producing wrong correlations (SQL-349).
1939            #[allow(deprecated)]
1940            inner.visit(0, &mut |e, _| visit(e));
1941        }
1942    }
1943    let mut new_col_map = BTreeMap::new();
1944    let mut key = vec![];
1945    if context.config.enable_fixed_correlated_cte_lowering {
1946        // The `Get` arm reconciles a CTE reference by equating key slots
1947        // `0..cte_outer_arity` with the CTE's outer columns, positionally, so
1948        // it needs every referenced CTE's outer columns at the head of the
1949        // key, in their original `outer` order. Those columns are exactly the
1950        // ones at `outer` positions `0..max_cte_outer_arity` (see the discovery
1951        // above), so we split the discovered columns into that prefix and the
1952        // rest, and emit the prefix first, sorted by position. That makes
1953        // `key[i] == i` over the prefix, which is what the `Get` arm assumes.
1954        //
1955        // A single prefix covers all referenced CTEs because their outer
1956        // relations are nested prefixes of one another (nested scopes only ever
1957        // append columns). The `rest`'s order is free: the final join in
1958        // `branch` compensates for any key order, and only the `Get` arm cares
1959        // about absolute slot positions (we keep `outer_cols`'s order).
1960        let mut prefix = Vec::new();
1961        let mut rest = Vec::new();
1962        for col in outer_cols {
1963            // `position` is `col`'s index in `outer`'s projection.
1964            let position = col_map.get(&ColumnRef {
1965                // `outer_cols` holds references as seen from within `inner`,
1966                // one level deeper than `outer`'s scope (the discovery walk and
1967                // `visit_columns` both record them that way), so undo that with
1968                // `-1` before looking `col` up in `outer`'s `col_map`.
1969                level: col.level - 1,
1970                column: col.column,
1971            });
1972            if position < max_cte_outer_arity {
1973                prefix.push((position, col));
1974            } else {
1975                rest.push((position, col));
1976            }
1977        }
1978        prefix.sort_unstable_by_key(|(position, _)| *position);
1979        // The discovery inserts every position `0..cte_outer_arity` of each
1980        // referenced CTE, and `col_map` is a bijection onto `0..col_map.len()`,
1981        // so the prefix positions are exactly `0..max_cte_outer_arity`, each
1982        // once, reading `0, 1, ..., max-1` after the sort. If they don't, the
1983        // `Get` arm's reconciliation would join on the wrong columns and
1984        // silently produce wrong results, so fail the query instead. There is
1985        // no safe fallback: the unpartitioned key order is the bug this is
1986        // fixing.
1987        if !prefix
1988            .iter()
1989            .map(|(position, _)| *position)
1990            .eq(0..max_cte_outer_arity)
1991        {
1992            return Err(PlanError::Internal(format!(
1993                "CTE outer columns are not a contiguous prefix of the branch key: \
1994                 prefix positions {:?}, max_cte_outer_arity {}",
1995                prefix
1996                    .iter()
1997                    .map(|(position, _)| *position)
1998                    .collect::<Vec<_>>(),
1999                max_cte_outer_arity,
2000            )));
2001        }
2002        // Give each chosen outer column its key slot: `new_col_map` records the
2003        // slot, `key` records which `outer` position feeds it. Over the prefix
2004        // the slot (`key.len()`) equals `position`, so `key[i] == i`; over the
2005        // rest they may differ, which is fine.
2006        for (position, col) in prefix.into_iter().chain(rest) {
2007            new_col_map.insert(col, key.len());
2008            key.push(position);
2009        }
2010    } else {
2011        // Note: this order can break the `Get` arm's prefix assumption when a
2012        // level-1 column sorts before a referenced CTE's outer column
2013        // (SQL-349).
2014        for col in outer_cols {
2015            new_col_map.insert(col, key.len());
2016            key.push(col_map.get(&ColumnRef {
2017                // Note: `outer_cols` contains the external column references within `inner`.
2018                // We must compensate for `inner`'s scope when translating column references
2019                // as seen within `inner` to column references as seen from `outer`'s context,
2020                // hence the -1.
2021                level: col.level - 1,
2022                column: col.column,
2023            }));
2024        }
2025    }
2026    let new_col_map = ColumnMap::new(new_col_map);
2027    outer.let_in(id_gen, |id_gen, get_outer| {
2028        let keyed_outer = if key.is_empty() {
2029            // Don't depend on outer at all if the branch is not correlated,
2030            // which yields vastly better query plans. Note that this is a bit
2031            // weird in that the branch will be computed even if outer has no
2032            // rows, whereas if it had been correlated it would not (and *could*
2033            // not) have been computed if outer had no rows, but the callers of
2034            // this function don't mind these somewhat-weird semantics.
2035            MirRelationExpr::constant(vec![vec![]], ReprRelationType::new(vec![]))
2036        } else {
2037            get_outer.clone().distinct_by(key.clone())
2038        };
2039        keyed_outer.let_in(id_gen, |id_gen, get_keyed_outer| {
2040            let oa = get_outer.arity();
2041            let branch = apply(
2042                id_gen,
2043                inner,
2044                get_keyed_outer,
2045                &new_col_map,
2046                cte_map,
2047                context,
2048            )?;
2049            let ba = branch.arity();
2050            let joined = MirRelationExpr::join(
2051                vec![get_outer.clone(), branch],
2052                key.iter()
2053                    .enumerate()
2054                    .map(|(i, &k)| vec![(0, k), (1, i)])
2055                    .collect(),
2056            )
2057            // throw away the right-hand copy of the key we just joined on
2058            .project((0..oa).chain((oa + key.len())..(oa + ba)).collect());
2059            Ok(joined)
2060        })
2061    })
2062}
2063
2064fn apply_scalar_subquery(
2065    id_gen: &mut mz_ore::id_gen::IdGen,
2066    outer: MirRelationExpr,
2067    col_map: &ColumnMap,
2068    cte_map: &mut CteMap,
2069    scalar_subquery: HirRelationExpr,
2070    apply_requires_distinct_outer: bool,
2071    context: &Context,
2072) -> Result<MirRelationExpr, PlanError> {
2073    branch(
2074        id_gen,
2075        outer,
2076        col_map,
2077        cte_map,
2078        scalar_subquery,
2079        apply_requires_distinct_outer,
2080        context,
2081        |id_gen, expr, get_inner, col_map, cte_map, context| {
2082            // compute for every row in get_inner
2083            let select = expr.applied_to(id_gen, get_inner.clone(), col_map, cte_map, context)?;
2084            let col_type = select.sql_typ().column_types.into_last();
2085
2086            let inner_arity = get_inner.arity();
2087            // We must determine a count for each `get_inner` prefix,
2088            // and report an error if that count exceeds one.
2089            let guarded = select.let_in(id_gen, |_id_gen, get_select| {
2090                // Count for each `get_inner` prefix.
2091                let counts = get_select.clone().reduce(
2092                    (0..inner_arity).collect::<Vec<_>>(),
2093                    vec![mz_expr::AggregateExpr {
2094                        func: mz_expr::AggregateFunc::Count,
2095                        expr: MirScalarExpr::literal_true(),
2096                        distinct: false,
2097                    }],
2098                    None,
2099                );
2100
2101                // Errors should result from counts > 1.
2102                let errors = counts
2103                    .flat_map(
2104                        mz_expr::TableFunc::GuardSubquerySize {
2105                            column_type: col_type.clone().scalar_type,
2106                        },
2107                        vec![MirScalarExpr::column(inner_arity)],
2108                    )
2109                    .project(
2110                        (0..inner_arity)
2111                            .chain(Some(inner_arity + 1))
2112                            .collect::<Vec<_>>(),
2113                    );
2114                // Return `get_select` and any errors added in.
2115                Ok::<_, PlanError>(get_select.union(errors))
2116            })?;
2117            // append Null to anything that didn't return any rows
2118            let default = vec![(Datum::Null, ReprScalarType::from(&col_type.scalar_type))];
2119            get_inner.lookup(id_gen, guarded, default)
2120        },
2121    )
2122}
2123
2124fn apply_existential_subquery(
2125    id_gen: &mut mz_ore::id_gen::IdGen,
2126    outer: MirRelationExpr,
2127    col_map: &ColumnMap,
2128    cte_map: &mut CteMap,
2129    subquery_expr: HirRelationExpr,
2130    apply_requires_distinct_outer: bool,
2131    context: &Context,
2132) -> Result<MirRelationExpr, PlanError> {
2133    branch(
2134        id_gen,
2135        outer,
2136        col_map,
2137        cte_map,
2138        subquery_expr,
2139        apply_requires_distinct_outer,
2140        context,
2141        |id_gen, expr, get_inner, col_map, cte_map, context| {
2142            let exists = expr
2143                // compute for every row in get_inner
2144                .applied_to(id_gen, get_inner.clone(), col_map, cte_map, context)?
2145                // throw away actual values and just remember whether or not there were __any__ rows
2146                .distinct_by((0..get_inner.arity()).collect())
2147                // Append true to anything that returned any rows.
2148                .map(vec![MirScalarExpr::literal_true()]);
2149
2150            // append False to anything that didn't return any rows
2151            get_inner.lookup(id_gen, exists, vec![(Datum::False, ReprScalarType::Bool)])
2152        },
2153    )
2154}
2155
2156impl AggregateExpr {
2157    fn applied_to(
2158        self,
2159        id_gen: &mut mz_ore::id_gen::IdGen,
2160        col_map: &ColumnMap,
2161        cte_map: &mut CteMap,
2162        inner: &mut MirRelationExpr,
2163        context: &Context,
2164    ) -> Result<mz_expr::AggregateExpr, PlanError> {
2165        let AggregateExpr {
2166            func,
2167            expr,
2168            distinct,
2169        } = self;
2170
2171        Ok(mz_expr::AggregateExpr {
2172            func: func.into_expr(),
2173            expr: expr.applied_to(id_gen, col_map, cte_map, inner, &None, context)?,
2174            distinct,
2175        })
2176    }
2177}
2178
2179/// Attempts an efficient outer join, if `on` has equijoin structure.
2180///
2181/// Both `left` and `right` are decorrelated inputs.
2182///
2183/// The first `oa` columns correspond to an outer context: we should do the
2184/// outer join independently for each prefix. In the case that `on` contains
2185/// just some equality tests between columns of `left` and `right` and some
2186/// local predicates, we can employ a relatively simple plan.
2187///
2188/// The last `on_subquery_types.len()` columns correspond to results from
2189/// subqueries defined in the `on` clause - we treat those as theta-join
2190/// conditions that prohibit the use of the simple plan attempted here.
2191fn attempt_outer_equijoin(
2192    left: MirRelationExpr,
2193    right: MirRelationExpr,
2194    on: MirScalarExpr,
2195    on_subquery_types: Vec<ReprColumnType>,
2196    kind: JoinKind,
2197    oa: usize,
2198    id_gen: &mut mz_ore::id_gen::IdGen,
2199    context: &Context,
2200) -> Result<Option<MirRelationExpr>, PlanError> {
2201    // TODO(database-issues#6827): In theory, we can be smarter and also handle `on`
2202    // predicates that reference subqueries as long as these subqueries don't
2203    // reference `left` and `right` at the same time.
2204    //
2205    // TODO(database-issues#6828): This code can be improved as follows:
2206    //
2207    // 1. Move the `canonicalize_predicates(...)` call to `applied_to`.
2208    // 2. Use the canonicalized `on` predicate in the non-equijoin based
2209    //    lowering strategy.
2210    // 3. Move the `OnPredicates::new(...)` call to `applied_to`.
2211    // 4. Pass the classified `OnPredicates` as a parameter.
2212    // 5. Guard calls of this function with `on_predicates.is_equijoin()`.
2213    //
2214    // Steps (1 + 2) require further investigation because we might change the
2215    // error semantics in case the `on` predicate contains a literal error..
2216
2217    let l_type = left.typ();
2218    let r_type = right.typ();
2219    let la = l_type.column_types.len() - oa;
2220    let ra = r_type.column_types.len() - oa;
2221    let sa = on_subquery_types.len();
2222
2223    // The output type contains [outer, left, right, sa] attributes.
2224    let mut output_type = Vec::with_capacity(oa + la + ra + sa);
2225    output_type.extend(l_type.column_types);
2226    output_type.extend(r_type.column_types.into_iter().skip(oa));
2227    output_type.extend(on_subquery_types);
2228
2229    // Generally healthy to do, but specifically `USING` conditions sometimes
2230    // put an `AND true` at the end of the `ON` condition.
2231    //
2232    // TODO(aalexandrov): maybe we should already be doing this in `applied_to`.
2233    // However, in that case it's not clear that we won't see regressions if
2234    // `on` simplifies to a literal error.
2235    let mut on = vec![on];
2236    mz_expr::canonicalize::canonicalize_predicates(&mut on, &output_type);
2237
2238    // Form the left and right types without the outer attributes.
2239    output_type.drain(0..oa);
2240    let lt = output_type.drain(0..la).collect_vec();
2241    let rt = output_type.drain(0..ra).collect_vec();
2242    assert!(output_type.len() == sa);
2243
2244    let on_predicates = OnPredicates::new(oa, la, ra, sa, on.clone(), context);
2245    if !on_predicates.is_equijoin(context) {
2246        return Ok(None);
2247    }
2248
2249    // If we've gotten this far, we can do the clever thing.
2250    // We'll want to use left and right multiple times
2251    let result = left.let_in(id_gen, |id_gen, get_left| {
2252        right.let_in(id_gen, |id_gen, get_right| {
2253            // TODO: we know that we can re-use the arrangements of left and right
2254            // needed for the inner join with each of the conditional outer joins.
2255            // It is not clear whether we should hint that, or just let the planner
2256            // and optimizer run and see what happens.
2257
2258            // We'll want the inner join (minus repeated columns)
2259            let join = MirRelationExpr::join(
2260                vec![get_left.clone(), get_right.clone()],
2261                (0..oa).map(|i| vec![(0, i), (1, i)]).collect(),
2262            )
2263            // remove those columns from `right` repeating the first `oa` columns.
2264            .project(
2265                (0..(oa + la))
2266                    .chain((oa + la + oa)..(oa + la + oa + ra))
2267                    .collect(),
2268            )
2269            // apply the filter constraints here, to ensure nulls are not matched.
2270            .filter(on);
2271
2272            // We'll want to re-use the results of the join multiple times.
2273            join.let_in(id_gen, |id_gen, get_join| {
2274                let mut result = get_join.clone();
2275
2276                // A collection of keys present in both left and right collections.
2277                let join_keys = on_predicates.join_keys();
2278                let both_keys_arity = join_keys.len();
2279                let both_keys = get_join.restrict(join_keys).distinct();
2280
2281                // The plan is now to determine the left and right rows matched in the
2282                // inner join, subtract them from left and right respectively, pad what
2283                // remains with nulls, and fold them in to `result`.
2284
2285                both_keys.let_in(id_gen, |_id_gen, get_both| {
2286                    if let JoinKind::LeftOuter { .. } | JoinKind::FullOuter = kind {
2287                        // Rows in `left` matched in the inner equijoin. This is
2288                        // a semi-join between `left` and `both_keys`.
2289                        let left_present = MirRelationExpr::join_scalars(
2290                            vec![
2291                                get_left
2292                                    .clone()
2293                                    // Push local predicates.
2294                                    .filter(on_predicates.lhs()),
2295                                get_both.clone(),
2296                            ],
2297                            itertools::zip_eq(
2298                                on_predicates.eq_lhs(),
2299                                (0..both_keys_arity).map(|k| MirScalarExpr::column(oa + la + k)),
2300                            )
2301                            .map(|(l_key, b_key)| [l_key, b_key].to_vec())
2302                            .collect(),
2303                        )
2304                        .project((0..(oa + la)).collect());
2305
2306                        // Determine the types of nulls to use as filler.
2307                        let right_fill = rt
2308                            .into_iter()
2309                            .map(|typ| MirScalarExpr::literal_null(typ.scalar_type))
2310                            .collect();
2311                        // Add to `result` absent elements, filled with typed nulls.
2312                        result = left_present
2313                            .negate()
2314                            .union(get_left.clone())
2315                            .map(right_fill)
2316                            .union(result);
2317                    }
2318
2319                    if let JoinKind::RightOuter | JoinKind::FullOuter = kind {
2320                        // Rows in `right` matched in the inner equijoin. This
2321                        // is a semi-join between `right` and `both_keys`.
2322                        let right_present = MirRelationExpr::join_scalars(
2323                            vec![
2324                                get_right
2325                                    .clone()
2326                                    // Push local predicates.
2327                                    .filter(on_predicates.rhs()),
2328                                get_both,
2329                            ],
2330                            itertools::zip_eq(
2331                                on_predicates.eq_rhs(),
2332                                (0..both_keys_arity).map(|k| MirScalarExpr::column(oa + ra + k)),
2333                            )
2334                            .map(|(r_key, b_key)| [r_key, b_key].to_vec())
2335                            .collect(),
2336                        )
2337                        .project((0..(oa + ra)).collect());
2338
2339                        // Determine the types of nulls to use as filler.
2340                        let left_fill = lt
2341                            .into_iter()
2342                            .map(|typ| MirScalarExpr::literal_null(typ.scalar_type))
2343                            .collect();
2344
2345                        // Add to `result` absent elements, prepended with typed nulls.
2346                        result = right_present
2347                            .negate()
2348                            .union(get_right.clone())
2349                            .map(left_fill)
2350                            // Permute left fill before right values.
2351                            .project(
2352                                itertools::chain!(
2353                                    0..oa,                 // Preserve `outer`.
2354                                    oa + ra..oa + la + ra, // Increment the next `la` cols by `ra`.
2355                                    oa..oa + ra            // Decrement the next `ra` cols by `la`.
2356                                )
2357                                .collect(),
2358                            )
2359                            .union(result)
2360                    }
2361
2362                    Ok::<_, PlanError>(result)
2363                })
2364            })
2365        })
2366    })?;
2367    Ok(Some(result))
2368}
2369
2370/// A struct that represents the predicates in the `on` clause in a form
2371/// suitable for efficient planning outer joins with equijoin predicates.
2372struct OnPredicates {
2373    /// A store for classified `ON` predicates.
2374    ///
2375    /// Predicates that reference a single side are adjusted to assume an
2376    /// `outer × <side>` schema.
2377    predicates: Vec<OnPredicate>,
2378    /// Number of outer context columns.
2379    oa: usize,
2380}
2381
2382impl OnPredicates {
2383    const I_OUT: usize = 0; // outer context input position
2384    const I_LHS: usize = 1; // lhs input position
2385    const I_RHS: usize = 2; // rhs input position
2386    const I_SUB: usize = 3; // on subqueries input position
2387
2388    /// Classify the predicates in the `on` clause of an outer join.
2389    ///
2390    /// The other parameters are arities of the input parts:
2391    ///
2392    /// - `oa` is the arity of the `outer` context.
2393    /// - `la` is the arity of the `left` input.
2394    /// - `ra` is the arity of the `right` input.
2395    /// - `sa` is the arity of the `on` subqueries.
2396    ///
2397    /// The constructor assumes that:
2398    ///
2399    /// 1. The `on` parameter will be applied on a result that has the following
2400    ///    schema `outer × left × right × on_subqueries`.
2401    /// 2. The `on` parameter is already adjusted to assume that schema.
2402    /// 3. The `on` parameter is obtained by canonicalizing the original `on:
2403    ///    MirScalarExpr` with `canonicalize_predicates`.
2404    fn new(
2405        oa: usize,
2406        la: usize,
2407        ra: usize,
2408        sa: usize,
2409        on: Vec<MirScalarExpr>,
2410        _context: &Context,
2411    ) -> Self {
2412        use mz_expr::BinaryFunc::Eq;
2413
2414        // Re-bind those locally for more compact pattern matching.
2415        const I_LHS: usize = OnPredicates::I_LHS;
2416        const I_RHS: usize = OnPredicates::I_RHS;
2417
2418        // Self parameters.
2419        let mut predicates = Vec::with_capacity(on.len());
2420
2421        // Helpers for populating `predicates`.
2422        let inner_join_mapper = mz_expr::JoinInputMapper::new_from_input_arities([oa, la, ra, sa]);
2423        let rhs_permutation = itertools::chain!(0..oa + la, oa..oa + ra).collect::<Vec<_>>();
2424        let lookup_inputs = |expr: &MirScalarExpr| -> Vec<usize> {
2425            inner_join_mapper
2426                .lookup_inputs(expr)
2427                .filter(|&i| i != Self::I_OUT)
2428                .collect()
2429        };
2430        let has_subquery_refs = |expr: &MirScalarExpr| -> bool {
2431            inner_join_mapper
2432                .lookup_inputs(expr)
2433                .any(|i| i == Self::I_SUB)
2434        };
2435
2436        // Iterate over `on` elements and populate `predicates`.
2437        for mut predicate in on {
2438            if predicate.might_error() {
2439                tracing::debug!(case = "thetajoin (error)", "OnPredicates::new");
2440                // Treat predicates that can produce a literal error as Theta.
2441                predicates.push(OnPredicate::Theta(predicate));
2442            } else if has_subquery_refs(&predicate) {
2443                tracing::debug!(case = "thetajoin (subquery)", "OnPredicates::new");
2444                // Treat predicates referencing an `on` subquery as Theta.
2445                predicates.push(OnPredicate::Theta(predicate));
2446            } else if let MirScalarExpr::CallBinary {
2447                func: Eq(_),
2448                expr1,
2449                expr2,
2450            } = &mut predicate
2451            {
2452                // Obtain the non-outer inputs referenced by each side.
2453                let inputs1 = lookup_inputs(expr1);
2454                let inputs2 = lookup_inputs(expr2);
2455
2456                match (&inputs1[..], &inputs2[..]) {
2457                    // Neither side references an input. This could be a
2458                    // constant expression or an expression that depends only on
2459                    // the outer context.
2460                    ([], []) => {
2461                        predicates.push(OnPredicate::Const(predicate));
2462                    }
2463                    // Both sides reference different inputs.
2464                    ([I_LHS], [I_RHS]) => {
2465                        let lhs = expr1.take();
2466                        let mut rhs = expr2.take();
2467                        rhs.permute(&rhs_permutation);
2468                        predicates.push(OnPredicate::Eq(lhs.clone(), rhs.clone()));
2469                        predicates.push(OnPredicate::LhsConsequence(lhs.call_is_null().not()));
2470                        predicates.push(OnPredicate::RhsConsequence(rhs.call_is_null().not()));
2471                    }
2472                    // Both sides reference different inputs (swapped).
2473                    ([I_RHS], [I_LHS]) => {
2474                        let lhs = expr2.take();
2475                        let mut rhs = expr1.take();
2476                        rhs.permute(&rhs_permutation);
2477                        predicates.push(OnPredicate::Eq(lhs.clone(), rhs.clone()));
2478                        predicates.push(OnPredicate::LhsConsequence(lhs.call_is_null().not()));
2479                        predicates.push(OnPredicate::RhsConsequence(rhs.call_is_null().not()));
2480                    }
2481                    // Both sides reference the left input or no input.
2482                    ([I_LHS], [I_LHS]) | ([I_LHS], []) | ([], [I_LHS]) => {
2483                        predicates.push(OnPredicate::Lhs(predicate));
2484                    }
2485                    // Both sides reference the right input or no input.
2486                    ([I_RHS], [I_RHS]) | ([I_RHS], []) | ([], [I_RHS]) => {
2487                        predicate.permute(&rhs_permutation);
2488                        predicates.push(OnPredicate::Rhs(predicate));
2489                    }
2490                    // At least one side references more than one input.
2491                    _ => {
2492                        tracing::debug!(case = "thetajoin (eq)", "OnPredicates::new");
2493                        predicates.push(OnPredicate::Theta(predicate));
2494                    }
2495                }
2496            } else {
2497                // Obtain the non-outer inputs referenced by this predicate.
2498                let inputs = lookup_inputs(&predicate);
2499
2500                match &inputs[..] {
2501                    // The predicate references no inputs. This could be a
2502                    // constant expression or an expression that depends only on
2503                    // the outer context.
2504                    [] => {
2505                        predicates.push(OnPredicate::Const(predicate));
2506                    }
2507                    // The predicate references only the left input.
2508                    [I_LHS] => {
2509                        predicates.push(OnPredicate::Lhs(predicate));
2510                    }
2511                    // The predicate references only the right input.
2512                    [I_RHS] => {
2513                        predicate.permute(&rhs_permutation);
2514                        predicates.push(OnPredicate::Rhs(predicate));
2515                    }
2516                    // The predicate references both inputs.
2517                    _ => {
2518                        tracing::debug!(case = "thetajoin (non-eq)", "OnPredicates::new");
2519                        predicates.push(OnPredicate::Theta(predicate));
2520                    }
2521                }
2522            }
2523        }
2524
2525        Self { predicates, oa }
2526    }
2527
2528    /// Check if the predicates can be lowered with an equijoin-based strategy.
2529    fn is_equijoin(&self, context: &Context) -> bool {
2530        // Count each `OnPredicate` variant in `self.predicates`.
2531        let (const_cnt, lhs_cnt, rhs_cnt, eq_cnt, eq_cols, theta_cnt) =
2532            self.predicates.iter().fold(
2533                (0, 0, 0, 0, 0, 0),
2534                |(const_cnt, lhs_cnt, rhs_cnt, eq_cnt, eq_cols, theta_cnt), p| {
2535                    (
2536                        const_cnt + usize::from(matches!(p, OnPredicate::Const(..))),
2537                        lhs_cnt + usize::from(matches!(p, OnPredicate::Lhs(..))),
2538                        rhs_cnt + usize::from(matches!(p, OnPredicate::Rhs(..))),
2539                        eq_cnt + usize::from(matches!(p, OnPredicate::Eq(..))),
2540                        eq_cols
2541                            + usize::from(matches!(
2542                                p,
2543                                OnPredicate::Eq(lhs, rhs) if lhs.is_column() && rhs.is_column()
2544                            )),
2545                        theta_cnt + usize::from(matches!(p, OnPredicate::Theta(..))),
2546                    )
2547                },
2548            );
2549
2550        let is_equijion = if context.config.enable_new_outer_join_lowering {
2551            // New classifier.
2552            eq_cnt > 0 && theta_cnt == 0
2553        } else {
2554            // Old classifier.
2555            eq_cnt > 0 && eq_cnt == eq_cols && theta_cnt + const_cnt + lhs_cnt + rhs_cnt == 0
2556        };
2557
2558        // Log an entry only if this is an equijoin according to the new classifier.
2559        if eq_cnt > 0 && theta_cnt == 0 {
2560            tracing::debug!(
2561                const_cnt,
2562                lhs_cnt,
2563                rhs_cnt,
2564                eq_cnt,
2565                eq_cols,
2566                theta_cnt,
2567                "OnPredicates::is_equijoin"
2568            );
2569        }
2570
2571        is_equijion
2572    }
2573
2574    /// Return an [`MirRelationExpr`] list that represents the keys for the
2575    /// equijoin. The list will contain the outer columns as a prefix.
2576    fn join_keys(&self) -> JoinKeys {
2577        // We could return either the `lhs` or the `rhs` of the keys used to
2578        // form the inner join as they are equated by the join condition.
2579        let join_keys = self.eq_lhs().collect::<Vec<_>>();
2580
2581        if join_keys.iter().all(|k| k.is_column()) {
2582            tracing::debug!(case = "outputs", "OnPredicates::join_keys");
2583            JoinKeys::Outputs(join_keys.iter().flat_map(|k| k.as_column()).collect())
2584        } else {
2585            tracing::debug!(case = "scalars", "OnPredicates::join_keys");
2586            JoinKeys::Scalars(join_keys)
2587        }
2588    }
2589
2590    /// Return an iterator over the left-hand sides of all [`OnPredicate::Eq`]
2591    /// conditions in the predicates list.
2592    ///
2593    /// The iterator will start with column references to the outer columns as a
2594    /// prefix.
2595    fn eq_lhs(&self) -> impl Iterator<Item = MirScalarExpr> + '_ {
2596        itertools::chain(
2597            (0..self.oa).map(MirScalarExpr::column),
2598            self.predicates.iter().filter_map(|e| match e {
2599                OnPredicate::Eq(lhs, _) => Some(lhs.clone()),
2600                _ => None,
2601            }),
2602        )
2603    }
2604
2605    /// Return an iterator over the right-hand sides of all [`OnPredicate::Eq`]
2606    /// conditions in the predicates list.
2607    ///
2608    /// The iterator will start with column references to the outer columns as a
2609    /// prefix.
2610    fn eq_rhs(&self) -> impl Iterator<Item = MirScalarExpr> + '_ {
2611        itertools::chain(
2612            (0..self.oa).map(MirScalarExpr::column),
2613            self.predicates.iter().filter_map(|e| match e {
2614                OnPredicate::Eq(_, rhs) => Some(rhs.clone()),
2615                _ => None,
2616            }),
2617        )
2618    }
2619
2620    /// Return an iterator over the [`OnPredicate::Lhs`], [`OnPredicate::LhsConsequence`] and
2621    /// [`OnPredicate::Const`] conditions in the predicates list.
2622    fn lhs(&self) -> impl Iterator<Item = MirScalarExpr> + '_ {
2623        self.predicates.iter().filter_map(|p| match p {
2624            // We treat Const predicates local to both inputs.
2625            OnPredicate::Const(p) => Some(p.clone()),
2626            OnPredicate::Lhs(p) => Some(p.clone()),
2627            OnPredicate::LhsConsequence(p) => Some(p.clone()),
2628            _ => None,
2629        })
2630    }
2631
2632    /// Return an iterator over the [`OnPredicate::Rhs`], [`OnPredicate::RhsConsequence`] and
2633    /// [`OnPredicate::Const`] conditions in the predicates list.
2634    fn rhs(&self) -> impl Iterator<Item = MirScalarExpr> + '_ {
2635        self.predicates.iter().filter_map(|p| match p {
2636            // We treat Const predicates local to both inputs.
2637            OnPredicate::Const(p) => Some(p.clone()),
2638            OnPredicate::Rhs(p) => Some(p.clone()),
2639            OnPredicate::RhsConsequence(p) => Some(p.clone()),
2640            _ => None,
2641        })
2642    }
2643}
2644
2645enum OnPredicate {
2646    // A predicate that is either constant or references only outer columns.
2647    Const(MirScalarExpr),
2648    // A local predicate on the left-hand side of the join, i.e., it references only the left input
2649    // and possibly outer columns.
2650    //
2651    // This is one of the original predicates from the ON clause.
2652    //
2653    // One _must_ apply this predicate.
2654    Lhs(MirScalarExpr),
2655    // A local predicate on the left-hand side of the join, i.e., it references only the left input
2656    // and possibly outer columns.
2657    //
2658    // This is not one of the original predicates from the ON clause, but is just a consequence
2659    // of an original predicate in the ON clause, where the original predicate references both
2660    // inputs, but the consequence references only the left input.
2661    //
2662    // For example, the original predicate `input1.x = input2.a` has the consequence
2663    // `input1.x IS NOT NULL`. Applying such a consequence before the input is fed into the join
2664    // prevents null skew, and also makes more CSE opportunities available when the left input's key
2665    // doesn't have a NOT NULL constraint, saving us an arrangement.
2666    //
2667    // Applying the predicate is optional, because the original predicate will be applied anyway.
2668    LhsConsequence(MirScalarExpr),
2669    // A local predicate on the right-hand side of the join.
2670    //
2671    // This is one of the original predicates from the ON clause.
2672    //
2673    // One _must_ apply this predicate.
2674    Rhs(MirScalarExpr),
2675    // A consequence of an original ON predicate, see above.
2676    RhsConsequence(MirScalarExpr),
2677    // An equality predicate between the two sides.
2678    Eq(MirScalarExpr, MirScalarExpr),
2679    // a non-equality predicate between the two sides.
2680    #[allow(dead_code)]
2681    Theta(MirScalarExpr),
2682}
2683
2684/// A set of join keys referencing an input.
2685///
2686/// This is used in the [`MirRelationExpr::Join`] lowering code in order to
2687/// avoid changes (and thereby possible regressions) in plans that have equijoin
2688/// predicates consisting only of column refs.
2689///
2690/// If we were running `CanonicalizeMfp` as part of `NormalizeOps` we might be
2691/// able to get rid of this code, but as it stands `Map` simplification seems
2692/// more cumbersome than `Project` simplification, so do this just to be sure.
2693enum JoinKeys {
2694    // A predicate that is either constant or references only outer columns.
2695    Outputs(Vec<usize>),
2696    // A local predicate on the left-hand side of the join.
2697    Scalars(Vec<MirScalarExpr>),
2698}
2699
2700impl JoinKeys {
2701    fn len(&self) -> usize {
2702        match self {
2703            JoinKeys::Outputs(outputs) => outputs.len(),
2704            JoinKeys::Scalars(scalars) => scalars.len(),
2705        }
2706    }
2707}
2708
2709/// Extension methods for [`MirRelationExpr`] required in the HIR ⇒ MIR lowering
2710/// code.
2711trait LoweringExt {
2712    /// See [`MirRelationExpr::restrict`].
2713    fn restrict(self, join_keys: JoinKeys) -> Self;
2714}
2715
2716impl LoweringExt for MirRelationExpr {
2717    /// Restrict the set of columns of an input to the sequence of [`JoinKeys`].
2718    fn restrict(self, join_keys: JoinKeys) -> Self {
2719        let num_keys = join_keys.len();
2720        match join_keys {
2721            JoinKeys::Outputs(outputs) => self.project(outputs),
2722            JoinKeys::Scalars(scalars) => {
2723                let input_arity = self.arity();
2724                let outputs = (input_arity..input_arity + num_keys).collect();
2725                self.map(scalars).project(outputs)
2726            }
2727        }
2728    }
2729}