Skip to main content

mz_sql/plan/
transform_ast.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//! Transformations of SQL ASTs.
11//!
12//! Most query optimizations are performed by the dataflow layer, but some
13//! are much easier to perform in SQL. Someday, we'll want our own SQL IR,
14//! but for now we just use the parser's AST directly.
15
16use itertools::Itertools;
17use mz_ore::id_gen::IdGen;
18use mz_ore::stack::{CheckedRecursion, RecursionGuard};
19use mz_repr::namespaces::{MZ_CATALOG_SCHEMA, MZ_UNSAFE_SCHEMA, PG_CATALOG_SCHEMA};
20use mz_sql_parser::ast::visit_mut::{self, VisitMut, VisitMutNode};
21use mz_sql_parser::ast::{
22    Expr, Function, FunctionArgs, HomogenizingFunction, Ident, IsExprConstruct, Op, OrderByExpr,
23    Query, Select, SelectItem, TableAlias, TableFactor, TableWithJoins, Value, WindowSpec,
24};
25use mz_sql_parser::ident;
26
27use crate::names::{Aug, PartialItemName, ResolvedDataType, ResolvedItemName};
28use crate::plan::{PlanError, StatementContext};
29use crate::session::vars::ENABLE_ANY_ALL_NULL_ARRAY_SEMANTICS;
30use crate::{ORDINALITY_COL_NAME, normalize};
31
32pub(crate) fn transform<N>(scx: &StatementContext, node: &mut N) -> Result<(), PlanError>
33where
34    N: for<'a> VisitMutNode<'a, Aug>,
35{
36    let mut func_rewriter = FuncRewriter::new(scx);
37    node.visit_mut(&mut func_rewriter);
38    func_rewriter.status?;
39
40    let mut desugarer = Desugarer::new(scx);
41    node.visit_mut(&mut desugarer);
42    desugarer.status
43}
44
45// Transforms various functions to forms that are more easily handled by the
46// planner.
47//
48// Specifically:
49//
50//   * Rewrites the `mod` function to the `%` binary operator, so the modulus
51//     code only needs to handle the operator form.
52//
53//   * Rewrites the `nullif` function to a `CASE` statement, to reuse the code
54//     for planning equality of datums.
55//
56//   * Rewrites `avg(col)` to `sum(col) / count(col)`, so that we can pretend
57//     the `avg` aggregate function doesn't exist from here on out. This also
58//     has the nice side effect of reusing the division planning logic, which
59//     is not trivial for some types, like decimals.
60//
61//   * Rewrites the suite of standard deviation and variance functions in a
62//     manner similar to `avg`.
63//
64// TODO(sploiselle): rewrite these in terms of func::sql_op!
65struct FuncRewriter<'a> {
66    scx: &'a StatementContext<'a>,
67    status: Result<(), PlanError>,
68    rewriting_table_factor: bool,
69}
70
71impl<'a> FuncRewriter<'a> {
72    fn new(scx: &'a StatementContext<'a>) -> FuncRewriter<'a> {
73        FuncRewriter {
74            scx,
75            status: Ok(()),
76            rewriting_table_factor: false,
77        }
78    }
79
80    fn resolve_known_valid_data_type(&self, name: &PartialItemName) -> ResolvedDataType {
81        let item = self
82            .scx
83            .catalog
84            .resolve_type(name)
85            .expect("data type known to be valid");
86        let full_name = self.scx.catalog.resolve_full_name(item.name());
87        ResolvedDataType::Named {
88            id: item.id(),
89            qualifiers: item.name().qualifiers.clone(),
90            full_name,
91            modifiers: vec![],
92            print_id: true,
93        }
94    }
95
96    fn int32_data_type(&self) -> ResolvedDataType {
97        self.resolve_known_valid_data_type(&PartialItemName {
98            database: None,
99            schema: Some(PG_CATALOG_SCHEMA.into()),
100            item: "int4".into(),
101        })
102    }
103
104    // Divides `lhs` by `rhs` but replaces division-by-zero errors with NULL;
105    // note that this is semantically equivalent to `NULLIF(rhs, 0)`.
106    fn plan_divide(lhs: Expr<Aug>, rhs: Expr<Aug>) -> Expr<Aug> {
107        lhs.divide(Expr::Case {
108            operand: None,
109            conditions: vec![rhs.clone().equals(Expr::number("0"))],
110            results: vec![Expr::null()],
111            else_result: Some(Box::new(rhs)),
112        })
113    }
114
115    fn plan_agg(
116        &mut self,
117        name: ResolvedItemName,
118        expr: Expr<Aug>,
119        order_by: Vec<OrderByExpr<Aug>>,
120        filter: Option<Box<Expr<Aug>>>,
121        distinct: bool,
122        over: Option<WindowSpec<Aug>>,
123    ) -> Expr<Aug> {
124        if self.rewriting_table_factor && self.status.is_ok() {
125            self.status = Err(PlanError::Unstructured(
126                "aggregate functions are not supported in functions in FROM".to_string(),
127            ))
128        }
129        Expr::Function(Function {
130            name,
131            args: FunctionArgs::Args {
132                args: vec![expr],
133                order_by,
134            },
135            filter,
136            over,
137            distinct,
138        })
139    }
140
141    fn plan_avg(
142        &mut self,
143        expr: Expr<Aug>,
144        filter: Option<Box<Expr<Aug>>>,
145        distinct: bool,
146        over: Option<WindowSpec<Aug>>,
147    ) -> Expr<Aug> {
148        let sum = self
149            .plan_agg(
150                self.scx
151                    .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "sum"]),
152                expr.clone(),
153                vec![],
154                filter.clone(),
155                distinct,
156                over.clone(),
157            )
158            .call_unary(
159                self.scx
160                    .dangerous_resolve_name(vec![MZ_UNSAFE_SCHEMA, "mz_avg_promotion"]),
161            );
162        let count = self.plan_agg(
163            self.scx
164                .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "count"]),
165            expr,
166            vec![],
167            filter,
168            distinct,
169            over,
170        );
171        Self::plan_divide(sum, count)
172    }
173
174    /// Same as `plan_avg` but internally uses `mz_avg_promotion_internal_v1`.
175    fn plan_avg_internal_v1(
176        &mut self,
177        expr: Expr<Aug>,
178        filter: Option<Box<Expr<Aug>>>,
179        distinct: bool,
180        over: Option<WindowSpec<Aug>>,
181    ) -> Expr<Aug> {
182        let sum = self
183            .plan_agg(
184                self.scx
185                    .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "sum"]),
186                expr.clone(),
187                vec![],
188                filter.clone(),
189                distinct,
190                over.clone(),
191            )
192            .call_unary(
193                self.scx
194                    .dangerous_resolve_name(vec![MZ_UNSAFE_SCHEMA, "mz_avg_promotion_internal_v1"]),
195            );
196        let count = self.plan_agg(
197            self.scx
198                .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "count"]),
199            expr,
200            vec![],
201            filter,
202            distinct,
203            over,
204        );
205        Self::plan_divide(sum, count)
206    }
207
208    fn plan_variance(
209        &mut self,
210        expr: Expr<Aug>,
211        filter: Option<Box<Expr<Aug>>>,
212        distinct: bool,
213        sample: bool,
214        over: Option<WindowSpec<Aug>>,
215    ) -> Expr<Aug> {
216        // N.B. this variance calculation uses the "textbook" algorithm, which
217        // is known to accumulate problematic amounts of error. The numerically
218        // stable variants, the most well-known of which is Welford's, are
219        // however difficult to implement inside of Differential Dataflow, as
220        // they do not obviously support retractions efficiently (database-issues#436).
221        //
222        // The code below converts var_samp(x) into
223        //
224        //     (sum(x²) - sum(x)² / count(x)) / (count(x) - 1)
225        //
226        // and var_pop(x) into:
227        //
228        //     (sum(x²) - sum(x)² / count(x)) / count(x)
229        //
230        let expr = expr.call_unary(
231            self.scx
232                .dangerous_resolve_name(vec![MZ_UNSAFE_SCHEMA, "mz_avg_promotion"]),
233        );
234        let expr_squared = expr.clone().multiply(expr.clone());
235        let sum_squares = if distinct {
236            // With DISTINCT, all three component aggregates must deduplicate
237            // on the values of x, not on the values of their own inputs.
238            // sum(DISTINCT x) and count(DISTINCT x) do so naturally, but
239            // sum(DISTINCT x²) deduplicates on x², wrongly collapsing values
240            // that differ only in sign, e.g. -2 and 2. Squaring is injective
241            // on the non-negative values and, separately, on the negative
242            // values, so summing the two sign classes independently makes
243            // deduplication on x² agree with deduplication on x:
244            //
245            //     sum(DISTINCT CASE WHEN x >= 0 THEN x² END)
246            //       + sum(DISTINCT CASE WHEN x < 0 THEN x² END)
247            //
248            // Either sum is NULL when its sign class is empty, so the two are
249            // combined with COALESCE(..., 0). When there are no input rows at
250            // all this yields 0 instead of NULL, but the overall result is
251            // still NULL then because sum(DISTINCT x) below is NULL.
252            let case_squared = |condition| Expr::Case {
253                operand: None,
254                conditions: vec![condition],
255                results: vec![expr_squared.clone()],
256                else_result: None,
257            };
258            let sum_squares_nonneg = self.plan_agg(
259                self.scx
260                    .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "sum"]),
261                case_squared(expr.clone().gt_eq(Expr::number("0"))),
262                vec![],
263                filter.clone(),
264                distinct,
265                over.clone(),
266            );
267            let sum_squares_neg = self.plan_agg(
268                self.scx
269                    .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "sum"]),
270                case_squared(expr.clone().lt(Expr::number("0"))),
271                vec![],
272                filter.clone(),
273                distinct,
274                over.clone(),
275            );
276            let coalesce_zero = |sum| Expr::HomogenizingFunction {
277                function: HomogenizingFunction::Coalesce,
278                exprs: vec![sum, Expr::number("0")],
279            };
280            coalesce_zero(sum_squares_nonneg).binop(Op::bare("+"), coalesce_zero(sum_squares_neg))
281        } else {
282            self.plan_agg(
283                self.scx
284                    .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "sum"]),
285                expr_squared,
286                vec![],
287                filter.clone(),
288                distinct,
289                over.clone(),
290            )
291        };
292        let sum = self.plan_agg(
293            self.scx
294                .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "sum"]),
295            expr.clone(),
296            vec![],
297            filter.clone(),
298            distinct,
299            over.clone(),
300        );
301        let sum_squared = sum.clone().multiply(sum);
302        let count = self.plan_agg(
303            self.scx
304                .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "count"]),
305            expr,
306            vec![],
307            filter,
308            distinct,
309            over,
310        );
311        let result = Self::plan_divide(
312            sum_squares.minus(Self::plan_divide(sum_squared, count.clone())),
313            if sample {
314                count.minus(Expr::number("1"))
315            } else {
316                count
317            },
318        );
319        // Result is _basically_ what we want, except
320        // that due to numerical inaccuracy, it might be a negative
321        // number very close to zero when it should mathematically be zero.
322        // This makes it so `stddev` fails as it tries to take the square root
323        // of a negative number.
324        // So, we need the following logic:
325        // If `result` is NULL, return NULL (no surprise here)
326        // Otherwise, if `result` is >0, return `result` (no surprise here either)
327        // Otherwise, return 0.
328        //
329        // Unfortunately, we can't use `GREATEST` directly for this,
330        // since `greatest(NULL, 0)` is 0, not NULL, so we need to
331        // create a `Case` expression that computes `result`
332        // twice. Hopefully the optimizer can deal with this!
333        let result_is_null = Expr::IsExpr {
334            expr: Box::new(result.clone()),
335            construct: IsExprConstruct::Null,
336            negated: false,
337        };
338        Expr::Case {
339            operand: None,
340            conditions: vec![result_is_null],
341            results: vec![Expr::Value(Value::Null)],
342            else_result: Some(Box::new(Expr::HomogenizingFunction {
343                function: HomogenizingFunction::Greatest,
344                exprs: vec![result, Expr::number("0")],
345            })),
346        }
347    }
348
349    fn plan_stddev(
350        &mut self,
351        expr: Expr<Aug>,
352        filter: Option<Box<Expr<Aug>>>,
353        distinct: bool,
354        sample: bool,
355        over: Option<WindowSpec<Aug>>,
356    ) -> Expr<Aug> {
357        self.plan_variance(expr, filter, distinct, sample, over)
358            .call_unary(
359                self.scx
360                    .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "sqrt"]),
361            )
362    }
363
364    fn plan_bool_and(
365        &mut self,
366        expr: Expr<Aug>,
367        filter: Option<Box<Expr<Aug>>>,
368        distinct: bool,
369        over: Option<WindowSpec<Aug>>,
370    ) -> Expr<Aug> {
371        // The code below converts `bool_and(x)` into:
372        //
373        //     sum((NOT x)::int4) = 0
374        //
375        // It is tempting to use `count` instead, but count does not return NULL
376        // when all input values are NULL, as required.
377        //
378        // The `NOT x` expression has the side effect of implicitly casting `x`
379        // to `bool`. We intentionally do not write `NOT x::bool`, because that
380        // would perform an explicit cast, and to match PostgreSQL we must
381        // perform only an implicit cast.
382        let sum = self.plan_agg(
383            self.scx
384                .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "sum"]),
385            expr.negate().cast(self.int32_data_type()),
386            vec![],
387            filter,
388            distinct,
389            over,
390        );
391        sum.equals(Expr::Value(Value::Number(0.to_string())))
392    }
393
394    fn plan_bool_or(
395        &mut self,
396        expr: Expr<Aug>,
397        filter: Option<Box<Expr<Aug>>>,
398        distinct: bool,
399        over: Option<WindowSpec<Aug>>,
400    ) -> Expr<Aug> {
401        // The code below converts `bool_or(x)`z into:
402        //
403        //     sum((x OR false)::int4) > 0
404        //
405        // It is tempting to use `count` instead, but count does not return NULL
406        // when all input values are NULL, as required.
407        //
408        // The `(x OR false)` expression implicitly casts `x` to `bool` without
409        // changing its logical value. It is tempting to use `x::bool` instead,
410        // but that performs an explicit cast, and to match PostgreSQL we must
411        // perform only an implicit cast.
412        let sum = self.plan_agg(
413            self.scx
414                .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "sum"]),
415            expr.or(Expr::Value(Value::Boolean(false)))
416                .cast(self.int32_data_type()),
417            vec![],
418            filter,
419            distinct,
420            over,
421        );
422        sum.gt(Expr::Value(Value::Number(0.to_string())))
423    }
424
425    fn rewrite_function(&mut self, func: &Function<Aug>) -> Option<(Ident, Expr<Aug>)> {
426        if let Function {
427            name,
428            args: FunctionArgs::Args { args, order_by: _ },
429            filter,
430            distinct,
431            over,
432        } = func
433        {
434            let pg_catalog_id = self
435                .scx
436                .catalog
437                .resolve_schema(None, PG_CATALOG_SCHEMA)
438                .expect("pg_catalog schema exists")
439                .id();
440            let mz_catalog_id = self
441                .scx
442                .catalog
443                .resolve_schema(None, MZ_CATALOG_SCHEMA)
444                .expect("mz_catalog schema exists")
445                .id();
446            let name = match name {
447                ResolvedItemName::Item {
448                    qualifiers,
449                    full_name,
450                    ..
451                } => {
452                    if ![*pg_catalog_id, *mz_catalog_id].contains(&qualifiers.schema_spec) {
453                        return None;
454                    }
455                    full_name.item.clone()
456                }
457                _ => unreachable!(),
458            };
459
460            let filter = filter.clone();
461            let distinct = *distinct;
462            let over = over.clone();
463            let expr = if args.len() == 1 {
464                let arg = args[0].clone();
465                match name.as_str() {
466                    "avg_internal_v1" => self.plan_avg_internal_v1(arg, filter, distinct, over),
467                    "avg" => self.plan_avg(arg, filter, distinct, over),
468                    "variance" | "var_samp" => {
469                        self.plan_variance(arg, filter, distinct, true, over)
470                    }
471                    "var_pop" => self.plan_variance(arg, filter, distinct, false, over),
472                    "stddev" | "stddev_samp" => self.plan_stddev(arg, filter, distinct, true, over),
473                    "stddev_pop" => self.plan_stddev(arg, filter, distinct, false, over),
474                    "bool_and" => self.plan_bool_and(arg, filter, distinct, over),
475                    "bool_or" => self.plan_bool_or(arg, filter, distinct, over),
476                    _ => return None,
477                }
478            } else if args.len() == 2 {
479                let (lhs, rhs) = (args[0].clone(), args[1].clone());
480                match name.as_str() {
481                    "mod" => lhs.modulo(rhs),
482                    "pow" => Expr::call(
483                        self.scx
484                            .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, "power"]),
485                        vec![lhs, rhs],
486                    ),
487                    _ => return None,
488                }
489            } else {
490                return None;
491            };
492            Some((Ident::new_unchecked(name), expr))
493        } else {
494            None
495        }
496    }
497
498    fn rewrite_expr(&mut self, expr: &Expr<Aug>) -> Option<(Ident, Expr<Aug>)> {
499        match expr {
500            Expr::Function(function) => self.rewrite_function(function),
501            // Rewrites special keywords that SQL considers to be function calls
502            // to actual function calls. For example, `SELECT current_timestamp`
503            // is rewritten to `SELECT current_timestamp()`.
504            Expr::Identifier(ident) if ident.len() == 1 => {
505                let ident = normalize::ident(ident[0].clone());
506                let fn_ident = match ident.as_str() {
507                    "current_role" => Some("current_user"),
508                    "current_schema" | "current_timestamp" | "current_user" | "session_user"
509                    | "current_catalog" => Some(ident.as_str()),
510                    _ => None,
511                };
512                match fn_ident {
513                    None => None,
514                    Some(fn_ident) => {
515                        let expr = Expr::call_nullary(
516                            self.scx
517                                .dangerous_resolve_name(vec![PG_CATALOG_SCHEMA, fn_ident]),
518                        );
519                        Some((Ident::new_unchecked(ident), expr))
520                    }
521                }
522            }
523            _ => None,
524        }
525    }
526}
527
528impl<'ast> VisitMut<'ast, Aug> for FuncRewriter<'_> {
529    fn visit_select_item_mut(&mut self, item: &'ast mut SelectItem<Aug>) {
530        if let SelectItem::Expr { expr, alias: None } = item {
531            visit_mut::visit_expr_mut(self, expr);
532            if let Some((alias, expr)) = self.rewrite_expr(expr) {
533                *item = SelectItem::Expr {
534                    expr,
535                    alias: Some(alias),
536                };
537            }
538        } else {
539            visit_mut::visit_select_item_mut(self, item);
540        }
541    }
542
543    fn visit_table_with_joins_mut(&mut self, item: &'ast mut TableWithJoins<Aug>) {
544        visit_mut::visit_table_with_joins_mut(self, item);
545        match &mut item.relation {
546            TableFactor::Function {
547                function,
548                alias,
549                with_ordinality,
550            } => {
551                self.rewriting_table_factor = true;
552                // Functions that get rewritten must be rewritten as exprs
553                // because their catalog functions cannot be planned.
554                if let Some((ident, expr)) = self.rewrite_function(function) {
555                    let mut select = Select::default().project(SelectItem::Expr {
556                        expr,
557                        alias: Some(match &alias {
558                            Some(TableAlias { name, columns, .. }) => {
559                                columns.get(0).unwrap_or(name).clone()
560                            }
561                            None => ident,
562                        }),
563                    });
564
565                    if *with_ordinality {
566                        select = select.project(SelectItem::Expr {
567                            expr: Expr::Value(Value::Number("1".into())),
568                            alias: Some(ident!(ORDINALITY_COL_NAME)),
569                        });
570                    }
571
572                    item.relation = TableFactor::Derived {
573                        lateral: false,
574                        subquery: Box::new(Query {
575                            ctes: mz_sql_parser::ast::CteBlock::Simple(vec![]),
576                            body: mz_sql_parser::ast::SetExpr::Select(Box::new(select)),
577                            order_by: vec![],
578                            limit: None,
579                            offset: None,
580                        }),
581                        alias: alias.clone(),
582                    }
583                }
584                self.rewriting_table_factor = false;
585            }
586            _ => {}
587        }
588    }
589
590    fn visit_expr_mut(&mut self, expr: &'ast mut Expr<Aug>) {
591        visit_mut::visit_expr_mut(self, expr);
592        if let Some((_name, new_expr)) = self.rewrite_expr(expr) {
593            *expr = new_expr;
594        }
595    }
596}
597
598/// Removes syntax sugar to simplify the planner.
599///
600/// For example, `<expr> NOT IN (<subquery>)` is rewritten to `expr <> ALL
601/// (<subquery>)`.
602struct Desugarer<'a> {
603    scx: &'a StatementContext<'a>,
604    status: Result<(), PlanError>,
605    id_gen: IdGen,
606    recursion_guard: RecursionGuard,
607}
608
609impl<'a> CheckedRecursion for Desugarer<'a> {
610    fn recursion_guard(&self) -> &RecursionGuard {
611        &self.recursion_guard
612    }
613}
614
615impl<'a, 'ast> VisitMut<'ast, Aug> for Desugarer<'a> {
616    fn visit_expr_mut(&mut self, expr: &'ast mut Expr<Aug>) {
617        self.visit_internal(Self::visit_expr_mut_internal, expr);
618    }
619}
620
621impl<'a> Desugarer<'a> {
622    fn visit_internal<F, X>(&mut self, f: F, x: X)
623    where
624        F: Fn(&mut Self, X) -> Result<(), PlanError>,
625    {
626        if self.status.is_ok() {
627            // self.status could have changed from a deeper call, so don't blindly
628            // overwrite it with the result of this call.
629            let status = self.checked_recur_mut(|d| f(d, x));
630            if self.status.is_ok() {
631                self.status = status;
632            }
633        }
634    }
635
636    fn new(scx: &'a StatementContext) -> Desugarer<'a> {
637        Desugarer {
638            scx,
639            status: Ok(()),
640            id_gen: Default::default(),
641            recursion_guard: RecursionGuard::with_limit(1024), // chosen arbitrarily
642        }
643    }
644
645    fn visit_expr_mut_internal(&mut self, expr: &mut Expr<Aug>) -> Result<(), PlanError> {
646        // `($expr)` => `$expr`
647        while let Expr::Nested(e) = expr {
648            *expr = e.take();
649        }
650
651        // `$expr BETWEEN $low AND $high` => `$expr >= $low AND $expr <= $low`
652        // `$expr NOT BETWEEN $low AND $high` => `$expr < $low OR $expr > $low`
653        if let Expr::Between {
654            expr: e,
655            low,
656            high,
657            negated,
658        } = expr
659        {
660            if *negated {
661                *expr = Expr::lt(*e.clone(), low.take()).or(e.take().gt(high.take()));
662            } else {
663                *expr = e.clone().gt_eq(low.take()).and(e.take().lt_eq(high.take()));
664            }
665        }
666
667        // When `$expr` is a `ROW` constructor, we need to desugar as described
668        // below in order to enable the row comparision expansion at the end of
669        // this function. We don't do this desugaring unconditionally (i.e.,
670        // when `$expr` is not a `ROW` constructor) because the implementation
671        // in `plan_in_list` is more efficient when row comparison expansion is
672        // not required.
673        //
674        // `$expr IN ($list)` => `$expr = $list[0] OR $expr = $list[1] ... OR $expr = $list[n]`
675        // `$expr NOT IN ($list)` => `$expr <> $list[0] AND $expr <> $list[1] ... AND $expr <> $list[n]`
676        if let Expr::InList {
677            expr: e,
678            list,
679            negated,
680        } = expr
681        {
682            if let Expr::Row { .. } = &**e {
683                if *negated {
684                    *expr = list
685                        .drain(..)
686                        .map(|r| e.clone().not_equals(r))
687                        .reduce(|e1, e2| e1.and(e2))
688                        .expect("list known to contain at least one element");
689                } else {
690                    *expr = list
691                        .drain(..)
692                        .map(|r| e.clone().equals(r))
693                        .reduce(|e1, e2| e1.or(e2))
694                        .expect("list known to contain at least one element");
695                }
696            }
697        }
698
699        // `$expr IN ($subquery)` => `$expr = ANY ($subquery)`
700        // `$expr NOT IN ($subquery)` => `$expr <> ALL ($subquery)`
701        if let Expr::InSubquery {
702            expr: e,
703            subquery,
704            negated,
705        } = expr
706        {
707            if *negated {
708                *expr = Expr::AllSubquery {
709                    left: Box::new(e.take()),
710                    op: Op::bare("<>"),
711                    right: Box::new(subquery.take()),
712                };
713            } else {
714                *expr = Expr::AnySubquery {
715                    left: Box::new(e.take()),
716                    op: Op::bare("="),
717                    right: Box::new(subquery.take()),
718                };
719            }
720        }
721
722        // `$expr = ALL ($array_expr)`
723        // =>
724        // `CASE WHEN $array_expr IS NULL THEN NULL
725        //       ELSE $expr = ALL (SELECT elem FROM unnest($array_expr) _ (elem)) END`
726        //
727        // and analogously for other operators and ANY.
728        //
729        // The `IS NULL` guard is required because a NULL array is distinct from
730        // an empty array. PG yields NULL for the former but the empty-set answer
731        // (false for ANY, true for ALL) for the latter, whereas `unnest` maps
732        // both to zero rows and so cannot tell them apart.
733        //
734        // The guard is gated because it costs plan quality in value context over a
735        // non-constant array. Several `ANY`/`ALL` operands in one clause used to be
736        // lowered together over a shared `distinct_inner`, but HIR lowering defers a
737        // subquery that sits under an `If`, so each guarded operand now lowers on its
738        // own and duplicates the `unnest`. In filter context, and whenever the array
739        // is constant, the guard folds away and plans are unchanged.
740        if let Expr::AnyExpr { left, op, right } | Expr::AllExpr { left, op, right } = expr {
741            let binding = ident!("elem");
742
743            // Cloned here because `right` is consumed into the `unnest` call below.
744            let array_is_null = self
745                .scx
746                .is_feature_flag_enabled(&ENABLE_ANY_ALL_NULL_ARRAY_SEMANTICS)
747                .then(|| Expr::IsExpr {
748                    expr: Box::new((**right).clone()),
749                    construct: IsExprConstruct::Null,
750                    negated: false,
751                });
752
753            let subquery = Query::select(
754                Select::default()
755                    .from(TableWithJoins {
756                        relation: TableFactor::Function {
757                            function: Function {
758                                name: self
759                                    .scx
760                                    .dangerous_resolve_name(vec![MZ_CATALOG_SCHEMA, "unnest"]),
761                                args: FunctionArgs::args(vec![right.take()]),
762                                filter: None,
763                                over: None,
764                                distinct: false,
765                            },
766                            alias: Some(TableAlias {
767                                name: ident!("_"),
768                                columns: vec![binding.clone()],
769                                strict: true,
770                            }),
771                            with_ordinality: false,
772                        },
773                        joins: vec![],
774                    })
775                    .project(SelectItem::Expr {
776                        expr: Expr::Identifier(vec![binding]),
777                        alias: None,
778                    }),
779            );
780
781            let left = Box::new(left.take());
782
783            let op = op.clone();
784
785            let any_all = match expr {
786                Expr::AnyExpr { .. } => Expr::AnySubquery {
787                    left,
788                    op,
789                    right: Box::new(subquery),
790                },
791                Expr::AllExpr { .. } => Expr::AllSubquery {
792                    left,
793                    op,
794                    right: Box::new(subquery),
795                },
796                _ => unreachable!(),
797            };
798
799            *expr = match array_is_null {
800                Some(array_is_null) => Expr::Case {
801                    operand: None,
802                    conditions: vec![array_is_null],
803                    results: vec![Expr::null()],
804                    else_result: Some(Box::new(any_all)),
805                },
806                None => any_all,
807            };
808        }
809
810        // `$expr = ALL ($subquery)`
811        // =>
812        // `(SELECT mz_unsafe.mz_all($expr = $binding) FROM ($subquery) AS _ ($binding))
813        //
814        // and analogously for other operators and ANY.
815        if let Expr::AnySubquery { left, op, right } | Expr::AllSubquery { left, op, right } = expr
816        {
817            let left = match &mut **left {
818                Expr::Row { .. } => left.take(),
819                _ => Expr::Row {
820                    exprs: vec![left.take()],
821                },
822            };
823
824            let arity = match &left {
825                Expr::Row { exprs } => exprs.len(),
826                _ => unreachable!(),
827            };
828
829            let bindings: Vec<_> = (0..arity)
830                // Note: using unchecked is okay here because we know the value will be less than
831                // our maximum length.
832                .map(|col| {
833                    let unique_id = self.id_gen.allocate_id();
834                    Ident::new_unchecked(format!("right_col{col}_{unique_id}"))
835                })
836                .collect();
837
838            let subquery_unique_id = self.id_gen.allocate_id();
839            // Note: kay to use unchecked here because we know the value will be small enough.
840            let subquery_name = Ident::new_unchecked(format!("subquery{subquery_unique_id}"));
841            let select = Select::default()
842                .from(TableWithJoins::subquery(
843                    right.take(),
844                    TableAlias {
845                        name: subquery_name,
846                        columns: bindings.clone(),
847                        strict: true,
848                    },
849                ))
850                .project(SelectItem::Expr {
851                    expr: left
852                        .binop(
853                            op.clone(),
854                            Expr::Row {
855                                exprs: bindings
856                                    .into_iter()
857                                    .map(|b| Expr::Identifier(vec![b]))
858                                    .collect(),
859                            },
860                        )
861                        .call_unary(self.scx.dangerous_resolve_name(match expr {
862                            Expr::AnySubquery { .. } => vec![MZ_UNSAFE_SCHEMA, "mz_any"],
863                            Expr::AllSubquery { .. } => vec![MZ_UNSAFE_SCHEMA, "mz_all"],
864                            _ => unreachable!(),
865                        })),
866                    alias: None,
867                });
868
869            *expr = Expr::Subquery(Box::new(Query::select(select)));
870        }
871
872        // Expands row comparisons.
873        //
874        // ROW($l1, $l2, ..., $ln) = ROW($r1, $r2, ..., $rn)
875        // =>
876        // $l1 = $r1 AND $l2 = $r2 AND ... AND $ln = $rn
877        //
878        // ROW($l1, $l2, ..., $ln) < ROW($r1, $r2, ..., $rn)
879        // =>
880        // $l1 < $r1 OR ($l1 = $r1 AND ($l2 < $r2 OR ($l2 = $r2 AND ... ($ln < $rn))))
881        //
882        // ROW($l1, $l2, ..., $ln) <= ROW($r1, $r2, ..., $rn)
883        // =>
884        // $l1 < $r1 OR ($l1 = $r1 AND ($l2 < $r2 OR ($l2 = $r2 AND ... ($ln <= $rn))))
885        //
886        // and analogously for the inverse operations !=, >, and >=.
887        if let Expr::Op {
888            op,
889            expr1: left,
890            expr2: Some(right),
891        } = expr
892        {
893            if let (Expr::Row { exprs: left }, Expr::Row { exprs: right }) =
894                (&mut **left, &mut **right)
895            {
896                if matches!(normalize::op(op)?, "=" | "<>" | "<" | "<=" | ">" | ">=") {
897                    if left.len() != right.len() {
898                        sql_bail!("unequal number of entries in row expressions");
899                    }
900                    if left.is_empty() {
901                        assert!(right.is_empty());
902                        sql_bail!("cannot compare rows of zero length");
903                    }
904                }
905                match normalize::op(op)? {
906                    "=" | "<>" => {
907                        let mut pairs = left.iter_mut().zip_eq(right);
908                        let mut new = pairs
909                            .next()
910                            .map(|(l, r)| l.take().equals(r.take()))
911                            .expect("cannot compare rows of zero length");
912                        for (l, r) in pairs {
913                            new = l.take().equals(r.take()).and(new);
914                        }
915                        if normalize::op(op)? == "<>" {
916                            new = new.negate();
917                        }
918                        *expr = new;
919                    }
920                    "<" | "<=" | ">" | ">=" => {
921                        let strict_op = match normalize::op(op)? {
922                            "<" | "<=" => "<",
923                            ">" | ">=" => ">",
924                            _ => unreachable!(),
925                        };
926                        let (l, r) = (left.last_mut().unwrap(), right.last_mut().unwrap());
927                        let mut new = l.take().binop(op.clone(), r.take());
928                        for (l, r) in left
929                            .iter_mut()
930                            .rev()
931                            .zip_eq(right.into_iter().rev())
932                            .skip(1)
933                        {
934                            new = l
935                                .clone()
936                                .binop(Op::bare(strict_op), r.clone())
937                                .or(l.take().equals(r.take()).and(new));
938                        }
939                        *expr = new;
940                    }
941                    _ if left.len() == 1 && right.len() == 1 => {
942                        let left = left.remove(0);
943                        let right = right.remove(0);
944                        *expr = left.binop(op.clone(), right);
945                    }
946                    _ => (),
947                }
948            }
949        }
950
951        visit_mut::visit_expr_mut(self, expr);
952        Ok(())
953    }
954}