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}