Skip to main content

mz_sql/
func.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//! TBD: Currently, `sql::func` handles matching arguments to their respective
11//! built-in functions (for most built-in functions, at least).
12
13use std::cell::RefCell;
14use std::collections::BTreeMap;
15use std::fmt;
16use std::sync::LazyLock;
17
18use itertools::Itertools;
19use mz_expr::func;
20use mz_expr::func::variadic;
21use mz_ore::collections::CollectionExt;
22use mz_ore::str::StrExt;
23use mz_pgrepr::oid;
24use mz_repr::role_id::RoleId;
25use mz_repr::{ColumnName, Datum, SqlRelationType, SqlScalarBaseType, SqlScalarType};
26
27use crate::ast::{SelectStatement, Statement};
28use crate::catalog::{CatalogType, TypeCategory, TypeReference};
29use crate::names::{self, ResolvedItemName};
30use crate::plan::error::PlanError;
31use crate::plan::hir::{
32    AggregateFunc, BinaryFunc, CoercibleScalarExpr, CoercibleScalarType, ColumnOrder,
33    HirRelationExpr, HirScalarExpr, ScalarWindowFunc, TableFunc, UnaryFunc, UnmaterializableFunc,
34    ValueWindowFunc, VariadicFunc,
35};
36use crate::plan::query::{self, ExprContext, QueryContext};
37use crate::plan::scope::Scope;
38use crate::plan::side_effecting_func::PG_CATALOG_SEF_BUILTINS;
39use crate::plan::transform_ast;
40use crate::plan::typeconv::{self, CastContext};
41use crate::session::vars::{self, ENABLE_TIME_AT_TIME_ZONE};
42
43/// A specifier for a function or an operator.
44#[derive(Clone, Copy, Debug)]
45pub enum FuncSpec<'a> {
46    /// A function name.
47    Func(&'a ResolvedItemName),
48    /// An operator name.
49    Op(&'a str),
50}
51
52impl<'a> fmt::Display for FuncSpec<'a> {
53    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
54        match self {
55            FuncSpec::Func(n) => n.fmt(f),
56            FuncSpec::Op(o) => o.fmt(f),
57        }
58    }
59}
60
61impl TypeCategory {
62    /// Extracted from PostgreSQL 9.6.
63    /// ```sql,ignore
64    /// SELECT array_agg(typname), typcategory
65    /// FROM pg_catalog.pg_type
66    /// WHERE typname IN (
67    ///  'bool', 'bytea', 'date', 'float4', 'float8', 'int4', 'int8', 'interval', 'jsonb',
68    ///  'numeric', 'text', 'time', 'timestamp', 'timestamptz'
69    /// )
70    /// GROUP BY typcategory
71    /// ORDER BY typcategory;
72    /// ```
73    pub fn from_type(typ: &SqlScalarType) -> Self {
74        // Keep this in sync with `from_catalog_type`.
75        match typ {
76            SqlScalarType::Array(..) | SqlScalarType::Int2Vector => Self::Array,
77            SqlScalarType::Bool => Self::Boolean,
78            SqlScalarType::AclItem
79            | SqlScalarType::Bytes
80            | SqlScalarType::Jsonb
81            | SqlScalarType::Uuid
82            | SqlScalarType::MzAclItem => Self::UserDefined,
83            SqlScalarType::Date
84            | SqlScalarType::Time
85            | SqlScalarType::Timestamp { .. }
86            | SqlScalarType::TimestampTz { .. } => Self::DateTime,
87            SqlScalarType::Float32
88            | SqlScalarType::Float64
89            | SqlScalarType::Int16
90            | SqlScalarType::Int32
91            | SqlScalarType::Int64
92            | SqlScalarType::UInt16
93            | SqlScalarType::UInt32
94            | SqlScalarType::UInt64
95            | SqlScalarType::Oid
96            | SqlScalarType::RegClass
97            | SqlScalarType::RegProc
98            | SqlScalarType::RegType
99            | SqlScalarType::Numeric { .. } => Self::Numeric,
100            SqlScalarType::Interval => Self::Timespan,
101            SqlScalarType::List { .. } => Self::List,
102            SqlScalarType::PgLegacyChar
103            | SqlScalarType::PgLegacyName
104            | SqlScalarType::String
105            | SqlScalarType::Char { .. }
106            | SqlScalarType::VarChar { .. } => Self::String,
107            SqlScalarType::Record { custom_id, .. } => {
108                if custom_id.is_some() {
109                    Self::Composite
110                } else {
111                    Self::Pseudo
112                }
113            }
114            SqlScalarType::Map { .. } => Self::Pseudo,
115            SqlScalarType::MzTimestamp => Self::Numeric,
116            SqlScalarType::Range { .. } => Self::Range,
117        }
118    }
119
120    pub fn from_param(param: &ParamType) -> Self {
121        match param {
122            ParamType::Any
123            | ParamType::AnyElement
124            | ParamType::ArrayAny
125            | ParamType::ArrayAnyCompatible
126            | ParamType::AnyCompatible
127            | ParamType::ListAny
128            | ParamType::ListAnyCompatible
129            | ParamType::ListElementAnyCompatible
130            | ParamType::Internal
131            | ParamType::NonVecAny
132            | ParamType::NonVecAnyCompatible
133            | ParamType::MapAny
134            | ParamType::MapAnyCompatible
135            | ParamType::RecordAny => Self::Pseudo,
136            ParamType::RangeAnyCompatible | ParamType::RangeAny => Self::Range,
137            ParamType::Plain(t) => Self::from_type(t),
138        }
139    }
140
141    /// Like [`TypeCategory::from_type`], but for catalog types.
142    // TODO(benesch): would be nice to figure out how to share code with
143    // `from_type`, but the refactor to enable that would be substantial.
144    pub fn from_catalog_type<T>(catalog_type: &CatalogType<T>) -> Self
145    where
146        T: TypeReference,
147    {
148        // Keep this in sync with `from_type`.
149        match catalog_type {
150            CatalogType::Array { .. } | CatalogType::Int2Vector => Self::Array,
151            CatalogType::Bool => Self::Boolean,
152            CatalogType::AclItem
153            | CatalogType::Bytes
154            | CatalogType::Jsonb
155            | CatalogType::Uuid
156            | CatalogType::MzAclItem => Self::UserDefined,
157            CatalogType::Date
158            | CatalogType::Time
159            | CatalogType::Timestamp
160            | CatalogType::TimestampTz => Self::DateTime,
161            CatalogType::Float32
162            | CatalogType::Float64
163            | CatalogType::Int16
164            | CatalogType::Int32
165            | CatalogType::Int64
166            | CatalogType::UInt16
167            | CatalogType::UInt32
168            | CatalogType::UInt64
169            | CatalogType::Oid
170            | CatalogType::RegClass
171            | CatalogType::RegProc
172            | CatalogType::RegType
173            | CatalogType::Numeric { .. } => Self::Numeric,
174            CatalogType::Interval => Self::Timespan,
175            CatalogType::List { .. } => Self::List,
176            CatalogType::PgLegacyChar
177            | CatalogType::PgLegacyName
178            | CatalogType::String
179            | CatalogType::Char { .. }
180            | CatalogType::VarChar { .. } => Self::String,
181            CatalogType::Record { .. } => TypeCategory::Composite,
182            CatalogType::Map { .. } | CatalogType::Pseudo => Self::Pseudo,
183            CatalogType::MzTimestamp => Self::String,
184            CatalogType::Range { .. } => Self::Range,
185        }
186    }
187
188    /// Extracted from PostgreSQL 9.6.
189    /// ```ignore
190    /// SELECT typcategory, typname, typispreferred
191    /// FROM pg_catalog.pg_type
192    /// WHERE typispreferred = true
193    /// ORDER BY typcategory;
194    /// ```
195    pub fn preferred_type(&self) -> Option<SqlScalarType> {
196        match self {
197            Self::Array
198            | Self::BitString
199            | Self::Composite
200            | Self::Enum
201            | Self::Geometric
202            | Self::List
203            | Self::NetworkAddress
204            | Self::Pseudo
205            | Self::Range
206            | Self::Unknown
207            | Self::UserDefined => None,
208            Self::Boolean => Some(SqlScalarType::Bool),
209            Self::DateTime => Some(SqlScalarType::TimestampTz { precision: None }),
210            Self::Numeric => Some(SqlScalarType::Float64),
211            Self::String => Some(SqlScalarType::String),
212            Self::Timespan => Some(SqlScalarType::Interval),
213        }
214    }
215}
216
217/// Builds an expression that evaluates a scalar function on the provided
218/// input expressions.
219pub struct Operation<R>(
220    pub  Box<
221        dyn Fn(
222                &ExprContext,
223                Vec<CoercibleScalarExpr>,
224                &ParamList,
225                Vec<ColumnOrder>,
226            ) -> Result<R, PlanError>
227            + Send
228            + Sync,
229    >,
230);
231
232impl<R> fmt::Debug for Operation<R> {
233    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
234        f.debug_struct("Operation").finish()
235    }
236}
237
238impl Operation<HirScalarExpr> {
239    /// Builds a unary operation that simply returns its input.
240    fn identity() -> Operation<HirScalarExpr> {
241        Operation::unary(|_ecx, e| Ok(e))
242    }
243}
244
245impl<R> Operation<R> {
246    fn new<F>(f: F) -> Operation<R>
247    where
248        F: Fn(
249                &ExprContext,
250                Vec<CoercibleScalarExpr>,
251                &ParamList,
252                Vec<ColumnOrder>,
253            ) -> Result<R, PlanError>
254            + Send
255            + Sync
256            + 'static,
257    {
258        Operation(Box::new(f))
259    }
260
261    /// Builds an operation that takes no arguments.
262    fn nullary<F>(f: F) -> Operation<R>
263    where
264        F: Fn(&ExprContext) -> Result<R, PlanError> + Send + Sync + 'static,
265    {
266        Self::variadic(move |ecx, exprs| {
267            assert!(exprs.is_empty());
268            f(ecx)
269        })
270    }
271
272    /// Builds an operation that takes one argument.
273    fn unary<F>(f: F) -> Operation<R>
274    where
275        F: Fn(&ExprContext, HirScalarExpr) -> Result<R, PlanError> + Send + Sync + 'static,
276    {
277        Self::variadic(move |ecx, exprs| f(ecx, exprs.into_element()))
278    }
279
280    /// Builds an operation that takes one argument and an order_by.
281    fn unary_ordered<F>(f: F) -> Operation<R>
282    where
283        F: Fn(&ExprContext, HirScalarExpr, Vec<ColumnOrder>) -> Result<R, PlanError>
284            + Send
285            + Sync
286            + 'static,
287    {
288        Self::new(move |ecx, cexprs, params, order_by| {
289            let exprs = coerce_args_to_types(ecx, cexprs, params)?;
290            f(ecx, exprs.into_element(), order_by)
291        })
292    }
293
294    /// Builds an operation that takes two arguments.
295    fn binary<F>(f: F) -> Operation<R>
296    where
297        F: Fn(&ExprContext, HirScalarExpr, HirScalarExpr) -> Result<R, PlanError>
298            + Send
299            + Sync
300            + 'static,
301    {
302        Self::variadic(move |ecx, exprs| {
303            assert_eq!(exprs.len(), 2);
304            let mut exprs = exprs.into_iter();
305            let left = exprs.next().unwrap();
306            let right = exprs.next().unwrap();
307            f(ecx, left, right)
308        })
309    }
310
311    /// Builds an operation that takes two arguments and an order_by.
312    ///
313    /// If returning an aggregate function, it should return `true` for
314    /// [`AggregateFunc::is_order_sensitive`].
315    fn binary_ordered<F>(f: F) -> Operation<R>
316    where
317        F: Fn(&ExprContext, HirScalarExpr, HirScalarExpr, Vec<ColumnOrder>) -> Result<R, PlanError>
318            + Send
319            + Sync
320            + 'static,
321    {
322        Self::new(move |ecx, cexprs, params, order_by| {
323            let exprs = coerce_args_to_types(ecx, cexprs, params)?;
324            assert_eq!(exprs.len(), 2);
325            let mut exprs = exprs.into_iter();
326            let left = exprs.next().unwrap();
327            let right = exprs.next().unwrap();
328            f(ecx, left, right, order_by)
329        })
330    }
331
332    /// Builds an operation that takes any number of arguments.
333    fn variadic<F>(f: F) -> Operation<R>
334    where
335        F: Fn(&ExprContext, Vec<HirScalarExpr>) -> Result<R, PlanError> + Send + Sync + 'static,
336    {
337        Self::new(move |ecx, cexprs, params, _order_by| {
338            let exprs = coerce_args_to_types(ecx, cexprs, params)?;
339            f(ecx, exprs)
340        })
341    }
342}
343
344/// Backing implementation for sql_impl_func and sql_impl_cast. See those
345/// functions for details.
346pub fn sql_impl(
347    expr: &str,
348) -> impl Fn(&ExprContext, Vec<SqlScalarType>) -> Result<HirScalarExpr, PlanError> + use<> {
349    let expr = mz_sql_parser::parser::parse_expr(expr).unwrap_or_else(|e| {
350        panic!(
351            "static function definition failed to parse {}: {}",
352            expr.quoted(),
353            e,
354        )
355    });
356    move |ecx, types| {
357        // Reconstruct an expression context where the parameter types are
358        // bound to the types of the expressions in `args`.
359        let mut scx = ecx.qcx.scx.clone();
360        scx.param_types = RefCell::new(
361            types
362                .into_iter()
363                .enumerate()
364                .map(|(i, ty)| (i + 1, ty))
365                .collect(),
366        );
367        let qcx = QueryContext::root(&scx, ecx.qcx.lifetime);
368
369        let (mut expr, new_ids) = names::resolve(qcx.scx.catalog, expr.clone())?;
370        scx.sql_impl_resolved_ids
371            .lock()
372            .expect("planning is single-threaded")
373            .extend_from(&new_ids);
374        // Desugar the expression
375        transform_ast::transform(&scx, &mut expr)?;
376
377        let ecx_name = format!(
378            "static function definition (or its outer context '{}')",
379            ecx.name
380        );
381        let ecx = ExprContext {
382            qcx: &qcx,
383            name: ecx_name.as_str(),
384            scope: &Scope::empty(),
385            relation_type: &SqlRelationType::empty(),
386            // Constrain the new context by the outer context's `allow_subqueries`.
387            // (We could potentially to the same for `allow_aggregates` and `allow_windows`, I just
388            // don't want to think these through until we have a concrete use case.)
389            // (`allow_parameters` we have to set true, because that is the machinery for function
390            // arguments, i.e., parameters from in here won't escape `sql_impl_func` or
391            // `sql_impl_cast`.)
392            allow_aggregates: false,
393            allow_subqueries: ecx.allow_subqueries,
394            allow_parameters: true,
395            allow_windows: false,
396        };
397
398        // Plan the expression.
399        query::plan_expr(&ecx, &expr)?.type_as_any(&ecx)
400    }
401}
402
403// Constructs a definition for a built-in function out of a static SQL
404// expression.
405//
406// The SQL expression should use the standard parameter syntax (`$1`, `$2`, ...)
407// to refer to the inputs to the function. For example, a built-in function that
408// takes two arguments and concatenates them with an arrow in between could be
409// defined like so:
410//
411//     sql_impl_func("$1 || '<->' || $2")
412//
413// The number of parameters in the SQL expression must exactly match the number
414// of parameters in the built-in's declaration. There is no support for variadic
415// functions.
416fn sql_impl_func(expr: &str) -> Operation<HirScalarExpr> {
417    let invoke = sql_impl(expr);
418    Operation::variadic(move |ecx, args| {
419        let types = args.iter().map(|arg| ecx.scalar_type(arg)).collect();
420        let mut out = invoke(ecx, types)?;
421        out.splice_parameters(&args, 0);
422        Ok(out)
423    })
424}
425
426// Defines a built-in table function from a static SQL SELECT statement.
427//
428// The SQL statement should use the standard parameter syntax (`$1`, `$2`, ...)
429// to refer to the inputs to the function; see sql_impl_func for an example.
430//
431// The number of parameters in the SQL expression must exactly match the number
432// of parameters in the built-in's declaration. There is no support for variadic
433// functions.
434//
435// As this is a full SQL statement, it returns a set of rows, similar to a
436// table function. The SELECT's projection's names are used and should be
437// aliased if needed.
438fn sql_impl_table_func_inner(
439    sql: &'static str,
440    feature_flag: Option<&'static vars::FeatureFlag>,
441) -> Operation<TableFuncPlan> {
442    let query = match mz_sql_parser::parser::parse_statements(sql)
443        .expect("static function definition failed to parse")
444        .expect_element(|| "static function definition must have exactly one statement")
445        .ast
446    {
447        Statement::Select(SelectStatement { query, as_of: None }) => query,
448        _ => panic!("static function definition expected SELECT statement"),
449    };
450    let invoke = move |qcx: &QueryContext, types: Vec<SqlScalarType>| {
451        // Reconstruct an expression context where the parameter types are
452        // bound to the types of the expressions in `args`.
453        let mut scx = qcx.scx.clone();
454        scx.param_types = RefCell::new(
455            types
456                .into_iter()
457                .enumerate()
458                .map(|(i, ty)| (i + 1, ty))
459                .collect(),
460        );
461        let mut qcx = QueryContext::root(&scx, qcx.lifetime);
462
463        let query = query.clone();
464        let (mut query, new_ids) = names::resolve(qcx.scx.catalog, query)?;
465        scx.sql_impl_resolved_ids
466            .lock()
467            .expect("planning is single-threaded")
468            .extend_from(&new_ids);
469        transform_ast::transform(&scx, &mut query)?;
470
471        query::plan_nested_query(&mut qcx, &query)
472    };
473
474    Operation::variadic(move |ecx, args| {
475        if let Some(feature_flag) = feature_flag {
476            ecx.require_feature_flag(feature_flag)?;
477        }
478        let types = args.iter().map(|arg| ecx.scalar_type(arg)).collect();
479        let (mut expr, scope) = invoke(ecx.qcx, types)?;
480        expr.splice_parameters(&args, 0);
481        Ok(TableFuncPlan {
482            imp: TableFuncImpl::Expr(expr),
483            column_names: scope.column_names().cloned().collect(),
484        })
485    })
486}
487
488/// Implements a table function using SQL.
489///
490/// Warning: These implementations are currently defective for WITH ORDINALITY / FROM ROWS, see
491/// comment in `plan_table_function_internal`.
492fn sql_impl_table_func(sql: &'static str) -> Operation<TableFuncPlan> {
493    sql_impl_table_func_inner(sql, None)
494}
495
496fn experimental_sql_impl_table_func(
497    feature: &'static vars::FeatureFlag,
498    sql: &'static str,
499) -> Operation<TableFuncPlan> {
500    sql_impl_table_func_inner(sql, Some(feature))
501}
502
503/// Describes a single function's implementation.
504pub struct FuncImpl<R> {
505    pub oid: u32,
506    pub params: ParamList,
507    pub return_type: ReturnType,
508    pub op: Operation<R>,
509}
510
511/// Describes how each implementation should be represented in the catalog.
512#[derive(Debug)]
513pub struct FuncImplCatalogDetails {
514    pub oid: u32,
515    pub arg_typs: Vec<&'static str>,
516    pub variadic_typ: Option<&'static str>,
517    pub return_typ: Option<&'static str>,
518    pub return_is_set: bool,
519}
520
521impl<R> FuncImpl<R> {
522    pub fn details(&self) -> FuncImplCatalogDetails {
523        FuncImplCatalogDetails {
524            oid: self.oid,
525            arg_typs: self.params.arg_names(),
526            variadic_typ: self.params.variadic_name(),
527            return_typ: self.return_type.typ.as_ref().map(|t| t.name()),
528            return_is_set: self.return_type.is_set_of,
529        }
530    }
531}
532
533impl<R> fmt::Debug for FuncImpl<R> {
534    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
535        f.debug_struct("FuncImpl")
536            .field("oid", &self.oid)
537            .field("params", &self.params)
538            .field("ret", &self.return_type)
539            .field("op", &"<omitted>")
540            .finish()
541    }
542}
543
544impl From<UnmaterializableFunc> for Operation<HirScalarExpr> {
545    fn from(n: UnmaterializableFunc) -> Operation<HirScalarExpr> {
546        Operation::nullary(move |_ecx| Ok(HirScalarExpr::call_unmaterializable(n.clone())))
547    }
548}
549
550impl From<UnaryFunc> for Operation<HirScalarExpr> {
551    fn from(u: UnaryFunc) -> Operation<HirScalarExpr> {
552        Operation::unary(move |_ecx, e| Ok(e.call_unary(u.clone())))
553    }
554}
555
556impl From<BinaryFunc> for Operation<HirScalarExpr> {
557    fn from(b: BinaryFunc) -> Operation<HirScalarExpr> {
558        Operation::binary(move |_ecx, left, right| Ok(left.call_binary(right, b.clone())))
559    }
560}
561
562impl From<VariadicFunc> for Operation<HirScalarExpr> {
563    fn from(v: VariadicFunc) -> Operation<HirScalarExpr> {
564        Operation::variadic(move |_ecx, exprs| Ok(HirScalarExpr::call_variadic(v.clone(), exprs)))
565    }
566}
567
568impl From<AggregateFunc> for Operation<(HirScalarExpr, AggregateFunc)> {
569    fn from(a: AggregateFunc) -> Operation<(HirScalarExpr, AggregateFunc)> {
570        Operation::unary(move |_ecx, e| Ok((e, a.clone())))
571    }
572}
573
574impl From<ScalarWindowFunc> for Operation<ScalarWindowFunc> {
575    fn from(a: ScalarWindowFunc) -> Operation<ScalarWindowFunc> {
576        Operation::nullary(move |_ecx| Ok(a.clone()))
577    }
578}
579
580impl From<ValueWindowFunc> for Operation<(HirScalarExpr, ValueWindowFunc)> {
581    fn from(a: ValueWindowFunc) -> Operation<(HirScalarExpr, ValueWindowFunc)> {
582        Operation::unary(move |_ecx, e| Ok((e, a.clone())))
583    }
584}
585
586#[derive(Debug, Clone, Eq, PartialEq, Hash)]
587/// Describes possible types of function parameters.
588///
589/// Note that this is not exhaustive and will likely require additions.
590pub enum ParamList {
591    Exact(Vec<ParamType>),
592    Variadic {
593        leading: Vec<ParamType>,
594        trailing: ParamType,
595    },
596}
597
598impl ParamList {
599    /// Determines whether `typs` are compatible with `self`.
600    fn matches_argtypes(&self, ecx: &ExprContext, typs: &[CoercibleScalarType]) -> bool {
601        if !self.validate_arg_len(typs.len()) {
602            return false;
603        }
604
605        for (i, typ) in typs.iter().enumerate() {
606            let param = &self[i];
607            if let CoercibleScalarType::Coerced(typ) = typ {
608                // Ensures either `typ` can at least be implicitly cast to a
609                // type `param` accepts. Implicit in this check is that unknown
610                // type arguments can be cast to any type.
611                //
612                // N.B. this will require more fallthrough checks once we
613                // support RECORD types in functions.
614                if !param.accepts_type(ecx, typ) {
615                    return false;
616                }
617            }
618        }
619
620        // Ensure a polymorphic solution exists (non-polymorphic functions have
621        // trivial polymorphic solutions that evaluate to `None`).
622        PolymorphicSolution::new(ecx, typs, self).is_some()
623    }
624
625    /// Validates that the number of input elements are viable for `self`.
626    fn validate_arg_len(&self, input_len: usize) -> bool {
627        match self {
628            Self::Exact(p) => p.len() == input_len,
629            Self::Variadic { leading, .. } => input_len > leading.len(),
630        }
631    }
632
633    /// Matches a `&[SqlScalarType]` derived from the user's function argument
634    /// against this `ParamList`'s permitted arguments.
635    fn exact_match(&self, types: &[&SqlScalarType]) -> bool {
636        types.iter().enumerate().all(|(i, t)| self[i] == **t)
637    }
638
639    /// Generates values underlying data for for `mz_catalog.mz_functions.arg_ids`.
640    fn arg_names(&self) -> Vec<&'static str> {
641        match self {
642            ParamList::Exact(p) => p.iter().map(|p| p.name()).collect::<Vec<_>>(),
643            ParamList::Variadic { leading, trailing } => leading
644                .iter()
645                .chain([trailing])
646                .map(|p| p.name())
647                .collect::<Vec<_>>(),
648        }
649    }
650
651    /// Generates values for `mz_catalog.mz_functions.variadic_id`.
652    fn variadic_name(&self) -> Option<&'static str> {
653        match self {
654            ParamList::Exact(_) => None,
655            ParamList::Variadic { trailing, .. } => Some(trailing.name()),
656        }
657    }
658}
659
660impl std::ops::Index<usize> for ParamList {
661    type Output = ParamType;
662
663    fn index(&self, i: usize) -> &Self::Output {
664        match self {
665            Self::Exact(p) => &p[i],
666            Self::Variadic { leading, trailing } => leading.get(i).unwrap_or(trailing),
667        }
668    }
669}
670
671/// Provides a shorthand function for writing `ParamList::Exact`.
672impl From<Vec<ParamType>> for ParamList {
673    fn from(p: Vec<ParamType>) -> ParamList {
674        ParamList::Exact(p)
675    }
676}
677
678#[derive(Debug, Clone, Eq, PartialEq, Hash)]
679/// Describes parameter types.
680///
681/// Parameters with "Compatible" in their name are used in conjunction with
682/// other "Compatible"-type parameters to determine the best common type to cast
683/// arguments to.
684///
685/// "Compatible" parameters contrast with parameters that contain "Any" in their
686/// name, but not "Compatible." These parameters require all other "Any"-type
687/// parameters be of the same type from the perspective of
688/// [`SqlScalarType::base_eq`].
689///
690/// For more details on polymorphic parameter resolution, see `PolymorphicSolution`.
691pub enum ParamType {
692    /// A pseudotype permitting any type. Note that this parameter does not
693    /// enforce the same "Any" constraint as the other "Any"-type parameters.
694    Any,
695    /// A pseudotype permitting any type, permitting other "Compatibility"-type
696    /// parameters to find the best common type.
697    AnyCompatible,
698    /// An pseudotype permitting any type, requiring other "Any"-type parameters
699    /// to be of the same type.
700    AnyElement,
701    /// An pseudotype permitting any array type, requiring other "Any"-type
702    /// parameters to be of the same type.
703    ArrayAny,
704    /// A pseudotype permitting any array type, permitting other "Compatibility"-type
705    /// parameters to find the best common type.
706    ArrayAnyCompatible,
707    /// An pseudotype permitting any list type, requiring other "Any"-type
708    /// parameters to be of the same type.
709    ListAny,
710    /// A pseudotype permitting any list type, permitting other
711    /// "Compatibility"-type parameters to find the best common type.
712    ListAnyCompatible,
713    /// A pseudotype permitting any type, permitting other "Compatibility"-type
714    /// parameters to find the best common type. Additionally, enforces a
715    /// constraint that when used with `ListAnyCompatible`, resolves to that
716    /// argument's element type.
717    ListElementAnyCompatible,
718    /// An pseudotype permitting any map type, requiring other "Any"-type
719    /// parameters to be of the same type.
720    MapAny,
721    /// A pseudotype permitting any map type, permitting other "Compatibility"-type
722    /// parameters to find the best common type.
723    MapAnyCompatible,
724    /// A pseudotype permitting any type except `SqlScalarType::List` and
725    /// `SqlScalarType::Array`, requiring other "Any"-type
726    /// parameters to be of the same type.
727    NonVecAny,
728    /// A pseudotype permitting any type except `SqlScalarType::List` and
729    /// `SqlScalarType::Array`, requiring other "Compatibility"-type
730    /// parameters to be of the same type.
731    NonVecAnyCompatible,
732    /// A standard parameter that accepts arguments that match its embedded
733    /// `SqlScalarType`.
734    Plain(SqlScalarType),
735    /// A polymorphic pseudotype permitting a `SqlScalarType::Record` of any type,
736    /// but all records must be structurally equal.
737    RecordAny,
738    /// An pseudotype permitting any range type, requiring other "Any"-type
739    /// parameters to be of the same type.
740    RangeAny,
741    /// A pseudotype permitting any range type, permitting other
742    /// "Compatibility"-type parameters to find the best common type.
743    ///
744    /// Prefer using [`ParamType::RangeAny`] over this type; it is easy to fool
745    /// this type into generating non-existent range types (e.g. ranges of
746    /// floats) that will panic.
747    RangeAnyCompatible,
748    /// A psuedotype indicating that the function is only meant to be called
749    /// internally by the database system.
750    Internal,
751}
752
753impl ParamType {
754    /// Does `self` accept arguments of type `t`?
755    fn accepts_type(&self, ecx: &ExprContext, t: &SqlScalarType) -> bool {
756        use ParamType::*;
757        use SqlScalarType::*;
758
759        match self {
760            Any | AnyElement | AnyCompatible | ListElementAnyCompatible => true,
761            ArrayAny | ArrayAnyCompatible => matches!(t, Array(..) | Int2Vector),
762            ListAny | ListAnyCompatible => matches!(t, List { .. }),
763            MapAny | MapAnyCompatible => matches!(t, Map { .. }),
764            RangeAny | RangeAnyCompatible => matches!(t, Range { .. }),
765            NonVecAny | NonVecAnyCompatible => !t.is_vec(),
766            Internal => false,
767            Plain(to) => typeconv::can_cast(ecx, CastContext::Implicit, t, to),
768            RecordAny => matches!(t, Record { .. }),
769        }
770    }
771
772    /// Does `t`'s [`TypeCategory`] prefer `self`? This question can make
773    /// more sense with the understanding that pseudotypes are never preferred.
774    fn is_preferred_by(&self, t: &SqlScalarType) -> bool {
775        if let Some(pt) = TypeCategory::from_type(t).preferred_type() {
776            *self == pt
777        } else {
778            false
779        }
780    }
781
782    /// Is `self` the [`ParamType`] corresponding to `t`'s [near match] value?
783    ///
784    /// [near match]: SqlScalarType::near_match
785    fn is_near_match(&self, t: &SqlScalarType) -> bool {
786        match (self, t.near_match()) {
787            (ParamType::Plain(t), Some(near_match)) => t.structural_eq(near_match),
788            _ => false,
789        }
790    }
791
792    /// Is `self` the preferred parameter type for its `TypeCategory`?
793    fn prefers_self(&self) -> bool {
794        if let Some(pt) = TypeCategory::from_param(self).preferred_type() {
795            *self == pt
796        } else {
797            false
798        }
799    }
800
801    fn is_polymorphic(&self) -> bool {
802        use ParamType::*;
803        match self {
804            AnyElement
805            | ArrayAny
806            | ArrayAnyCompatible
807            | AnyCompatible
808            | ListAny
809            | ListAnyCompatible
810            | ListElementAnyCompatible
811            | MapAny
812            | MapAnyCompatible
813            | NonVecAny
814            | NonVecAnyCompatible
815            // In PG, RecordAny isn't polymorphic even though it offers
816            // polymorphic behavior. For more detail, see
817            // `PolymorphicCompatClass::StructuralEq`.
818            | RecordAny
819            | RangeAny
820            | RangeAnyCompatible => true,
821            Any | Internal | Plain(_)  => false,
822        }
823    }
824
825    fn name(&self) -> &'static str {
826        match self {
827            ParamType::Plain(t) => {
828                assert!(
829                    !t.is_custom_type(),
830                    "custom types cannot currently be used as \
831                     parameters; use a polymorphic parameter that \
832                     accepts the custom type instead"
833                );
834                let t: mz_pgrepr::Type = t.into();
835                t.catalog_name()
836            }
837            ParamType::Any => "any",
838            ParamType::AnyCompatible => "anycompatible",
839            ParamType::AnyElement => "anyelement",
840            ParamType::ArrayAny => "anyarray",
841            ParamType::ArrayAnyCompatible => "anycompatiblearray",
842            ParamType::Internal => "internal",
843            ParamType::ListAny => "list",
844            ParamType::ListAnyCompatible => "anycompatiblelist",
845            // ListElementAnyCompatible is not identical to
846            // AnyCompatible, but reusing its ID appears harmless
847            ParamType::ListElementAnyCompatible => "anycompatible",
848            ParamType::MapAny => "map",
849            ParamType::MapAnyCompatible => "anycompatiblemap",
850            ParamType::NonVecAny => "anynonarray",
851            ParamType::NonVecAnyCompatible => "anycompatiblenonarray",
852            ParamType::RecordAny => "record",
853            ParamType::RangeAny => "anyrange",
854            ParamType::RangeAnyCompatible => "anycompatiblerange",
855        }
856    }
857}
858
859impl PartialEq<SqlScalarType> for ParamType {
860    fn eq(&self, other: &SqlScalarType) -> bool {
861        match self {
862            ParamType::Plain(s) => s.base_eq(other),
863            // Pseudotypes never equal concrete types
864            _ => false,
865        }
866    }
867}
868
869impl PartialEq<ParamType> for SqlScalarType {
870    fn eq(&self, other: &ParamType) -> bool {
871        other == self
872    }
873}
874
875impl From<SqlScalarType> for ParamType {
876    fn from(s: SqlScalarType) -> ParamType {
877        ParamType::Plain(s)
878    }
879}
880
881impl From<SqlScalarBaseType> for ParamType {
882    fn from(s: SqlScalarBaseType) -> ParamType {
883        use SqlScalarBaseType::*;
884        let s = match s {
885            Array | List | Map | Record | Range => {
886                panic!("use polymorphic parameters rather than {:?}", s);
887            }
888            AclItem => SqlScalarType::AclItem,
889            Bool => SqlScalarType::Bool,
890            Int16 => SqlScalarType::Int16,
891            Int32 => SqlScalarType::Int32,
892            Int64 => SqlScalarType::Int64,
893            UInt16 => SqlScalarType::UInt16,
894            UInt32 => SqlScalarType::UInt32,
895            UInt64 => SqlScalarType::UInt64,
896            Float32 => SqlScalarType::Float32,
897            Float64 => SqlScalarType::Float64,
898            Numeric => SqlScalarType::Numeric { max_scale: None },
899            Date => SqlScalarType::Date,
900            Time => SqlScalarType::Time,
901            Timestamp => SqlScalarType::Timestamp { precision: None },
902            TimestampTz => SqlScalarType::TimestampTz { precision: None },
903            Interval => SqlScalarType::Interval,
904            Bytes => SqlScalarType::Bytes,
905            String => SqlScalarType::String,
906            Char => SqlScalarType::Char { length: None },
907            VarChar => SqlScalarType::VarChar { max_length: None },
908            PgLegacyChar => SqlScalarType::PgLegacyChar,
909            PgLegacyName => SqlScalarType::PgLegacyName,
910            Jsonb => SqlScalarType::Jsonb,
911            Uuid => SqlScalarType::Uuid,
912            Oid => SqlScalarType::Oid,
913            RegClass => SqlScalarType::RegClass,
914            RegProc => SqlScalarType::RegProc,
915            RegType => SqlScalarType::RegType,
916            Int2Vector => SqlScalarType::Int2Vector,
917            MzTimestamp => SqlScalarType::MzTimestamp,
918            MzAclItem => SqlScalarType::MzAclItem,
919        };
920        ParamType::Plain(s)
921    }
922}
923
924#[derive(Debug, Clone, Eq, PartialEq, Hash)]
925pub struct ReturnType {
926    pub typ: Option<ParamType>,
927    pub is_set_of: bool,
928}
929
930impl ReturnType {
931    /// Expresses that a function's return type is a scalar value.
932    fn scalar(typ: ParamType) -> ReturnType {
933        ReturnType {
934            typ: Some(typ),
935            is_set_of: false,
936        }
937    }
938
939    /// Expresses that a function's return type is a set of values, e.g. a table
940    /// function.
941    fn set_of(typ: ParamType) -> ReturnType {
942        ReturnType {
943            typ: Some(typ),
944            is_set_of: true,
945        }
946    }
947
948    /// Expresses that a function's return type is None.
949    fn none(is_set_of: bool) -> ReturnType {
950        ReturnType {
951            typ: None,
952            is_set_of,
953        }
954    }
955}
956
957impl From<ParamType> for ReturnType {
958    fn from(typ: ParamType) -> ReturnType {
959        ReturnType::scalar(typ)
960    }
961}
962
963impl From<SqlScalarBaseType> for ReturnType {
964    fn from(s: SqlScalarBaseType) -> ReturnType {
965        ParamType::from(s).into()
966    }
967}
968
969impl From<SqlScalarType> for ReturnType {
970    fn from(s: SqlScalarType) -> ReturnType {
971        ParamType::Plain(s).into()
972    }
973}
974
975#[derive(Clone, Debug)]
976/// Tracks candidate implementations.
977pub struct Candidate<'a, R> {
978    /// The implementation under consideration.
979    fimpl: &'a FuncImpl<R>,
980    exact_matches: usize,
981    preferred_types: usize,
982    near_matches: usize,
983}
984
985/// Selects the best implementation given the provided `args` using a
986/// process similar to [PostgreSQL's parser][pgparser], and returns the
987/// `ScalarExpr` to invoke that function.
988///
989/// Inline comments prefixed with number are taken from the "Function Type
990/// Resolution" section of the aforelinked page.
991///
992/// # Errors
993/// - When the provided arguments are not valid for any implementation, e.g.
994///   cannot be converted to the appropriate types.
995/// - When all implementations are equally valid.
996///
997/// [pgparser]: https://www.postgresql.org/docs/current/typeconv-oper.html
998pub fn select_impl<R>(
999    ecx: &ExprContext,
1000    spec: FuncSpec,
1001    impls: &[FuncImpl<R>],
1002    args: Vec<CoercibleScalarExpr>,
1003    order_by: Vec<ColumnOrder>,
1004) -> Result<R, PlanError>
1005where
1006    R: fmt::Debug,
1007{
1008    let name = spec.to_string();
1009    let ecx = &ecx.with_name(&name);
1010    let mut types: Vec<_> = args.iter().map(|e| ecx.scalar_type(e)).collect();
1011
1012    // PostgreSQL force coerces all record types before function selection. We
1013    // may want to do something smarter in the future (e.g., a function that
1014    // accepts multiple `RecordAny` parameters should perhaps coerce to the
1015    // result of calling `guess_best_common_type` on all those parameters), but
1016    // for now we just directly match PostgreSQL's behavior.
1017    for ty in &mut types {
1018        ty.force_coerced_if_record();
1019    }
1020
1021    // 4.a. Discard candidate functions for which the input types do not
1022    // match and cannot be converted (using an implicit conversion) to
1023    // match. unknown literals are assumed to be convertible to anything for
1024    // this purpose.
1025    let impls: Vec<_> = impls
1026        .iter()
1027        .filter(|i| i.params.matches_argtypes(ecx, &types))
1028        .collect();
1029
1030    let f = find_match(ecx, &types, impls).map_err(|candidates| {
1031        let arg_types: Vec<_> = types
1032            .into_iter()
1033            .map(|ty| match ty {
1034                // This will be used in error msgs, therefore we call with `postgres_compat` false.
1035                CoercibleScalarType::Coerced(ty) => ecx.humanize_sql_scalar_type(&ty, false),
1036                CoercibleScalarType::Record(_) => "record".to_string(),
1037                CoercibleScalarType::Uncoerced => "unknown".to_string(),
1038            })
1039            .collect();
1040
1041        if candidates == 0 {
1042            match spec {
1043                FuncSpec::Func(name) => PlanError::UnknownFunction {
1044                    name: ecx
1045                        .qcx
1046                        .scx
1047                        .humanize_resolved_name(name)
1048                        .expect("resolved to object")
1049                        .to_string(),
1050                    arg_types,
1051                },
1052                FuncSpec::Op(name) => PlanError::UnknownOperator {
1053                    name: name.to_string(),
1054                    arg_types,
1055                },
1056            }
1057        } else {
1058            match spec {
1059                FuncSpec::Func(name) => PlanError::IndistinctFunction {
1060                    name: ecx
1061                        .qcx
1062                        .scx
1063                        .humanize_resolved_name(name)
1064                        .expect("resolved to object")
1065                        .to_string(),
1066                    arg_types,
1067                },
1068                FuncSpec::Op(name) => PlanError::IndistinctOperator {
1069                    name: name.to_string(),
1070                    arg_types,
1071                },
1072            }
1073        }
1074    })?;
1075
1076    (f.op.0)(ecx, args, &f.params, order_by)
1077}
1078
1079/// Finds an exact match based on the arguments, or, if no exact match, finds
1080/// the best match available. Patterned after [PostgreSQL's type conversion
1081/// matching algorithm][pgparser].
1082///
1083/// [pgparser]: https://www.postgresql.org/docs/current/typeconv-func.html
1084fn find_match<'a, R: std::fmt::Debug>(
1085    ecx: &ExprContext,
1086    types: &[CoercibleScalarType],
1087    impls: Vec<&'a FuncImpl<R>>,
1088) -> Result<&'a FuncImpl<R>, usize> {
1089    let all_types_known = types.iter().all(|t| t.is_coerced());
1090
1091    // Check for exact match.
1092    if all_types_known {
1093        let known_types: Vec<_> = types.iter().filter_map(|t| t.as_coerced()).collect();
1094        let matching_impls: Vec<&FuncImpl<_>> = impls
1095            .iter()
1096            .filter(|i| i.params.exact_match(&known_types))
1097            .cloned()
1098            .collect();
1099
1100        if matching_impls.len() == 1 {
1101            return Ok(matching_impls[0]);
1102        }
1103    }
1104
1105    // No exact match. Apply PostgreSQL's best match algorithm. Generate
1106    // candidates by assessing their compatibility with each implementation's
1107    // parameters.
1108    let mut candidates: Vec<Candidate<_>> = Vec::new();
1109    macro_rules! maybe_get_last_candidate {
1110        () => {
1111            if candidates.len() == 1 {
1112                return Ok(&candidates[0].fimpl);
1113            }
1114        };
1115    }
1116    let mut max_exact_matches = 0;
1117
1118    for fimpl in impls {
1119        let mut exact_matches = 0;
1120        let mut preferred_types = 0;
1121        let mut near_matches = 0;
1122
1123        for (i, arg_type) in types.iter().enumerate() {
1124            let param_type = &fimpl.params[i];
1125
1126            match arg_type {
1127                CoercibleScalarType::Coerced(arg_type) => {
1128                    if param_type == arg_type {
1129                        exact_matches += 1;
1130                    }
1131                    if param_type.is_preferred_by(arg_type) {
1132                        preferred_types += 1;
1133                    }
1134                    if param_type.is_near_match(arg_type) {
1135                        near_matches += 1;
1136                    }
1137                }
1138                CoercibleScalarType::Record(_) | CoercibleScalarType::Uncoerced => {
1139                    if param_type.prefers_self() {
1140                        preferred_types += 1;
1141                    }
1142                }
1143            }
1144        }
1145
1146        // 4.a. Discard candidate functions for which the input types do not
1147        // match and cannot be converted (using an implicit conversion) to
1148        // match. unknown literals are assumed to be convertible to anything for
1149        // this purpose.
1150        max_exact_matches = std::cmp::max(max_exact_matches, exact_matches);
1151        candidates.push(Candidate {
1152            fimpl,
1153            exact_matches,
1154            preferred_types,
1155            near_matches,
1156        });
1157    }
1158
1159    if candidates.is_empty() {
1160        return Err(0);
1161    }
1162
1163    maybe_get_last_candidate!();
1164
1165    // 4.c. Run through all candidates and keep those with the most exact
1166    // matches on input types. Keep all candidates if none have exact matches.
1167    candidates.retain(|c| c.exact_matches >= max_exact_matches);
1168
1169    maybe_get_last_candidate!();
1170
1171    // 4.c.i. (MZ extension) Run through all candidates and keep those with the
1172    // most 'near' matches on input types. Keep all candidates if none have near
1173    // matches. If only one candidate remains, use it; else continue to the next
1174    // step.
1175    let mut max_near_matches = 0;
1176    for c in &candidates {
1177        max_near_matches = std::cmp::max(max_near_matches, c.near_matches);
1178    }
1179    candidates.retain(|c| c.near_matches >= max_near_matches);
1180
1181    // 4.d. Run through all candidates and keep those that accept preferred
1182    // types (of the input data type's type category) at the most positions
1183    // where type conversion will be required.
1184    let mut max_preferred_types = 0;
1185    for c in &candidates {
1186        max_preferred_types = std::cmp::max(max_preferred_types, c.preferred_types);
1187    }
1188    candidates.retain(|c| c.preferred_types >= max_preferred_types);
1189
1190    maybe_get_last_candidate!();
1191
1192    if all_types_known {
1193        return Err(candidates.len());
1194    }
1195
1196    let mut found_known = false;
1197    let mut types_match = true;
1198    let mut common_type: Option<SqlScalarType> = None;
1199
1200    for (i, arg_type) in types.iter().enumerate() {
1201        let mut selected_category: Option<TypeCategory> = None;
1202        let mut categories_match = true;
1203
1204        match arg_type {
1205            // 4.e. If any input arguments are unknown, check the type
1206            // categories accepted at those argument positions by the remaining
1207            // candidates.
1208            CoercibleScalarType::Uncoerced | CoercibleScalarType::Record(_) => {
1209                for c in candidates.iter() {
1210                    let this_category = TypeCategory::from_param(&c.fimpl.params[i]);
1211                    // 4.e. cont: Select the string category if any candidate
1212                    // accepts that category. (This bias towards string is
1213                    // appropriate since an unknown-type literal looks like a
1214                    // string.)
1215                    if this_category == TypeCategory::String {
1216                        selected_category = Some(TypeCategory::String);
1217                        break;
1218                    }
1219                    match selected_category {
1220                        Some(ref mut selected_category) => {
1221                            // 4.e. cont: [...otherwise,] if all the remaining candidates
1222                            // accept the same type category, select that category.
1223                            categories_match =
1224                                selected_category == &this_category && categories_match;
1225                        }
1226                        None => selected_category = Some(this_category.clone()),
1227                    }
1228                }
1229
1230                // 4.e. cont: Otherwise fail because the correct choice cannot
1231                // be deduced without more clues.
1232                // (ed: this doesn't mean fail entirely, simply moving onto 4.f)
1233                if selected_category != Some(TypeCategory::String) && !categories_match {
1234                    break;
1235                }
1236
1237                // 4.e. cont: Now discard candidates that do not accept the
1238                // selected type category. Furthermore, if any candidate accepts
1239                // a preferred type in that category, discard candidates that
1240                // accept non-preferred types for that argument.
1241                let selected_category = selected_category.unwrap();
1242
1243                let preferred_type = selected_category.preferred_type();
1244                let mut found_preferred_type_candidate = false;
1245                candidates.retain(|c| {
1246                    if let Some(typ) = &preferred_type {
1247                        found_preferred_type_candidate = c.fimpl.params[i].accepts_type(ecx, typ)
1248                            || found_preferred_type_candidate;
1249                    }
1250                    selected_category == TypeCategory::from_param(&c.fimpl.params[i])
1251                });
1252
1253                if found_preferred_type_candidate {
1254                    let preferred_type = preferred_type.unwrap();
1255                    candidates.retain(|c| c.fimpl.params[i].accepts_type(ecx, &preferred_type));
1256                }
1257            }
1258            CoercibleScalarType::Coerced(typ) => {
1259                found_known = true;
1260                // Track if all known types are of the same type; use this info
1261                // in 4.f.
1262                match common_type {
1263                    Some(ref common_type) => types_match = common_type == typ && types_match,
1264                    None => common_type = Some(typ.clone()),
1265                }
1266            }
1267        }
1268    }
1269
1270    maybe_get_last_candidate!();
1271
1272    // 4.f. If there are both unknown and known-type arguments, and all the
1273    // known-type arguments have the same type, assume that the unknown
1274    // arguments are also of that type, and check which candidates can accept
1275    // that type at the unknown-argument positions.
1276    // (ed: We know unknown argument exists if we're in this part of the code.)
1277    if found_known && types_match {
1278        let common_type = common_type.unwrap();
1279        let common_typed: Vec<_> = types
1280            .iter()
1281            .map(|t| match t {
1282                CoercibleScalarType::Coerced(t) => CoercibleScalarType::Coerced(t.clone()),
1283                CoercibleScalarType::Uncoerced | CoercibleScalarType::Record(_) => {
1284                    CoercibleScalarType::Coerced(common_type.clone())
1285                }
1286            })
1287            .collect();
1288
1289        candidates.retain(|c| c.fimpl.params.matches_argtypes(ecx, &common_typed));
1290
1291        maybe_get_last_candidate!();
1292    }
1293
1294    Err(candidates.len())
1295}
1296
1297#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
1298enum PolymorphicCompatClass {
1299    /// Represents the older "Any"-style matching of PG polymorphic types, which
1300    /// constrains all types to be of the same type, i.e. does not attempt to
1301    /// promote parameters to a best common type.
1302    Any,
1303    /// Represent's Postgres' "anycompatible"-type polymorphic resolution.
1304    ///
1305    /// > Selection of the common type considers the actual types of
1306    /// > anycompatible and anycompatiblenonarray inputs, the array element
1307    /// > types of anycompatiblearray inputs, the range subtypes of
1308    /// > anycompatiblerange inputs, and the multirange subtypes of
1309    /// > anycompatiblemultirange inputs. If anycompatiblenonarray is present
1310    /// > then the common type is required to be a non-array type. Once a common
1311    /// > type is identified, arguments in anycompatible and
1312    /// > anycompatiblenonarray positions are automatically cast to that type,
1313    /// > and arguments in anycompatiblearray positions are automatically cast
1314    /// > to the array type for that type.
1315    ///
1316    /// For details, see
1317    /// <https://www.postgresql.org/docs/current/extend-type-system.html#EXTEND-TYPES-POLYMORPHIC>
1318    BestCommonAny,
1319    /// Represents polymorphic compatibility operations for Materialize LIST
1320    /// types. This differs from PG's "anycompatible" type resolution, which
1321    /// focuses on determining a common type, and e.g. using that as
1322    /// `AnyCompatibleArray`'s element type. Instead, our list compatibility
1323    /// focuses on finding a common list type, and then casting
1324    /// `ListElementAnyCompatible` parameters to that list type's elements. This
1325    /// approach is necessary to let us polymorphically resolve custom list
1326    /// types without losing their OIDs.
1327    BestCommonList,
1328    /// Represents an operation similar to LIST compatibility, but for MAP. This
1329    /// is distinct from `BestCommonList` in as much as the parameter types that
1330    /// work with `BestCommonList` are incommensurate with the parameter types
1331    /// used with `BestCommonMap`.
1332    BestCommonMap,
1333    /// Represents type resolution for `SqlScalarType::Record` types, which e.g.
1334    /// ignores custom types and type modifications.
1335    ///
1336    /// In [PG], this is handled by invocation of the function calls that take
1337    /// `RecordAny` params, which we want to avoid if at all possible.
1338    ///
1339    /// [PG]: https://github.com/postgres/postgres/blob/
1340    ///     33a377608fc29cdd1f6b63be561eab0aee5c81f0/
1341    ///     src/backend/utils/adt/rowtypes.c#L1041
1342    StructuralEq,
1343}
1344
1345impl TryFrom<&ParamType> for PolymorphicCompatClass {
1346    type Error = ();
1347    fn try_from(param: &ParamType) -> Result<PolymorphicCompatClass, Self::Error> {
1348        use ParamType::*;
1349
1350        Ok(match param {
1351            AnyElement | ArrayAny | ListAny | MapAny | NonVecAny | RangeAny => {
1352                PolymorphicCompatClass::Any
1353            }
1354            ArrayAnyCompatible | AnyCompatible | RangeAnyCompatible | NonVecAnyCompatible => {
1355                PolymorphicCompatClass::BestCommonAny
1356            }
1357            ListAnyCompatible | ListElementAnyCompatible => PolymorphicCompatClass::BestCommonList,
1358            MapAnyCompatible => PolymorphicCompatClass::BestCommonMap,
1359            RecordAny => PolymorphicCompatClass::StructuralEq,
1360            _ => return Err(()),
1361        })
1362    }
1363}
1364
1365impl PolymorphicCompatClass {
1366    fn compatible(&self, ecx: &ExprContext, from: &SqlScalarType, to: &SqlScalarType) -> bool {
1367        use PolymorphicCompatClass::*;
1368        match self {
1369            StructuralEq => from.structural_eq(to),
1370            Any => from.base_eq(to),
1371            _ => typeconv::can_cast(ecx, CastContext::Implicit, from, to),
1372        }
1373    }
1374}
1375
1376/// Represents a solution to a set of polymorphic constraints, expressed as the
1377/// `params` of a function and the user-supplied `args`.
1378#[derive(Debug)]
1379pub(crate) struct PolymorphicSolution {
1380    /// Constrains this solution to a particular form of polymorphic
1381    /// compatibility.
1382    compat: Option<PolymorphicCompatClass>,
1383    seen: Vec<CoercibleScalarType>,
1384    /// An internal representation of the discovered polymorphic type.
1385    key: Option<SqlScalarType>,
1386}
1387
1388impl PolymorphicSolution {
1389    /// Provides a solution to the polymorphic type constraints expressed in
1390    /// `params` based on the users' input in `args`. Returns `None` if a
1391    /// solution cannot be found.
1392    ///
1393    /// After constructing the `PolymorphicSolution`, access its solution using
1394    /// [`PolymorphicSolution::target_for_param_type`].
1395    fn new(
1396        ecx: &ExprContext,
1397        args: &[CoercibleScalarType],
1398        params: &ParamList,
1399    ) -> Option<PolymorphicSolution> {
1400        let mut r = PolymorphicSolution {
1401            compat: None,
1402            seen: vec![],
1403            key: None,
1404        };
1405
1406        for (i, scalar_type) in args.iter().cloned().enumerate() {
1407            r.track_seen(&params[i], scalar_type);
1408        }
1409
1410        if !r.determine_key(ecx) { None } else { Some(r) }
1411    }
1412
1413    /// Determines the desired type of polymorphic compatibility, as well as the
1414    /// values to determine a polymorphic solution.
1415    fn track_seen(&mut self, param: &ParamType, seen: CoercibleScalarType) {
1416        use ParamType::*;
1417
1418        self.seen.push(match param {
1419            // These represent the keys of their respective compatibility classes.
1420            AnyElement | AnyCompatible | ListAnyCompatible | MapAnyCompatible | NonVecAny
1421            | RecordAny => seen,
1422            MapAny => seen.map_coerced(|array| array.unwrap_map_value_type().clone()),
1423            ListAny => seen.map_coerced(|array| array.unwrap_list_element_type().clone()),
1424            ArrayAny | ArrayAnyCompatible => {
1425                seen.map_coerced(|array| array.unwrap_array_element_type().clone())
1426            }
1427            RangeAny | RangeAnyCompatible => {
1428                seen.map_coerced(|range| range.unwrap_range_element_type().clone())
1429            }
1430            ListElementAnyCompatible => seen.map_coerced(|el| SqlScalarType::List {
1431                custom_id: None,
1432                element_type: Box::new(el),
1433            }),
1434            o => {
1435                assert!(
1436                    !o.is_polymorphic(),
1437                    "polymorphic parameters must track types they \
1438                     encounter to determine polymorphic solution"
1439                );
1440                return;
1441            }
1442        });
1443
1444        let compat_class = param
1445            .try_into()
1446            .expect("already returned for non-polymorphic params");
1447
1448        match &self.compat {
1449            None => self.compat = Some(compat_class),
1450            Some(c) => {
1451                assert_eq!(
1452                    c, &compat_class,
1453                    "do not know how to correlate polymorphic classes {:?} and {:?}",
1454                    c, compat_class,
1455                )
1456            }
1457        };
1458    }
1459
1460    /// Attempt to resolve all polymorphic types to a single "key" type. For
1461    /// `target_for_param_type` to be useful, this must have already been
1462    /// called.
1463    fn determine_key(&mut self, ecx: &ExprContext) -> bool {
1464        self.key = if !self.seen.iter().any(|v| v.is_coerced()) {
1465            match &self.compat {
1466                // No encountered param was polymorphic
1467                None => None,
1468                // Params were polymorphic, but we never received a known type.
1469                // This cannot be delegated to `guess_best_common_type`, which
1470                // will incorrectly guess string, which is incompatible with
1471                // `BestCommonList`, `BestCommonMap`.
1472                Some(t) => match t {
1473                    PolymorphicCompatClass::BestCommonAny => Some(SqlScalarType::String),
1474                    PolymorphicCompatClass::BestCommonList => Some(SqlScalarType::List {
1475                        custom_id: None,
1476                        element_type: Box::new(SqlScalarType::String),
1477                    }),
1478                    PolymorphicCompatClass::BestCommonMap => Some(SqlScalarType::Map {
1479                        value_type: Box::new(SqlScalarType::String),
1480                        custom_id: None,
1481                    }),
1482                    // Do not infer type.
1483                    PolymorphicCompatClass::StructuralEq | PolymorphicCompatClass::Any => None,
1484                },
1485            }
1486        } else {
1487            // If we saw any polymorphic parameters, we must have determined the
1488            // compatibility type.
1489            let compat = self.compat.as_ref().unwrap();
1490
1491            let r = match compat {
1492                PolymorphicCompatClass::Any => {
1493                    let mut s = self
1494                        .seen
1495                        .iter()
1496                        .filter_map(|f| f.as_coerced().cloned())
1497                        .collect::<Vec<_>>();
1498                    let (candiate, remaining) =
1499                        s.split_first().expect("have at least one non-None element");
1500                    if remaining.iter().all(|r| r.base_eq(candiate)) {
1501                        s.remove(0)
1502                    } else {
1503                        return false;
1504                    }
1505                }
1506                _ => match typeconv::guess_best_common_type(ecx, &self.seen) {
1507                    Ok(r) => r,
1508                    Err(_) => return false,
1509                },
1510            };
1511
1512            // Ensure the best common type is compatible.
1513            for t in self.seen.iter() {
1514                if let CoercibleScalarType::Coerced(t) = t {
1515                    if !compat.compatible(ecx, t, &r) {
1516                        return false;
1517                    }
1518                }
1519            }
1520            Some(r)
1521        };
1522
1523        true
1524    }
1525
1526    // Determines the appropriate `SqlScalarType` for the given `ParamType` based
1527    // on the polymorphic solution.
1528    fn target_for_param_type(&self, param: &ParamType) -> Option<SqlScalarType> {
1529        use ParamType::*;
1530        assert_eq!(
1531            self.compat,
1532            Some(
1533                param
1534                    .try_into()
1535                    .expect("target_for_param_type only supports polymorphic parameters")
1536            ),
1537            "cannot use polymorphic solution for different compatibility classes"
1538        );
1539
1540        assert!(
1541            !matches!(param, RecordAny),
1542            "RecordAny should not be cast to a target type"
1543        );
1544
1545        match param {
1546            AnyElement | AnyCompatible | ListAnyCompatible | MapAnyCompatible | NonVecAny => {
1547                self.key.clone()
1548            }
1549            ArrayAny | ArrayAnyCompatible => self
1550                .key
1551                .as_ref()
1552                .map(|key| SqlScalarType::Array(Box::new(key.clone()))),
1553            ListAny => self.key.as_ref().map(|key| SqlScalarType::List {
1554                element_type: Box::new(key.clone()),
1555                custom_id: None,
1556            }),
1557            MapAny => self.key.as_ref().map(|key| SqlScalarType::Map {
1558                value_type: Box::new(key.clone()),
1559                custom_id: None,
1560            }),
1561            RangeAny | RangeAnyCompatible => self.key.as_ref().map(|key| SqlScalarType::Range {
1562                element_type: Box::new(key.clone()),
1563            }),
1564            ListElementAnyCompatible => self
1565                .key
1566                .as_ref()
1567                .map(|key| key.unwrap_list_element_type().clone()),
1568            _ => unreachable!(
1569                "cannot use polymorphic solution to resolve target type for param {:?}",
1570                param,
1571            ),
1572        }
1573    }
1574}
1575
1576fn coerce_args_to_types(
1577    ecx: &ExprContext,
1578    args: Vec<CoercibleScalarExpr>,
1579    params: &ParamList,
1580) -> Result<Vec<HirScalarExpr>, PlanError> {
1581    use ParamType::*;
1582
1583    let mut scalar_types: Vec<_> = args.iter().map(|e| ecx.scalar_type(e)).collect();
1584
1585    // See comment in `select_impl`.
1586    for ty in &mut scalar_types {
1587        ty.force_coerced_if_record();
1588    }
1589
1590    let polymorphic_solution = PolymorphicSolution::new(ecx, &scalar_types, params)
1591        .expect("polymorphic solution previously determined to be valid");
1592
1593    let do_convert =
1594        |arg: CoercibleScalarExpr, ty: &SqlScalarType| arg.cast_to(ecx, CastContext::Implicit, ty);
1595
1596    let mut res_exprs = Vec::with_capacity(args.len());
1597    for (i, cexpr) in args.into_iter().enumerate() {
1598        let expr = match &params[i] {
1599            Any => match cexpr {
1600                CoercibleScalarExpr::Parameter(n) => {
1601                    sql_bail!("could not determine data type of parameter ${}", n)
1602                }
1603                _ => cexpr.type_as_any(ecx)?,
1604            },
1605            RecordAny => match cexpr {
1606                CoercibleScalarExpr::LiteralString(_) => {
1607                    sql_bail!("input of anonymous composite types is not implemented");
1608                }
1609                // By passing the creation of the polymorphic solution, we've
1610                // already ensured that all of the record types are
1611                // intrinsically well-typed enough to move onto the next step.
1612                _ => cexpr.type_as_any(ecx)?,
1613            },
1614            Plain(ty) => do_convert(cexpr, ty)?,
1615            Internal => return Err(PlanError::InternalFunctionCall),
1616            p => {
1617                let target = polymorphic_solution
1618                    .target_for_param_type(p)
1619                    .ok_or_else(|| {
1620                        // n.b. This errors here, rather than during building
1621                        // the polymorphic solution, to make the error clearer.
1622                        // If we errored while constructing the polymorphic
1623                        // solution, an implementation would get discarded even
1624                        // if it were the only one, and it would appear as if a
1625                        // compatible solution did not exist. Instead, the
1626                        // problem is simply that we couldn't resolve the
1627                        // polymorphic type.
1628                        PlanError::UnsolvablePolymorphicFunctionInput
1629                    })?;
1630                if let SqlScalarType::Array(elem) = &target {
1631                    if matches!(
1632                        **elem,
1633                        SqlScalarType::List { .. } | SqlScalarType::Map { .. }
1634                    ) {
1635                        bail_unsupported!(format!(
1636                            "{}[]",
1637                            ecx.humanize_sql_scalar_type(elem, false)
1638                        ));
1639                    }
1640                }
1641                do_convert(cexpr, &target)?
1642            }
1643        };
1644        res_exprs.push(expr);
1645    }
1646
1647    Ok(res_exprs)
1648}
1649
1650/// Provides shorthand for converting `Vec<SqlScalarType>` into `Vec<ParamType>`.
1651macro_rules! params {
1652    ([$($p:expr),*], $v:ident...) => {
1653        ParamList::Variadic {
1654            leading: vec![$($p.into(),)*],
1655            trailing: $v.into(),
1656        }
1657    };
1658    ($v:ident...) => { ParamList::Variadic { leading: vec![], trailing: $v.into() } };
1659    ($($p:expr),*) => { ParamList::Exact(vec![$($p.into(),)*]) };
1660}
1661
1662macro_rules! impl_def {
1663    // Return type explicitly specified. This must be the case in situations
1664    // such as:
1665    // - Polymorphic functions: We have no way of understanding if the input
1666    //   type affects the return type, so you must tell us what the return type
1667    //   is.
1668    // - Explicitly defined Operations whose returned expression does not
1669    //   appropriately correlate to the function itself, e.g. returning a
1670    //   UnaryFunc from a FuncImpl that takes two parameters.
1671    // - Unimplemented/catalog-only functions
1672    ($params:expr, $op:expr, $return_type:expr, $oid:expr) => {{
1673        FuncImpl {
1674            oid: $oid,
1675            params: $params.into(),
1676            op: $op.into(),
1677            return_type: $return_type.into(),
1678        }
1679    }};
1680}
1681
1682/// Constructs builtin function map.
1683macro_rules! builtins {
1684    {
1685        $(
1686            $name:expr => $ty:ident {
1687                $($params:expr => $op:expr => $return_type:expr, $oid:expr;)+
1688            }
1689        ),+
1690    } => {{
1691
1692        let mut builtins = BTreeMap::new();
1693        $(
1694            let impls = vec![$(impl_def!($params, $op, $return_type, $oid)),+];
1695            let func = Func::$ty(impls);
1696            let expect_set_return = matches!(&func, Func::Table(_));
1697            for imp in func.func_impls() {
1698                assert_eq!(
1699                    imp.return_is_set, expect_set_return,
1700                    "wrong set return value for func with oid {}",
1701                    imp.oid
1702                );
1703            }
1704            let old = builtins.insert($name, func);
1705            mz_ore::assert_none!(old, "duplicate entry in builtins list");
1706        )+
1707        builtins
1708    }};
1709}
1710
1711#[derive(Debug)]
1712pub struct TableFuncPlan {
1713    pub imp: TableFuncImpl,
1714    pub column_names: Vec<ColumnName>,
1715}
1716
1717/// The implementation of a table function is either
1718/// 1. just a `CallTable` HIR node (in which case we can put `WITH ORDINALITY` into it when we
1719///    create the actual HIR node in `plan_table_function_internal`),
1720/// 2. or a general HIR expression. This happens when it's implemented as SQL, i.e., by a call to
1721///    `sql_impl_table_func_inner`.
1722///
1723/// TODO(ggevay, database-issues#9598): when a table function in 2. is used with WITH ORDINALITY or
1724/// ROWS FROM, we can't use the new implementation of WITH ORDINALITY. Depending on
1725/// `enable_with_ordinality_legacy_fallback`, we either fall back to the legacy implementation or
1726/// error out the query planning. The legacy WITH ORDINALITY implementation relies on the
1727/// row_number window function, and is mostly broken. It can give an incorrect ordering, and also
1728/// has an extreme performance problem in some cases. Discussed in
1729/// <https://github.com/MaterializeInc/database-issues/issues/4764#issuecomment-2854572614>
1730///
1731/// These table functions are somewhat exotic, and WITH ORDINALITY / ROWS FROM are also somewhat
1732/// exotic, so let's hope that the combination of these is so exotic that nobody will need it for
1733/// quite a while. Note that the SQL standard only allows WITH ORDINALITY on `unnest_...` functions,
1734/// of which none fall into the 2. category, so we are fine with these; it's only a Postgres
1735/// extension to support WITH ORDINALITY on arbitrary table functions. When this combination arises,
1736/// we emit a Sentry error, so that we'll know about it.
1737///
1738/// When we eventually need to fix this, a possible approach would be to write two SQL
1739/// implementations: one would be their current implementation, and the other would be
1740/// WITH ORDINALITY.
1741/// - This will be trivial for some table functions, e.g., those that end with an UNNEST just need a
1742///   WITH ORDINALITY on this UNNEST (e.g., `regexp_split_to_table`).
1743/// - `_pg_expandarray` and `date_bin_hopping` also look easy.
1744/// - `mz_name_rank` and `mz_resolve_object_name` look more complicated but hopefully solvable.
1745///
1746/// Another approach to fixing this would be to add an ORDER BY to the SQL definitions and then
1747/// move this ORDER BY into a row_number window function call. This would at least solve the
1748/// correctness problem, but not the performance problem.
1749#[derive(Debug)]
1750pub enum TableFuncImpl {
1751    CallTable {
1752        func: TableFunc,
1753        exprs: Vec<HirScalarExpr>,
1754    },
1755    Expr(HirRelationExpr),
1756}
1757
1758#[derive(Debug)]
1759pub enum Func {
1760    Scalar(Vec<FuncImpl<HirScalarExpr>>),
1761    Aggregate(Vec<FuncImpl<(HirScalarExpr, AggregateFunc)>>),
1762    Table(Vec<FuncImpl<TableFuncPlan>>),
1763    ScalarWindow(Vec<FuncImpl<ScalarWindowFunc>>),
1764    ValueWindow(Vec<FuncImpl<(HirScalarExpr, ValueWindowFunc)>>),
1765}
1766
1767impl Func {
1768    pub fn func_impls(&self) -> Vec<FuncImplCatalogDetails> {
1769        match self {
1770            Func::Scalar(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1771            Func::Aggregate(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1772            Func::Table(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1773            Func::ScalarWindow(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1774            Func::ValueWindow(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1775        }
1776    }
1777
1778    pub fn class(&self) -> &str {
1779        match self {
1780            Func::Scalar(..) => "scalar",
1781            Func::Aggregate(..) => "aggregate",
1782            Func::Table(..) => "table",
1783            Func::ScalarWindow(..) => "window",
1784            Func::ValueWindow(..) => "window",
1785        }
1786    }
1787}
1788
1789/// Functions using this macro should be transformed/planned away before
1790/// reaching function selection code, but still need to be present in the
1791/// catalog during planning.
1792macro_rules! catalog_name_only {
1793    ($name:expr) => {
1794        panic!(
1795            "{} should be planned away before reaching function selection",
1796            $name
1797        )
1798    };
1799}
1800
1801/// Generates an (OID, OID, TEXT) SQL implementation for has_X_privilege style functions.
1802macro_rules! privilege_fn {
1803    ( $fn_name:expr, $catalog_tbl:expr ) => {{
1804        let fn_name = $fn_name;
1805        let catalog_tbl = $catalog_tbl;
1806        let public_role = RoleId::Public;
1807        format!(
1808            "
1809                    CASE
1810                    -- We need to validate the privileges to return a proper error before anything
1811                    -- else.
1812                    WHEN NOT mz_internal.mz_validate_privileges($3)
1813                    OR $1 IS NULL
1814                    OR $2 IS NULL
1815                    OR $3 IS NULL
1816                    OR $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
1817                    OR $2 NOT IN (SELECT oid FROM {catalog_tbl})
1818                    THEN NULL
1819                    ELSE COALESCE(
1820                        (
1821                            SELECT
1822                                bool_or(
1823                                    mz_internal.mz_acl_item_contains_privilege(privilege, $3)
1824                                )
1825                                    AS {fn_name}
1826                            FROM
1827                                (
1828                                    SELECT
1829                                        unnest(privileges)
1830                                    FROM
1831                                        {catalog_tbl}
1832                                    WHERE
1833                                        {catalog_tbl}.oid = $2
1834                                )
1835                                    AS user_privs (privilege)
1836                                LEFT JOIN mz_catalog.mz_roles ON
1837                                        mz_internal.mz_aclitem_grantee(privilege) = mz_roles.id
1838                            WHERE
1839                                mz_internal.mz_aclitem_grantee(privilege) = '{public_role}'
1840                                OR pg_has_role($1, mz_roles.oid, 'USAGE')
1841                        ),
1842                        false
1843                    )
1844                    END
1845                ",
1846        )
1847    }};
1848}
1849
1850/// Correlates a built-in function name to its implementations.
1851pub static PG_CATALOG_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
1852    use ParamType::*;
1853    use SqlScalarBaseType::*;
1854    let mut builtins = builtins! {
1855        // Literal OIDs collected from PG 13 using a version of this query
1856        // ```sql
1857        // SELECT oid, proname, proargtypes::regtype[]
1858        // FROM pg_proc
1859        // WHERE proname IN (
1860        //      'ascii', 'array_upper', 'jsonb_build_object'
1861        // );
1862        // ```
1863        // Values are also available through
1864        // https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_proc.dat
1865
1866        // Scalars.
1867        "abs" => Scalar {
1868            params!(Int16) => UnaryFunc::AbsInt16(func::AbsInt16) => Int16, 1398;
1869            params!(Int32) => UnaryFunc::AbsInt32(func::AbsInt32) => Int32, 1397;
1870            params!(Int64) => UnaryFunc::AbsInt64(func::AbsInt64) => Int64, 1396;
1871            params!(Numeric) => UnaryFunc::AbsNumeric(func::AbsNumeric) => Numeric, 1705;
1872            params!(Float32) => UnaryFunc::AbsFloat32(func::AbsFloat32) => Float32, 1394;
1873            params!(Float64) => UnaryFunc::AbsFloat64(func::AbsFloat64) => Float64, 1395;
1874        },
1875        "aclexplode" => Table {
1876            params!(SqlScalarType::Array(Box::new(
1877                SqlScalarType::AclItem,
1878            ))) => Operation::unary(move |_ecx, aclitems| {
1879                Ok(TableFuncPlan {
1880                    imp: TableFuncImpl::CallTable {
1881                        func: TableFunc::AclExplode,
1882                        exprs: vec![aclitems],
1883                    },
1884                    column_names: vec![
1885                        "grantor".into(), "grantee".into(),
1886                        "privilege_type".into(), "is_grantable".into(),
1887                    ],
1888                })
1889            }) => ReturnType::set_of(RecordAny), 1689;
1890        },
1891        "array_cat" => Scalar {
1892            params!(ArrayAnyCompatible, ArrayAnyCompatible) => Operation::binary(|_ecx, lhs, rhs| {
1893                Ok(lhs.call_binary(rhs, func::ArrayArrayConcat))
1894            }) => ArrayAnyCompatible, 383;
1895        },
1896        "array_fill" => Scalar {
1897            params!(AnyElement, SqlScalarType::Array(Box::new(SqlScalarType::Int32)))
1898                => Operation::binary(|ecx, elem, dims| {
1899                let elem_type = ecx.scalar_type(&elem);
1900
1901                let elem_type = match elem_type.array_of_self_elem_type() {
1902                    Ok(elem_type) => elem_type,
1903                    Err(elem_type) => bail_unsupported!(
1904                        // This will be used in error msgs, therefore
1905                        // we call with `postgres_compat` false.
1906                        format!("array_fill on {}", ecx.humanize_sql_scalar_type(&elem_type, false))
1907                    ),
1908                };
1909
1910                Ok(HirScalarExpr::call_variadic(
1911                    variadic::ArrayFill { elem_type },
1912                    vec![elem, dims]
1913                ))
1914            }) => ArrayAny, 1193;
1915            params!(
1916                AnyElement,
1917                SqlScalarType::Array(Box::new(SqlScalarType::Int32)),
1918                SqlScalarType::Array(Box::new(SqlScalarType::Int32))
1919            ) => Operation::variadic(|ecx, exprs| {
1920                let elem_type = ecx.scalar_type(&exprs[0]);
1921
1922                let elem_type = match elem_type.array_of_self_elem_type() {
1923                    Ok(elem_type) => elem_type,
1924                    Err(elem_type) => bail_unsupported!(
1925                        format!("array_fill on {}", ecx.humanize_sql_scalar_type(&elem_type, false))
1926                    ),
1927                };
1928
1929                Ok(HirScalarExpr::call_variadic(variadic::ArrayFill { elem_type }, exprs))
1930            }) => ArrayAny, 1286;
1931        },
1932        "array_length" => Scalar {
1933            params![ArrayAny, Int64] => BinaryFunc::from(func::ArrayLength) => Int32, 2176;
1934        },
1935        "array_lower" => Scalar {
1936            params!(ArrayAny, Int64) => BinaryFunc::from(func::ArrayLower) => Int32, 2091;
1937        },
1938        "array_position" => Scalar {
1939            params!(ArrayAnyCompatible, AnyCompatible)
1940                => VariadicFunc::from(variadic::ArrayPosition) => Int32, 3277;
1941            params!(ArrayAnyCompatible, AnyCompatible, Int32)
1942                => VariadicFunc::from(variadic::ArrayPosition) => Int32, 3278;
1943        },
1944        "array_remove" => Scalar {
1945            params!(ArrayAnyCompatible, AnyCompatible)
1946                => BinaryFunc::from(func::ArrayRemove)
1947                => ArrayAnyCompatible, 3167;
1948        },
1949        "array_to_string" => Scalar {
1950            params!(ArrayAny, String) => Operation::variadic(array_to_string) => String, 395;
1951            params!(ArrayAny, String, String)
1952                => Operation::variadic(array_to_string) => String, 384;
1953        },
1954        "array_upper" => Scalar {
1955            params!(ArrayAny, Int64) => BinaryFunc::from(func::ArrayUpper) => Int32, 2092;
1956        },
1957        "ascii" => Scalar {
1958            params!(String) => UnaryFunc::Ascii(func::Ascii) => Int32, 1620;
1959        },
1960        "avg" => Scalar {
1961            params!(Int64) => Operation::nullary(|_ecx| catalog_name_only!("avg")) => Numeric, 2100;
1962            params!(Int32) => Operation::nullary(|_ecx| catalog_name_only!("avg")) => Numeric, 2101;
1963            params!(Int16) => Operation::nullary(|_ecx| catalog_name_only!("avg")) => Numeric, 2102;
1964            params!(UInt64) =>
1965                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1966                => Numeric, oid::FUNC_AVG_UINT64_OID;
1967            params!(UInt32) =>
1968                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1969                => Numeric, oid::FUNC_AVG_UINT32_OID;
1970            params!(UInt16) =>
1971                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1972                => Numeric, oid::FUNC_AVG_UINT16_OID;
1973            params!(Float32) =>
1974                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1975                => Float64, 2104;
1976            params!(Float64) =>
1977                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1978                => Float64, 2105;
1979            params!(Interval) =>
1980                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1981                => Interval, 2106;
1982        },
1983        "bit_count" => Scalar {
1984            params!(Bytes) => UnaryFunc::BitCountBytes(func::BitCountBytes) => Int64, 6163;
1985        },
1986        "bit_length" => Scalar {
1987            params!(Bytes) => UnaryFunc::BitLengthBytes(func::BitLengthBytes) => Int32, 1810;
1988            params!(String) => UnaryFunc::BitLengthString(func::BitLengthString) => Int32, 1811;
1989        },
1990        "btrim" => Scalar {
1991            params!(String) => UnaryFunc::TrimWhitespace(func::TrimWhitespace) => String, 885;
1992            params!(String, String) => BinaryFunc::from(func::Trim) => String, 884;
1993        },
1994        "cbrt" => Scalar {
1995            params!(Float64) => UnaryFunc::CbrtFloat64(func::CbrtFloat64) => Float64, 1345;
1996        },
1997        "ceil" => Scalar {
1998            params!(Float32) => UnaryFunc::CeilFloat32(func::CeilFloat32)
1999                => Float32, oid::FUNC_CEIL_F32_OID;
2000            params!(Float64) => UnaryFunc::CeilFloat64(func::CeilFloat64) => Float64, 2308;
2001            params!(Numeric) => UnaryFunc::CeilNumeric(func::CeilNumeric) => Numeric, 1711;
2002        },
2003        "ceiling" => Scalar {
2004            params!(Float32) => UnaryFunc::CeilFloat32(func::CeilFloat32)
2005                => Float32, oid::FUNC_CEILING_F32_OID;
2006            params!(Float64) => UnaryFunc::CeilFloat64(func::CeilFloat64) => Float64, 2320;
2007            params!(Numeric) => UnaryFunc::CeilNumeric(func::CeilNumeric) => Numeric, 2167;
2008        },
2009        "char_length" => Scalar {
2010            params!(String) => UnaryFunc::CharLength(func::CharLength) => Int32, 1381;
2011        },
2012        // SQL exactly matches PostgreSQL's implementation.
2013        "col_description" => Scalar {
2014            params!(Oid, Int32) => sql_impl_func(
2015                "(SELECT description
2016                    FROM pg_description
2017                    WHERE objoid = $1 AND classoid = 'pg_class'::regclass AND objsubid = $2)"
2018                ) => String, 1216;
2019        },
2020        "concat" => Scalar {
2021            params!(Any...) => Operation::variadic(|ecx, cexprs| {
2022                if cexprs.is_empty() {
2023                    sql_bail!("No function matches the given name and argument types. \
2024                    You might need to add explicit type casts.")
2025                }
2026                let mut exprs = vec![];
2027                for expr in cexprs {
2028                    exprs.push(match ecx.scalar_type(&expr) {
2029                        // concat uses nonstandard bool -> string casts
2030                        // to match historical baggage in PostgreSQL.
2031                        SqlScalarType::Bool => expr.call_unary(
2032                            UnaryFunc::CastBoolToStringNonstandard(
2033                                func::CastBoolToStringNonstandard,
2034                            ),
2035                        ),
2036                        // TODO(see <materialize#7572>): remove call to PadChar
2037                        SqlScalarType::Char { length } => {
2038                            expr.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
2039                        }
2040                        _ => typeconv::to_string(ecx, expr)?
2041                    });
2042                }
2043                Ok(HirScalarExpr::call_variadic(variadic::Concat, exprs))
2044            }) => String, 3058;
2045        },
2046        "concat_ws" => Scalar {
2047            params!([String], Any...) => Operation::variadic(|ecx, cexprs| {
2048                if cexprs.len() < 2 {
2049                    sql_bail!("No function matches the given name and argument types. \
2050                    You might need to add explicit type casts.")
2051                }
2052                let mut exprs = vec![];
2053                for expr in cexprs {
2054                    exprs.push(match ecx.scalar_type(&expr) {
2055                        // concat uses nonstandard bool -> string casts
2056                        // to match historical baggage in PostgreSQL.
2057                        SqlScalarType::Bool => expr.call_unary(
2058                            UnaryFunc::CastBoolToStringNonstandard(
2059                                func::CastBoolToStringNonstandard,
2060                            ),
2061                        ),
2062                        // TODO(see <materialize#7572>): remove call to PadChar
2063                        SqlScalarType::Char { length } => {
2064                            expr.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
2065                        }
2066                        _ => typeconv::to_string(ecx, expr)?
2067                    });
2068                }
2069                Ok(HirScalarExpr::call_variadic(variadic::ConcatWs, exprs))
2070            }) => String, 3059;
2071        },
2072        "convert_from" => Scalar {
2073            params!(Bytes, String) => BinaryFunc::from(func::ConvertFrom) => String, 1714;
2074        },
2075        "cos" => Scalar {
2076            params!(Float64) => UnaryFunc::Cos(func::Cos) => Float64, 1605;
2077        },
2078        "acos" => Scalar {
2079            params!(Float64) => UnaryFunc::Acos(func::Acos) => Float64, 1601;
2080        },
2081        "cosh" => Scalar {
2082            params!(Float64) => UnaryFunc::Cosh(func::Cosh) => Float64, 2463;
2083        },
2084        "acosh" => Scalar {
2085            params!(Float64) => UnaryFunc::Acosh(func::Acosh) => Float64, 2466;
2086        },
2087        "cot" => Scalar {
2088            params!(Float64) => UnaryFunc::Cot(func::Cot) => Float64, 1607;
2089        },
2090        "current_schema" => Scalar {
2091            // TODO: this should be `name`. This is tricky in Materialize
2092            // because `name` truncates to 63 characters but Materialize does
2093            // not have a limit on identifier length.
2094            params!() => UnmaterializableFunc::CurrentSchema => String, 1402;
2095        },
2096        "current_schemas" => Scalar {
2097            params!(Bool) => Operation::unary(|_ecx, e| {
2098                Ok(HirScalarExpr::if_then_else(
2099                     e,
2100                     HirScalarExpr::call_unmaterializable(
2101                         UnmaterializableFunc::CurrentSchemasWithSystem,
2102                     ),
2103                     HirScalarExpr::call_unmaterializable(
2104                         UnmaterializableFunc::CurrentSchemasWithoutSystem,
2105                     ),
2106                ))
2107                // TODO: this should be `name[]`. This is tricky in Materialize
2108                // because `name` truncates to 63 characters but Materialize
2109                // does not have a limit on identifier length.
2110            }) => SqlScalarType::Array(Box::new(SqlScalarType::String)), 1403;
2111        },
2112        "current_database" => Scalar {
2113            params!() => UnmaterializableFunc::CurrentDatabase => String, 861;
2114        },
2115        "current_catalog" => Scalar {
2116            params!() => UnmaterializableFunc::CurrentDatabase => String, oid::FUNC_CURRENT_CATALOG;
2117        },
2118        "current_setting" => Scalar {
2119            params!(String) => Operation::unary(|_ecx, name| {
2120                current_settings(name, HirScalarExpr::literal_false())
2121            }) => SqlScalarType::String, 2077;
2122            params!(String, Bool) => Operation::binary(|_ecx, name, missing_ok| {
2123                current_settings(name, missing_ok)
2124            }) => SqlScalarType::String, 3294;
2125        },
2126        "current_timestamp" => Scalar {
2127            params!() => UnmaterializableFunc::CurrentTimestamp
2128                => TimestampTz, oid::FUNC_CURRENT_TIMESTAMP_OID;
2129        },
2130        "current_user" => Scalar {
2131            params!() => UnmaterializableFunc::CurrentUser => String, 745;
2132        },
2133        "current_role" => Scalar {
2134            params!() => UnmaterializableFunc::CurrentUser => String, oid::FUNC_CURRENT_ROLE;
2135        },
2136        "user" => Scalar {
2137            params!() => UnmaterializableFunc::CurrentUser => String, oid::FUNC_USER;
2138        },
2139        "session_user" => Scalar {
2140            params!() => UnmaterializableFunc::SessionUser => String, 746;
2141        },
2142        "chr" => Scalar {
2143            params!(Int32) => UnaryFunc::Chr(func::Chr) => String, 1621;
2144        },
2145        "date" => Scalar {
2146            params!(String) => UnaryFunc::CastStringToDate(func::CastStringToDate)
2147                => Date, oid::FUNC_DATE_FROM_TEXT;
2148            params!(Timestamp) => UnaryFunc::CastTimestampToDate(func::CastTimestampToDate)
2149                => Date, 2029;
2150            params!(TimestampTz) => UnaryFunc::CastTimestampTzToDate(func::CastTimestampTzToDate)
2151                => Date, 1178;
2152        },
2153        "date_bin" => Scalar {
2154            params!(Interval, Timestamp) => Operation::binary(|ecx, stride, source| {
2155                ecx.require_feature_flag(&vars::ENABLE_BINARY_DATE_BIN)?;
2156                Ok(stride.call_binary(source, func::DateBinTimestamp))
2157            }) => Timestamp, oid::FUNC_MZ_DATE_BIN_UNIX_EPOCH_TS_OID;
2158            params!(Interval, TimestampTz) => Operation::binary(|ecx, stride, source| {
2159                ecx.require_feature_flag(&vars::ENABLE_BINARY_DATE_BIN)?;
2160                Ok(stride.call_binary(source, func::DateBinTimestampTz))
2161            }) => TimestampTz, oid::FUNC_MZ_DATE_BIN_UNIX_EPOCH_TSTZ_OID;
2162            params!(Interval, Timestamp, Timestamp)
2163                => VariadicFunc::from(variadic::DateBinTimestamp) => Timestamp, 6177;
2164            params!(Interval, TimestampTz, TimestampTz)
2165                => VariadicFunc::from(variadic::DateBinTimestampTz) => TimestampTz, 6178;
2166        },
2167        "extract" => Scalar {
2168            params!(String, Interval)
2169                => BinaryFunc::from(func::DatePartIntervalNumeric) => Numeric, 6204;
2170            params!(String, Time)
2171                => BinaryFunc::from(func::DatePartTimeNumeric) => Numeric, 6200;
2172            params!(String, Timestamp)
2173                => BinaryFunc::from(func::DatePartTimestampTimestampNumeric)
2174                => Numeric, 6202;
2175            params!(String, TimestampTz)
2176                => BinaryFunc::from(func::DatePartTimestampTimestampTzNumeric)
2177                => Numeric, 6203;
2178            params!(String, Date) => BinaryFunc::from(func::ExtractDateUnits) => Numeric, 6199;
2179        },
2180        "date_part" => Scalar {
2181            params!(String, Interval)
2182                => BinaryFunc::from(func::DatePartIntervalF64) => Float64, 1172;
2183            params!(String, Time)
2184                => BinaryFunc::from(func::DatePartTimeF64) => Float64, 1385;
2185            params!(String, Timestamp)
2186                => BinaryFunc::from(func::DatePartTimestampTimestampF64)
2187                => Float64, 2021;
2188            params!(String, TimestampTz)
2189                => BinaryFunc::from(func::DatePartTimestampTimestampTzF64)
2190                => Float64, 1171;
2191        },
2192        "date_trunc" => Scalar {
2193            params!(String, Timestamp)
2194                => BinaryFunc::from(func::DateTruncUnitsTimestamp) => Timestamp, 2020;
2195            params!(String, TimestampTz)
2196                => BinaryFunc::from(func::DateTruncUnitsTimestampTz)
2197                => TimestampTz, 1217;
2198            params!(String, Interval)
2199                => BinaryFunc::from(func::DateTruncInterval) => Interval, 1218;
2200        },
2201        "daterange" => Scalar {
2202            params!(Date, Date) => Operation::variadic(|_ecx, mut exprs| {
2203                exprs.push(HirScalarExpr::literal(
2204                    Datum::String("[)"), SqlScalarType::String,
2205                ));
2206                Ok(HirScalarExpr::call_variadic(
2207                    variadic::RangeCreate { elem_type: SqlScalarType::Date },
2208                    exprs,
2209                ))
2210            }) => SqlScalarType::Range {
2211                element_type: Box::new(SqlScalarType::Date),
2212            }, 3941;
2213            params!(Date, Date, String) => Operation::variadic(|_ecx, exprs| {
2214                Ok(HirScalarExpr::call_variadic(
2215                    variadic::RangeCreate { elem_type: SqlScalarType::Date },
2216                    exprs,
2217                ))
2218            }) => SqlScalarType::Range {
2219                element_type: Box::new(SqlScalarType::Date),
2220            }, 3942;
2221        },
2222        "degrees" => Scalar {
2223            params!(Float64) => UnaryFunc::Degrees(func::Degrees) => Float64, 1608;
2224        },
2225        "digest" => Scalar {
2226            params!(String, String) => BinaryFunc::from(func::DigestString)
2227                => Bytes, oid::FUNC_PG_DIGEST_STRING;
2228            params!(Bytes, String) => BinaryFunc::from(func::DigestBytes)
2229                => Bytes, oid::FUNC_PG_DIGEST_BYTES;
2230        },
2231        "exp" => Scalar {
2232            params!(Float64) => UnaryFunc::Exp(func::Exp) => Float64, 1347;
2233            params!(Numeric) => UnaryFunc::ExpNumeric(func::ExpNumeric) => Numeric, 1732;
2234        },
2235        "floor" => Scalar {
2236            params!(Float32) => UnaryFunc::FloorFloat32(func::FloorFloat32)
2237                => Float32, oid::FUNC_FLOOR_F32_OID;
2238            params!(Float64) => UnaryFunc::FloorFloat64(func::FloorFloat64) => Float64, 2309;
2239            params!(Numeric) => UnaryFunc::FloorNumeric(func::FloorNumeric) => Numeric, 1712;
2240        },
2241        "format_type" => Scalar {
2242            params!(Oid, Int32) => sql_impl_func(
2243                "CASE
2244                        WHEN $1 IS NULL THEN NULL
2245                        -- timestamp and timestamptz have the typmod in
2246                        -- a nonstandard location that requires special
2247                        -- handling.
2248                        WHEN $1 = 1114 AND $2 >= 0 THEN 'timestamp(' || $2 || ') without time zone'
2249                        WHEN $1 = 1184 AND $2 >= 0 THEN 'timestamp(' || $2 || ') with time zone'
2250                        ELSE coalesce(
2251                            (SELECT pg_catalog.concat(
2252                                coalesce(mz_internal.mz_type_name($1), name),
2253                                mz_internal.mz_render_typmod($1, $2))
2254                             FROM mz_catalog.mz_types WHERE oid = $1),
2255                            '???')
2256                    END"
2257            ) => String, 1081;
2258        },
2259        "get_bit" => Scalar {
2260            params!(Bytes, Int32) => BinaryFunc::from(func::GetBit) => Int32, 723;
2261        },
2262        "get_byte" => Scalar {
2263            params!(Bytes, Int32) => BinaryFunc::from(func::GetByte) => Int32, 721;
2264        },
2265        "pg_get_ruledef" => Scalar {
2266            params!(Oid) => sql_impl_func("NULL::pg_catalog.text") => String, 1573;
2267            params!(Oid, Bool) => sql_impl_func("NULL::pg_catalog.text") => String, 2504;
2268        },
2269        "has_schema_privilege" => Scalar {
2270            params!(String, String, String) => sql_impl_func(
2271                "has_schema_privilege(\
2272                 mz_internal.mz_role_oid($1), \
2273                 mz_internal.mz_schema_oid($2), $3)",
2274            ) => Bool, 2268;
2275            params!(String, Oid, String) => sql_impl_func(
2276                "has_schema_privilege(\
2277                 mz_internal.mz_role_oid($1), $2, $3)",
2278            ) => Bool, 2269;
2279            params!(Oid, String, String) => sql_impl_func(
2280                "has_schema_privilege(\
2281                 $1, mz_internal.mz_schema_oid($2), $3)",
2282            ) => Bool, 2270;
2283            params!(Oid, Oid, String) => sql_impl_func(
2284                &privilege_fn!(
2285                    "has_schema_privilege", "mz_schemas"
2286                ),
2287            ) => Bool, 2271;
2288            params!(String, String) => sql_impl_func(
2289                "has_schema_privilege(current_user, $1, $2)",
2290            ) => Bool, 2272;
2291            params!(Oid, String) => sql_impl_func(
2292                "has_schema_privilege(current_user, $1, $2)",
2293            ) => Bool, 2273;
2294        },
2295        "has_database_privilege" => Scalar {
2296            params!(String, String, String) => sql_impl_func(
2297                "has_database_privilege(\
2298                 mz_internal.mz_role_oid($1), \
2299                 mz_internal.mz_database_oid($2), $3)",
2300            ) => Bool, 2250;
2301            params!(String, Oid, String) => sql_impl_func(
2302                "has_database_privilege(\
2303                 mz_internal.mz_role_oid($1), $2, $3)",
2304            ) => Bool, 2251;
2305            params!(Oid, String, String) => sql_impl_func(
2306                "has_database_privilege(\
2307                 $1, mz_internal.mz_database_oid($2), $3)",
2308            ) => Bool, 2252;
2309            params!(Oid, Oid, String) => sql_impl_func(
2310                &privilege_fn!(
2311                    "has_database_privilege", "mz_databases"
2312                ),
2313            ) => Bool, 2253;
2314            params!(String, String) => sql_impl_func(
2315                "has_database_privilege(current_user, $1, $2)",
2316            ) => Bool, 2254;
2317            params!(Oid, String) => sql_impl_func(
2318                "has_database_privilege(current_user, $1, $2)",
2319            ) => Bool, 2255;
2320        },
2321        "has_table_privilege" => Scalar {
2322            params!(String, String, String) => sql_impl_func(
2323                "has_table_privilege(\
2324                 mz_internal.mz_role_oid($1), \
2325                 $2::regclass::oid, $3)",
2326            ) => Bool, 1922;
2327            params!(String, Oid, String) => sql_impl_func(
2328                "has_table_privilege(\
2329                 mz_internal.mz_role_oid($1), $2, $3)",
2330            ) => Bool, 1923;
2331            params!(Oid, String, String) => sql_impl_func(
2332                "has_table_privilege(\
2333                 $1, $2::regclass::oid, $3)",
2334            ) => Bool, 1924;
2335            params!(Oid, Oid, String) => sql_impl_func(
2336                &privilege_fn!(
2337                    "has_table_privilege", "mz_relations"
2338                ),
2339            ) => Bool, 1925;
2340            params!(String, String) => sql_impl_func(
2341                "has_table_privilege(current_user, $1, $2)",
2342            ) => Bool, 1926;
2343            params!(Oid, String) => sql_impl_func(
2344                "has_table_privilege(current_user, $1, $2)",
2345            ) => Bool, 1927;
2346        },
2347        "hmac" => Scalar {
2348            params!(String, String, String) => VariadicFunc::from(variadic::HmacString)
2349                => Bytes, oid::FUNC_PG_HMAC_STRING;
2350            params!(Bytes, Bytes, String) => VariadicFunc::from(variadic::HmacBytes)
2351                => Bytes, oid::FUNC_PG_HMAC_BYTES;
2352        },
2353        "initcap" => Scalar {
2354            params!(String) => UnaryFunc::Initcap(func::Initcap) => String, 872;
2355        },
2356        "int4range" => Scalar {
2357            params!(Int32, Int32) => Operation::variadic(|_ecx, mut exprs| {
2358                exprs.push(HirScalarExpr::literal(
2359                    Datum::String("[)"), SqlScalarType::String,
2360                ));
2361                Ok(HirScalarExpr::call_variadic(
2362                    variadic::RangeCreate { elem_type: SqlScalarType::Int32 },
2363                    exprs,
2364                ))
2365            }) => SqlScalarType::Range {
2366                element_type: Box::new(SqlScalarType::Int32),
2367            }, 3840;
2368            params!(Int32, Int32, String) => Operation::variadic(|_ecx, exprs| {
2369                Ok(HirScalarExpr::call_variadic(
2370                    variadic::RangeCreate { elem_type: SqlScalarType::Int32 },
2371                    exprs,
2372                ))
2373            }) => SqlScalarType::Range {
2374                element_type: Box::new(SqlScalarType::Int32),
2375            }, 3841;
2376        },
2377        "int8range" => Scalar {
2378            params!(Int64, Int64) => Operation::variadic(|_ecx, mut exprs| {
2379                exprs.push(HirScalarExpr::literal(
2380                    Datum::String("[)"), SqlScalarType::String,
2381                ));
2382                Ok(HirScalarExpr::call_variadic(
2383                    variadic::RangeCreate { elem_type: SqlScalarType::Int64 },
2384                    exprs,
2385                ))
2386            }) => SqlScalarType::Range {
2387                element_type: Box::new(SqlScalarType::Int64),
2388            }, 3945;
2389            params!(Int64, Int64, String) => Operation::variadic(|_ecx, exprs| {
2390                Ok(HirScalarExpr::call_variadic(
2391                    variadic::RangeCreate { elem_type: SqlScalarType::Int64 },
2392                    exprs,
2393                ))
2394            }) => SqlScalarType::Range {
2395                element_type: Box::new(SqlScalarType::Int64),
2396            }, 3946;
2397        },
2398        "isempty" => Scalar {
2399            params!(RangeAny) => UnaryFunc::RangeEmpty(func::RangeEmpty) => Bool, 3850;
2400        },
2401        "jsonb_array_length" => Scalar {
2402            params!(Jsonb) => UnaryFunc::JsonbArrayLength(func::JsonbArrayLength) => Int32, 3207;
2403        },
2404        "jsonb_build_array" => Scalar {
2405            params!() => VariadicFunc::from(variadic::JsonbBuildArray) => Jsonb, 3272;
2406            params!(Any...) => Operation::variadic(|ecx, exprs| {
2407                Ok(HirScalarExpr::call_variadic(
2408                    variadic::JsonbBuildArray,
2409                    exprs
2410                        .into_iter()
2411                        .map(|e| typeconv::to_jsonb(ecx, e))
2412                        .collect::<Result<Vec<_>, _>>()?,
2413                ))
2414            }) => Jsonb, 3271;
2415        },
2416        "jsonb_build_object" => Scalar {
2417            params!() => VariadicFunc::from(variadic::JsonbBuildObject) => Jsonb, 3274;
2418            params!(Any...) => Operation::variadic(|ecx, exprs| {
2419                if exprs.len() % 2 != 0 {
2420                    sql_bail!("argument list must have even number of elements")
2421                }
2422                let mut elems = Vec::with_capacity(exprs.len());
2423                for (key, val) in exprs.into_iter().tuples() {
2424                    elems.push(typeconv::to_string(ecx, key)?);
2425                    elems.push(typeconv::to_jsonb(ecx, val)?);
2426                }
2427                Ok(HirScalarExpr::call_variadic(variadic::JsonbBuildObject, elems))
2428            }) => Jsonb, 3273;
2429        },
2430        "jsonb_pretty" => Scalar {
2431            params!(Jsonb) => UnaryFunc::JsonbPretty(func::JsonbPretty) => String, 3306;
2432        },
2433        "jsonb_strip_nulls" => Scalar {
2434            params!(Jsonb) => UnaryFunc::JsonbStripNulls(func::JsonbStripNulls) => Jsonb, 3262;
2435        },
2436        "jsonb_typeof" => Scalar {
2437            params!(Jsonb) => UnaryFunc::JsonbTypeof(func::JsonbTypeof) => String, 3210;
2438        },
2439        "justify_days" => Scalar {
2440            params!(Interval) => UnaryFunc::JustifyDays(func::JustifyDays) => Interval, 1295;
2441        },
2442        "justify_hours" => Scalar {
2443            params!(Interval) => UnaryFunc::JustifyHours(func::JustifyHours) => Interval, 1175;
2444        },
2445        "justify_interval" => Scalar {
2446            params!(Interval) => UnaryFunc::JustifyInterval(func::JustifyInterval)
2447                => Interval, 2711;
2448        },
2449        "left" => Scalar {
2450            params!(String, Int32) => BinaryFunc::from(func::Left) => String, 3060;
2451        },
2452        "length" => Scalar {
2453            params!(Bytes) => UnaryFunc::ByteLengthBytes(func::ByteLengthBytes) => Int32, 2010;
2454            // bpcharlen is redundant with automatic coercion to string, 1318.
2455            params!(String) => UnaryFunc::CharLength(func::CharLength) => Int32, 1317;
2456            params!(Bytes, String) => BinaryFunc::from(func::EncodedBytesCharLength) => Int32, 1713;
2457        },
2458        "like_escape" => Scalar {
2459            params!(String, String) => BinaryFunc::from(func::LikeEscape) => String, 1637;
2460        },
2461        "ln" => Scalar {
2462            params!(Float64) => UnaryFunc::Ln(func::Ln) => Float64, 1341;
2463            params!(Numeric) => UnaryFunc::LnNumeric(func::LnNumeric) => Numeric, 1734;
2464        },
2465        "log10" => Scalar {
2466            params!(Float64) => UnaryFunc::Log10(func::Log10) => Float64, 1194;
2467            params!(Numeric) => UnaryFunc::Log10Numeric(func::Log10Numeric) => Numeric, 1481;
2468        },
2469        "log" => Scalar {
2470            params!(Float64) => UnaryFunc::Log10(func::Log10) => Float64, 1340;
2471            params!(Numeric) => UnaryFunc::Log10Numeric(func::Log10Numeric) => Numeric, 1741;
2472            params!(Numeric, Numeric) => BinaryFunc::from(func::LogBaseNumeric) => Numeric, 1736;
2473        },
2474        "lower" => Scalar {
2475            params!(String) => UnaryFunc::Lower(func::Lower) => String, 870;
2476            params!(RangeAny) => UnaryFunc::RangeLower(func::RangeLower) => AnyElement, 3848;
2477        },
2478        "lower_inc" => Scalar {
2479            params!(RangeAny) => UnaryFunc::RangeLowerInc(func::RangeLowerInc) => Bool, 3851;
2480        },
2481        "lower_inf" => Scalar {
2482            params!(RangeAny) => UnaryFunc::RangeLowerInf(func::RangeLowerInf) => Bool, 3853;
2483        },
2484        "lpad" => Scalar {
2485            params!(String, Int32) => VariadicFunc::from(variadic::PadLeading) => String, 879;
2486            params!(String, Int32, String) => VariadicFunc::from(variadic::PadLeading)
2487                => String, 873;
2488        },
2489        "ltrim" => Scalar {
2490            params!(String) => UnaryFunc::TrimLeadingWhitespace(
2491                func::TrimLeadingWhitespace,
2492            ) => String, 881;
2493            params!(String, String) => BinaryFunc::from(func::TrimLeading) => String, 875;
2494        },
2495        "makeaclitem" => Scalar {
2496            params!(Oid, Oid, String, Bool)
2497                => VariadicFunc::from(variadic::MakeAclItem) => AclItem, 1365;
2498        },
2499        "make_timestamp" => Scalar {
2500            params!(Int64, Int64, Int64, Int64, Int64, Float64)
2501                => VariadicFunc::from(variadic::MakeTimestamp) => Timestamp, 3461;
2502        },
2503        "md5" => Scalar {
2504            params!(String) => Operation::unary(move |_ecx, input| {
2505                let algorithm = HirScalarExpr::literal(Datum::String("md5"), SqlScalarType::String);
2506                let encoding = HirScalarExpr::literal(Datum::String("hex"), SqlScalarType::String);
2507                Ok(input
2508                    .call_binary(algorithm, func::DigestString)
2509                    .call_binary(encoding, func::Encode))
2510            }) => String, 2311;
2511            params!(Bytes) => Operation::unary(move |_ecx, input| {
2512                let algorithm = HirScalarExpr::literal(
2513                    Datum::String("md5"), SqlScalarType::String,
2514                );
2515                let encoding = HirScalarExpr::literal(
2516                    Datum::String("hex"), SqlScalarType::String,
2517                );
2518                Ok(input
2519                    .call_binary(algorithm, func::DigestBytes)
2520                    .call_binary(encoding, func::Encode))
2521            }) => String, 2321;
2522        },
2523        "mod" => Scalar {
2524            params!(Numeric, Numeric) =>
2525                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2526                => Numeric, 1728;
2527            params!(Int16, Int16) =>
2528                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2529                => Int16, 940;
2530            params!(Int32, Int32) =>
2531                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2532                => Int32, 941;
2533            params!(Int64, Int64) =>
2534                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2535                => Int64, 947;
2536            params!(UInt16, UInt16) =>
2537                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2538                => UInt16, oid::FUNC_MOD_UINT16_OID;
2539            params!(UInt32, UInt32) =>
2540                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2541                => UInt32, oid::FUNC_MOD_UINT32_OID;
2542            params!(UInt64, UInt64) =>
2543                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2544                => UInt64, oid::FUNC_MOD_UINT64_OID;
2545        },
2546        "normalize" => Scalar {
2547            // Parser always provides two arguments (defaults second to "NFC" when omitted)
2548            params!(String, String) => BinaryFunc::Normalize(func::Normalize)
2549                => String, oid::FUNC_NORMALIZE_OID;
2550        },
2551        "now" => Scalar {
2552            params!() => UnmaterializableFunc::CurrentTimestamp => TimestampTz, 1299;
2553        },
2554        "numrange" => Scalar {
2555            params!(Numeric, Numeric) => Operation::variadic(|_ecx, mut exprs| {
2556                exprs.push(HirScalarExpr::literal(
2557                    Datum::String("[)"), SqlScalarType::String,
2558                ));
2559                Ok(HirScalarExpr::call_variadic(
2560                    variadic::RangeCreate {
2561                        elem_type: SqlScalarType::Numeric { max_scale: None },
2562                    },
2563                    exprs,
2564                ))
2565            }) => SqlScalarType::Range {
2566                element_type: Box::new(SqlScalarType::Numeric { max_scale: None }),
2567            }, 3844;
2568            params!(Numeric, Numeric, String) => Operation::variadic(|_ecx, exprs| {
2569                Ok(HirScalarExpr::call_variadic(
2570                    variadic::RangeCreate {
2571                        elem_type: SqlScalarType::Numeric { max_scale: None },
2572                    },
2573                    exprs,
2574                ))
2575            }) => SqlScalarType::Range {
2576                element_type: Box::new(SqlScalarType::Numeric { max_scale: None }),
2577            }, 3845;
2578        },
2579        "octet_length" => Scalar {
2580            params!(Bytes) => UnaryFunc::ByteLengthBytes(func::ByteLengthBytes) => Int32, 720;
2581            params!(String) => UnaryFunc::ByteLengthString(func::ByteLengthString) => Int32, 1374;
2582            params!(Char) => Operation::unary(|ecx, e| {
2583                let length = ecx.scalar_type(&e).unwrap_char_length();
2584                Ok(e.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
2585                    .call_unary(UnaryFunc::ByteLengthString(func::ByteLengthString))
2586                )
2587            }) => Int32, 1375;
2588        },
2589        // SQL closely matches PostgreSQL's implementation.
2590        // We don't yet support casting to regnamespace, so use our constant for
2591        // the oid of 'pg_catalog'.
2592        "obj_description" => Scalar {
2593            params!(Oid, String) => sql_impl_func(&format!(
2594                "(SELECT description FROM pg_description
2595                  WHERE objoid = $1
2596                    AND classoid = (
2597                      SELECT oid FROM pg_class WHERE relname = $2 AND relnamespace = {})
2598                    AND objsubid = 0)",
2599                oid::SCHEMA_PG_CATALOG_OID
2600            )) => String, 1215;
2601        },
2602        "pg_column_size" => Scalar {
2603            params!(Any) => UnaryFunc::PgColumnSize(func::PgColumnSize) => Int32, 1269;
2604        },
2605        "pg_size_pretty" => Scalar {
2606            params!(Numeric) => UnaryFunc::PgSizePretty(func::PgSizePretty) => String, 3166;
2607        },
2608        "mz_row_size" => Scalar {
2609            params!(Any) => Operation::unary(|ecx, e| {
2610                let s = ecx.scalar_type(&e);
2611                if !matches!(s, SqlScalarType::Record{..}) {
2612                    sql_bail!("mz_row_size requires a record type");
2613                }
2614                Ok(e.call_unary(UnaryFunc::MzRowSize(func::MzRowSize)))
2615            }) => Int32, oid::FUNC_MZ_ROW_SIZE;
2616        },
2617        "parse_ident" => Scalar {
2618            params!(String) => Operation::unary(|_ecx, ident| {
2619                Ok(ident.call_binary(HirScalarExpr::literal_true(), func::ParseIdent))
2620            }) => SqlScalarType::Array(Box::new(SqlScalarType::String)),
2621                oid::FUNC_PARSE_IDENT_DEFAULT_STRICT;
2622            params!(String, Bool) => BinaryFunc::from(func::ParseIdent)
2623                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 1268;
2624        },
2625        "pg_encoding_to_char" => Scalar {
2626            // Materialize only supports UT8-encoded databases. Return 'UTF8' if Postgres'
2627            // encoding id for UTF8 (6) is provided, otherwise return 'NULL'.
2628            params!(Int64) => sql_impl_func(
2629                "CASE WHEN $1 = 6 THEN 'UTF8' ELSE NULL END",
2630            ) => String, 1597;
2631        },
2632        "pg_backend_pid" => Scalar {
2633            params!() => UnmaterializableFunc::PgBackendPid => Int32, 2026;
2634        },
2635        // pg_get_constraintdef gives more info about a constraint within the `pg_constraint`
2636        // view. Certain meta commands rely on this function not throwing an error, but the
2637        // `pg_constraint` view is empty in materialize. Therefore we know any oid provided is
2638        // not a valid constraint, so we can return NULL which is what PostgreSQL does when
2639        // provided an invalid OID.
2640        "pg_get_constraintdef" => Scalar {
2641            params!(Oid) => Operation::unary(|_ecx, _oid|
2642                Ok(HirScalarExpr::literal_null(SqlScalarType::String))) => String, 1387;
2643            params!(Oid, Bool) => Operation::binary(|_ecx, _oid, _pretty|
2644                Ok(HirScalarExpr::literal_null(SqlScalarType::String))) => String, 2508;
2645        },
2646        // pg_get_indexdef reconstructs the creating command for an index. We only support
2647        // arrangement based indexes, so we can hardcode that in.
2648        // TODO(jkosh44): In order to include the index WITH options,
2649        // they will need to be saved somewhere in the catalog
2650        "pg_get_indexdef" => Scalar {
2651            params!(Oid) => sql_impl_func(
2652                "(SELECT 'CREATE INDEX ' || i.name
2653                    || ' ON ' || r.name
2654                    || ' USING arrangement (' || (
2655                    SELECT pg_catalog.string_agg(
2656                        cols.col_exp, ',' ORDER BY cols.index_position)
2657                    FROM (
2658                        SELECT c.name AS col_exp, ic.index_position
2659                        FROM mz_catalog.mz_index_columns AS ic
2660                        JOIN mz_catalog.mz_indexes AS i2
2661                            ON ic.index_id = i2.id
2662                        JOIN mz_catalog.mz_columns AS c
2663                            ON i2.on_id = c.id
2664                            AND ic.on_position = c.position
2665                        WHERE ic.index_id = i.id
2666                            AND ic.on_expression IS NULL
2667                        UNION
2668                        SELECT ic.on_expression AS col_exp,
2669                            ic.index_position
2670                        FROM mz_catalog.mz_index_columns AS ic
2671                        WHERE ic.index_id = i.id
2672                            AND ic.on_expression IS NOT NULL
2673                    ) AS cols
2674                ) || ')'
2675                FROM mz_catalog.mz_indexes AS i
2676                JOIN mz_catalog.mz_relations AS r
2677                    ON i.on_id = r.id
2678                WHERE i.oid = $1)"
2679            ) => String, 1643;
2680            // A position of 0 is treated as if no position was given.
2681            // Third parameter, pretty, is ignored.
2682            params!(Oid, Int32, Bool) => sql_impl_func(
2683                "(SELECT CASE
2684                    WHEN $2 = 0
2685                    THEN pg_catalog.pg_get_indexdef($1)
2686                    ELSE (
2687                        SELECT c.name
2688                        FROM mz_catalog.mz_indexes AS i
2689                        JOIN mz_catalog.mz_index_columns AS ic
2690                            ON i.id = ic.index_id
2691                        JOIN mz_catalog.mz_columns AS c
2692                            ON i.on_id = c.id
2693                            AND ic.on_position = c.position
2694                        WHERE i.oid = $1
2695                            AND ic.on_expression IS NULL
2696                            AND ic.index_position = $2
2697                        UNION
2698                        SELECT ic.on_expression
2699                        FROM mz_catalog.mz_indexes AS i
2700                        JOIN mz_catalog.mz_index_columns AS ic
2701                            ON i.id = ic.index_id
2702                        WHERE i.oid = $1
2703                            AND ic.on_expression IS NOT NULL
2704                            AND ic.index_position = $2)
2705                    END)"
2706            ) => String, 2507;
2707        },
2708        // pg_get_viewdef returns the (query part of) the given view's definition.
2709        // We currently don't support pretty-printing (the `Bool`/`Int32` parameters).
2710        "pg_get_viewdef" => Scalar {
2711            params!(String) => sql_impl_func(
2712                "(SELECT definition FROM mz_catalog.mz_views WHERE name = $1)"
2713            ) => String, 1640;
2714            params!(Oid) => sql_impl_func(
2715                "(SELECT definition FROM mz_catalog.mz_views WHERE oid = $1)"
2716            ) => String, 1641;
2717            params!(String, Bool) => sql_impl_func(
2718                "(SELECT definition FROM mz_catalog.mz_views WHERE name = $1)"
2719            ) => String, 2505;
2720            params!(Oid, Bool) => sql_impl_func(
2721                "(SELECT definition FROM mz_catalog.mz_views WHERE oid = $1)"
2722            ) => String, 2506;
2723            params!(Oid, Int32) => sql_impl_func(
2724                "(SELECT definition FROM mz_catalog.mz_views WHERE oid = $1)"
2725            ) => String, 3159;
2726        },
2727        // pg_get_expr is meant to convert the textual version of
2728        // pg_node_tree data into parseable expressions. However, we don't
2729        // use the pg_get_expr structure anywhere and the equivalent columns
2730        // in Materialize (e.g. index expressions) are already stored as
2731        // parseable expressions. So, we offer this function in the catalog
2732        // for ORM support, but make no effort to provide its semantics,
2733        // e.g. this also means we drop the Oid argument on the floor.
2734        "pg_get_expr" => Scalar {
2735            params!(String, Oid) => Operation::binary(|_ecx, l, _r| Ok(l)) => String, 1716;
2736            params!(String, Oid, Bool) => Operation::variadic(
2737                move |_ecx, mut args| Ok(args.remove(0)),
2738            ) => String, 2509;
2739        },
2740        "pg_get_userbyid" => Scalar {
2741            params!(Oid) => sql_impl_func(
2742                "CASE \
2743                   WHEN $1 IS NULL THEN NULL \
2744                   ELSE COALESCE(\
2745                     (SELECT name FROM mz_catalog.mz_roles WHERE oid = $1),\
2746                     'unknown (OID=' || $1 || ')'\
2747                   ) \
2748                END"
2749            ) => String, 1642;
2750        },
2751        // The privilege param is validated but ignored. That's because we haven't implemented
2752        // NOINHERIT roles, so it has no effect on the result.
2753        //
2754        // In PostgreSQL, this should always return true for superusers. In Materialize it's
2755        // impossible to determine if a role is a superuser since it's specific to a session. So we
2756        // cannot copy PostgreSQL semantics there.
2757        "pg_has_role" => Scalar {
2758            params!(String, String, String) => sql_impl_func(
2759                "pg_has_role(\
2760                 mz_internal.mz_role_oid($1), \
2761                 mz_internal.mz_role_oid($2), $3)",
2762            ) => Bool, 2705;
2763            params!(String, Oid, String) => sql_impl_func(
2764                "pg_has_role(\
2765                 mz_internal.mz_role_oid($1), $2, $3)",
2766            ) => Bool, 2706;
2767            params!(Oid, String, String) => sql_impl_func(
2768                "pg_has_role(\
2769                 $1, mz_internal.mz_role_oid($2), $3)",
2770            ) => Bool, 2707;
2771            params!(Oid, Oid, String) => sql_impl_func(
2772                "CASE
2773                -- We need to validate the privilege to return a proper error before anything
2774                -- else.
2775                WHEN NOT mz_internal.mz_validate_role_privilege($3)
2776                OR $1 IS NULL
2777                OR $2 IS NULL
2778                OR $3 IS NULL
2779                THEN NULL
2780                WHEN $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
2781                OR $2 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
2782                THEN false
2783                ELSE $2::text IN (SELECT UNNEST(mz_internal.mz_role_oid_memberships() -> $1::text))
2784                END",
2785            ) => Bool, 2708;
2786            params!(String, String) => sql_impl_func(
2787                "pg_has_role(current_user, $1, $2)",
2788            ) => Bool, 2709;
2789            params!(Oid, String) => sql_impl_func(
2790                "pg_has_role(current_user, $1, $2)",
2791            ) => Bool, 2710;
2792        },
2793        // pg_is_in_recovery indicates whether a recovery is still in progress. Materialize does
2794        // not have a concept of recovery, so we default to always returning false.
2795        "pg_is_in_recovery" => Scalar {
2796            params!() => Operation::nullary(|_ecx| {
2797                Ok(HirScalarExpr::literal_false())
2798            }) => Bool, 3810;
2799        },
2800        "pg_postmaster_start_time" => Scalar {
2801            params!() => UnmaterializableFunc::PgPostmasterStartTime => TimestampTz, 2560;
2802        },
2803        "pg_relation_size" => Scalar {
2804            params!(RegClass, String) => sql_impl_func(
2805                "CASE WHEN $1 IS NULL OR $2 IS NULL \
2806                 THEN NULL ELSE -1::pg_catalog.int8 END",
2807            ) => Int64, 2332;
2808            params!(RegClass) => sql_impl_func(
2809                "CASE WHEN $1 IS NULL \
2810                 THEN NULL ELSE -1::pg_catalog.int8 END",
2811            ) => Int64, 2325;
2812        },
2813        "pg_stat_get_numscans" => Scalar {
2814            params!(Oid) => sql_impl_func(
2815                "CASE WHEN $1 IS NULL \
2816                 THEN NULL ELSE -1::pg_catalog.int8 END",
2817            ) => Int64, 1928;
2818        },
2819        "pg_table_is_visible" => Scalar {
2820            params!(Oid) => sql_impl_func(
2821                "(SELECT s.name = ANY(pg_catalog.current_schemas(true))
2822                     FROM mz_catalog.mz_objects o JOIN mz_catalog.mz_schemas s ON o.schema_id = s.id
2823                     WHERE o.oid = $1)"
2824            ) => Bool, 2079;
2825        },
2826        "pg_type_is_visible" => Scalar {
2827            params!(Oid) => sql_impl_func(
2828                "(SELECT s.name = ANY(pg_catalog.current_schemas(true))
2829                     FROM mz_catalog.mz_types t JOIN mz_catalog.mz_schemas s ON t.schema_id = s.id
2830                     WHERE t.oid = $1)"
2831            ) => Bool, 2080;
2832        },
2833        "pg_function_is_visible" => Scalar {
2834            params!(Oid) => sql_impl_func(
2835                "(SELECT s.name = ANY(pg_catalog.current_schemas(true))
2836                     FROM mz_catalog.mz_functions f
2837                     JOIN mz_catalog.mz_schemas s
2838                         ON f.schema_id = s.id
2839                     WHERE f.oid = $1)"
2840            ) => Bool, 2081;
2841        },
2842        // pg_tablespace_location indicates what path in the filesystem that a given tablespace is
2843        // located in. This concept does not make sense though in Materialize which is a cloud
2844        // native database, so we just return the null value.
2845        "pg_tablespace_location" => Scalar {
2846            params!(Oid) => Operation::unary(|_ecx, _e| {
2847                Ok(HirScalarExpr::literal_null(SqlScalarType::String))
2848            }) => String, 3778;
2849        },
2850        "pg_typeof" => Scalar {
2851            params!(Any) => Operation::new(|ecx, exprs, params, _order_by| {
2852                // pg_typeof reports the type *before* coercion.
2853                let name = match ecx.scalar_type(&exprs[0]) {
2854                    CoercibleScalarType::Uncoerced => "unknown".to_string(),
2855                    CoercibleScalarType::Record(_) => "record".to_string(),
2856                    CoercibleScalarType::Coerced(ty) => ecx.humanize_sql_scalar_type(&ty, true),
2857                };
2858
2859                // For consistency with other functions, verify that
2860                // coercion is possible, though we don't actually care about
2861                // the coerced results.
2862                coerce_args_to_types(ecx, exprs, params)?;
2863
2864                // TODO(benesch): make this function have return type
2865                // regtype, when we support that type. Document the function
2866                // at that point. For now, it's useful enough to have this
2867                // halfway version that returns a string.
2868                Ok(HirScalarExpr::literal(Datum::String(&name), SqlScalarType::String))
2869            }) => String, 1619;
2870        },
2871        "position" => Scalar {
2872            params!(String, String) => BinaryFunc::from(func::Position) => Int32, 849;
2873        },
2874        "pow" => Scalar {
2875            params!(Float64, Float64) =>
2876                Operation::nullary(|_ecx| catalog_name_only!("pow"))
2877                => Float64, 1346;
2878        },
2879        "power" => Scalar {
2880            params!(Float64, Float64) => BinaryFunc::from(func::Power) => Float64, 1368;
2881            params!(Numeric, Numeric) => BinaryFunc::from(func::PowerNumeric) => Numeric, 2169;
2882        },
2883        "quote_ident" => Scalar {
2884            params!(String) => UnaryFunc::QuoteIdent(func::QuoteIdent) => String, 1282;
2885        },
2886        "radians" => Scalar {
2887            params!(Float64) => UnaryFunc::Radians(func::Radians) => Float64, 1609;
2888        },
2889        "repeat" => Scalar {
2890            params!(String, Int32) => BinaryFunc::RepeatString(func::RepeatString) => String, 1622;
2891        },
2892        "regexp_match" => Scalar {
2893            params!(String, String) => VariadicFunc::from(variadic::RegexpMatch)
2894                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 3396;
2895            params!(String, String, String) => VariadicFunc::from(variadic::RegexpMatch)
2896                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 3397;
2897        },
2898        "replace" => Scalar {
2899            params!(String, String, String) => VariadicFunc::from(variadic::Replace)
2900                => String, 2087;
2901        },
2902        "right" => Scalar {
2903            params!(String, Int32) => BinaryFunc::from(func::Right) => String, 3061;
2904        },
2905        "round" => Scalar {
2906            params!(Float32) => UnaryFunc::RoundFloat32(func::RoundFloat32)
2907                => Float32, oid::FUNC_ROUND_F32_OID;
2908            params!(Float64) => UnaryFunc::RoundFloat64(func::RoundFloat64) => Float64, 1342;
2909            params!(Numeric) => UnaryFunc::RoundNumeric(func::RoundNumeric) => Numeric, 1708;
2910            params!(Numeric, Int32) => BinaryFunc::from(func::RoundNumericBinary) => Numeric, 1707;
2911        },
2912        "rtrim" => Scalar {
2913            params!(String) => UnaryFunc::TrimTrailingWhitespace(
2914                func::TrimTrailingWhitespace,
2915            ) => String, 882;
2916            params!(String, String) => BinaryFunc::from(func::TrimTrailing) => String, 876;
2917        },
2918        "sha224" => Scalar {
2919            params!(Bytes) => digest("sha224") => Bytes, 3419;
2920        },
2921        "sha256" => Scalar {
2922            params!(Bytes) => digest("sha256") => Bytes, 3420;
2923        },
2924        "sha384" => Scalar {
2925            params!(Bytes) => digest("sha384") => Bytes, 3421;
2926        },
2927        "sha512" => Scalar {
2928            params!(Bytes) => digest("sha512") => Bytes, 3422;
2929        },
2930        "sin" => Scalar {
2931            params!(Float64) => UnaryFunc::Sin(func::Sin) => Float64, 1604;
2932        },
2933        "asin" => Scalar {
2934            params!(Float64) => UnaryFunc::Asin(func::Asin) => Float64, 1600;
2935        },
2936        "sinh" => Scalar {
2937            params!(Float64) => UnaryFunc::Sinh(func::Sinh) => Float64, 2462;
2938        },
2939        "asinh" => Scalar {
2940            params!(Float64) => UnaryFunc::Asinh(func::Asinh) => Float64, 2465;
2941        },
2942        "strpos" => Scalar {
2943            params!(String, String) => BinaryFunc::from(func::Strpos) => Int32, 868;
2944        },
2945        "split_part" => Scalar {
2946            params!(String, String, Int32) => VariadicFunc::from(variadic::SplitPart)
2947                => String, 2088;
2948        },
2949        "stddev" => Scalar {
2950            params!(Float32) =>
2951                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2952                => Float64, 2157;
2953            params!(Float64) =>
2954                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2955                => Float64, 2158;
2956            params!(Int16) =>
2957                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2958                => Numeric, 2156;
2959            params!(Int32) =>
2960                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2961                => Numeric, 2155;
2962            params!(Int64) =>
2963                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2964                => Numeric, 2154;
2965            params!(UInt16) =>
2966                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2967                => Numeric, oid::FUNC_STDDEV_UINT16_OID;
2968            params!(UInt32) =>
2969                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2970                => Numeric, oid::FUNC_STDDEV_UINT32_OID;
2971            params!(UInt64) =>
2972                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2973                => Numeric, oid::FUNC_STDDEV_UINT64_OID;
2974        },
2975        "stddev_pop" => Scalar {
2976            params!(Float32) =>
2977                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2978                => Float64, 2727;
2979            params!(Float64) =>
2980                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2981                => Float64, 2728;
2982            params!(Int16) =>
2983                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2984                => Numeric, 2726;
2985            params!(Int32) =>
2986                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2987                => Numeric, 2725;
2988            params!(Int64) =>
2989                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2990                => Numeric, 2724;
2991            params!(UInt16) =>
2992                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2993                => Numeric, oid::FUNC_STDDEV_POP_UINT16_OID;
2994            params!(UInt32) =>
2995                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2996                => Numeric, oid::FUNC_STDDEV_POP_UINT32_OID;
2997            params!(UInt64) =>
2998                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2999                => Numeric, oid::FUNC_STDDEV_POP_UINT64_OID;
3000        },
3001        "stddev_samp" => Scalar {
3002            params!(Float32) =>
3003                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3004                => Float64, 2715;
3005            params!(Float64) =>
3006                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3007                => Float64, 2716;
3008            params!(Int16) =>
3009                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3010                => Numeric, 2714;
3011            params!(Int32) =>
3012                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3013                => Numeric, 2713;
3014            params!(Int64) =>
3015                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3016                => Numeric, 2712;
3017            params!(UInt16) =>
3018                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3019                => Numeric, oid::FUNC_STDDEV_SAMP_UINT16_OID;
3020            params!(UInt32) =>
3021                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3022                => Numeric, oid::FUNC_STDDEV_SAMP_UINT32_OID;
3023            params!(UInt64) =>
3024                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3025                => Numeric, oid::FUNC_STDDEV_SAMP_UINT64_OID;
3026        },
3027        "substr" => Scalar {
3028            params!(String, Int32) => VariadicFunc::from(variadic::Substr) => String, 883;
3029            params!(String, Int32, Int32) => VariadicFunc::from(variadic::Substr) => String, 877;
3030        },
3031        "substring" => Scalar {
3032            params!(String, Int32) => VariadicFunc::from(variadic::Substr) => String, 937;
3033            params!(String, Int32, Int32) => VariadicFunc::from(variadic::Substr) => String, 936;
3034        },
3035        "sqrt" => Scalar {
3036            params!(Float64) => UnaryFunc::SqrtFloat64(func::SqrtFloat64) => Float64, 1344;
3037            params!(Numeric) => UnaryFunc::SqrtNumeric(func::SqrtNumeric) => Numeric, 1730;
3038        },
3039        "tan" => Scalar {
3040            params!(Float64) => UnaryFunc::Tan(func::Tan) => Float64, 1606;
3041        },
3042        "atan" => Scalar {
3043            params!(Float64) => UnaryFunc::Atan(func::Atan) => Float64, 1602;
3044        },
3045        "tanh" => Scalar {
3046            params!(Float64) => UnaryFunc::Tanh(func::Tanh) => Float64, 2464;
3047        },
3048        "atanh" => Scalar {
3049            params!(Float64) => UnaryFunc::Atanh(func::Atanh) => Float64, 2467;
3050        },
3051        "age" => Scalar {
3052            params!(Timestamp, Timestamp) => BinaryFunc::from(func::AgeTimestamp) => Interval, 2058;
3053            params!(TimestampTz, TimestampTz)
3054                => BinaryFunc::from(func::AgeTimestampTz) => Interval, 1199;
3055        },
3056        "timezone" => Scalar {
3057            params!(String, Timestamp)
3058                => BinaryFunc::TimezoneTimestampBinary(
3059                    func::TimezoneTimestampBinary,
3060                ) => TimestampTz, 2069;
3061            params!(String, TimestampTz)
3062                => BinaryFunc::TimezoneTimestampTzBinary(
3063                    func::TimezoneTimestampTzBinary,
3064                ) => Timestamp, 1159;
3065            // PG defines this as `text timetz`
3066            params!(String, Time) => Operation::binary(|ecx, lhs, rhs| {
3067                // NOTE: this overload is wrong. It should take and return a
3068                // `timetz`, which is a type we don't support because it has
3069                // inscrutable semantics (timezones are meaningless without a
3070                // date). This implementation attempted to extend those already
3071                // inscrutable semantics to the `time` type, which makes matters
3072                // even worse.
3073                //
3074                // This feature flag ensures we don't get *new* uses of this
3075                // function. At some point in the future, we should either
3076                // remove this overload entirely, after validating there are no
3077                // catalogs in production that rely on this overload, or we
3078                // should properly support the `timetz` type and adjust this
3079                // overload accordingly.
3080                ecx.require_feature_flag(&ENABLE_TIME_AT_TIME_ZONE)?;
3081                Ok(HirScalarExpr::call_variadic(
3082                    variadic::TimezoneTimeVariadic,
3083                    vec![
3084                        lhs,
3085                        rhs,
3086                        HirScalarExpr::call_unmaterializable(
3087                            UnmaterializableFunc::CurrentTimestamp,
3088                        ),
3089                    ],
3090                ))
3091            }) => Time, 2037;
3092            params!(Interval, Timestamp)
3093                => BinaryFunc::from(func::TimezoneIntervalTimestampBinary)
3094                => TimestampTz, 2070;
3095            params!(Interval, TimestampTz)
3096                => BinaryFunc::from(func::TimezoneIntervalTimestampTzBinary)
3097                => Timestamp, 1026;
3098            // PG defines this as `interval timetz`
3099            params!(Interval, Time)
3100                => BinaryFunc::from(func::TimezoneIntervalTimeBinary) => Time, 2038;
3101        },
3102        "to_char" => Scalar {
3103            params!(Timestamp, String)
3104                => BinaryFunc::from(func::ToCharTimestampFormat) => String, 2049;
3105            params!(TimestampTz, String)
3106                => BinaryFunc::from(func::ToCharTimestampTzFormat) => String, 1770;
3107        },
3108        // > Returns the value as json or jsonb. Arrays and composites
3109        // > are converted (recursively) to arrays and objects;
3110        // > otherwise, if there is a cast from the type to json, the
3111        // > cast function will be used to perform the conversion;
3112        // > otherwise, a scalar value is produced. For any scalar type
3113        // > other than a number, a Boolean, or a null value, the text
3114        // > representation will be used, in such a fashion that it is a
3115        // > valid json or jsonb value.
3116        //
3117        // https://www.postgresql.org/docs/current/functions-json.html
3118        "to_jsonb" => Scalar {
3119            params!(Any) => Operation::unary(|ecx, e| {
3120                // TODO(see <materialize#7572>): remove this
3121                let e = match ecx.scalar_type(&e) {
3122                    SqlScalarType::Char { length } => {
3123                        e.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
3124                    }
3125                    _ => e,
3126                };
3127                typeconv::to_jsonb(ecx, e)
3128            }) => Jsonb, 3787;
3129        },
3130        "to_timestamp" => Scalar {
3131            params!(Float64) => UnaryFunc::ToTimestamp(func::ToTimestamp) => TimestampTz, 1158;
3132        },
3133        "translate" => Scalar {
3134            params!(String, String, String) => VariadicFunc::from(variadic::Translate)
3135                => String, 878;
3136        },
3137        "trunc" => Scalar {
3138            params!(Float32) => UnaryFunc::TruncFloat32(func::TruncFloat32)
3139                => Float32, oid::FUNC_TRUNC_F32_OID;
3140            params!(Float64) => UnaryFunc::TruncFloat64(func::TruncFloat64) => Float64, 1343;
3141            params!(Numeric) => UnaryFunc::TruncNumeric(func::TruncNumeric) => Numeric, 1710;
3142        },
3143        "tsrange" => Scalar {
3144            params!(Timestamp, Timestamp) => Operation::variadic(|_ecx, mut exprs| {
3145                exprs.push(HirScalarExpr::literal(
3146                    Datum::String("[)"), SqlScalarType::String,
3147                ));
3148                Ok(HirScalarExpr::call_variadic(
3149                    variadic::RangeCreate {
3150                        elem_type: SqlScalarType::Timestamp { precision: None },
3151                    },
3152                    exprs,
3153                ))
3154            }) => SqlScalarType::Range {
3155                element_type: Box::new(SqlScalarType::Timestamp { precision: None }),
3156            }, 3933;
3157            params!(Timestamp, Timestamp, String) => Operation::variadic(|_ecx, exprs| {
3158                Ok(HirScalarExpr::call_variadic(
3159                    variadic::RangeCreate {
3160                        elem_type: SqlScalarType::Timestamp { precision: None },
3161                    },
3162                    exprs,
3163                ))
3164            }) => SqlScalarType::Range {
3165                element_type: Box::new(SqlScalarType::Timestamp { precision: None }),
3166            }, 3934;
3167        },
3168        "tstzrange" => Scalar {
3169            params!(TimestampTz, TimestampTz) => Operation::variadic(|_ecx, mut exprs| {
3170                exprs.push(HirScalarExpr::literal(
3171                    Datum::String("[)"), SqlScalarType::String,
3172                ));
3173                Ok(HirScalarExpr::call_variadic(
3174                    variadic::RangeCreate {
3175                        elem_type: SqlScalarType::TimestampTz { precision: None },
3176                    },
3177                    exprs,
3178                ))
3179            }) => SqlScalarType::Range {
3180                element_type: Box::new(SqlScalarType::TimestampTz { precision: None }),
3181            }, 3937;
3182            params!(TimestampTz, TimestampTz, String) => Operation::variadic(|_ecx, exprs| {
3183                Ok(HirScalarExpr::call_variadic(
3184                    variadic::RangeCreate {
3185                        elem_type: SqlScalarType::TimestampTz { precision: None },
3186                    },
3187                    exprs,
3188                ))
3189            }) => SqlScalarType::Range {
3190                element_type: Box::new(SqlScalarType::TimestampTz { precision: None }),
3191            }, 3938;
3192        },
3193        "upper" => Scalar {
3194            params!(String) => UnaryFunc::Upper(func::Upper) => String, 871;
3195            params!(RangeAny) => UnaryFunc::RangeUpper(func::RangeUpper) => AnyElement, 3849;
3196        },
3197        "upper_inc" => Scalar {
3198            params!(RangeAny) => UnaryFunc::RangeUpperInc(func::RangeUpperInc) => Bool, 3852;
3199        },
3200        "upper_inf" => Scalar {
3201            params!(RangeAny) => UnaryFunc::RangeUpperInf(func::RangeUpperInf) => Bool, 3854;
3202        },
3203        "uuid_generate_v5" => Scalar {
3204            params!(Uuid, String) => BinaryFunc::from(func::UuidGenerateV5)
3205                => Uuid, oid::FUNC_PG_UUID_GENERATE_V5;
3206        },
3207        "variance" => Scalar {
3208            params!(Float32) =>
3209                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3210                => Float64, 2151;
3211            params!(Float64) =>
3212                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3213                => Float64, 2152;
3214            params!(Int16) =>
3215                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3216                => Numeric, 2150;
3217            params!(Int32) =>
3218                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3219                => Numeric, 2149;
3220            params!(Int64) =>
3221                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3222                => Numeric, 2148;
3223            params!(UInt16) =>
3224                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3225                => Numeric, oid::FUNC_VARIANCE_UINT16_OID;
3226            params!(UInt32) =>
3227                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3228                => Numeric, oid::FUNC_VARIANCE_UINT32_OID;
3229            params!(UInt64) =>
3230                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3231                => Numeric, oid::FUNC_VARIANCE_UINT64_OID;
3232        },
3233        "var_pop" => Scalar {
3234            params!(Float32) =>
3235                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3236                => Float64, 2721;
3237            params!(Float64) =>
3238                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3239                => Float64, 2722;
3240            params!(Int16) =>
3241                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3242                => Numeric, 2720;
3243            params!(Int32) =>
3244                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3245                => Numeric, 2719;
3246            params!(Int64) =>
3247                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3248                => Numeric, 2718;
3249            params!(UInt16) =>
3250                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3251                => Numeric, oid::FUNC_VAR_POP_UINT16_OID;
3252            params!(UInt32) =>
3253                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3254                => Numeric, oid::FUNC_VAR_POP_UINT32_OID;
3255            params!(UInt64) =>
3256                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3257                => Numeric, oid::FUNC_VAR_POP_UINT64_OID;
3258        },
3259        "var_samp" => Scalar {
3260            params!(Float32) =>
3261                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3262                => Float64, 2644;
3263            params!(Float64) =>
3264                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3265                => Float64, 2645;
3266            params!(Int16) =>
3267                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3268                => Numeric, 2643;
3269            params!(Int32) =>
3270                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3271                => Numeric, 2642;
3272            params!(Int64) =>
3273                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3274                => Numeric, 2641;
3275            params!(UInt16) =>
3276                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3277                => Numeric, oid::FUNC_VAR_SAMP_UINT16_OID;
3278            params!(UInt32) =>
3279                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3280                => Numeric, oid::FUNC_VAR_SAMP_UINT32_OID;
3281            params!(UInt64) =>
3282                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3283                => Numeric, oid::FUNC_VAR_SAMP_UINT64_OID;
3284        },
3285        "version" => Scalar {
3286            params!() => UnmaterializableFunc::Version => String, 89;
3287        },
3288
3289        // Internal conversion stubs.
3290        "aclitemin" => Scalar {
3291            params!(String) => Operation::variadic(|_ecx, _exprs| {
3292                bail_unsupported!("aclitemin")
3293            }) => AclItem, 1031;
3294        },
3295        "any_in" => Scalar {
3296            params!(String) => Operation::variadic(|_ecx, _exprs| {
3297                bail_unsupported!("any_in")
3298            }) => Any, 2294;
3299        },
3300        "anyarray_in" => Scalar {
3301            params!(String) => Operation::variadic(|_ecx, _exprs| {
3302                bail_unsupported!("anyarray_in")
3303            }) => ArrayAny, 2296;
3304        },
3305        "anycompatible_in" => Scalar {
3306            params!(String) => Operation::variadic(|_ecx, _exprs| {
3307                bail_unsupported!("anycompatible_in")
3308            }) => AnyCompatible, 5086;
3309        },
3310        "anycompatiblearray_in" => Scalar {
3311            params!(String) => Operation::variadic(|_ecx, _exprs| {
3312                bail_unsupported!("anycompatiblearray_in")
3313            }) => ArrayAnyCompatible, 5088;
3314        },
3315        "anycompatiblenonarray_in" => Scalar {
3316            params!(String) => Operation::variadic(|_ecx, _exprs| {
3317                bail_unsupported!("anycompatiblenonarray_in")
3318            }) => NonVecAnyCompatible, 5092;
3319        },
3320        "anycompatiblerange_in" => Scalar {
3321            params!(String, Oid, Int32) =>
3322                Operation::variadic(|_ecx, _exprs| {
3323                    bail_unsupported!("anycompatiblerange_in")
3324                }) => RangeAnyCompatible, 5094;
3325        },
3326        "anyelement_in" => Scalar {
3327            params!(String) => Operation::variadic(|_ecx, _exprs| {
3328                bail_unsupported!("anyelement_in")
3329            }) => AnyElement, 2312;
3330        },
3331        "anynonarray_in" => Scalar {
3332            params!(String) => Operation::variadic(|_ecx, _exprs| {
3333                bail_unsupported!("anynonarray_in")
3334            }) => NonVecAny, 2777;
3335        },
3336        "anyrange_in" => Scalar {
3337            params!(String, Oid, Int32) =>
3338                Operation::variadic(|_ecx, _exprs| {
3339                    bail_unsupported!("anyrange_in")
3340                }) => RangeAny, 3832;
3341        },
3342        "array_in" => Scalar {
3343            params!(String, Oid, Int32) =>
3344                Operation::variadic(|_ecx, _exprs| {
3345                    bail_unsupported!("array_in")
3346                }) => ArrayAny, 750;
3347        },
3348        "boolin" => Scalar {
3349            params!(String) => Operation::variadic(|_ecx, _exprs| {
3350                bail_unsupported!("boolin")
3351            }) => Bool, 1242;
3352        },
3353        "bpcharin" => Scalar {
3354            params!(String, Oid, Int32) =>
3355                Operation::variadic(|_ecx, _exprs| {
3356                    bail_unsupported!("bpcharin")
3357                }) => Char, 1044;
3358        },
3359        "byteain" => Scalar {
3360            params!(String) => Operation::variadic(|_ecx, _exprs| {
3361                bail_unsupported!("byteain")
3362            }) => Bytes, 1244;
3363        },
3364        "charin" => Scalar {
3365            params!(String) => Operation::variadic(|_ecx, _exprs| {
3366                bail_unsupported!("charin")
3367            }) => PgLegacyChar, 1245;
3368        },
3369        "date_in" => Scalar {
3370            params!(String) => Operation::variadic(|_ecx, _exprs| {
3371                bail_unsupported!("date_in")
3372            }) => Date, 1084;
3373        },
3374        "float4in" => Scalar {
3375            params!(String) => Operation::variadic(|_ecx, _exprs| {
3376                bail_unsupported!("float4in")
3377            }) => Float32, 200;
3378        },
3379        "float8in" => Scalar {
3380            params!(String) => Operation::variadic(|_ecx, _exprs| {
3381                bail_unsupported!("float8in")
3382            }) => Float64, 214;
3383        },
3384        "int2in" => Scalar {
3385            params!(String) => Operation::variadic(|_ecx, _exprs| {
3386                bail_unsupported!("int2in")
3387            }) => Int16, 38;
3388        },
3389        "int2vectorin" => Scalar {
3390            params!(String) => Operation::variadic(|_ecx, _exprs| {
3391                bail_unsupported!("int2vectorin")
3392            }) => Int2Vector, 40;
3393        },
3394        "int4in" => Scalar {
3395            params!(String) => Operation::variadic(|_ecx, _exprs| {
3396                bail_unsupported!("int4in")
3397            }) => Int32, 42;
3398        },
3399        "int8in" => Scalar {
3400            params!(String) => Operation::variadic(|_ecx, _exprs| {
3401                bail_unsupported!("int8in")
3402            }) => Int64, 460;
3403        },
3404        "internal_in" => Scalar {
3405            params!(String) => Operation::variadic(|_ecx, _exprs| {
3406                bail_unsupported!("internal_in")
3407            }) => Internal, 2304;
3408        },
3409        "interval_in" => Scalar {
3410            params!(String, Oid, Int32) =>
3411                Operation::variadic(|_ecx, _exprs| {
3412                    bail_unsupported!("interval_in")
3413                }) => Interval, 1160;
3414        },
3415        "jsonb_in" => Scalar {
3416            params!(String) => Operation::variadic(|_ecx, _exprs| {
3417                bail_unsupported!("jsonb_in")
3418            }) => Jsonb, 3806;
3419        },
3420        "namein" => Scalar {
3421            params!(String) => Operation::variadic(|_ecx, _exprs| {
3422                bail_unsupported!("namein")
3423            }) => PgLegacyName, 34;
3424        },
3425        "numeric_in" => Scalar {
3426            params!(String, Oid, Int32) =>
3427                Operation::variadic(|_ecx, _exprs| {
3428                    bail_unsupported!("numeric_in")
3429                }) => Numeric, 1701;
3430        },
3431        "oidin" => Scalar {
3432            params!(String) => Operation::variadic(|_ecx, _exprs| {
3433                bail_unsupported!("oidin")
3434            }) => Oid, 1798;
3435        },
3436        "range_in" => Scalar {
3437            params!(String, Oid, Int32) =>
3438                Operation::variadic(|_ecx, _exprs| {
3439                    bail_unsupported!("range_in")
3440                }) => RangeAny, 3834;
3441        },
3442        "record_in" => Scalar {
3443            params!(String, Oid, Int32) =>
3444                Operation::variadic(|_ecx, _exprs| {
3445                    bail_unsupported!("record_in")
3446                }) => RecordAny, 2290;
3447        },
3448        "regclassin" => Scalar {
3449            params!(String) => Operation::variadic(|_ecx, _exprs| {
3450                bail_unsupported!("regclassin")
3451            }) => RegClass, 2218;
3452        },
3453        "regprocin" => Scalar {
3454            params!(String) => Operation::variadic(|_ecx, _exprs| {
3455                bail_unsupported!("regprocin")
3456            }) => RegProc, 44;
3457        },
3458        "regtypein" => Scalar {
3459            params!(String) => Operation::variadic(|_ecx, _exprs| {
3460                bail_unsupported!("regtypein")
3461            }) => RegType, 2220;
3462        },
3463        "textin" => Scalar {
3464            params!(String) => Operation::variadic(|_ecx, _exprs| {
3465                bail_unsupported!("textin")
3466            }) => String, 46;
3467        },
3468        "time_in" => Scalar {
3469            params!(String, Oid, Int32) =>
3470                Operation::variadic(|_ecx, _exprs| {
3471                    bail_unsupported!("time_in")
3472                }) => Time, 1143;
3473        },
3474        "timestamp_in" => Scalar {
3475            params!(String, Oid, Int32) =>
3476                Operation::variadic(|_ecx, _exprs| {
3477                    bail_unsupported!("timestamp_in")
3478                }) => Timestamp, 1312;
3479        },
3480        "timestamptz_in" => Scalar {
3481            params!(String, Oid, Int32) =>
3482                Operation::variadic(|_ecx, _exprs| {
3483                    bail_unsupported!("timestamptz_in")
3484                }) => TimestampTz, 1150;
3485        },
3486        "varcharin" => Scalar {
3487            params!(String, Oid, Int32) =>
3488                Operation::variadic(|_ecx, _exprs| {
3489                    bail_unsupported!("varcharin")
3490                }) => VarChar, 1046;
3491        },
3492        "uuid_in" => Scalar {
3493            params!(String) => Operation::variadic(|_ecx, _exprs| {
3494                bail_unsupported!("uuid_in")
3495            }) => Uuid, 2952;
3496        },
3497        "boolrecv" => Scalar {
3498            params!(Internal) =>
3499                Operation::nullary(|_ecx| catalog_name_only!("boolrecv"))
3500                => Bool, 2436;
3501        },
3502        "textrecv" => Scalar {
3503            params!(Internal) =>
3504                Operation::nullary(|_ecx| catalog_name_only!("textrecv"))
3505                => String, 2414;
3506        },
3507        "anyarray_recv" => Scalar {
3508            params!(Internal) =>
3509                Operation::nullary(|_ecx| {
3510                    catalog_name_only!("anyarray_recv")
3511                }) => ArrayAny, 2502;
3512        },
3513        "bytearecv" => Scalar {
3514            params!(Internal) =>
3515                Operation::nullary(|_ecx| catalog_name_only!("bytearecv"))
3516                => Bytes, 2412;
3517        },
3518        "bpcharrecv" => Scalar {
3519            params!(Internal) =>
3520                Operation::nullary(|_ecx| {
3521                    catalog_name_only!("bpcharrecv")
3522                }) => Char, 2430;
3523        },
3524        "charrecv" => Scalar {
3525            params!(Internal) =>
3526                Operation::nullary(|_ecx| catalog_name_only!("charrecv"))
3527                => PgLegacyChar, 2434;
3528        },
3529        "date_recv" => Scalar {
3530            params!(Internal) =>
3531                Operation::nullary(|_ecx| catalog_name_only!("date_recv"))
3532                => Date, 2468;
3533        },
3534        "float4recv" => Scalar {
3535            params!(Internal) =>
3536                Operation::nullary(|_ecx| {
3537                    catalog_name_only!("float4recv")
3538                }) => Float32, 2424;
3539        },
3540        "float8recv" => Scalar {
3541            params!(Internal) =>
3542                Operation::nullary(|_ecx| {
3543                    catalog_name_only!("float8recv")
3544                }) => Float64, 2426;
3545        },
3546        "int4recv" => Scalar {
3547            params!(Internal) =>
3548                Operation::nullary(|_ecx| catalog_name_only!("int4recv"))
3549                => Int32, 2406;
3550        },
3551        "int8recv" => Scalar {
3552            params!(Internal) =>
3553                Operation::nullary(|_ecx| catalog_name_only!("int8recv"))
3554                => Int64, 2408;
3555        },
3556        "interval_recv" => Scalar {
3557            params!(Internal) =>
3558                Operation::nullary(|_ecx| {
3559                    catalog_name_only!("interval_recv")
3560                }) => Interval, 2478;
3561        },
3562        "jsonb_recv" => Scalar {
3563            params!(Internal) =>
3564                Operation::nullary(|_ecx| {
3565                    catalog_name_only!("jsonb_recv")
3566                }) => Jsonb, 3805;
3567        },
3568        "namerecv" => Scalar {
3569            params!(Internal) =>
3570                Operation::nullary(|_ecx| catalog_name_only!("namerecv"))
3571                => PgLegacyName, 2422;
3572        },
3573        "numeric_recv" => Scalar {
3574            params!(Internal) =>
3575                Operation::nullary(|_ecx| {
3576                    catalog_name_only!("numeric_recv")
3577                }) => Numeric, 2460;
3578        },
3579        "oidrecv" => Scalar {
3580            params!(Internal) =>
3581                Operation::nullary(|_ecx| catalog_name_only!("oidrecv"))
3582                => Oid, 2418;
3583        },
3584        "record_recv" => Scalar {
3585            params!(Internal) =>
3586                Operation::nullary(|_ecx| {
3587                    catalog_name_only!("recordrerecord_recvcv")
3588                }) => RecordAny, 2402;
3589        },
3590        "regclassrecv" => Scalar {
3591            params!(Internal) =>
3592                Operation::nullary(|_ecx| {
3593                    catalog_name_only!("regclassrecv")
3594                }) => RegClass, 2452;
3595        },
3596        "regprocrecv" => Scalar {
3597            params!(Internal) =>
3598                Operation::nullary(|_ecx| {
3599                    catalog_name_only!("regprocrecv")
3600                }) => RegProc, 2444;
3601        },
3602        "regtyperecv" => Scalar {
3603            params!(Internal) =>
3604                Operation::nullary(|_ecx| {
3605                    catalog_name_only!("regtyperecv")
3606                }) => RegType, 2454;
3607        },
3608        "int2recv" => Scalar {
3609            params!(Internal) =>
3610                Operation::nullary(|_ecx| catalog_name_only!("int2recv"))
3611                => Int16, 2404;
3612        },
3613        "time_recv" => Scalar {
3614            params!(Internal) =>
3615                Operation::nullary(|_ecx| catalog_name_only!("time_recv"))
3616                => Time, 2470;
3617        },
3618        "timestamp_recv" => Scalar {
3619            params!(Internal) =>
3620                Operation::nullary(|_ecx| {
3621                    catalog_name_only!("timestamp_recv")
3622                }) => Timestamp, 2474;
3623        },
3624        "timestamptz_recv" => Scalar {
3625            params!(Internal) =>
3626                Operation::nullary(|_ecx| {
3627                    catalog_name_only!("timestamptz_recv")
3628                }) => TimestampTz, 2476;
3629        },
3630        "uuid_recv" => Scalar {
3631            params!(Internal) =>
3632                Operation::nullary(|_ecx| catalog_name_only!("uuid_recv"))
3633                => Uuid, 2961;
3634        },
3635        "varcharrecv" => Scalar {
3636            params!(Internal) =>
3637                Operation::nullary(|_ecx| {
3638                    catalog_name_only!("varcharrecv")
3639                }) => VarChar, 2432;
3640        },
3641        "int2vectorrecv" => Scalar {
3642            params!(Internal) =>
3643                Operation::nullary(|_ecx| {
3644                    catalog_name_only!("int2vectorrecv")
3645                }) => Int2Vector, 2410;
3646        },
3647        "anycompatiblearray_recv" => Scalar {
3648            params!(Internal) =>
3649                Operation::nullary(|_ecx| {
3650                    catalog_name_only!("anycompatiblearray_recv")
3651                }) => ArrayAnyCompatible, 5090;
3652        },
3653        "array_recv" => Scalar {
3654            params!(Internal) =>
3655                Operation::nullary(|_ecx| {
3656                    catalog_name_only!("array_recv")
3657                }) => ArrayAny, 2400;
3658        },
3659        "range_recv" => Scalar {
3660            params!(Internal) =>
3661                Operation::nullary(|_ecx| {
3662                    catalog_name_only!("range_recv")
3663                }) => RangeAny, 3836;
3664        },
3665
3666
3667        // Aggregates.
3668        "array_agg" => Aggregate {
3669            params!(NonVecAny) => Operation::unary_ordered(|ecx, e, order_by| {
3670                let elem_type = ecx.scalar_type(&e);
3671
3672                let elem_type = match elem_type.array_of_self_elem_type() {
3673                    Ok(elem_type) => elem_type,
3674                    Err(elem_type) => bail_unsupported!(
3675                        format!("array_agg on {}", ecx.humanize_sql_scalar_type(&elem_type, false))
3676                    ),
3677                };
3678
3679                // ArrayConcat excepts all inputs to be arrays, so wrap all input datums into
3680                // arrays.
3681                let e_arr = HirScalarExpr::call_variadic(
3682                    variadic::ArrayCreate { elem_type },
3683                    vec![e],
3684                );
3685                Ok((e_arr, AggregateFunc::ArrayConcat { order_by }))
3686            }) => ArrayAny, 2335;
3687            params!(ArrayAny) => Operation::unary(|_ecx, _e| {
3688                bail_unsupported!("array_agg on arrays")
3689            }) => ArrayAny, 4053;
3690        },
3691        "bool_and" => Aggregate {
3692            params!(Bool) =>
3693                Operation::nullary(|_ecx| catalog_name_only!("bool_and"))
3694                => Bool, 2517;
3695        },
3696        "bool_or" => Aggregate {
3697            params!(Bool) => Operation::nullary(|_ecx| catalog_name_only!("bool_or")) => Bool, 2518;
3698        },
3699        "count" => Aggregate {
3700            params!() => Operation::nullary(|_ecx| {
3701                // COUNT(*) is equivalent to COUNT(true).
3702                // This is mirrored in `AggregateExpr::is_count_asterisk`, so if you modify this,
3703                // then attend to that code also (in both HIR and MIR).
3704                Ok((HirScalarExpr::literal_true(), AggregateFunc::Count))
3705            }) => Int64, 2803;
3706            params!(Any) => AggregateFunc::Count => Int64, 2147;
3707        },
3708        "max" => Aggregate {
3709            params!(Bool) => AggregateFunc::MaxBool => Bool, oid::FUNC_MAX_BOOL_OID;
3710            params!(Int16) => AggregateFunc::MaxInt16 => Int16, 2117;
3711            params!(Int32) => AggregateFunc::MaxInt32 => Int32, 2116;
3712            params!(Int64) => AggregateFunc::MaxInt64 => Int64, 2115;
3713            params!(UInt16) => AggregateFunc::MaxUInt16 => UInt16, oid::FUNC_MAX_UINT16_OID;
3714            params!(UInt32) => AggregateFunc::MaxUInt32 => UInt32, oid::FUNC_MAX_UINT32_OID;
3715            params!(UInt64) => AggregateFunc::MaxUInt64 => UInt64, oid::FUNC_MAX_UINT64_OID;
3716            params!(MzTimestamp) => AggregateFunc::MaxMzTimestamp
3717                => MzTimestamp, oid::FUNC_MAX_MZ_TIMESTAMP_OID;
3718            params!(Float32) => AggregateFunc::MaxFloat32 => Float32, 2119;
3719            params!(Float64) => AggregateFunc::MaxFloat64 => Float64, 2120;
3720            params!(String) => AggregateFunc::MaxString => String, 2129;
3721            // TODO(see <materialize#7572>): make this its own function
3722            params!(Char) => AggregateFunc::MaxString => Char, 2244;
3723            params!(Date) => AggregateFunc::MaxDate => Date, 2122;
3724            params!(Timestamp) => AggregateFunc::MaxTimestamp => Timestamp, 2126;
3725            params!(TimestampTz) => AggregateFunc::MaxTimestampTz => TimestampTz, 2127;
3726            params!(Numeric) => AggregateFunc::MaxNumeric => Numeric, oid::FUNC_MAX_NUMERIC_OID;
3727            params!(Interval) => AggregateFunc::MaxInterval => Interval, 2128;
3728            params!(Time) => AggregateFunc::MaxTime => Time, 2123;
3729        },
3730        "min" => Aggregate {
3731            params!(Bool) => AggregateFunc::MinBool => Bool, oid::FUNC_MIN_BOOL_OID;
3732            params!(Int16) => AggregateFunc::MinInt16 => Int16, 2133;
3733            params!(Int32) => AggregateFunc::MinInt32 => Int32, 2132;
3734            params!(Int64) => AggregateFunc::MinInt64 => Int64, 2131;
3735            params!(UInt16) => AggregateFunc::MinUInt16 => UInt16, oid::FUNC_MIN_UINT16_OID;
3736            params!(UInt32) => AggregateFunc::MinUInt32 => UInt32, oid::FUNC_MIN_UINT32_OID;
3737            params!(UInt64) => AggregateFunc::MinUInt64 => UInt64, oid::FUNC_MIN_UINT64_OID;
3738            params!(MzTimestamp) => AggregateFunc::MinMzTimestamp
3739                => MzTimestamp, oid::FUNC_MIN_MZ_TIMESTAMP_OID;
3740            params!(Float32) => AggregateFunc::MinFloat32 => Float32, 2135;
3741            params!(Float64) => AggregateFunc::MinFloat64 => Float64, 2136;
3742            params!(String) => AggregateFunc::MinString => String, 2145;
3743            // TODO(see <materialize#7572>): make this its own function
3744            params!(Char) => AggregateFunc::MinString => Char, 2245;
3745            params!(Date) => AggregateFunc::MinDate => Date, 2138;
3746            params!(Timestamp) => AggregateFunc::MinTimestamp => Timestamp, 2142;
3747            params!(TimestampTz) => AggregateFunc::MinTimestampTz => TimestampTz, 2143;
3748            params!(Numeric) => AggregateFunc::MinNumeric => Numeric, oid::FUNC_MIN_NUMERIC_OID;
3749            params!(Interval) => AggregateFunc::MinInterval => Interval, 2144;
3750            params!(Time) => AggregateFunc::MinTime => Time, 2139;
3751        },
3752        "jsonb_agg" => Aggregate {
3753            params!(Any) => Operation::unary_ordered(|ecx, e, order_by| {
3754                // TODO(see <materialize#7572>): remove this
3755                let e = match ecx.scalar_type(&e) {
3756                    SqlScalarType::Char { length } => {
3757                        e.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
3758                    }
3759                    _ => e,
3760                };
3761                // `AggregateFunc::JsonbAgg` filters out `Datum::Null` (it
3762                // needs to have *some* identity input), but the semantics
3763                // of the SQL function require that `Datum::Null` is treated
3764                // as `Datum::JsonbNull`. This call to `coalesce` converts
3765                // between the two semantics.
3766                let json_null = HirScalarExpr::literal(Datum::JsonNull, SqlScalarType::Jsonb);
3767                let e = HirScalarExpr::call_variadic(
3768                    variadic::Coalesce,
3769                    vec![typeconv::to_jsonb(ecx, e)?, json_null],
3770                );
3771                Ok((e, AggregateFunc::JsonbAgg { order_by }))
3772            }) => Jsonb, 3267;
3773        },
3774        "jsonb_object_agg" => Aggregate {
3775            params!(Any, Any) => Operation::binary_ordered(|ecx, key, val, order_by| {
3776                // TODO(see <materialize#7572>): remove this
3777                let key = match ecx.scalar_type(&key) {
3778                    SqlScalarType::Char { length } => {
3779                        key.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
3780                    }
3781                    _ => key,
3782                };
3783                let val = match ecx.scalar_type(&val) {
3784                    SqlScalarType::Char { length } => {
3785                        val.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
3786                    }
3787                    _ => val,
3788                };
3789
3790                let json_null = HirScalarExpr::literal(Datum::JsonNull, SqlScalarType::Jsonb);
3791                let key = typeconv::to_string(ecx, key)?;
3792                // `AggregateFunc::JsonbObjectAgg` uses the same underlying
3793                // implementation as `AggregateFunc::MapAgg`, so it's our
3794                // responsibility to apply the JSON-specific behavior of casting
3795                // SQL nulls to JSON nulls; otherwise the produced `Datum::Map`
3796                // can contain `Datum::Null` values that are not valid for the
3797                // `SqlScalarType::Jsonb` type.
3798                let val = HirScalarExpr::call_variadic(
3799                    variadic::Coalesce,
3800                    vec![typeconv::to_jsonb(ecx, val)?, json_null],
3801                );
3802                let e = HirScalarExpr::call_variadic(
3803                    variadic::RecordCreate {
3804                        field_names: vec![ColumnName::from("key"), ColumnName::from("val")],
3805                    },
3806                    vec![key, val],
3807                );
3808                Ok((e, AggregateFunc::JsonbObjectAgg { order_by }))
3809            }) => Jsonb, 3270;
3810        },
3811        "string_agg" => Aggregate {
3812            params!(String, String) => Operation::binary_ordered(|_ecx, value, sep, order_by| {
3813                let e = HirScalarExpr::call_variadic(
3814                    variadic::RecordCreate {
3815                        field_names: vec![ColumnName::from("value"), ColumnName::from("sep")],
3816                    },
3817                    vec![value, sep],
3818                );
3819                Ok((e, AggregateFunc::StringAgg { order_by }))
3820            }) => String, 3538;
3821            params!(Bytes, Bytes) =>
3822                Operation::binary(|_ecx, _l, _r| {
3823                    bail_unsupported!("string_agg on BYTEA")
3824                }) => Bytes, 3545;
3825        },
3826        "string_to_array" => Scalar {
3827            params!(String, String) => VariadicFunc::from(variadic::StringToArray)
3828                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 376;
3829            params!(String, String, String) => VariadicFunc::from(variadic::StringToArray)
3830                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 394;
3831        },
3832        "sum" => Aggregate {
3833            params!(Int16) => AggregateFunc::SumInt16 => Int64, 2109;
3834            params!(Int32) => AggregateFunc::SumInt32 => Int64, 2108;
3835            params!(Int64) => AggregateFunc::SumInt64 => Numeric, 2107;
3836            params!(UInt16) => AggregateFunc::SumUInt16 => UInt64, oid::FUNC_SUM_UINT16_OID;
3837            params!(UInt32) => AggregateFunc::SumUInt32 => UInt64, oid::FUNC_SUM_UINT32_OID;
3838            params!(UInt64) => AggregateFunc::SumUInt64 => Numeric, oid::FUNC_SUM_UINT64_OID;
3839            params!(Float32) => AggregateFunc::SumFloat32 => Float32, 2110;
3840            params!(Float64) => AggregateFunc::SumFloat64 => Float64, 2111;
3841            params!(Numeric) => AggregateFunc::SumNumeric => Numeric, 2114;
3842            params!(Interval) => Operation::unary(|_ecx, _e| {
3843                // Explicitly providing this unsupported overload
3844                // prevents `sum(NULL)` from choosing the `Float64`
3845                // implementation, so that we match PostgreSQL's behavior.
3846                // Plus we will one day want to support this overload.
3847                //
3848                // The message mentions `avg` because `avg(interval)` desugars
3849                // to `sum(interval) / count(interval)` before type checking
3850                // (see `plan_avg` in `transform_ast.rs`), so this error is all
3851                // a user who typed only `avg` gets to see.
3852                bail_unsupported!("sum(interval) and avg(interval)");
3853            }) => Interval, 2113;
3854        },
3855
3856        // Scalar window functions.
3857        "row_number" => ScalarWindow {
3858            params!() => ScalarWindowFunc::RowNumber => Int64, 3100;
3859        },
3860        "rank" => ScalarWindow {
3861            params!() => ScalarWindowFunc::Rank => Int64, 3101;
3862        },
3863        "dense_rank" => ScalarWindow {
3864            params!() => ScalarWindowFunc::DenseRank => Int64, 3102;
3865        },
3866        "lag" => ValueWindow {
3867            // All args are encoded into a single record to be handled later
3868            params!(AnyElement) => Operation::unary(|ecx, e| {
3869                let typ = ecx.scalar_type(&e);
3870                let e = HirScalarExpr::call_variadic(
3871                    variadic::RecordCreate {
3872                        field_names: vec![
3873                            ColumnName::from("expr"),
3874                            ColumnName::from("offset"),
3875                            ColumnName::from("default"),
3876                        ],
3877                    },
3878                    vec![
3879                        e,
3880                        HirScalarExpr::literal(Datum::Int32(1), SqlScalarType::Int32),
3881                        HirScalarExpr::literal_null(typ),
3882                    ],
3883                );
3884                Ok((e, ValueWindowFunc::Lag))
3885            }) => AnyElement, 3106;
3886            params!(AnyElement, Int32) => Operation::binary(|ecx, e, offset| {
3887                let typ = ecx.scalar_type(&e);
3888                let e = HirScalarExpr::call_variadic(
3889                    variadic::RecordCreate {
3890                        field_names: vec![
3891                            ColumnName::from("expr"),
3892                            ColumnName::from("offset"),
3893                            ColumnName::from("default"),
3894                        ],
3895                    },
3896                    vec![e, offset, HirScalarExpr::literal_null(typ)],
3897                );
3898                Ok((e, ValueWindowFunc::Lag))
3899            }) => AnyElement, 3107;
3900            params!(AnyCompatible, Int32, AnyCompatible) => Operation::variadic(|_ecx, exprs| {
3901                let e = HirScalarExpr::call_variadic(
3902                    variadic::RecordCreate {
3903                        field_names: vec![
3904                            ColumnName::from("expr"),
3905                            ColumnName::from("offset"),
3906                            ColumnName::from("default"),
3907                        ],
3908                    },
3909                    exprs,
3910                );
3911                Ok((e, ValueWindowFunc::Lag))
3912            }) => AnyCompatible, 3108;
3913        },
3914        "lead" => ValueWindow {
3915            // All args are encoded into a single record to be handled later
3916            params!(AnyElement) => Operation::unary(|ecx, e| {
3917                let typ = ecx.scalar_type(&e);
3918                let e = HirScalarExpr::call_variadic(
3919                    variadic::RecordCreate {
3920                        field_names: vec![
3921                            ColumnName::from("expr"),
3922                            ColumnName::from("offset"),
3923                            ColumnName::from("default"),
3924                        ],
3925                    },
3926                    vec![
3927                        e,
3928                        HirScalarExpr::literal(Datum::Int32(1), SqlScalarType::Int32),
3929                        HirScalarExpr::literal_null(typ),
3930                    ],
3931                );
3932                Ok((e, ValueWindowFunc::Lead))
3933            }) => AnyElement, 3109;
3934            params!(AnyElement, Int32) => Operation::binary(|ecx, e, offset| {
3935                let typ = ecx.scalar_type(&e);
3936                let e = HirScalarExpr::call_variadic(
3937                    variadic::RecordCreate {
3938                        field_names: vec![
3939                            ColumnName::from("expr"),
3940                            ColumnName::from("offset"),
3941                            ColumnName::from("default"),
3942                        ],
3943                    },
3944                    vec![e, offset, HirScalarExpr::literal_null(typ)],
3945                );
3946                Ok((e, ValueWindowFunc::Lead))
3947            }) => AnyElement, 3110;
3948            params!(AnyCompatible, Int32, AnyCompatible) => Operation::variadic(|_ecx, exprs| {
3949                let e = HirScalarExpr::call_variadic(
3950                    variadic::RecordCreate {
3951                        field_names: vec![
3952                            ColumnName::from("expr"),
3953                            ColumnName::from("offset"),
3954                            ColumnName::from("default"),
3955                        ],
3956                    },
3957                    exprs,
3958                );
3959                Ok((e, ValueWindowFunc::Lead))
3960            }) => AnyCompatible, 3111;
3961        },
3962        "first_value" => ValueWindow {
3963            params!(AnyElement) => ValueWindowFunc::FirstValue => AnyElement, 3112;
3964        },
3965        "last_value" => ValueWindow {
3966            params!(AnyElement) => ValueWindowFunc::LastValue => AnyElement, 3113;
3967        },
3968
3969        // Table functions.
3970        "generate_series" => Table {
3971            params!(Int32, Int32, Int32) => Operation::variadic(move |_ecx, exprs| {
3972                Ok(TableFuncPlan {
3973                    imp: TableFuncImpl::CallTable {
3974                        func: TableFunc::GenerateSeriesInt32,
3975                        exprs,
3976                    },
3977                    column_names: vec!["generate_series".into()],
3978                })
3979            }) => ReturnType::set_of(Int32.into()), 1066;
3980            params!(Int32, Int32) => Operation::binary(move |_ecx, start, stop| {
3981                Ok(TableFuncPlan {
3982                    imp: TableFuncImpl::CallTable {
3983                        func: TableFunc::GenerateSeriesInt32,
3984                        exprs: vec![
3985                            start, stop,
3986                            HirScalarExpr::literal(Datum::Int32(1), SqlScalarType::Int32),
3987                        ],
3988                    },
3989                    column_names: vec!["generate_series".into()],
3990                })
3991            }) => ReturnType::set_of(Int32.into()), 1067;
3992            params!(Int64, Int64, Int64) => Operation::variadic(move |_ecx, exprs| {
3993                Ok(TableFuncPlan {
3994                    imp: TableFuncImpl::CallTable {
3995                        func: TableFunc::GenerateSeriesInt64,
3996                        exprs,
3997                    },
3998                    column_names: vec!["generate_series".into()],
3999                })
4000            }) => ReturnType::set_of(Int64.into()), 1068;
4001            params!(Int64, Int64) => Operation::binary(move |_ecx, start, stop| {
4002                Ok(TableFuncPlan {
4003                    imp: TableFuncImpl::CallTable {
4004                        func: TableFunc::GenerateSeriesInt64,
4005                        exprs: vec![
4006                            start, stop,
4007                            HirScalarExpr::literal(Datum::Int64(1), SqlScalarType::Int64),
4008                        ],
4009                    },
4010                    column_names: vec!["generate_series".into()],
4011                })
4012            }) => ReturnType::set_of(Int64.into()), 1069;
4013            params!(Timestamp, Timestamp, Interval) => Operation::variadic(move |_ecx, exprs| {
4014                Ok(TableFuncPlan {
4015                    imp: TableFuncImpl::CallTable {
4016                        func: TableFunc::GenerateSeriesTimestamp,
4017                        exprs,
4018                    },
4019                    column_names: vec!["generate_series".into()],
4020                })
4021            }) => ReturnType::set_of(Timestamp.into()), 938;
4022            params!(TimestampTz, TimestampTz, Interval) => Operation::variadic(move |_ecx, exprs| {
4023                Ok(TableFuncPlan {
4024                    imp: TableFuncImpl::CallTable {
4025                        func: TableFunc::GenerateSeriesTimestampTz,
4026                        exprs,
4027                    },
4028                    column_names: vec!["generate_series".into()],
4029                })
4030            }) => ReturnType::set_of(TimestampTz.into()), 939;
4031        },
4032
4033        "generate_subscripts" => Table {
4034            params!(ArrayAny, Int32) => Operation::variadic(move |_ecx, exprs| {
4035                Ok(TableFuncPlan {
4036                    imp: TableFuncImpl::CallTable {
4037                        func: TableFunc::GenerateSubscriptsArray,
4038                        exprs,
4039                    },
4040                    column_names: vec!["generate_subscripts".into()],
4041                })
4042            }) => ReturnType::set_of(Int32.into()), 1192;
4043        },
4044
4045        "jsonb_array_elements" => Table {
4046            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4047                Ok(TableFuncPlan {
4048                    imp: TableFuncImpl::CallTable {
4049                        func: TableFunc::JsonbArrayElements,
4050                        exprs: vec![jsonb],
4051                    },
4052                    column_names: vec!["value".into()],
4053                })
4054            }) => ReturnType::set_of(Jsonb.into()), 3219;
4055        },
4056        "jsonb_array_elements_text" => Table {
4057            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4058                Ok(TableFuncPlan {
4059                    imp: TableFuncImpl::CallTable {
4060                        func: TableFunc::JsonbArrayElementsStringify,
4061                        exprs: vec![jsonb],
4062                    },
4063                    column_names: vec!["value".into()],
4064                })
4065            }) => ReturnType::set_of(String.into()), 3465;
4066        },
4067        "jsonb_each" => Table {
4068            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4069                Ok(TableFuncPlan {
4070                    imp: TableFuncImpl::CallTable {
4071                        func: TableFunc::JsonbEach,
4072                        exprs: vec![jsonb],
4073                    },
4074                    column_names: vec!["key".into(), "value".into()],
4075                })
4076            }) => ReturnType::set_of(RecordAny), 3208;
4077        },
4078        "jsonb_each_text" => Table {
4079            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4080                Ok(TableFuncPlan {
4081                    imp: TableFuncImpl::CallTable {
4082                        func: TableFunc::JsonbEachStringify,
4083                        exprs: vec![jsonb],
4084                    },
4085                    column_names: vec!["key".into(), "value".into()],
4086                })
4087            }) => ReturnType::set_of(RecordAny), 3932;
4088        },
4089        "jsonb_object_keys" => Table {
4090            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4091                Ok(TableFuncPlan {
4092                    imp: TableFuncImpl::CallTable {
4093                        func: TableFunc::JsonbObjectKeys,
4094                        exprs: vec![jsonb],
4095                    },
4096                    column_names: vec!["jsonb_object_keys".into()],
4097                })
4098            }) => ReturnType::set_of(String.into()), 3931;
4099        },
4100        // Note that these implementations' input to `generate_series` is
4101        // contrived to match Flink's expected values. There are other,
4102        // equally valid windows we could generate.
4103        "date_bin_hopping" => Table {
4104            // (hop, width, timestamp)
4105            params!(Interval, Interval, Timestamp)
4106                => experimental_sql_impl_table_func(
4107                    &vars::ENABLE_DATE_BIN_HOPPING, "
4108                    SELECT *
4109                    FROM pg_catalog.generate_series(
4110                        pg_catalog.date_bin($1, $3 + $1, '1970-01-01') - $2, $3, $1
4111                    ) AS dbh(date_bin_hopping)
4112                ") => ReturnType::set_of(Timestamp.into()),
4113                oid::FUNC_MZ_DATE_BIN_HOPPING_UNIX_EPOCH_TS_OID;
4114            // (hop, width, timestamp)
4115            params!(Interval, Interval, TimestampTz)
4116                => experimental_sql_impl_table_func(
4117                    &vars::ENABLE_DATE_BIN_HOPPING, "
4118                    SELECT *
4119                    FROM pg_catalog.generate_series(
4120                        pg_catalog.date_bin($1, $3 + $1, '1970-01-01') - $2, $3, $1
4121                    ) AS dbh(date_bin_hopping)
4122                ") => ReturnType::set_of(TimestampTz.into()),
4123                oid::FUNC_MZ_DATE_BIN_HOPPING_UNIX_EPOCH_TSTZ_OID;
4124            // (hop, width, timestamp, origin)
4125            params!(Interval, Interval, Timestamp, Timestamp)
4126                => experimental_sql_impl_table_func(
4127                    &vars::ENABLE_DATE_BIN_HOPPING, "
4128                    SELECT *
4129                    FROM pg_catalog.generate_series(
4130                        pg_catalog.date_bin($1, $3 + $1, $4) - $2, $3, $1
4131                    ) AS dbh(date_bin_hopping)
4132                ") => ReturnType::set_of(Timestamp.into()),
4133                oid::FUNC_MZ_DATE_BIN_HOPPING_TS_OID;
4134            // (hop, width, timestamp, origin)
4135            params!(Interval, Interval, TimestampTz, TimestampTz)
4136                => experimental_sql_impl_table_func(
4137                    &vars::ENABLE_DATE_BIN_HOPPING, "
4138                    SELECT *
4139                    FROM pg_catalog.generate_series(
4140                        pg_catalog.date_bin($1, $3 + $1, $4) - $2, $3, $1
4141                    ) AS dbh(date_bin_hopping)
4142                ") => ReturnType::set_of(TimestampTz.into()),
4143                oid::FUNC_MZ_DATE_BIN_HOPPING_TSTZ_OID;
4144        },
4145        "encode" => Scalar {
4146            params!(Bytes, String) => BinaryFunc::from(func::Encode) => String, 1946;
4147        },
4148        "decode" => Scalar {
4149            params!(String, String) => BinaryFunc::from(func::Decode) => Bytes, 1947;
4150        },
4151        "regexp_split_to_array" => Scalar {
4152            params!(String, String) => VariadicFunc::from(variadic::RegexpSplitToArray)
4153                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 2767;
4154            params!(String, String, String) => VariadicFunc::from(variadic::RegexpSplitToArray)
4155                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 2768;
4156        },
4157        "regexp_split_to_table" => Table {
4158            params!(String, String) => sql_impl_table_func("
4159                SELECT unnest(regexp_split_to_array($1, $2))
4160            ") => ReturnType::set_of(String.into()), 2765;
4161            params!(String, String, String) => sql_impl_table_func("
4162                SELECT unnest(regexp_split_to_array($1, $2, $3))
4163            ") => ReturnType::set_of(String.into()), 2766;
4164        },
4165        "regexp_replace" => Scalar {
4166            params!(String, String, String)
4167                => VariadicFunc::from(variadic::RegexpReplace) => String, 2284;
4168            params!(String, String, String, String)
4169                => VariadicFunc::from(variadic::RegexpReplace) => String, 2285;
4170            // TODO: PostgreSQL supports additional five and six argument
4171            // forms of this function which allow controlling where to
4172            // start the replacement and how many replacements to make.
4173        },
4174        "regexp_matches" => Table {
4175            params!(String, String) => Operation::variadic(move |_ecx, exprs| {
4176                let column_names = vec!["regexp_matches".into()];
4177                Ok(TableFuncPlan {
4178                    imp: TableFuncImpl::CallTable {
4179                        func: TableFunc::RegexpMatches,
4180                        exprs: vec![exprs[0].clone(), exprs[1].clone()],
4181                    },
4182                    column_names,
4183                })
4184            }) => ReturnType::set_of(
4185                SqlScalarType::Array(Box::new(SqlScalarType::String)).into(),
4186            ), 2763;
4187            params!(String, String, String) => Operation::variadic(move |_ecx, exprs| {
4188                let column_names = vec!["regexp_matches".into()];
4189                Ok(TableFuncPlan {
4190                    imp: TableFuncImpl::CallTable {
4191                        func: TableFunc::RegexpMatches,
4192                        exprs: vec![exprs[0].clone(), exprs[1].clone(), exprs[2].clone()],
4193                    },
4194                    column_names,
4195                })
4196            }) => ReturnType::set_of(
4197                SqlScalarType::Array(Box::new(SqlScalarType::String)).into(),
4198            ), 2764;
4199        },
4200        "reverse" => Scalar {
4201            params!(String) => UnaryFunc::Reverse(func::Reverse) => String, 3062;
4202        }
4203    };
4204
4205    // Add side-effecting functions, which are defined in a separate module
4206    // using a restricted set of function definition features (e.g., no
4207    // overloads) to make them easier to plan.
4208    for sef_builtin in PG_CATALOG_SEF_BUILTINS.values() {
4209        builtins.insert(
4210            sef_builtin.name,
4211            Func::Scalar(vec![FuncImpl {
4212                oid: sef_builtin.oid,
4213                params: ParamList::Exact(
4214                    sef_builtin
4215                        .param_types
4216                        .iter()
4217                        .map(|t| ParamType::from(t.clone()))
4218                        .collect(),
4219                ),
4220                return_type: ReturnType::scalar(ParamType::from(
4221                    sef_builtin.return_type.scalar_type.clone(),
4222                )),
4223                op: Operation::variadic(|_ecx, _e| {
4224                    bail_unsupported!(format!("{} in this position", sef_builtin.name))
4225                }),
4226            }]),
4227        );
4228    }
4229
4230    builtins
4231});
4232
4233pub static INFORMATION_SCHEMA_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> =
4234    LazyLock::new(|| {
4235        use ParamType::*;
4236        builtins! {
4237            "_pg_expandarray" => Table {
4238                // See: https://github.com/postgres/postgres/blob/
4239                // 16e3ad5d143795b05a21dc887c2ab384cce4bcb8/
4240                // src/backend/catalog/information_schema.sql#L43
4241                params!(ArrayAny) => sql_impl_table_func("
4242                    SELECT
4243                        $1[s] AS x,
4244                        s - pg_catalog.array_lower($1, 1) + 1 AS n
4245                    FROM pg_catalog.generate_series(
4246                        pg_catalog.array_lower($1, 1),
4247                        pg_catalog.array_upper($1, 1),
4248                        1) as g(s)
4249                ") => ReturnType::set_of(RecordAny), oid::FUNC_PG_EXPAND_ARRAY;
4250            }
4251        }
4252    });
4253
4254pub static MZ_CATALOG_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
4255    use ParamType::*;
4256    use SqlScalarBaseType::*;
4257    builtins! {
4258        "constant_time_eq" => Scalar {
4259            params!(Bytes, Bytes) => BinaryFunc::from(func::ConstantTimeEqBytes)
4260                => Bool, oid::FUNC_CONSTANT_TIME_EQ_BYTES_OID;
4261            params!(String, String) => BinaryFunc::from(func::ConstantTimeEqString)
4262                => Bool, oid::FUNC_CONSTANT_TIME_EQ_STRING_OID;
4263        },
4264        // Note: this is the original version of the AVG(...) function, as it existed prior to
4265        // v0.66. We updated the internal type promotion used when summing values to increase
4266        // precision, but objects (e.g. materialized views) that already used the AVG(...) function
4267        // could not be changed. So we migrated all existing uses of the AVG(...) function to this
4268        // version.
4269        //
4270        // TODO(parkmycar): When objects no longer depend on this function we can safely delete it.
4271        "avg_internal_v1" => Scalar {
4272            params!(Int64) =>
4273                Operation::nullary(|_ecx| {
4274                    catalog_name_only!("avg_internal_v1")
4275                }) => Numeric,
4276                oid::FUNC_AVG_INTERNAL_V1_INT64_OID;
4277            params!(Int32) =>
4278                Operation::nullary(|_ecx| {
4279                    catalog_name_only!("avg_internal_v1")
4280                }) => Numeric,
4281                oid::FUNC_AVG_INTERNAL_V1_INT32_OID;
4282            params!(Int16) =>
4283                Operation::nullary(|_ecx| {
4284                    catalog_name_only!("avg_internal_v1")
4285                }) => Numeric,
4286                oid::FUNC_AVG_INTERNAL_V1_INT16_OID;
4287            params!(UInt64) =>
4288                Operation::nullary(|_ecx| {
4289                    catalog_name_only!("avg_internal_v1")
4290                }) => Numeric,
4291                oid::FUNC_AVG_INTERNAL_V1_UINT64_OID;
4292            params!(UInt32) =>
4293                Operation::nullary(|_ecx| {
4294                    catalog_name_only!("avg_internal_v1")
4295                }) => Numeric,
4296                oid::FUNC_AVG_INTERNAL_V1_UINT32_OID;
4297            params!(UInt16) =>
4298                Operation::nullary(|_ecx| {
4299                    catalog_name_only!("avg_internal_v1")
4300                }) => Numeric,
4301                oid::FUNC_AVG_INTERNAL_V1_UINT16_OID;
4302            params!(Float32) =>
4303                Operation::nullary(|_ecx| {
4304                    catalog_name_only!("avg_internal_v1")
4305                }) => Float64,
4306                oid::FUNC_AVG_INTERNAL_V1_FLOAT32_OID;
4307            params!(Float64) =>
4308                Operation::nullary(|_ecx| {
4309                    catalog_name_only!("avg_internal_v1")
4310                }) => Float64,
4311                oid::FUNC_AVG_INTERNAL_V1_FLOAT64_OID;
4312            params!(Interval) =>
4313                Operation::nullary(|_ecx| {
4314                    catalog_name_only!("avg_internal_v1")
4315                }) => Interval,
4316                oid::FUNC_AVG_INTERNAL_V1_INTERVAL_OID;
4317        },
4318        "csv_extract" => Table {
4319            params!(Int64, String) => Operation::binary(move |_ecx, ncols, input| {
4320                const MAX_EXTRACT_COLUMNS: i64 = 8192;
4321                const TOO_MANY_EXTRACT_COLUMNS: i64 = MAX_EXTRACT_COLUMNS + 1;
4322
4323                let ncols = match ncols.into_literal_int64() {
4324                    None | Some(i64::MIN..=0) => {
4325                        sql_bail!(
4326                            "csv_extract number of columns \
4327                             must be a positive integer literal"
4328                        );
4329                    },
4330                    Some(ncols @ 1..=MAX_EXTRACT_COLUMNS) => ncols,
4331                    Some(ncols @ TOO_MANY_EXTRACT_COLUMNS..) => {
4332                        return Err(PlanError::TooManyColumns {
4333                            max_num_columns: usize::try_from(MAX_EXTRACT_COLUMNS)
4334                                .unwrap_or(usize::MAX),
4335                            req_num_columns: usize::try_from(ncols)
4336                                .unwrap_or(usize::MAX),
4337                        });
4338                    },
4339                };
4340                let ncols = usize::try_from(ncols).expect("known to be greater than zero");
4341
4342                let column_names = (1..=ncols).map(|i| format!("column{}", i).into()).collect();
4343                Ok(TableFuncPlan {
4344                    imp: TableFuncImpl::CallTable {
4345                        func: TableFunc::CsvExtract(ncols),
4346                        exprs: vec![input],
4347                    },
4348                    column_names,
4349                })
4350            }) => ReturnType::set_of(RecordAny), oid::FUNC_CSV_EXTRACT_OID;
4351        },
4352        "concat_agg" => Aggregate {
4353            params!(Any) => Operation::unary(|_ecx, _e| {
4354                bail_unsupported!("concat_agg")
4355            }) => String, oid::FUNC_CONCAT_AGG_OID;
4356        },
4357        "crc32" => Scalar {
4358            params!(String) => UnaryFunc::Crc32String(func::Crc32String)
4359                => UInt32, oid::FUNC_CRC32_STRING_OID;
4360            params!(Bytes) => UnaryFunc::Crc32Bytes(func::Crc32Bytes)
4361                => UInt32, oid::FUNC_CRC32_BYTES_OID;
4362        },
4363        "datediff" => Scalar {
4364            params!(String, Timestamp, Timestamp)
4365                => VariadicFunc::from(variadic::DateDiffTimestamp)
4366                => Int64, oid::FUNC_DATEDIFF_TIMESTAMP;
4367            params!(String, TimestampTz, TimestampTz)
4368                => VariadicFunc::from(variadic::DateDiffTimestampTz)
4369                => Int64, oid::FUNC_DATEDIFF_TIMESTAMPTZ;
4370            params!(String, Date, Date) => VariadicFunc::from(variadic::DateDiffDate)
4371                => Int64, oid::FUNC_DATEDIFF_DATE;
4372            params!(String, Time, Time) => VariadicFunc::from(variadic::DateDiffTime)
4373                => Int64, oid::FUNC_DATEDIFF_TIME;
4374        },
4375        // We can't use the `privilege_fn!` macro because the macro relies on the object having an
4376        // OID, and clusters do not have OIDs.
4377        "has_cluster_privilege" => Scalar {
4378            params!(String, String, String) => sql_impl_func(
4379                "has_cluster_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4380            ) => Bool, oid::FUNC_HAS_CLUSTER_PRIVILEGE_TEXT_TEXT_TEXT_OID;
4381            params!(Oid, String, String) => sql_impl_func(&format!("
4382                CASE
4383                -- We must first check $2 to avoid a potentially
4384                -- null error message (an error itself).
4385                WHEN $2 IS NULL
4386                THEN NULL
4387                -- Validate the cluster name to return a proper error.
4388                WHEN NOT EXISTS (
4389                    SELECT name FROM mz_clusters WHERE name = $2)
4390                THEN mz_unsafe.mz_error_if_null(
4391                    NULL::boolean,
4392                    'error cluster \"' || $2 || '\" does not exist')
4393                -- Validate the privileges and other arguments.
4394                WHEN NOT mz_internal.mz_validate_privileges($3)
4395                OR $1 IS NULL
4396                OR $3 IS NULL
4397                OR $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
4398                THEN NULL
4399                ELSE COALESCE(
4400                    (
4401                        SELECT
4402                            bool_or(
4403                                mz_internal.mz_acl_item_contains_privilege(privilege, $3)
4404                            )
4405                                AS has_cluster_privilege
4406                        FROM
4407                            (
4408                                SELECT
4409                                    unnest(privileges)
4410                                FROM
4411                                    mz_clusters
4412                                WHERE
4413                                    mz_clusters.name = $2
4414                            )
4415                                AS user_privs (privilege)
4416                            LEFT JOIN mz_catalog.mz_roles ON
4417                                    mz_internal.mz_aclitem_grantee(privilege) = mz_roles.id
4418                        WHERE
4419                            mz_internal.mz_aclitem_grantee(privilege) = '{}'
4420                            OR pg_has_role($1, mz_roles.oid, 'USAGE')
4421                    ),
4422                    false
4423                )
4424                END
4425            ", RoleId::Public))
4426                => Bool, oid::FUNC_HAS_CLUSTER_PRIVILEGE_OID_TEXT_TEXT_OID;
4427            params!(String, String) => sql_impl_func(
4428                "has_cluster_privilege(current_user, $1, $2)",
4429            ) => Bool, oid::FUNC_HAS_CLUSTER_PRIVILEGE_TEXT_TEXT_OID;
4430        },
4431        "has_connection_privilege" => Scalar {
4432            params!(String, String, String) => sql_impl_func(
4433                "has_connection_privilege(\
4434                 mz_internal.mz_role_oid($1), \
4435                 mz_internal.mz_connection_oid($2), $3)",
4436            ) => Bool,
4437                oid::FUNC_HAS_CONNECTION_PRIVILEGE_TEXT_TEXT_TEXT_OID;
4438            params!(String, Oid, String) => sql_impl_func(
4439                "has_connection_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4440            ) => Bool,
4441                oid::FUNC_HAS_CONNECTION_PRIVILEGE_TEXT_OID_TEXT_OID;
4442            params!(Oid, String, String) => sql_impl_func(
4443                "has_connection_privilege($1, mz_internal.mz_connection_oid($2), $3)",
4444            ) => Bool,
4445                oid::FUNC_HAS_CONNECTION_PRIVILEGE_OID_TEXT_TEXT_OID;
4446            params!(Oid, Oid, String) => sql_impl_func(
4447                &privilege_fn!("has_connection_privilege", "mz_connections"),
4448            ) => Bool,
4449                oid::FUNC_HAS_CONNECTION_PRIVILEGE_OID_OID_TEXT_OID;
4450            params!(String, String) => sql_impl_func(
4451                "has_connection_privilege(current_user, $1, $2)",
4452            ) => Bool,
4453                oid::FUNC_HAS_CONNECTION_PRIVILEGE_TEXT_TEXT_OID;
4454            params!(Oid, String) => sql_impl_func(
4455                "has_connection_privilege(current_user, $1, $2)",
4456            ) => Bool,
4457                oid::FUNC_HAS_CONNECTION_PRIVILEGE_OID_TEXT_OID;
4458        },
4459        "has_role" => Scalar {
4460            params!(String, String, String)
4461                => sql_impl_func("pg_has_role($1, $2, $3)")
4462                => Bool, oid::FUNC_HAS_ROLE_TEXT_TEXT_TEXT_OID;
4463            params!(String, Oid, String)
4464                => sql_impl_func("pg_has_role($1, $2, $3)")
4465                => Bool, oid::FUNC_HAS_ROLE_TEXT_OID_TEXT_OID;
4466            params!(Oid, String, String)
4467                => sql_impl_func("pg_has_role($1, $2, $3)")
4468                => Bool, oid::FUNC_HAS_ROLE_OID_TEXT_TEXT_OID;
4469            params!(Oid, Oid, String)
4470                => sql_impl_func("pg_has_role($1, $2, $3)")
4471                => Bool, oid::FUNC_HAS_ROLE_OID_OID_TEXT_OID;
4472            params!(String, String)
4473                => sql_impl_func("pg_has_role($1, $2)")
4474                => Bool, oid::FUNC_HAS_ROLE_TEXT_TEXT_OID;
4475            params!(Oid, String)
4476                => sql_impl_func("pg_has_role($1, $2)")
4477                => Bool, oid::FUNC_HAS_ROLE_OID_TEXT_OID;
4478        },
4479        "has_secret_privilege" => Scalar {
4480            params!(String, String, String) => sql_impl_func(
4481                "has_secret_privilege(\
4482                 mz_internal.mz_role_oid($1), \
4483                 mz_internal.mz_secret_oid($2), $3)",
4484            ) => Bool,
4485                oid::FUNC_HAS_SECRET_PRIVILEGE_TEXT_TEXT_TEXT_OID;
4486            params!(String, Oid, String) => sql_impl_func(
4487                "has_secret_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4488            ) => Bool,
4489                oid::FUNC_HAS_SECRET_PRIVILEGE_TEXT_OID_TEXT_OID;
4490            params!(Oid, String, String) => sql_impl_func(
4491                "has_secret_privilege($1, mz_internal.mz_secret_oid($2), $3)",
4492            ) => Bool,
4493                oid::FUNC_HAS_SECRET_PRIVILEGE_OID_TEXT_TEXT_OID;
4494            params!(Oid, Oid, String) => sql_impl_func(
4495                &privilege_fn!("has_secret_privilege", "mz_secrets"),
4496            ) => Bool,
4497                oid::FUNC_HAS_SECRET_PRIVILEGE_OID_OID_TEXT_OID;
4498            params!(String, String) => sql_impl_func(
4499                "has_secret_privilege(current_user, $1, $2)",
4500            ) => Bool,
4501                oid::FUNC_HAS_SECRET_PRIVILEGE_TEXT_TEXT_OID;
4502            params!(Oid, String) => sql_impl_func(
4503                "has_secret_privilege(current_user, $1, $2)",
4504            ) => Bool,
4505                oid::FUNC_HAS_SECRET_PRIVILEGE_OID_TEXT_OID;
4506        },
4507        "has_system_privilege" => Scalar {
4508            params!(String, String) => sql_impl_func(
4509                "has_system_privilege(mz_internal.mz_role_oid($1), $2)",
4510            ) => Bool, oid::FUNC_HAS_SYSTEM_PRIVILEGE_TEXT_TEXT_OID;
4511            params!(Oid, String) => sql_impl_func(&format!("
4512                CASE
4513                -- We need to validate the privileges to return a proper error before
4514                -- anything else.
4515                WHEN NOT mz_internal.mz_validate_privileges($2)
4516                OR $1 IS NULL
4517                OR $2 IS NULL
4518                OR $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
4519                THEN NULL
4520                ELSE COALESCE(
4521                    (
4522                        SELECT
4523                            bool_or(
4524                                mz_internal.mz_acl_item_contains_privilege(privileges, $2)
4525                            )
4526                                AS has_system_privilege
4527                        FROM mz_catalog.mz_system_privileges
4528                        LEFT JOIN mz_catalog.mz_roles ON
4529                                mz_internal.mz_aclitem_grantee(privileges) = mz_roles.id
4530                        WHERE
4531                            mz_internal.mz_aclitem_grantee(privileges) = '{}'
4532                            OR pg_has_role($1, mz_roles.oid, 'USAGE')
4533                    ),
4534                    false
4535                )
4536                END
4537            ", RoleId::Public))
4538                => Bool, oid::FUNC_HAS_SYSTEM_PRIVILEGE_OID_TEXT_OID;
4539            params!(String) => sql_impl_func(
4540                "has_system_privilege(current_user, $1)",
4541            ) => Bool, oid::FUNC_HAS_SYSTEM_PRIVILEGE_TEXT_OID;
4542        },
4543        "has_type_privilege" => Scalar {
4544            params!(String, String, String) => sql_impl_func(
4545                "has_type_privilege(mz_internal.mz_role_oid($1), $2::regtype::oid, $3)",
4546            ) => Bool, 3138;
4547            params!(String, Oid, String) => sql_impl_func(
4548                "has_type_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4549            ) => Bool, 3139;
4550            params!(Oid, String, String) => sql_impl_func(
4551                "has_type_privilege($1, $2::regtype::oid, $3)",
4552            ) => Bool, 3140;
4553            params!(Oid, Oid, String) => sql_impl_func(
4554                &privilege_fn!("has_type_privilege", "mz_types"),
4555            ) => Bool, 3141;
4556            params!(String, String) => sql_impl_func(
4557                "has_type_privilege(current_user, $1, $2)",
4558            ) => Bool, 3142;
4559            params!(Oid, String) => sql_impl_func(
4560                "has_type_privilege(current_user, $1, $2)",
4561            ) => Bool, 3143;
4562        },
4563        "kafka_murmur2" => Scalar {
4564            params!(String) => UnaryFunc::KafkaMurmur2String(func::KafkaMurmur2String)
4565                => Int32, oid::FUNC_KAFKA_MURMUR2_STRING_OID;
4566            params!(Bytes) => UnaryFunc::KafkaMurmur2Bytes(func::KafkaMurmur2Bytes)
4567                => Int32, oid::FUNC_KAFKA_MURMUR2_BYTES_OID;
4568        },
4569        "list_agg" => Aggregate {
4570            params!(Any) => Operation::unary_ordered(|ecx, e, order_by| {
4571                if let SqlScalarType::Char {.. }  = ecx.scalar_type(&e) {
4572                    bail_unsupported!("list_agg on char");
4573                };
4574                // ListConcat excepts all inputs to be lists, so wrap all input datums into
4575                // lists.
4576                let e_arr = HirScalarExpr::call_variadic(
4577                    variadic::ListCreate { elem_type: ecx.scalar_type(&e) },
4578                    vec![e],
4579                );
4580                Ok((e_arr, AggregateFunc::ListConcat { order_by }))
4581            }) => ListAnyCompatible,  oid::FUNC_LIST_AGG_OID;
4582        },
4583        "list_append" => Scalar {
4584            vec![ListAnyCompatible, ListElementAnyCompatible]
4585                => BinaryFunc::from(func::ListElementConcat)
4586                => ListAnyCompatible, oid::FUNC_LIST_APPEND_OID;
4587        },
4588        "list_cat" => Scalar {
4589            vec![ListAnyCompatible, ListAnyCompatible]
4590                => BinaryFunc::from(func::ListListConcat)
4591                => ListAnyCompatible, oid::FUNC_LIST_CAT_OID;
4592        },
4593        "list_n_layers" => Scalar {
4594            vec![ListAny] => Operation::unary(|ecx, e| {
4595                ecx.require_feature_flag(&vars::ENABLE_LIST_N_LAYERS)?;
4596                let d = ecx.scalar_type(&e).unwrap_list_n_layers();
4597                match i32::try_from(d) {
4598                    Ok(d) => Ok(HirScalarExpr::literal(Datum::Int32(d), SqlScalarType::Int32)),
4599                    Err(_) => sql_bail!("list has more than {} layers", i32::MAX),
4600                }
4601
4602            }) => Int32, oid::FUNC_LIST_N_LAYERS_OID;
4603        },
4604        "list_length" => Scalar {
4605            vec![ListAny] => UnaryFunc::ListLength(func::ListLength)
4606                => Int32, oid::FUNC_LIST_LENGTH_OID;
4607        },
4608        "list_length_max" => Scalar {
4609            vec![ListAny, Plain(SqlScalarType::Int64)] => Operation::binary(|ecx, lhs, rhs| {
4610                ecx.require_feature_flag(&vars::ENABLE_LIST_LENGTH_MAX)?;
4611                let max_layer = ecx.scalar_type(&lhs).unwrap_list_n_layers();
4612                Ok(lhs.call_binary(rhs, BinaryFunc::from(func::ListLengthMax { max_layer })))
4613            }) => Int32, oid::FUNC_LIST_LENGTH_MAX_OID;
4614        },
4615        "list_prepend" => Scalar {
4616            vec![ListElementAnyCompatible, ListAnyCompatible]
4617                => BinaryFunc::from(func::ElementListConcat)
4618                => ListAnyCompatible, oid::FUNC_LIST_PREPEND_OID;
4619        },
4620        "list_remove" => Scalar {
4621            vec![ListAnyCompatible, ListElementAnyCompatible] => Operation::binary(|ecx, lhs, rhs| {
4622                ecx.require_feature_flag(&vars::ENABLE_LIST_REMOVE)?;
4623                Ok(lhs.call_binary(rhs, func::ListRemove))
4624            }) => ListAnyCompatible, oid::FUNC_LIST_REMOVE_OID;
4625        },
4626        "map_agg" => Aggregate {
4627            params!(String, Any) => Operation::binary_ordered(|ecx, key, val, order_by| {
4628                let (value_type, val) = match ecx.scalar_type(&val) {
4629                    // TODO(see <materialize#7572>): remove this
4630                    SqlScalarType::Char { length } => (
4631                        SqlScalarType::Char { length },
4632                        val.call_unary(UnaryFunc::PadChar(func::PadChar { length })),
4633                    ),
4634                    typ => (typ, val),
4635                };
4636
4637                let e = HirScalarExpr::call_variadic(
4638                    variadic::RecordCreate {
4639                        field_names: vec![ColumnName::from("key"), ColumnName::from("val")],
4640                    },
4641                    vec![key, val],
4642                );
4643
4644                Ok((e, AggregateFunc::MapAgg { order_by, value_type }))
4645            }) => MapAny, oid::FUNC_MAP_AGG;
4646        },
4647        "map_build" => Scalar {
4648            // TODO: support a function to construct maps that looks like...
4649            //
4650            // params!([String], Any...) => Operation::variadic(|ecx, exprs| {
4651            //
4652            // ...the challenge here is that we don't support constructing other
4653            // complex types from varidaic functions and instead use a SQL
4654            // keyword; however that doesn't work very well for map because the
4655            // intuitive syntax would be something akin to `MAP[key=>value]`,
4656            // but that doesn't work out of the box because `key=>value` looks
4657            // like an expression.
4658            params!(ListAny) => Operation::unary(|ecx, expr| {
4659                let ty = ecx.scalar_type(&expr);
4660
4661                // This is a fake error but should suffice given how exotic the
4662                // function is.
4663                let err = || {
4664                    Err(sql_err!(
4665                        "function map_build({}) does not exist",
4666                        ecx.humanize_sql_scalar_type(&ty.clone(), false)
4667                    ))
4668                };
4669
4670                // This function only accepts lists of records whose schema is
4671                // (text, T).
4672                let value_type = match &ty {
4673                    SqlScalarType::List { element_type, .. } => match &**element_type {
4674                        SqlScalarType::Record { fields, .. } if fields.len() == 2 => {
4675                            if fields[0].1.scalar_type != SqlScalarType::String {
4676                                return err();
4677                            }
4678
4679                            fields[1].1.scalar_type.clone()
4680                        }
4681                        _ => return err(),
4682                    },
4683                    _ => unreachable!("input guaranteed to be list"),
4684                };
4685
4686                Ok(expr.call_unary(UnaryFunc::MapBuildFromRecordList(
4687                    func::MapBuildFromRecordList { value_type },
4688                )))
4689            }) => MapAny, oid::FUNC_MAP_BUILD;
4690        },
4691        "map_length" => Scalar {
4692            params![MapAny] => UnaryFunc::MapLength(func::MapLength)
4693                => Int32, oid::FUNC_MAP_LENGTH_OID;
4694        },
4695        // `mz_environment_id` is a plan-time constant: its value is fixed
4696        // for the lifetime of the envd process. Fold directly to a literal
4697        // here so downstream layers (MVs, indexes, dataflow) can treat it
4698        // as an ordinary string, not an unmaterializable function.
4699        //
4700        // Unlike the other system-information functions in this file, it is
4701        // intentionally not gated by `restrict_to_user_objects`. The
4702        // environment ID is not sensitive, and because the fold bakes the
4703        // value into any stored view that references it, a gate here could
4704        // only ever be partial (it would catch direct calls but not values
4705        // already materialized into a view), which is more misleading than
4706        // no gate at all. See
4707        // doc/developer/design/20260508_restrict_to_user_objects.md.
4708        "mz_environment_id" => Scalar {
4709            params!() => Operation::nullary(|ecx| {
4710                let env_id = ecx.catalog().config().environment_id.to_string();
4711                Ok(HirScalarExpr::literal(
4712                    Datum::String(&env_id),
4713                    SqlScalarType::String,
4714                ))
4715            }) => String, oid::FUNC_MZ_ENVIRONMENT_ID_OID;
4716        },
4717        // The three AWS-context functions below are plan-time constants, fixed
4718        // for the lifetime of the envd process, folded to a literal here for the
4719        // same reason as `mz_environment_id`: the mz_aws_connections and
4720        // mz_aws_privatelink_connections materialized views reproduce the AWS
4721        // principal, external id, and trust policy in SQL, and a materialized
4722        // view cannot reference an unmaterializable function. They return NULL
4723        // on environments without the corresponding context (non-cloud/local).
4724        //
4725        // Like `mz_environment_id`, they are not gated by
4726        // `restrict_to_user_objects`. The fold bakes the value into any stored
4727        // view that references it, so a gate here could only catch direct calls,
4728        // not values already materialized into a view, which is more misleading
4729        // than no gate. Restricted sessions are still blocked from the system
4730        // connection views themselves (they are system relations). See
4731        // doc/developer/design/20260508_restrict_to_user_objects.md.
4732        "mz_aws_account_id" => Scalar {
4733            params!() => Operation::nullary(|ecx| {
4734                Ok(match &ecx.catalog().config().aws_account_id {
4735                    Some(account_id) => {
4736                        HirScalarExpr::literal(Datum::String(account_id), SqlScalarType::String)
4737                    }
4738                    None => HirScalarExpr::literal_null(SqlScalarType::String),
4739                })
4740            }) => String, oid::FUNC_MZ_AWS_ACCOUNT_ID_OID;
4741        },
4742        "mz_aws_external_id_prefix" => Scalar {
4743            params!() => Operation::nullary(|ecx| {
4744                let prefix = ecx
4745                    .catalog()
4746                    .config()
4747                    .connection_context
4748                    .aws_external_id_prefix
4749                    .as_ref()
4750                    .map(|p| p.to_string());
4751                Ok(match prefix {
4752                    Some(prefix) => {
4753                        HirScalarExpr::literal(Datum::String(&prefix), SqlScalarType::String)
4754                    }
4755                    None => HirScalarExpr::literal_null(SqlScalarType::String),
4756                })
4757            }) => String, oid::FUNC_MZ_AWS_EXTERNAL_ID_PREFIX_OID;
4758        },
4759        "mz_aws_connection_role_arn" => Scalar {
4760            params!() => Operation::nullary(|ecx| {
4761                Ok(match &ecx.catalog().config().connection_context.aws_connection_role_arn {
4762                    Some(arn) => HirScalarExpr::literal(Datum::String(arn), SqlScalarType::String),
4763                    None => HirScalarExpr::literal_null(SqlScalarType::String),
4764                })
4765            }) => String, oid::FUNC_MZ_AWS_CONNECTION_ROLE_ARN_OID;
4766        },
4767        "mz_is_superuser" => Scalar {
4768            params!() => UnmaterializableFunc::MzIsSuperuser
4769                => SqlScalarType::Bool, oid::FUNC_MZ_IS_SUPERUSER;
4770        },
4771        "mz_logical_timestamp" => Scalar {
4772            params!() => Operation::nullary(|_ecx| {
4773                sql_bail!("mz_logical_timestamp() has been renamed to mz_now()")
4774            }) => MzTimestamp, oid::FUNC_MZ_LOGICAL_TIMESTAMP_OID;
4775        },
4776        "mz_now" => Scalar {
4777            params!() => UnmaterializableFunc::MzNow => MzTimestamp, oid::FUNC_MZ_NOW_OID;
4778        },
4779        "mz_uptime" => Scalar {
4780            params!() => UnmaterializableFunc::MzUptime => Interval, oid::FUNC_MZ_UPTIME_OID;
4781        },
4782        "mz_version" => Scalar {
4783            params!() => UnmaterializableFunc::MzVersion => String, oid::FUNC_MZ_VERSION_OID;
4784        },
4785        "mz_version_num" => Scalar {
4786            params!() => UnmaterializableFunc::MzVersionNum => Int32, oid::FUNC_MZ_VERSION_NUM_OID;
4787        },
4788        "pretty_sql" => Scalar {
4789            params!(String, Int32) => BinaryFunc::from(func::PrettySql)
4790                => String, oid::FUNC_PRETTY_SQL;
4791            params!(String) => Operation::unary(|_ecx, s| {
4792                let w: i32 = mz_sql_pretty::DEFAULT_WIDTH.try_into().expect("must fit");
4793                let width = HirScalarExpr::literal(Datum::Int32(w), SqlScalarType::Int32);
4794                Ok(s.call_binary(width, func::PrettySql))
4795            }) => String, oid::FUNC_PRETTY_SQL_NOWIDTH;
4796        },
4797        "regexp_extract" => Table {
4798            params!(String, String) => Operation::binary(move |_ecx, regex, haystack| {
4799                let regex = match regex.into_literal_string() {
4800                    None => sql_bail!(
4801                        "regexp_extract requires a string \
4802                         literal as its first argument"
4803                    ),
4804                    Some(regex) => {
4805                        let opts = mz_expr::AnalyzedRegexOpts::default();
4806                        mz_expr::AnalyzedRegex::new(&regex, opts)
4807                            .map_err(|e| {
4808                                sql_err!("analyzing regex: {}", e)
4809                            })?
4810                    },
4811                };
4812                let column_names = regex
4813                    .capture_groups_iter()
4814                    .map(|cg| {
4815                        cg.name.clone().unwrap_or_else(|| format!("column{}", cg.index)).into()
4816                    })
4817                    .collect::<Vec<_>>();
4818                if column_names.is_empty(){
4819                    sql_bail!("regexp_extract must specify at least one capture group");
4820                }
4821                Ok(TableFuncPlan {
4822                    imp: TableFuncImpl::CallTable {
4823                        func: TableFunc::RegexpExtract(regex),
4824                        exprs: vec![haystack],
4825                    },
4826                    column_names,
4827                })
4828            }) => ReturnType::set_of(RecordAny), oid::FUNC_REGEXP_EXTRACT_OID;
4829        },
4830        mz_expr::REPEAT_ROW_NAME => Table {
4831            params!(Int64) => Operation::unary(move |ecx, n| {
4832                ecx.require_feature_flag(&vars::ENABLE_REPEAT_ROW)?;
4833                Ok(TableFuncPlan {
4834                    imp: TableFuncImpl::CallTable {
4835                        func: TableFunc::RepeatRow,
4836                        exprs: vec![n],
4837                    },
4838                    column_names: vec![]
4839                })
4840            }) => ReturnType::none(true), oid::FUNC_REPEAT_ROW_OID;
4841        },
4842        "repeat_row_non_negative" => Table {
4843            params!(Int64) => Operation::unary(move |ecx, n| {
4844                ecx.require_feature_flag(&vars::ENABLE_REPEAT_ROW_NON_NEGATIVE)?;
4845                Ok(TableFuncPlan {
4846                    imp: TableFuncImpl::CallTable {
4847                        func: TableFunc::RepeatRowNonNegative,
4848                        exprs: vec![n],
4849                    },
4850                    column_names: vec![]
4851                })
4852            }) => ReturnType::none(true), oid::FUNC_REPEAT_ROW_NON_NEGATIVE_OID;
4853        },
4854        "seahash" => Scalar {
4855            params!(String) => UnaryFunc::SeahashString(func::SeahashString)
4856                => UInt64, oid::FUNC_SEAHASH_STRING_OID;
4857            params!(Bytes) => UnaryFunc::SeahashBytes(func::SeahashBytes)
4858                => UInt64, oid::FUNC_SEAHASH_BYTES_OID;
4859        },
4860        "starts_with" => Scalar {
4861            params!(String, String) => BinaryFunc::from(func::StartsWith) => Bool, 3696;
4862        },
4863        "timezone_offset" => Scalar {
4864            params!(String, TimestampTz) => BinaryFunc::from(func::TimezoneOffset)
4865                => RecordAny, oid::FUNC_TIMEZONE_OFFSET;
4866        },
4867        "try_parse_monotonic_iso8601_timestamp" => Scalar {
4868            params!(String) => Operation::unary(move |_ecx, e| {
4869                Ok(e.call_unary(UnaryFunc::TryParseMonotonicIso8601Timestamp(
4870                    func::TryParseMonotonicIso8601Timestamp,
4871                )))
4872            }) => Timestamp, oid::FUNC_TRY_PARSE_MONOTONIC_ISO8601_TIMESTAMP;
4873        },
4874        "unnest" => Table {
4875            vec![ArrayAny] => Operation::unary(move |ecx, e| {
4876                let el_typ = ecx.scalar_type(&e).unwrap_array_element_type().clone();
4877                Ok(TableFuncPlan {
4878                    imp: TableFuncImpl::CallTable {
4879                        func: TableFunc::UnnestArray { el_typ },
4880                        exprs: vec![e],
4881                    },
4882                    column_names: vec!["unnest".into()],
4883                })
4884            }) =>
4885                // This return type should be equivalent to
4886                // "ArrayElementAny", but this would be its sole use.
4887                ReturnType::set_of(AnyElement), 2331;
4888            vec![ListAny] => Operation::unary(move |ecx, e| {
4889                let el_typ = ecx.scalar_type(&e).unwrap_list_element_type().clone();
4890                Ok(TableFuncPlan {
4891                    imp: TableFuncImpl::CallTable {
4892                        func: TableFunc::UnnestList { el_typ },
4893                        exprs: vec![e],
4894                    },
4895                    column_names: vec!["unnest".into()],
4896                })
4897            }) =>
4898                // This return type should be equivalent to
4899                // "ListElementAny", but this would be its sole use.
4900                ReturnType::set_of(Any), oid::FUNC_UNNEST_LIST_OID;
4901            vec![MapAny] => Operation::unary(move |ecx, e| {
4902                let value_type = ecx.scalar_type(&e).unwrap_map_value_type().clone();
4903                Ok(TableFuncPlan {
4904                    imp: TableFuncImpl::CallTable {
4905                        func: TableFunc::UnnestMap { value_type },
4906                        exprs: vec![e],
4907                    },
4908                    column_names: vec!["key".into(), "value".into()],
4909                })
4910            }) =>
4911                // This return type should be equivalent to
4912                // "ListElementAny", but this would be its sole use.
4913                ReturnType::set_of(Any), oid::FUNC_UNNEST_MAP_OID;
4914        }
4915    }
4916});
4917
4918pub static MZ_INTERNAL_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
4919    use ParamType::*;
4920    use SqlScalarBaseType::*;
4921    builtins! {
4922        "aclitem_grantor" => Scalar {
4923            params!(AclItem) => UnaryFunc::AclItemGrantor(func::AclItemGrantor)
4924                => Oid, oid::FUNC_ACL_ITEM_GRANTOR_OID;
4925        },
4926        "aclitem_grantee" => Scalar {
4927            params!(AclItem) => UnaryFunc::AclItemGrantee(func::AclItemGrantee)
4928                => Oid, oid::FUNC_ACL_ITEM_GRANTEE_OID;
4929        },
4930        "aclitem_privileges" => Scalar {
4931            params!(AclItem) => UnaryFunc::AclItemPrivileges(func::AclItemPrivileges)
4932                => String, oid::FUNC_ACL_ITEM_PRIVILEGES_OID;
4933        },
4934        "is_rbac_enabled" => Scalar {
4935            params!() => UnmaterializableFunc::IsRbacEnabled => Bool, oid::FUNC_IS_RBAC_ENABLED_OID;
4936        },
4937        "make_mz_aclitem" => Scalar {
4938            params!(String, String, String) => VariadicFunc::from(variadic::MakeMzAclItem)
4939                => MzAclItem, oid::FUNC_MAKE_MZ_ACL_ITEM_OID;
4940        },
4941        "mz_acl_item_contains_privilege" => Scalar {
4942            params!(MzAclItem, String)
4943                => BinaryFunc::from(func::MzAclItemContainsPrivilege)
4944                => Bool, oid::FUNC_MZ_ACL_ITEM_CONTAINS_PRIVILEGE_OID;
4945        },
4946        "mz_aclexplode" => Table {
4947            params!(SqlScalarType::Array(Box::new(SqlScalarType::MzAclItem)))
4948                => Operation::unary(move |_ecx, mz_aclitems| {
4949                Ok(TableFuncPlan {
4950                    imp: TableFuncImpl::CallTable {
4951                        func: TableFunc::MzAclExplode,
4952                        exprs: vec![mz_aclitems],
4953                    },
4954                    column_names: vec![
4955                        "grantor".into(), "grantee".into(),
4956                        "privilege_type".into(), "is_grantable".into(),
4957                    ],
4958                })
4959            }) => ReturnType::set_of(RecordAny), oid::FUNC_MZ_ACL_ITEM_EXPLODE_OID;
4960        },
4961        "mz_aclitem_grantor" => Scalar {
4962            params!(MzAclItem) => UnaryFunc::MzAclItemGrantor(func::MzAclItemGrantor)
4963                => String, oid::FUNC_MZ_ACL_ITEM_GRANTOR_OID;
4964        },
4965        "mz_aclitem_grantee" => Scalar {
4966            params!(MzAclItem) => UnaryFunc::MzAclItemGrantee(func::MzAclItemGrantee)
4967                => String, oid::FUNC_MZ_ACL_ITEM_GRANTEE_OID;
4968        },
4969        "mz_aclitem_privileges" => Scalar {
4970            params!(MzAclItem) => UnaryFunc::MzAclItemPrivileges(
4971                func::MzAclItemPrivileges,
4972            ) => String, oid::FUNC_MZ_ACL_ITEM_PRIVILEGES_OID;
4973        },
4974        // There is no regclass equivalent for roles to look up connections, so we
4975        // have this helper function instead.
4976        //
4977        // TODO: invent an OID alias for connections
4978        "mz_connection_oid" => Scalar {
4979            params!(String) => sql_impl_func("
4980                CASE
4981                WHEN $1 IS NULL THEN NULL
4982                ELSE (
4983                    mz_unsafe.mz_error_if_null(
4984                        (SELECT oid FROM mz_catalog.mz_objects
4985                         WHERE name = $1 AND type = 'connection'),
4986                        'connection \"' || $1 || '\" does not exist'
4987                    )
4988                )
4989                END
4990            ") => Oid, oid::FUNC_CONNECTION_OID_OID;
4991        },
4992        "mz_format_privileges" => Scalar {
4993            params!(String) => UnaryFunc::MzFormatPrivileges(func::MzFormatPrivileges)
4994                => SqlScalarType::Array(Box::new(SqlScalarType::String)),
4995                oid::FUNC_MZ_FORMAT_PRIVILEGES_OID;
4996        },
4997        "mz_name_rank" => Table {
4998            // Determines the id, rank of all objects that can be matched using
4999            // the provided args.
5000            params!(
5001                // Database
5002                String,
5003                // Schemas/search path
5004                ParamType::Plain(SqlScalarType::Array(Box::new(SqlScalarType::String))),
5005                // Item name
5006                String,
5007                // Get rank among particular OID alias (e.g. regclass)
5008                String
5009            ) =>
5010            // credit for using rank() to @def-
5011            sql_impl_table_func("
5012            -- The best ranked name is the one that belongs to the schema correlated with the lowest
5013            -- index in the search path
5014            SELECT id, name, count, min(schema_pref) OVER () = schema_pref AS best_ranked FROM (
5015                SELECT DISTINCT
5016                    o.id,
5017                    ARRAY[CASE WHEN s.database_id IS NULL THEN NULL ELSE d.name END, s.name, o.name]
5018                    AS name,
5019                    o.count,
5020                    pg_catalog.array_position($2, s.name) AS schema_pref
5021                FROM
5022                    (
5023                        SELECT
5024                            o.id,
5025                            o.schema_id,
5026                            o.name,
5027                            count(*)
5028                        FROM mz_catalog.mz_objects AS o
5029                        JOIN mz_internal.mz_object_oid_alias AS a
5030                            ON o.type = a.object_type
5031                        WHERE o.name = CAST($3 AS pg_catalog.text) AND a.oid_alias = $4
5032                        GROUP BY 1, 2, 3
5033                    )
5034                        AS o
5035                    JOIN mz_catalog.mz_schemas AS s ON o.schema_id = s.id
5036                    JOIN
5037                        unnest($2) AS search_schema (name)
5038                        ON search_schema.name = s.name
5039                    JOIN
5040                        (
5041                            SELECT id, name FROM mz_catalog.mz_databases
5042                            -- If the provided database does not exist, add a row for it so that it
5043                            -- can still join against ambient schemas.
5044                            UNION ALL
5045                            SELECT '', $1 WHERE $1 NOT IN (SELECT name FROM mz_catalog.mz_databases)
5046                        ) AS d
5047                        ON d.id = COALESCE(s.database_id, d.id)
5048                WHERE d.name = CAST($1 AS pg_catalog.text)
5049            );
5050            ") => ReturnType::set_of(RecordAny), oid::FUNC_MZ_NAME_RANK;
5051        },
5052        "mz_resolve_object_name" => Table {
5053            params!(String, String) =>
5054            // Normalize the input name, and for any NULL values (e.g. not database qualified), use
5055            // the defaults used during name resolution.
5056            sql_impl_table_func("
5057                SELECT
5058                    o.id, o.oid, o.schema_id, o.name, o.type, o.owner_id, o.privileges
5059                FROM
5060                    (SELECT mz_internal.mz_normalize_object_name($2))
5061                            AS normalized (n),
5062                    mz_internal.mz_name_rank(
5063                        COALESCE(n[1], pg_catalog.current_database()),
5064                        CASE
5065                            WHEN n[2] IS NULL
5066                                THEN pg_catalog.current_schemas(true)
5067                            ELSE
5068                                ARRAY[n[2]]
5069                        END,
5070                        n[3],
5071                        $1
5072                    ) AS r,
5073                    mz_catalog.mz_objects AS o
5074                WHERE r.id = o.id AND r.best_ranked;
5075            ") => ReturnType::set_of(RecordAny), oid::FUNC_MZ_RESOLVE_OBJECT_NAME;
5076        },
5077        // Returns the an array representing the minimal namespace a user must
5078        // provide to refer to an item whose name is the first argument.
5079        //
5080        // The first argument must be a fully qualified name (i.e. contain
5081        // database.schema.object), with each level of the namespace being an
5082        // element.
5083        //
5084        // The second argument represents the `GlobalId` of the resolved object.
5085        // This is a safeguard to ensure that the name we are resolving refers
5086        // to the expected entry. For example, this helps us disambiguate cases
5087        // where e.g. types and functions have the same name.
5088        "mz_minimal_name_qualification" => Scalar {
5089            params!(SqlScalarType::Array(Box::new(SqlScalarType::String)), String) => {
5090                sql_impl_func("(
5091                    SELECT
5092                    CASE
5093                        WHEN $1::pg_catalog.text[] IS NULL
5094                            THEN NULL
5095                    -- If DB doesn't match, requires full qual
5096                        WHEN $1[1] != pg_catalog.current_database()
5097                            THEN $1
5098                    -- If not in currently searchable schema, must be schema qualified
5099                        WHEN NOT $1[2] = ANY(pg_catalog.current_schemas(true))
5100                            THEN ARRAY[$1[2], $1[3]]
5101                    ELSE
5102                        minimal_name
5103                    END
5104                FROM (
5105                    -- Subquery so we return one null row in the cases where
5106                    -- there are no matches.
5107                    SELECT (
5108                        SELECT DISTINCT
5109                            CASE
5110                                -- If there is only one item with this name and it's rank 1,
5111                                -- it is uniquely nameable with just the final element
5112                                WHEN best_ranked AND count = 1
5113                                    THEN ARRAY[r.name[3]]
5114                                -- Otherwise, it is findable in the search path, so does not
5115                                -- need database qualification
5116                                ELSE
5117                                    ARRAY[r.name[2], r.name[3]]
5118                            END AS minimal_name
5119                        FROM mz_catalog.mz_objects AS o
5120                            JOIN mz_internal.mz_object_oid_alias AS a
5121                                ON o.type = a.object_type,
5122                            -- implied lateral to put the OID alias into scope
5123                            mz_internal.mz_name_rank(
5124                                pg_catalog.current_database(),
5125                                pg_catalog.current_schemas(true),
5126                                $1[3],
5127                                a.oid_alias
5128                            ) AS r
5129                        WHERE o.id = $2 AND r.id = $2
5130                    )
5131                )
5132            )")
5133            } => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5134                oid::FUNC_MZ_MINIMINAL_NAME_QUALIFICATION;
5135        },
5136        "mz_global_id_to_name" => Scalar {
5137            params!(String) => sql_impl_func("
5138            CASE
5139                WHEN $1 IS NULL THEN NULL
5140                ELSE (
5141                    SELECT array_to_string(minimal_name, '.')
5142                    FROM (
5143                        SELECT mz_unsafe.mz_error_if_null(
5144                            (
5145                                -- Return the fully-qualified name
5146                                SELECT DISTINCT ARRAY[qual.d, qual.s, item.name]
5147                                FROM
5148                                    mz_catalog.mz_objects AS item
5149                                JOIN
5150                                (
5151                                    SELECT
5152                                        d.name AS d,
5153                                        s.name AS s,
5154                                        s.id AS schema_id
5155                                    FROM
5156                                        mz_catalog.mz_schemas AS s
5157                                        LEFT JOIN
5158                                            (SELECT id, name FROM mz_catalog.mz_databases)
5159                                            AS d
5160                                            ON s.database_id = d.id
5161                                ) AS qual
5162                                ON qual.schema_id = item.schema_id
5163                                WHERE item.id = CAST($1 AS text)
5164                            ),
5165                            'global ID ' || $1 || ' does not exist'
5166                        )
5167                    ) AS n (fqn),
5168                    LATERAL (
5169                        -- Get the minimal qualification of the fully qualified name
5170                        SELECT mz_internal.mz_minimal_name_qualification(fqn, $1)
5171                    ) AS m (minimal_name)
5172                )
5173                END
5174            ") => String, oid::FUNC_MZ_GLOBAL_ID_TO_NAME;
5175        },
5176        "mz_normalize_object_name" => Scalar {
5177            params!(String) => sql_impl_func("
5178            (
5179                SELECT
5180                    CASE
5181                        WHEN $1 IS NULL OR ident IS NULL THEN NULL
5182                        WHEN pg_catalog.array_length(ident, 1) > 3
5183                            THEN mz_unsafe.mz_error_if_null(
5184                                NULL::pg_catalog.text[],
5185                                'improper relation name (too many dotted names): ' || $1
5186                            )
5187                        ELSE pg_catalog.array_cat(
5188                            pg_catalog.array_fill(
5189                                CAST(NULL AS pg_catalog.text),
5190                                ARRAY[3 - pg_catalog.array_length(ident, 1)]
5191                            ),
5192                            ident
5193                        )
5194                    END
5195                FROM (
5196                    SELECT pg_catalog.parse_ident($1) AS ident
5197                ) AS i
5198            )") => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5199                oid::FUNC_MZ_NORMALIZE_OBJECT_NAME;
5200        },
5201        "mz_normalize_schema_name" => Scalar {
5202            params!(String) => sql_impl_func("
5203             (
5204                SELECT
5205                    CASE
5206                        WHEN $1 IS NULL OR ident IS NULL THEN NULL
5207                        WHEN pg_catalog.array_length(ident, 1) > 2
5208                            THEN mz_unsafe.mz_error_if_null(
5209                                NULL::pg_catalog.text[],
5210                                'improper schema name (too many dotted names): ' || $1
5211                            )
5212                        ELSE pg_catalog.array_cat(
5213                            pg_catalog.array_fill(
5214                                CAST(NULL AS pg_catalog.text),
5215                                ARRAY[2 - pg_catalog.array_length(ident, 1)]
5216                            ),
5217                            ident
5218                        )
5219                    END
5220                FROM (
5221                    SELECT pg_catalog.parse_ident($1) AS ident
5222                ) AS i
5223            )") => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5224                oid::FUNC_MZ_NORMALIZE_SCHEMA_NAME;
5225        },
5226        "mz_render_typmod" => Scalar {
5227            params!(Oid, Int32) => BinaryFunc::from(func::MzRenderTypmod)
5228                => String, oid::FUNC_MZ_RENDER_TYPMOD_OID;
5229        },
5230        "mz_role_oid_memberships" => Scalar {
5231            params!() => UnmaterializableFunc::MzRoleOidMemberships
5232                => SqlScalarType::Map {
5233                    value_type: Box::new(SqlScalarType::Array(
5234                        Box::new(SqlScalarType::String),
5235                    )),
5236                    custom_id: None,
5237                }, oid::FUNC_MZ_ROLE_OID_MEMBERSHIPS;
5238        },
5239        "mz_session_role_memberships" => Scalar {
5240            params!() => UnmaterializableFunc::MzSessionRoleMemberships
5241                => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5242                oid::FUNC_MZ_SESSION_ROLE_MEMBERSHIPS_OID;
5243        },
5244        // There is no regclass equivalent for databases to look up
5245        // oids, so we have this helper function instead.
5246        "mz_database_oid" => Scalar {
5247            params!(String) => sql_impl_func("
5248                CASE
5249                WHEN $1 IS NULL THEN NULL
5250                ELSE (
5251                    mz_unsafe.mz_error_if_null(
5252                        (SELECT oid FROM mz_databases WHERE name = $1),
5253                        'database \"' || $1 || '\" does not exist'
5254                    )
5255                )
5256                END
5257            ") => Oid, oid::FUNC_DATABASE_OID_OID;
5258        },
5259        // There is no regclass equivalent for schemas to look up
5260        // oids, so we have this helper function instead.
5261        "mz_schema_oid" => Scalar {
5262            params!(String) => sql_impl_func("
5263            CASE
5264                WHEN $1 IS NULL THEN NULL
5265            ELSE
5266                mz_unsafe.mz_error_if_null(
5267                    (
5268                        SELECT
5269                            (
5270                                SELECT s.oid
5271                                FROM mz_catalog.mz_schemas AS s
5272                                LEFT JOIN mz_databases AS d ON s.database_id = d.id
5273                                WHERE
5274                                    (
5275                                        -- Filter to only schemas in the named database or the
5276                                        -- current database if no database was specified.
5277                                        d.name = COALESCE(n[1], pg_catalog.current_database())
5278                                        -- Always include all ambient schemas.
5279                                        OR s.database_id IS NULL
5280                                    )
5281                                    AND s.name = n[2]
5282                            )
5283                        FROM mz_internal.mz_normalize_schema_name($1) AS n
5284                    ),
5285                    'schema \"' || $1 || '\" does not exist'
5286                )
5287            END
5288            ") => Oid, oid::FUNC_SCHEMA_OID_OID;
5289        },
5290        // There is no regclass equivalent for roles to look up
5291        // oids, so we have this helper function instead.
5292        "mz_role_oid" => Scalar {
5293            params!(String) => sql_impl_func("
5294                CASE
5295                WHEN $1 IS NULL THEN NULL
5296                ELSE (
5297                    mz_unsafe.mz_error_if_null(
5298                        (SELECT oid FROM mz_catalog.mz_roles WHERE name = $1),
5299                        'role \"' || $1 || '\" does not exist'
5300                    )
5301                )
5302                END
5303            ") => Oid, oid::FUNC_ROLE_OID_OID;
5304        },
5305        // There is no regclass equivalent for roles to look up secrets, so we
5306        // have this helper function instead.
5307        //
5308        // TODO: invent an OID alias for secrets
5309        "mz_secret_oid" => Scalar {
5310            params!(String) => sql_impl_func("
5311                CASE
5312                WHEN $1 IS NULL THEN NULL
5313                ELSE (
5314                    mz_unsafe.mz_error_if_null(
5315                        (SELECT oid FROM mz_catalog.mz_objects WHERE name = $1 AND type = 'secret'),
5316                        'secret \"' || $1 || '\" does not exist'
5317                    )
5318                )
5319                END
5320            ") => Oid, oid::FUNC_SECRET_OID_OID;
5321        },
5322        // This ought to be exposed in `mz_catalog`, but its name is rather
5323        // confusing. It does not identify the SQL session, but the
5324        // invocation of this `environmentd` process.
5325        "mz_session_id" => Scalar {
5326            params!() => UnmaterializableFunc::MzSessionId => Uuid, oid::FUNC_MZ_SESSION_ID_OID;
5327        },
5328        "mz_type_name" => Scalar {
5329            params!(Oid) => UnaryFunc::MzTypeName(func::MzTypeName)
5330                => String, oid::FUNC_MZ_TYPE_NAME;
5331        },
5332        "mz_validate_privileges" => Scalar {
5333            params!(String) => UnaryFunc::MzValidatePrivileges(func::MzValidatePrivileges)
5334                => Bool, oid::FUNC_MZ_VALIDATE_PRIVILEGES_OID;
5335        },
5336        "mz_validate_role_privilege" => Scalar {
5337            params!(String) => UnaryFunc::MzValidateRolePrivilege(
5338                func::MzValidateRolePrivilege,
5339            ) => Bool, oid::FUNC_MZ_VALIDATE_ROLE_PRIVILEGE_OID;
5340        },
5341        "parse_catalog_acl_mode" => Scalar {
5342            params!(Jsonb) => UnaryFunc::ParseCatalogAclMode(func::ParseCatalogAclMode)
5343                => String, oid::FUNC_PARSE_CATALOG_ACL_MODE_OID;
5344        },
5345        "parse_catalog_audit_log_details" => Scalar {
5346            params!(Jsonb) => UnaryFunc::ParseCatalogAuditLogDetails(
5347                func::ParseCatalogAuditLogDetails,
5348            ) => Jsonb, oid::FUNC_PARSE_CATALOG_AUDIT_LOG_DETAILS_OID;
5349        },
5350        "parse_catalog_create_sql" => Scalar {
5351            params!(String) => UnaryFunc::ParseCatalogCreateSql(func::ParseCatalogCreateSql)
5352                => Jsonb, oid::FUNC_PARSE_CATALOG_CREATE_SQL_OID;
5353        },
5354        "parse_catalog_id" => Scalar {
5355            params!(Jsonb) => UnaryFunc::ParseCatalogId(func::ParseCatalogId)
5356                => String, oid::FUNC_PARSE_CATALOG_ID_OID;
5357        },
5358        "parse_catalog_privileges" => Scalar {
5359            params!(Jsonb) => UnaryFunc::ParseCatalogPrivileges(func::ParseCatalogPrivileges)
5360                => SqlScalarType::Array(Box::new(SqlScalarType::MzAclItem)),
5361                oid::FUNC_PARSE_CATALOG_PRIVILEGES_OID;
5362        },
5363        "parse_kafka_source_details" => Scalar {
5364            params!(String) => UnaryFunc::ParseKafkaSourceDetails(
5365                func::ParseKafkaSourceDetails,
5366            ) => Jsonb, oid::FUNC_PARSE_KAFKA_SOURCE_DETAILS_OID;
5367        },
5368        "parse_postgres_source_details" => Scalar {
5369            params!(String) => UnaryFunc::ParsePostgresSourceDetails(
5370                func::ParsePostgresSourceDetails,
5371            ) => Jsonb, oid::FUNC_PARSE_POSTGRES_SOURCE_DETAILS_OID;
5372        },
5373        "parse_source_export_details" => Scalar {
5374            params!(String) => UnaryFunc::ParseSourceExportDetails(
5375                func::ParseSourceExportDetails,
5376            ) => Jsonb, oid::FUNC_PARSE_SOURCE_EXPORT_DETAILS_OID;
5377        },
5378        "parse_connection_details" => Scalar {
5379            params!(String) => UnaryFunc::ParseConnectionDetails(
5380                func::ParseConnectionDetails,
5381            ) => Jsonb, oid::FUNC_PARSE_CONNECTION_DETAILS_OID;
5382        },
5383        "redact_sql" => Scalar {
5384            params!(String) => UnaryFunc::RedactSql(func::RedactSql)
5385                => String, oid::FUNC_REDACT_SQL_OID;
5386        }
5387    }
5388});
5389
5390pub static MZ_UNSAFE_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
5391    use ParamType::*;
5392    use SqlScalarBaseType::*;
5393    builtins! {
5394        // `mz_all`/`mz_any` back the `ALL`/`ANY` subquery operators, whose
5395        // rewrite in `transform_ast` always feeds them a boolean comparison.
5396        // The parameter must stay `Bool`: `AggregateFunc::All`/`Any` render as
5397        // accumulable reduces whose accumulator only accepts boolean datums, so
5398        // a non-boolean argument would crash a compute worker at runtime rather
5399        // than being rejected here at plan time (database-issues#9298).
5400        "mz_all" => Aggregate {
5401            params!(Bool) => AggregateFunc::All => Bool, oid::FUNC_MZ_ALL_OID;
5402        },
5403        "mz_any" => Aggregate {
5404            params!(Bool) => AggregateFunc::Any => Bool, oid::FUNC_MZ_ANY_OID;
5405        },
5406        "mz_avg_promotion_internal_v1" => Scalar {
5407            // Promotes a numeric type to the smallest fractional type that
5408            // can represent it. This is primarily useful for the avg
5409            // aggregate function, so that the avg of an integer column does
5410            // not get truncated to an integer, which would be surprising to
5411            // users (#549).
5412            params!(Float32) => Operation::identity()
5413                => Float32, oid::FUNC_MZ_AVG_PROMOTION_F32_OID_INTERNAL_V1;
5414            params!(Float64) => Operation::identity()
5415                => Float64, oid::FUNC_MZ_AVG_PROMOTION_F64_OID_INTERNAL_V1;
5416            params!(Int16) => Operation::unary(|ecx, e| {
5417                typeconv::plan_cast(
5418                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5419                )
5420            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I16_OID_INTERNAL_V1;
5421            params!(Int32) => Operation::unary(|ecx, e| {
5422                typeconv::plan_cast(
5423                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5424                )
5425            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I32_OID_INTERNAL_V1;
5426            params!(UInt16) => Operation::unary(|ecx, e| {
5427                typeconv::plan_cast(
5428                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5429                )
5430            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U16_OID_INTERNAL_V1;
5431            params!(UInt32) => Operation::unary(|ecx, e| {
5432                typeconv::plan_cast(
5433                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5434                )
5435            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U32_OID_INTERNAL_V1;
5436        },
5437        "mz_avg_promotion" => Scalar {
5438            // Promotes a numeric type to the smallest fractional type that
5439            // can represent it. This is primarily useful for the avg
5440            // aggregate function, so that the avg of an integer column does
5441            // not get truncated to an integer, which would be surprising to
5442            // users (#549).
5443            params!(Float32) => Operation::identity()
5444                => Float32, oid::FUNC_MZ_AVG_PROMOTION_F32_OID;
5445            params!(Float64) => Operation::identity()
5446                => Float64, oid::FUNC_MZ_AVG_PROMOTION_F64_OID;
5447            params!(Int16) => Operation::unary(|ecx, e| {
5448                typeconv::plan_cast(
5449                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5450                )
5451            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I16_OID;
5452            params!(Int32) => Operation::unary(|ecx, e| {
5453                typeconv::plan_cast(
5454                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5455                )
5456            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I32_OID;
5457            params!(Int64) => Operation::unary(|ecx, e| {
5458                typeconv::plan_cast(
5459                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5460                )
5461            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I64_OID;
5462            params!(UInt16) => Operation::unary(|ecx, e| {
5463                typeconv::plan_cast(
5464                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5465                )
5466            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U16_OID;
5467            params!(UInt32) => Operation::unary(|ecx, e| {
5468                typeconv::plan_cast(
5469                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5470                )
5471            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U32_OID;
5472            params!(UInt64) => Operation::unary(|ecx, e| {
5473                typeconv::plan_cast(
5474                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5475                )
5476            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U64_OID;
5477            params!(Numeric) => Operation::unary(|ecx, e| {
5478                typeconv::plan_cast(
5479                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5480                )
5481            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_NUMERIC_OID;
5482        },
5483        "mz_error_if_null" => Scalar {
5484            // If the first argument is NULL, returns an EvalError::Internal whose error
5485            // message is the second argument.
5486            params!(Any, String) => VariadicFunc::from(variadic::ErrorIfNull)
5487                => Any, oid::FUNC_MZ_ERROR_IF_NULL_OID;
5488        },
5489        "generate_series_unoptimized" => Table {
5490            // An int64 `generate_series` the optimizer promises to leave as an
5491            // enumeration (see `TableFunc::GenerateSeriesUnoptimized`). For
5492            // tests that rely on the enumeration work actually happening; not
5493            // a supported surface.
5494            params!(Int64, Int64) => Operation::binary(move |_ecx, start, stop| {
5495                Ok(TableFuncPlan {
5496                    imp: TableFuncImpl::CallTable {
5497                        func: TableFunc::GenerateSeriesUnoptimized,
5498                        exprs: vec![
5499                            start, stop,
5500                            HirScalarExpr::literal(Datum::Int64(1), SqlScalarType::Int64),
5501                        ],
5502                    },
5503                    column_names: vec!["generate_series_unoptimized".into()],
5504                })
5505            }) => ReturnType::set_of(Int64.into()), oid::FUNC_MZ_GEN_SERIES_UNOPT_OID;
5506            params!(Int64, Int64, Int64) => Operation::variadic(move |_ecx, exprs| {
5507                Ok(TableFuncPlan {
5508                    imp: TableFuncImpl::CallTable {
5509                        func: TableFunc::GenerateSeriesUnoptimized,
5510                        exprs,
5511                    },
5512                    column_names: vec!["generate_series_unoptimized".into()],
5513                })
5514            }) => ReturnType::set_of(Int64.into()), oid::FUNC_MZ_GEN_SERIES_UNOPT_STEP_OID;
5515        },
5516        "mz_sleep" => Scalar {
5517            params!(Float64) => UnaryFunc::Sleep(func::Sleep)
5518                => TimestampTz, oid::FUNC_MZ_SLEEP_OID;
5519        },
5520        "mz_panic" => Scalar {
5521            params!(String) => UnaryFunc::Panic(func::Panic) => String, oid::FUNC_MZ_PANIC_OID;
5522        }
5523    }
5524});
5525
5526fn digest(algorithm: &'static str) -> Operation<HirScalarExpr> {
5527    Operation::unary(move |_ecx, input| {
5528        let algorithm = HirScalarExpr::literal(Datum::String(algorithm), SqlScalarType::String);
5529        Ok(input.call_binary(algorithm, BinaryFunc::from(func::DigestBytes)))
5530    })
5531}
5532
5533fn array_to_string(
5534    ecx: &ExprContext,
5535    exprs: Vec<HirScalarExpr>,
5536) -> Result<HirScalarExpr, PlanError> {
5537    let elem_type = match ecx.scalar_type(&exprs[0]) {
5538        SqlScalarType::Array(elem_type) => *elem_type,
5539        _ => unreachable!("array_to_string is guaranteed to receive array as first argument"),
5540    };
5541    Ok(HirScalarExpr::call_variadic(
5542        variadic::ArrayToString { elem_type },
5543        exprs,
5544    ))
5545}
5546
5547/// Correlates an operator with all of its implementations.
5548pub static OP_IMPLS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
5549    use BinaryFunc as BF;
5550    use ParamType::*;
5551    use SqlScalarBaseType::*;
5552    builtins! {
5553        // Literal OIDs collected from PG 13 using a version of this query
5554        // ```sql
5555        // SELECT
5556        //     oid,
5557        //     oprname,
5558        //     oprleft::regtype,
5559        //     oprright::regtype
5560        // FROM
5561        //     pg_operator
5562        // WHERE
5563        //     oprname IN (
5564        //         '+', '-', '*', '/', '%',
5565        //         '|', '&', '#', '~', '<<', '>>',
5566        //         '~~', '!~~'
5567        //     )
5568        // ORDER BY
5569        //     oprname;
5570        // ```
5571        // Values are also available through
5572        // https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_operator.dat
5573
5574        // ARITHMETIC
5575        "+" => Scalar {
5576            params!(Any) => Operation::new(|ecx, exprs, _params, _order_by| {
5577                // Unary plus has unusual compatibility requirements.
5578                //
5579                // In PostgreSQL, it is only defined for numeric types, so
5580                // `+$1` and `+'1'` get coerced to `Float64` per the usual
5581                // rules, but `+'1'::text` is rejected.
5582                //
5583                // In SQLite, unary plus can be applied to *any* type, and
5584                // is always the identity function.
5585                //
5586                // To try to be compatible with both PostgreSQL and SQlite,
5587                // we accept explicitly-typed arguments of any type, but try
5588                // to coerce unknown-type arguments as `Float64`.
5589                typeconv::plan_coerce(ecx, exprs.into_element(), &SqlScalarType::Float64)
5590            }) => Any, oid::OP_UNARY_PLUS_OID;
5591            params!(Int16, Int16) => BF::from(func::AddInt16) => Int16, 550;
5592            params!(Int32, Int32) => BF::from(func::AddInt32) => Int32, 551;
5593            params!(Int64, Int64) => BF::from(func::AddInt64) => Int64, 684;
5594            params!(UInt16, UInt16) => BF::from(func::AddUint16) => UInt16, oid::FUNC_ADD_UINT16;
5595            params!(UInt32, UInt32) => BF::from(func::AddUint32) => UInt32, oid::FUNC_ADD_UINT32;
5596            params!(UInt64, UInt64) => BF::from(func::AddUint64) => UInt64, oid::FUNC_ADD_UINT64;
5597            params!(Float32, Float32) => BF::from(func::AddFloat32) => Float32, 586;
5598            params!(Float64, Float64) => BF::from(func::AddFloat64) => Float64, 591;
5599            params!(Interval, Interval) => BF::from(func::AddInterval) => Interval, 1337;
5600            params!(Timestamp, Interval) => BF::from(func::AddTimestampInterval) => Timestamp, 2066;
5601            params!(Interval, Timestamp) => {
5602                Operation::binary(|_ecx, lhs, rhs| {
5603                    Ok(rhs.call_binary(lhs, func::AddTimestampInterval))
5604                })
5605            } => Timestamp, 2553;
5606            params!(TimestampTz, Interval)
5607                => BF::from(func::AddTimestampTzInterval) => TimestampTz, 1327;
5608            params!(Interval, TimestampTz) => {
5609                Operation::binary(|_ecx, lhs, rhs| {
5610                    Ok(rhs.call_binary(lhs, func::AddTimestampTzInterval))
5611                })
5612            } => TimestampTz, 2554;
5613            params!(Date, Interval) => BF::from(func::AddDateInterval) => Timestamp, 1076;
5614            params!(Interval, Date) => {
5615                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::AddDateInterval)))
5616            } => Timestamp, 2551;
5617            params!(Date, Time) => BF::from(func::AddDateTime) => Timestamp, 1360;
5618            params!(Time, Date) => {
5619                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::AddDateTime)))
5620            } => Timestamp, 1363;
5621            params!(Time, Interval) => BF::from(func::AddTimeInterval) => Time, 1800;
5622            params!(Interval, Time) => {
5623                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::AddTimeInterval)))
5624            } => Time, 1849;
5625            params!(Numeric, Numeric) => BF::from(func::AddNumeric) => Numeric, 1758;
5626            params!(RangeAny, RangeAny) => BF::from(func::RangeUnion) => RangeAny, 3898;
5627        },
5628        "-" => Scalar {
5629            params!(Int16) => UnaryFunc::NegInt16(func::NegInt16) => Int16, 559;
5630            params!(Int32) => UnaryFunc::NegInt32(func::NegInt32) => Int32, 558;
5631            params!(Int64) => UnaryFunc::NegInt64(func::NegInt64) => Int64, 484;
5632            params!(Float32) => UnaryFunc::NegFloat32(func::NegFloat32) => Float32, 584;
5633            params!(Float64) => UnaryFunc::NegFloat64(func::NegFloat64) => Float64, 585;
5634            params!(Numeric) => UnaryFunc::NegNumeric(func::NegNumeric) => Numeric, 17510;
5635            params!(Interval) => UnaryFunc::NegInterval(func::NegInterval) => Interval, 1336;
5636            params!(Int32, Int32) => BF::from(func::SubInt32) => Int32, 555;
5637            params!(Int64, Int64) => BF::from(func::SubInt64) => Int64, 685;
5638            params!(UInt16, UInt16) => BF::from(func::SubUint16) => UInt16, oid::FUNC_SUB_UINT16;
5639            params!(UInt32, UInt32) => BF::from(func::SubUint32) => UInt32, oid::FUNC_SUB_UINT32;
5640            params!(UInt64, UInt64) => BF::from(func::SubUint64) => UInt64, oid::FUNC_SUB_UINT64;
5641            params!(Float32, Float32) => BF::from(func::SubFloat32) => Float32, 587;
5642            params!(Float64, Float64) => BF::from(func::SubFloat64) => Float64, 592;
5643            params!(Numeric, Numeric) => BF::from(func::SubNumeric) => Numeric, 17590;
5644            params!(Interval, Interval) => BF::from(func::SubInterval) => Interval, 1338;
5645            params!(Timestamp, Timestamp) => BF::from(func::SubTimestamp) => Interval, 2067;
5646            params!(TimestampTz, TimestampTz) => BF::from(func::SubTimestampTz) => Interval, 1328;
5647            params!(Timestamp, Interval) => BF::from(func::SubTimestampInterval) => Timestamp, 2068;
5648            params!(TimestampTz, Interval)
5649                => BF::from(func::SubTimestampTzInterval) => TimestampTz, 1329;
5650            params!(Date, Date) => BF::from(func::SubDate) => Int32, 1099;
5651            params!(Date, Interval) => BF::from(func::SubDateInterval) => Timestamp, 1077;
5652            params!(Time, Time) => BF::from(func::SubTime) => Interval, 1399;
5653            params!(Time, Interval) => BF::from(func::SubTimeInterval) => Time, 1801;
5654            params!(Jsonb, Int64) => BF::from(func::JsonbDeleteInt64) => Jsonb, 3286;
5655            params!(Jsonb, String) => BF::from(func::JsonbDeleteString) => Jsonb, 3285;
5656            params!(RangeAny, RangeAny) => BF::from(func::RangeDifference) => RangeAny, 3899;
5657            // TODO(jamii) there should be corresponding overloads for
5658            // Array(Int64) and Array(String)
5659        },
5660        "*" => Scalar {
5661            params!(Int16, Int16) => BF::from(func::MulInt16) => Int16, 526;
5662            params!(Int32, Int32) => BF::from(func::MulInt32) => Int32, 514;
5663            params!(Int64, Int64) => BF::from(func::MulInt64) => Int64, 686;
5664            params!(UInt16, UInt16) => BF::from(func::MulUint16) => UInt16, oid::FUNC_MUL_UINT16;
5665            params!(UInt32, UInt32) => BF::from(func::MulUint32) => UInt32, oid::FUNC_MUL_UINT32;
5666            params!(UInt64, UInt64) => BF::from(func::MulUint64) => UInt64, oid::FUNC_MUL_UINT64;
5667            params!(Float32, Float32) => BF::from(func::MulFloat32) => Float32, 589;
5668            params!(Float64, Float64) => BF::from(func::MulFloat64) => Float64, 594;
5669            params!(Interval, Float64) => BF::from(func::MulInterval) => Interval, 1583;
5670            params!(Float64, Interval) => {
5671                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::MulInterval)))
5672            } => Interval, 1584;
5673            params!(Numeric, Numeric) => BF::from(func::MulNumeric) => Numeric, 1760;
5674            params!(RangeAny, RangeAny) => BF::from(func::RangeIntersection) => RangeAny, 3900;
5675        },
5676        "/" => Scalar {
5677            params!(Int16, Int16) => BF::from(func::DivInt16) => Int16, 527;
5678            params!(Int32, Int32) => BF::from(func::DivInt32) => Int32, 528;
5679            params!(Int64, Int64) => BF::from(func::DivInt64) => Int64, 687;
5680            params!(UInt16, UInt16) => BF::from(func::DivUint16) => UInt16, oid::FUNC_DIV_UINT16;
5681            params!(UInt32, UInt32) => BF::from(func::DivUint32) => UInt32, oid::FUNC_DIV_UINT32;
5682            params!(UInt64, UInt64) => BF::from(func::DivUint64) => UInt64, oid::FUNC_DIV_UINT64;
5683            params!(Float32, Float32) => BF::from(func::DivFloat32) => Float32, 588;
5684            params!(Float64, Float64) => BF::from(func::DivFloat64) => Float64, 593;
5685            params!(Interval, Float64) => BF::from(func::DivInterval) => Interval, 1585;
5686            params!(Numeric, Numeric) => BF::from(func::DivNumeric) => Numeric, 1761;
5687        },
5688        "%" => Scalar {
5689            params!(Int16, Int16) => BF::from(func::ModInt16) => Int16, 529;
5690            params!(Int32, Int32) => BF::from(func::ModInt32) => Int32, 530;
5691            params!(Int64, Int64) => BF::from(func::ModInt64) => Int64, 439;
5692            params!(UInt16, UInt16) => BF::from(func::ModUint16) => UInt16, oid::FUNC_MOD_UINT16;
5693            params!(UInt32, UInt32) => BF::from(func::ModUint32) => UInt32, oid::FUNC_MOD_UINT32;
5694            params!(UInt64, UInt64) => BF::from(func::ModUint64) => UInt64, oid::FUNC_MOD_UINT64;
5695            params!(Float32, Float32) => BF::from(func::ModFloat32) => Float32, oid::OP_MOD_F32_OID;
5696            params!(Float64, Float64) => BF::from(func::ModFloat64) => Float64, oid::OP_MOD_F64_OID;
5697            params!(Numeric, Numeric) => BF::from(func::ModNumeric) => Numeric, 1762;
5698        },
5699        "&" => Scalar {
5700            params!(Int16, Int16) => BF::from(func::BitAndInt16) => Int16, 1874;
5701            params!(Int32, Int32) => BF::from(func::BitAndInt32) => Int32, 1880;
5702            params!(Int64, Int64) => BF::from(func::BitAndInt64) => Int64, 1886;
5703            params!(UInt16, UInt16) => BF::from(func::BitAndUint16) => UInt16, oid::FUNC_AND_UINT16;
5704            params!(UInt32, UInt32) => BF::from(func::BitAndUint32) => UInt32, oid::FUNC_AND_UINT32;
5705            params!(UInt64, UInt64) => BF::from(func::BitAndUint64) => UInt64, oid::FUNC_AND_UINT64;
5706        },
5707        "|" => Scalar {
5708            params!(Int16, Int16) => BF::from(func::BitOrInt16) => Int16, 1875;
5709            params!(Int32, Int32) => BF::from(func::BitOrInt32) => Int32, 1881;
5710            params!(Int64, Int64) => BF::from(func::BitOrInt64) => Int64, 1887;
5711            params!(UInt16, UInt16) => BF::from(func::BitOrUint16) => UInt16, oid::FUNC_OR_UINT16;
5712            params!(UInt32, UInt32) => BF::from(func::BitOrUint32) => UInt32, oid::FUNC_OR_UINT32;
5713            params!(UInt64, UInt64) => BF::from(func::BitOrUint64) => UInt64, oid::FUNC_OR_UINT64;
5714        },
5715        "#" => Scalar {
5716            params!(Int16, Int16) => BF::from(func::BitXorInt16) => Int16, 1876;
5717            params!(Int32, Int32) => BF::from(func::BitXorInt32) => Int32, 1882;
5718            params!(Int64, Int64) => BF::from(func::BitXorInt64) => Int64, 1888;
5719            params!(UInt16, UInt16) => BF::from(func::BitXorUint16) => UInt16, oid::FUNC_XOR_UINT16;
5720            params!(UInt32, UInt32) => BF::from(func::BitXorUint32) => UInt32, oid::FUNC_XOR_UINT32;
5721            params!(UInt64, UInt64) => BF::from(func::BitXorUint64) => UInt64, oid::FUNC_XOR_UINT64;
5722        },
5723        "<<" => Scalar {
5724            params!(Int16, Int32) => BF::from(func::BitShiftLeftInt16) => Int16, 1878;
5725            params!(Int32, Int32) => BF::from(func::BitShiftLeftInt32) => Int32, 1884;
5726            params!(Int64, Int32) => BF::from(func::BitShiftLeftInt64) => Int64, 1890;
5727            params!(UInt16, UInt32) => BF::from(func::BitShiftLeftUint16)
5728                => UInt16, oid::FUNC_SHIFT_LEFT_UINT16;
5729            params!(UInt32, UInt32) => BF::from(func::BitShiftLeftUint32)
5730                => UInt32, oid::FUNC_SHIFT_LEFT_UINT32;
5731            params!(UInt64, UInt32) => BF::from(func::BitShiftLeftUint64)
5732                => UInt64, oid::FUNC_SHIFT_LEFT_UINT64;
5733            params!(RangeAny, RangeAny) => BF::from(func::RangeBefore) => Bool, 3893;
5734        },
5735        ">>" => Scalar {
5736            params!(Int16, Int32) => BF::from(func::BitShiftRightInt16) => Int16, 1879;
5737            params!(Int32, Int32) => BF::from(func::BitShiftRightInt32) => Int32, 1885;
5738            params!(Int64, Int32) => BF::from(func::BitShiftRightInt64) => Int64, 1891;
5739            params!(UInt16, UInt32) => BF::from(func::BitShiftRightUint16)
5740                => UInt16, oid::FUNC_SHIFT_RIGHT_UINT16;
5741            params!(UInt32, UInt32) => BF::from(func::BitShiftRightUint32)
5742                => UInt32, oid::FUNC_SHIFT_RIGHT_UINT32;
5743            params!(UInt64, UInt32) => BF::from(func::BitShiftRightUint64)
5744                => UInt64, oid::FUNC_SHIFT_RIGHT_UINT64;
5745            params!(RangeAny, RangeAny) => BF::from(func::RangeAfter) => Bool, 3894;
5746        },
5747
5748        // ILIKE
5749        "~~*" => Scalar {
5750            params!(String, String) => BF::from(func::IsLikeMatchCaseInsensitive) => Bool, 1627;
5751            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5752                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5753                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5754                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
5755                )
5756            }) => Bool, 1629;
5757        },
5758        "!~~*" => Scalar {
5759            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5760                Ok(lhs
5761                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
5762                    .call_unary(UnaryFunc::Not(func::Not)))
5763            }) => Bool, 1628;
5764            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5765                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5766                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5767                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
5768                    .call_unary(UnaryFunc::Not(func::Not))
5769                )
5770            }) => Bool, 1630;
5771        },
5772
5773
5774        // LIKE
5775        "~~" => Scalar {
5776            params!(String, String) => BF::from(func::IsLikeMatchCaseSensitive) => Bool, 1209;
5777            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5778                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5779                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5780                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
5781                )
5782            }) => Bool, 1211;
5783        },
5784        "!~~" => Scalar {
5785            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5786                Ok(lhs
5787                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
5788                    .call_unary(UnaryFunc::Not(func::Not)))
5789            }) => Bool, 1210;
5790            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5791                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5792                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5793                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
5794                    .call_unary(UnaryFunc::Not(func::Not))
5795                )
5796            }) => Bool, 1212;
5797        },
5798
5799        // REGEX
5800        "~" => Scalar {
5801            params!(Int16) => UnaryFunc::BitNotInt16(func::BitNotInt16) => Int16, 1877;
5802            params!(Int32) => UnaryFunc::BitNotInt32(func::BitNotInt32) => Int32, 1883;
5803            params!(Int64) => UnaryFunc::BitNotInt64(func::BitNotInt64) => Int64, 1889;
5804            params!(UInt16) => UnaryFunc::BitNotUint16(func::BitNotUint16)
5805                => UInt16, oid::FUNC_BIT_NOT_UINT16_OID;
5806            params!(UInt32) => UnaryFunc::BitNotUint32(func::BitNotUint32)
5807                => UInt32, oid::FUNC_BIT_NOT_UINT32_OID;
5808            params!(UInt64) => UnaryFunc::BitNotUint64(func::BitNotUint64)
5809                => UInt64, oid::FUNC_BIT_NOT_UINT64_OID;
5810            params!(String, String)
5811                => BinaryFunc::IsRegexpMatchCaseSensitive(
5812                    func::IsRegexpMatchCaseSensitive,
5813                ) => Bool, 641;
5814            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5815                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5816                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5817                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseSensitive(
5818                        func::IsRegexpMatchCaseSensitive,
5819                    )))
5820            }) => Bool, 1055;
5821        },
5822        "~*" => Scalar {
5823            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5824                Ok(lhs.call_binary(
5825                    rhs,
5826                    BinaryFunc::IsRegexpMatchCaseInsensitive(
5827                        func::IsRegexpMatchCaseInsensitive,
5828                    ),
5829                ))
5830            }) => Bool, 1228;
5831            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5832                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5833                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5834                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseInsensitive(
5835                        func::IsRegexpMatchCaseInsensitive,
5836                    )))
5837            }) => Bool, 1234;
5838        },
5839        "!~" => Scalar {
5840            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5841                Ok(lhs
5842                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseSensitive(
5843                        func::IsRegexpMatchCaseSensitive,
5844                    ))
5845                    .call_unary(UnaryFunc::Not(func::Not)))
5846            }) => Bool, 642;
5847            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5848                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5849                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5850                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseSensitive(
5851                        func::IsRegexpMatchCaseSensitive,
5852                    ))
5853                    .call_unary(UnaryFunc::Not(func::Not)))
5854            }) => Bool, 1056;
5855        },
5856        "!~*" => Scalar {
5857            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5858                Ok(lhs
5859                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseInsensitive(
5860                        func::IsRegexpMatchCaseInsensitive,
5861                    ))
5862                    .call_unary(UnaryFunc::Not(func::Not)))
5863            }) => Bool, 1229;
5864            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5865                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5866                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5867                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseInsensitive(
5868                        func::IsRegexpMatchCaseInsensitive,
5869                    ))
5870                    .call_unary(UnaryFunc::Not(func::Not)))
5871            }) => Bool, 1235;
5872        },
5873
5874        // CONCAT
5875        "||" => Scalar {
5876            params!(String, NonVecAny) => Operation::binary(|ecx, lhs, rhs| {
5877                let rhs = typeconv::plan_cast(
5878                    ecx,
5879                    CastContext::Explicit,
5880                    rhs,
5881                    &SqlScalarType::String,
5882                )?;
5883                Ok(lhs.call_binary(rhs, func::TextConcatBinary))
5884            }) => String, 2779;
5885            params!(NonVecAny, String) => Operation::binary(|ecx, lhs, rhs| {
5886                let lhs = typeconv::plan_cast(
5887                    ecx,
5888                    CastContext::Explicit,
5889                    lhs,
5890                    &SqlScalarType::String,
5891                )?;
5892                Ok(lhs.call_binary(rhs, func::TextConcatBinary))
5893            }) => String, 2780;
5894            params!(String, String) => BF::from(func::TextConcatBinary) => String, 654;
5895            params!(Jsonb, Jsonb) => BF::from(func::JsonbConcat) => Jsonb, 3284;
5896            params!(ArrayAnyCompatible, ArrayAnyCompatible)
5897                => BF::from(func::ArrayArrayConcat) => ArrayAnyCompatible, 375;
5898            params!(ListAnyCompatible, ListAnyCompatible)
5899                => BF::from(func::ListListConcat)
5900                => ListAnyCompatible, oid::OP_CONCAT_LIST_LIST_OID;
5901            params!(ListAnyCompatible, ListElementAnyCompatible)
5902                => BF::from(func::ListElementConcat)
5903                => ListAnyCompatible, oid::OP_CONCAT_LIST_ELEMENT_OID;
5904            params!(ListElementAnyCompatible, ListAnyCompatible)
5905                => BF::from(func::ElementListConcat)
5906                => ListAnyCompatible, oid::OP_CONCAT_ELEMENY_LIST_OID;
5907        },
5908
5909        // JSON, MAP, RANGE, LIST, ARRAY
5910        "->" => Scalar {
5911            params!(Jsonb, Int64) => BF::from(func::JsonbGetInt64) => Jsonb, 3212;
5912            params!(Jsonb, String) => BF::from(func::JsonbGetString) => Jsonb, 3211;
5913            params!(MapAny, String) => BF::from(func::MapGetValue)
5914                => Any, oid::OP_GET_VALUE_MAP_OID;
5915        },
5916        "->>" => Scalar {
5917            params!(Jsonb, Int64) => BF::from(func::JsonbGetInt64Stringify) => String, 3481;
5918            params!(Jsonb, String) => BF::from(func::JsonbGetStringStringify) => String, 3477;
5919        },
5920        "#>" => Scalar {
5921            params!(Jsonb, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5922                => BF::from(func::JsonbGetPath) => Jsonb, 3213;
5923        },
5924        "#>>" => Scalar {
5925            params!(Jsonb, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5926                => BF::from(func::JsonbGetPathStringify) => String, 3206;
5927        },
5928        "@>" => Scalar {
5929            params!(Jsonb, Jsonb) => BF::from(func::JsonbContainsJsonb) => Bool, 3246;
5930            params!(Jsonb, String) => Operation::binary(|_ecx, lhs, rhs| {
5931                Ok(lhs.call_binary(
5932                    rhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb)),
5933                    BinaryFunc::from(func::JsonbContainsJsonb),
5934                ))
5935            }) => Bool, oid::OP_CONTAINS_JSONB_STRING_OID;
5936            params!(String, Jsonb) => Operation::binary(|_ecx, lhs, rhs| {
5937                Ok(lhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb))
5938                      .call_binary(rhs, func::JsonbContainsJsonb))
5939            }) => Bool, oid::OP_CONTAINS_STRING_JSONB_OID;
5940            params!(MapAnyCompatible, MapAnyCompatible)
5941                => BF::from(func::MapContainsMap)
5942                => Bool, oid::OP_CONTAINS_MAP_MAP_OID;
5943            params!(RangeAny, AnyElement) => Operation::binary(|ecx, lhs, rhs| {
5944                let elem_type = ecx.scalar_type(&lhs).unwrap_range_element_type().clone();
5945                let f = match elem_type {
5946                    SqlScalarType::Int32 => BF::from(func::RangeContainsI32),
5947                    SqlScalarType::Int64 => BF::from(func::RangeContainsI64),
5948                    SqlScalarType::Date => BF::from(func::RangeContainsDate),
5949                    SqlScalarType::Numeric { .. } => BF::from(func::RangeContainsNumeric),
5950                    SqlScalarType::Timestamp { .. } => BF::from(func::RangeContainsTimestamp),
5951                    SqlScalarType::TimestampTz { .. } => BF::from(func::RangeContainsTimestampTz),
5952                    _ => bail_unsupported!(format!("range element type: {elem_type:?}")),
5953                };
5954                Ok(lhs.call_binary(rhs, f))
5955            }) => Bool, 3889;
5956            params!(RangeAny, RangeAny) => Operation::binary(|_ecx, lhs, rhs| {
5957                Ok(lhs.call_binary(rhs, BF::from(func::RangeContainsRange)))
5958            }) => Bool, 3890;
5959            params!(ArrayAny, ArrayAny) => Operation::binary(|_ecx, lhs, rhs| {
5960                Ok(lhs.call_binary(rhs, BF::from(func::ArrayContainsArray)))
5961            }) => Bool, 2751;
5962            params!(ListAny, ListAny) => Operation::binary(|_ecx, lhs, rhs| {
5963                Ok(lhs.call_binary(rhs, BF::from(func::ListContainsList)))
5964            }) => Bool, oid::OP_CONTAINS_LIST_LIST_OID;
5965        },
5966        "<@" => Scalar {
5967            params!(Jsonb, Jsonb) => Operation::binary(|_ecx, lhs, rhs| {
5968                Ok(rhs.call_binary(
5969                    lhs,
5970                    func::JsonbContainsJsonb
5971                ))
5972            }) => Bool, 3250;
5973            params!(Jsonb, String) => Operation::binary(|_ecx, lhs, rhs| {
5974                Ok(rhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb))
5975                      .call_binary(lhs, func::JsonbContainsJsonb))
5976            }) => Bool, oid::OP_CONTAINED_JSONB_STRING_OID;
5977            params!(String, Jsonb) => Operation::binary(|_ecx, lhs, rhs| {
5978                Ok(rhs.call_binary(
5979                    lhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb)),
5980                    func::JsonbContainsJsonb,
5981                ))
5982            }) => Bool, oid::OP_CONTAINED_STRING_JSONB_OID;
5983            params!(MapAnyCompatible, MapAnyCompatible) => Operation::binary(|_ecx, lhs, rhs| {
5984                Ok(rhs.call_binary(lhs, func::MapContainsMap))
5985            }) => Bool, oid::OP_CONTAINED_MAP_MAP_OID;
5986            params!(AnyElement, RangeAny) => Operation::binary(|ecx, lhs, rhs| {
5987                let elem_type = ecx.scalar_type(&rhs).unwrap_range_element_type().clone();
5988                let f = match elem_type {
5989                    SqlScalarType::Int32 => BF::from(func::RangeContainsI32Rev),
5990                    SqlScalarType::Int64 => BF::from(func::RangeContainsI64Rev),
5991                    SqlScalarType::Date => BF::from(func::RangeContainsDateRev),
5992                    SqlScalarType::Numeric { .. } => BF::from(func::RangeContainsNumericRev),
5993                    SqlScalarType::Timestamp { .. } => BF::from(func::RangeContainsTimestampRev),
5994                    SqlScalarType::TimestampTz { .. } => {
5995                        BF::from(func::RangeContainsTimestampTzRev)
5996                    }
5997                    _ => bail_unsupported!(format!("range element type: {elem_type:?}")),
5998                };
5999                Ok(rhs.call_binary(lhs, f))
6000            }) => Bool, 3891;
6001            params!(RangeAny, RangeAny) => Operation::binary(|_ecx, lhs, rhs| {
6002                Ok(rhs.call_binary(lhs, BF::from(func::RangeContainsRangeRev)))
6003            }) => Bool, 3892;
6004            params!(ArrayAny, ArrayAny) => Operation::binary(|_ecx, lhs, rhs| {
6005                Ok(lhs.call_binary(rhs, BF::from(func::ArrayContainsArrayRev)))
6006            }) => Bool, 2752;
6007            params!(ListAny, ListAny) => Operation::binary(|_ecx, lhs, rhs| {
6008                Ok(lhs.call_binary(rhs, BF::from(func::ListContainsListRev)))
6009            }) => Bool, oid::OP_IS_CONTAINED_LIST_LIST_OID;
6010        },
6011        "?" => Scalar {
6012            params!(Jsonb, String) => BF::from(func::JsonbContainsString) => Bool, 3247;
6013            params!(MapAny, String) => BF::from(func::MapContainsKey)
6014                => Bool, oid::OP_CONTAINS_KEY_MAP_OID;
6015        },
6016        "?&" => Scalar {
6017            params!(MapAny, SqlScalarType::Array(Box::new(SqlScalarType::String)))
6018                => BF::from(func::MapContainsAllKeys)
6019                => Bool, oid::OP_CONTAINS_ALL_KEYS_MAP_OID;
6020        },
6021        "?|" => Scalar {
6022            params!(MapAny, SqlScalarType::Array(Box::new(SqlScalarType::String)))
6023                => BF::from(func::MapContainsAnyKeys)
6024                => Bool, oid::OP_CONTAINS_ANY_KEYS_MAP_OID;
6025        },
6026        "&&" => Scalar {
6027            params!(RangeAny, RangeAny) => BF::from(func::RangeOverlaps) => Bool, 3888;
6028        },
6029        "&<" => Scalar {
6030            params!(RangeAny, RangeAny) => BF::from(func::RangeOverleft) => Bool, 3895;
6031        },
6032        "&>" => Scalar {
6033            params!(RangeAny, RangeAny) => BF::from(func::RangeOverright) => Bool, 3896;
6034        },
6035        "-|-" => Scalar {
6036            params!(RangeAny, RangeAny) => BF::from(func::RangeAdjacent) => Bool, 3897;
6037        },
6038
6039        // COMPARISON OPS
6040        "<" => Scalar {
6041            params!(Numeric, Numeric) => BF::from(func::Lt) => Bool, 1754;
6042            params!(Bool, Bool) => BF::from(func::Lt) => Bool, 58;
6043            params!(Int16, Int16) => BF::from(func::Lt) => Bool, 95;
6044            params!(Int32, Int32) => BF::from(func::Lt) => Bool, 97;
6045            params!(Int64, Int64) => BF::from(func::Lt) => Bool, 412;
6046            params!(UInt16, UInt16) => BF::from(func::Lt) => Bool, oid::FUNC_LT_UINT16_OID;
6047            params!(UInt32, UInt32) => BF::from(func::Lt) => Bool, oid::FUNC_LT_UINT32_OID;
6048            params!(UInt64, UInt64) => BF::from(func::Lt) => Bool, oid::FUNC_LT_UINT64_OID;
6049            params!(Float32, Float32) => BF::from(func::Lt) => Bool, 622;
6050            params!(Float64, Float64) => BF::from(func::Lt) => Bool, 672;
6051            params!(Oid, Oid) => BF::from(func::Lt) => Bool, 609;
6052            params!(Date, Date) => BF::from(func::Lt) => Bool, 1095;
6053            params!(Time, Time) => BF::from(func::Lt) => Bool, 1110;
6054            params!(Timestamp, Timestamp) => BF::from(func::Lt) => Bool, 2062;
6055            params!(TimestampTz, TimestampTz) => BF::from(func::Lt) => Bool, 1322;
6056            params!(Uuid, Uuid) => BF::from(func::Lt) => Bool, 2974;
6057            params!(Interval, Interval) => BF::from(func::Lt) => Bool, 1332;
6058            params!(Bytes, Bytes) => BF::from(func::Lt) => Bool, 1957;
6059            params!(String, String) => BF::from(func::Lt) => Bool, 664;
6060            params!(Char, Char) => BF::from(func::Lt) => Bool, 1058;
6061            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Lt) => Bool, 631;
6062            params!(PgLegacyName, PgLegacyName) => BF::from(func::Lt) => Bool, 660;
6063            params!(Jsonb, Jsonb) => BF::from(func::Lt) => Bool, 3242;
6064            params!(ArrayAny, ArrayAny) => BF::from(func::Lt) => Bool, 1072;
6065            params!(RecordAny, RecordAny) => BF::from(func::Lt) => Bool, 2990;
6066            params!(MzTimestamp, MzTimestamp) => BF::from(func::Lt)
6067                => Bool, oid::FUNC_MZ_TIMESTAMP_LT_MZ_TIMESTAMP_OID;
6068            params!(RangeAny, RangeAny) => BF::from(func::Lt) => Bool, 3884;
6069        },
6070        "<=" => Scalar {
6071            params!(Numeric, Numeric) => BF::from(func::Lte) => Bool, 1755;
6072            params!(Bool, Bool) => BF::from(func::Lte) => Bool, 1694;
6073            params!(Int16, Int16) => BF::from(func::Lte) => Bool, 522;
6074            params!(Int32, Int32) => BF::from(func::Lte) => Bool, 523;
6075            params!(Int64, Int64) => BF::from(func::Lte) => Bool, 414;
6076            params!(UInt16, UInt16) => BF::from(func::Lte) => Bool, oid::FUNC_LTE_UINT16_OID;
6077            params!(UInt32, UInt32) => BF::from(func::Lte) => Bool, oid::FUNC_LTE_UINT32_OID;
6078            params!(UInt64, UInt64) => BF::from(func::Lte) => Bool, oid::FUNC_LTE_UINT64_OID;
6079            params!(Float32, Float32) => BF::from(func::Lte) => Bool, 624;
6080            params!(Float64, Float64) => BF::from(func::Lte) => Bool, 673;
6081            params!(Oid, Oid) => BF::from(func::Lte) => Bool, 611;
6082            params!(Date, Date) => BF::from(func::Lte) => Bool, 1096;
6083            params!(Time, Time) => BF::from(func::Lte) => Bool, 1111;
6084            params!(Timestamp, Timestamp) => BF::from(func::Lte) => Bool, 2063;
6085            params!(TimestampTz, TimestampTz) => BF::from(func::Lte) => Bool, 1323;
6086            params!(Uuid, Uuid) => BF::from(func::Lte) => Bool, 2976;
6087            params!(Interval, Interval) => BF::from(func::Lte) => Bool, 1333;
6088            params!(Bytes, Bytes) => BF::from(func::Lte) => Bool, 1958;
6089            params!(String, String) => BF::from(func::Lte) => Bool, 665;
6090            params!(Char, Char) => BF::from(func::Lte) => Bool, 1059;
6091            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Lte) => Bool, 632;
6092            params!(PgLegacyName, PgLegacyName) => BF::from(func::Lte) => Bool, 661;
6093            params!(Jsonb, Jsonb) => BF::from(func::Lte) => Bool, 3244;
6094            params!(ArrayAny, ArrayAny) => BF::from(func::Lte) => Bool, 1074;
6095            params!(RecordAny, RecordAny) => BF::from(func::Lte) => Bool, 2992;
6096            params!(MzTimestamp, MzTimestamp) => BF::from(func::Lte)
6097                => Bool, oid::FUNC_MZ_TIMESTAMP_LTE_MZ_TIMESTAMP_OID;
6098            params!(RangeAny, RangeAny) => BF::from(func::Lte) => Bool, 3885;
6099        },
6100        ">" => Scalar {
6101            params!(Numeric, Numeric) => BF::from(func::Gt) => Bool, 1756;
6102            params!(Bool, Bool) => BF::from(func::Gt) => Bool, 59;
6103            params!(Int16, Int16) => BF::from(func::Gt) => Bool, 520;
6104            params!(Int32, Int32) => BF::from(func::Gt) => Bool, 521;
6105            params!(Int64, Int64) => BF::from(func::Gt) => Bool, 413;
6106            params!(UInt16, UInt16) => BF::from(func::Gt) => Bool, oid::FUNC_GT_UINT16_OID;
6107            params!(UInt32, UInt32) => BF::from(func::Gt) => Bool, oid::FUNC_GT_UINT32_OID;
6108            params!(UInt64, UInt64) => BF::from(func::Gt) => Bool, oid::FUNC_GT_UINT64_OID;
6109            params!(Float32, Float32) => BF::from(func::Gt) => Bool, 623;
6110            params!(Float64, Float64) => BF::from(func::Gt) => Bool, 674;
6111            params!(Oid, Oid) => BF::from(func::Gt) => Bool, 610;
6112            params!(Date, Date) => BF::from(func::Gt) => Bool, 1097;
6113            params!(Time, Time) => BF::from(func::Gt) => Bool, 1112;
6114            params!(Timestamp, Timestamp) => BF::from(func::Gt) => Bool, 2064;
6115            params!(TimestampTz, TimestampTz) => BF::from(func::Gt) => Bool, 1324;
6116            params!(Uuid, Uuid) => BF::from(func::Gt) => Bool, 2975;
6117            params!(Interval, Interval) => BF::from(func::Gt) => Bool, 1334;
6118            params!(Bytes, Bytes) => BF::from(func::Gt) => Bool, 1959;
6119            params!(String, String) => BF::from(func::Gt) => Bool, 666;
6120            params!(Char, Char) => BF::from(func::Gt) => Bool, 1060;
6121            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Gt) => Bool, 633;
6122            params!(PgLegacyName, PgLegacyName) => BF::from(func::Gt) => Bool, 662;
6123            params!(Jsonb, Jsonb) => BF::from(func::Gt) => Bool, 3243;
6124            params!(ArrayAny, ArrayAny) => BF::from(func::Gt) => Bool, 1073;
6125            params!(RecordAny, RecordAny) => BF::from(func::Gt) => Bool, 2991;
6126            params!(MzTimestamp, MzTimestamp) => BF::from(func::Gt)
6127                => Bool, oid::FUNC_MZ_TIMESTAMP_GT_MZ_TIMESTAMP_OID;
6128            params!(RangeAny, RangeAny) => BF::from(func::Gt) => Bool, 3887;
6129        },
6130        ">=" => Scalar {
6131            params!(Numeric, Numeric) => BF::from(func::Gte) => Bool, 1757;
6132            params!(Bool, Bool) => BF::from(func::Gte) => Bool, 1695;
6133            params!(Int16, Int16) => BF::from(func::Gte) => Bool, 524;
6134            params!(Int32, Int32) => BF::from(func::Gte) => Bool, 525;
6135            params!(Int64, Int64) => BF::from(func::Gte) => Bool, 415;
6136            params!(UInt16, UInt16) => BF::from(func::Gte) => Bool, oid::FUNC_GTE_UINT16_OID;
6137            params!(UInt32, UInt32) => BF::from(func::Gte) => Bool, oid::FUNC_GTE_UINT32_OID;
6138            params!(UInt64, UInt64) => BF::from(func::Gte) => Bool, oid::FUNC_GTE_UINT64_OID;
6139            params!(Float32, Float32) => BF::from(func::Gte) => Bool, 625;
6140            params!(Float64, Float64) => BF::from(func::Gte) => Bool, 675;
6141            params!(Oid, Oid) => BF::from(func::Gte) => Bool, 612;
6142            params!(Date, Date) => BF::from(func::Gte) => Bool, 1098;
6143            params!(Time, Time) => BF::from(func::Gte) => Bool, 1113;
6144            params!(Timestamp, Timestamp) => BF::from(func::Gte) => Bool, 2065;
6145            params!(TimestampTz, TimestampTz) => BF::from(func::Gte) => Bool, 1325;
6146            params!(Uuid, Uuid) => BF::from(func::Gte) => Bool, 2977;
6147            params!(Interval, Interval) => BF::from(func::Gte) => Bool, 1335;
6148            params!(Bytes, Bytes) => BF::from(func::Gte) => Bool, 1960;
6149            params!(String, String) => BF::from(func::Gte) => Bool, 667;
6150            params!(Char, Char) => BF::from(func::Gte) => Bool, 1061;
6151            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Gte) => Bool, 634;
6152            params!(PgLegacyName, PgLegacyName) => BF::from(func::Gte) => Bool, 663;
6153            params!(Jsonb, Jsonb) => BF::from(func::Gte) => Bool, 3245;
6154            params!(ArrayAny, ArrayAny) => BF::from(func::Gte) => Bool, 1075;
6155            params!(RecordAny, RecordAny) => BF::from(func::Gte) => Bool, 2993;
6156            params!(MzTimestamp, MzTimestamp) => BF::from(func::Gte)
6157                => Bool, oid::FUNC_MZ_TIMESTAMP_GTE_MZ_TIMESTAMP_OID;
6158            params!(RangeAny, RangeAny) => BF::from(func::Gte) => Bool, 3886;
6159        },
6160        // Warning!
6161        // - If you are writing functions here that do not simply use
6162        //   `BinaryFunc::Eq`, you will break row equality (used in e.g.
6163        //   DISTINCT operations and JOINs). In short, this is totally verboten.
6164        // - The implementation of `BinaryFunc::Eq` is byte equality on two
6165        //   datums, and we enforce that both inputs to the function are of the
6166        //   same type in planning. However, it's possible that we will perform
6167        //   equality on types not listed here (e.g. `Varchar`) due to decisions
6168        //   made in the optimizer.
6169        // - Null inputs are handled by `BinaryFunc::eval` checking `propagates_nulls`.
6170        "=" => Scalar {
6171            params!(Numeric, Numeric) => BF::from(func::Eq) => Bool, 1752;
6172            params!(Bool, Bool) => BF::from(func::Eq) => Bool, 91;
6173            params!(Int16, Int16) => BF::from(func::Eq) => Bool, 94;
6174            params!(Int32, Int32) => BF::from(func::Eq) => Bool, 96;
6175            params!(Int64, Int64) => BF::from(func::Eq) => Bool, 410;
6176            params!(UInt16, UInt16) => BF::from(func::Eq) => Bool, oid::FUNC_EQ_UINT16_OID;
6177            params!(UInt32, UInt32) => BF::from(func::Eq) => Bool, oid::FUNC_EQ_UINT32_OID;
6178            params!(UInt64, UInt64) => BF::from(func::Eq) => Bool, oid::FUNC_EQ_UINT64_OID;
6179            params!(Float32, Float32) => BF::from(func::Eq) => Bool, 620;
6180            params!(Float64, Float64) => BF::from(func::Eq) => Bool, 670;
6181            params!(Oid, Oid) => BF::from(func::Eq) => Bool, 607;
6182            params!(Date, Date) => BF::from(func::Eq) => Bool, 1093;
6183            params!(Time, Time) => BF::from(func::Eq) => Bool, 1108;
6184            params!(Timestamp, Timestamp) => BF::from(func::Eq) => Bool, 2060;
6185            params!(TimestampTz, TimestampTz) => BF::from(func::Eq) => Bool, 1320;
6186            params!(Uuid, Uuid) => BF::from(func::Eq) => Bool, 2972;
6187            params!(Interval, Interval) => BF::from(func::Eq) => Bool, 1330;
6188            params!(Bytes, Bytes) => BF::from(func::Eq) => Bool, 1955;
6189            params!(String, String) => BF::from(func::Eq) => Bool, 98;
6190            params!(Char, Char) => BF::from(func::Eq) => Bool, 1054;
6191            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Eq) => Bool, 92;
6192            params!(PgLegacyName, PgLegacyName) => BF::from(func::Eq) => Bool, 93;
6193            params!(Jsonb, Jsonb) => BF::from(func::Eq) => Bool, 3240;
6194            params!(ListAny, ListAny) => BF::from(func::Eq) => Bool, oid::FUNC_LIST_EQ_OID;
6195            params!(ArrayAny, ArrayAny) => BF::from(func::Eq) => Bool, 1070;
6196            params!(RecordAny, RecordAny) => BF::from(func::Eq) => Bool, 2988;
6197            params!(MzTimestamp, MzTimestamp) => BF::from(func::Eq)
6198                => Bool, oid::FUNC_MZ_TIMESTAMP_EQ_MZ_TIMESTAMP_OID;
6199            params!(RangeAny, RangeAny) => BF::from(func::Eq) => Bool, 3882;
6200            params!(MzAclItem, MzAclItem) => BF::from(func::Eq)
6201                => Bool, oid::FUNC_MZ_ACL_ITEM_EQ_MZ_ACL_ITEM_OID;
6202            params!(AclItem, AclItem) => BF::from(func::Eq) => Bool, 974;
6203        },
6204        "<>" => Scalar {
6205            params!(Numeric, Numeric) => BF::from(func::NotEq) => Bool, 1753;
6206            params!(Bool, Bool) => BF::from(func::NotEq) => Bool, 85;
6207            params!(Int16, Int16) => BF::from(func::NotEq) => Bool, 519;
6208            params!(Int32, Int32) => BF::from(func::NotEq) => Bool, 518;
6209            params!(Int64, Int64) => BF::from(func::NotEq) => Bool, 411;
6210            params!(UInt16, UInt16) => BF::from(func::NotEq) => Bool, oid::FUNC_NOT_EQ_UINT16_OID;
6211            params!(UInt32, UInt32) => BF::from(func::NotEq) => Bool, oid::FUNC_NOT_EQ_UINT32_OID;
6212            params!(UInt64, UInt64) => BF::from(func::NotEq) => Bool, oid::FUNC_NOT_EQ_UINT64_OID;
6213            params!(Float32, Float32) => BF::from(func::NotEq) => Bool, 621;
6214            params!(Float64, Float64) => BF::from(func::NotEq) => Bool, 671;
6215            params!(Oid, Oid) => BF::from(func::NotEq) => Bool, 608;
6216            params!(Date, Date) => BF::from(func::NotEq) => Bool, 1094;
6217            params!(Time, Time) => BF::from(func::NotEq) => Bool, 1109;
6218            params!(Timestamp, Timestamp) => BF::from(func::NotEq) => Bool, 2061;
6219            params!(TimestampTz, TimestampTz) => BF::from(func::NotEq) => Bool, 1321;
6220            params!(Uuid, Uuid) => BF::from(func::NotEq) => Bool, 2973;
6221            params!(Interval, Interval) => BF::from(func::NotEq) => Bool, 1331;
6222            params!(Bytes, Bytes) => BF::from(func::NotEq) => Bool, 1956;
6223            params!(String, String) => BF::from(func::NotEq) => Bool, 531;
6224            params!(Char, Char) => BF::from(func::NotEq) => Bool, 1057;
6225            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::NotEq) => Bool, 630;
6226            params!(PgLegacyName, PgLegacyName) => BF::from(func::NotEq) => Bool, 643;
6227            params!(Jsonb, Jsonb) => BF::from(func::NotEq) => Bool, 3241;
6228            params!(ArrayAny, ArrayAny) => BF::from(func::NotEq) => Bool, 1071;
6229            params!(RecordAny, RecordAny) => BF::from(func::NotEq) => Bool, 2989;
6230            params!(MzTimestamp, MzTimestamp) => BF::from(func::NotEq)
6231                => Bool, oid::FUNC_MZ_TIMESTAMP_NOT_EQ_MZ_TIMESTAMP_OID;
6232            params!(RangeAny, RangeAny) => BF::from(func::NotEq) => Bool, 3883;
6233            params!(MzAclItem, MzAclItem) => BF::from(func::NotEq)
6234                => Bool, oid::FUNC_MZ_ACL_ITEM_NOT_EQ_MZ_ACL_ITEM_OID;
6235        }
6236    }
6237});
6238
6239/// Resolves the operator to a set of function implementations.
6240pub fn resolve_op(op: &str) -> Result<&'static [FuncImpl<HirScalarExpr>], PlanError> {
6241    match OP_IMPLS.get(op) {
6242        Some(Func::Scalar(impls)) => Ok(impls),
6243        Some(_) => unreachable!("all operators must be scalar functions"),
6244        // TODO: these require sql arrays
6245        // JsonContainsAnyFields
6246        // JsonContainsAllFields
6247        // TODO: these require json paths
6248        // JsonGetPath
6249        // JsonGetPathAsText
6250        // JsonDeletePath
6251        // JsonContainsPath
6252        // JsonApplyPathPredicate
6253        None => bail_unsupported!(format!("[{}]", op)),
6254    }
6255}
6256
6257// Since ViewableVariables is unmaterializeable (which can't be eval'd) that
6258// depend on their arguments, implement directly with Hir.
6259fn current_settings(
6260    name: HirScalarExpr,
6261    missing_ok: HirScalarExpr,
6262) -> Result<HirScalarExpr, PlanError> {
6263    // MapGetValue returns Null if the key doesn't exist in the map.
6264    let expr = HirScalarExpr::call_binary(
6265        HirScalarExpr::call_unmaterializable(UnmaterializableFunc::ViewableVariables),
6266        HirScalarExpr::call_unary(name, UnaryFunc::Lower(func::Lower)),
6267        func::MapGetValue,
6268    );
6269    let expr = HirScalarExpr::if_then_else(
6270        missing_ok,
6271        expr.clone(),
6272        HirScalarExpr::call_variadic(
6273            variadic::ErrorIfNull,
6274            vec![
6275                expr,
6276                HirScalarExpr::literal(
6277                    Datum::String("unrecognized configuration parameter"),
6278                    SqlScalarType::String,
6279                ),
6280            ],
6281        ),
6282    );
6283    Ok(expr)
6284}