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                bail_unsupported!("sum(interval)");
3848            }) => Interval, 2113;
3849        },
3850
3851        // Scalar window functions.
3852        "row_number" => ScalarWindow {
3853            params!() => ScalarWindowFunc::RowNumber => Int64, 3100;
3854        },
3855        "rank" => ScalarWindow {
3856            params!() => ScalarWindowFunc::Rank => Int64, 3101;
3857        },
3858        "dense_rank" => ScalarWindow {
3859            params!() => ScalarWindowFunc::DenseRank => Int64, 3102;
3860        },
3861        "lag" => ValueWindow {
3862            // All args are encoded into a single record to be handled later
3863            params!(AnyElement) => Operation::unary(|ecx, e| {
3864                let typ = ecx.scalar_type(&e);
3865                let e = HirScalarExpr::call_variadic(
3866                    variadic::RecordCreate {
3867                        field_names: vec![
3868                            ColumnName::from("expr"),
3869                            ColumnName::from("offset"),
3870                            ColumnName::from("default"),
3871                        ],
3872                    },
3873                    vec![
3874                        e,
3875                        HirScalarExpr::literal(Datum::Int32(1), SqlScalarType::Int32),
3876                        HirScalarExpr::literal_null(typ),
3877                    ],
3878                );
3879                Ok((e, ValueWindowFunc::Lag))
3880            }) => AnyElement, 3106;
3881            params!(AnyElement, Int32) => Operation::binary(|ecx, e, offset| {
3882                let typ = ecx.scalar_type(&e);
3883                let e = HirScalarExpr::call_variadic(
3884                    variadic::RecordCreate {
3885                        field_names: vec![
3886                            ColumnName::from("expr"),
3887                            ColumnName::from("offset"),
3888                            ColumnName::from("default"),
3889                        ],
3890                    },
3891                    vec![e, offset, HirScalarExpr::literal_null(typ)],
3892                );
3893                Ok((e, ValueWindowFunc::Lag))
3894            }) => AnyElement, 3107;
3895            params!(AnyCompatible, Int32, AnyCompatible) => Operation::variadic(|_ecx, exprs| {
3896                let e = HirScalarExpr::call_variadic(
3897                    variadic::RecordCreate {
3898                        field_names: vec![
3899                            ColumnName::from("expr"),
3900                            ColumnName::from("offset"),
3901                            ColumnName::from("default"),
3902                        ],
3903                    },
3904                    exprs,
3905                );
3906                Ok((e, ValueWindowFunc::Lag))
3907            }) => AnyCompatible, 3108;
3908        },
3909        "lead" => ValueWindow {
3910            // All args are encoded into a single record to be handled later
3911            params!(AnyElement) => Operation::unary(|ecx, e| {
3912                let typ = ecx.scalar_type(&e);
3913                let e = HirScalarExpr::call_variadic(
3914                    variadic::RecordCreate {
3915                        field_names: vec![
3916                            ColumnName::from("expr"),
3917                            ColumnName::from("offset"),
3918                            ColumnName::from("default"),
3919                        ],
3920                    },
3921                    vec![
3922                        e,
3923                        HirScalarExpr::literal(Datum::Int32(1), SqlScalarType::Int32),
3924                        HirScalarExpr::literal_null(typ),
3925                    ],
3926                );
3927                Ok((e, ValueWindowFunc::Lead))
3928            }) => AnyElement, 3109;
3929            params!(AnyElement, Int32) => Operation::binary(|ecx, e, offset| {
3930                let typ = ecx.scalar_type(&e);
3931                let e = HirScalarExpr::call_variadic(
3932                    variadic::RecordCreate {
3933                        field_names: vec![
3934                            ColumnName::from("expr"),
3935                            ColumnName::from("offset"),
3936                            ColumnName::from("default"),
3937                        ],
3938                    },
3939                    vec![e, offset, HirScalarExpr::literal_null(typ)],
3940                );
3941                Ok((e, ValueWindowFunc::Lead))
3942            }) => AnyElement, 3110;
3943            params!(AnyCompatible, Int32, AnyCompatible) => Operation::variadic(|_ecx, exprs| {
3944                let e = HirScalarExpr::call_variadic(
3945                    variadic::RecordCreate {
3946                        field_names: vec![
3947                            ColumnName::from("expr"),
3948                            ColumnName::from("offset"),
3949                            ColumnName::from("default"),
3950                        ],
3951                    },
3952                    exprs,
3953                );
3954                Ok((e, ValueWindowFunc::Lead))
3955            }) => AnyCompatible, 3111;
3956        },
3957        "first_value" => ValueWindow {
3958            params!(AnyElement) => ValueWindowFunc::FirstValue => AnyElement, 3112;
3959        },
3960        "last_value" => ValueWindow {
3961            params!(AnyElement) => ValueWindowFunc::LastValue => AnyElement, 3113;
3962        },
3963
3964        // Table functions.
3965        "generate_series" => Table {
3966            params!(Int32, Int32, Int32) => Operation::variadic(move |_ecx, exprs| {
3967                Ok(TableFuncPlan {
3968                    imp: TableFuncImpl::CallTable {
3969                        func: TableFunc::GenerateSeriesInt32,
3970                        exprs,
3971                    },
3972                    column_names: vec!["generate_series".into()],
3973                })
3974            }) => ReturnType::set_of(Int32.into()), 1066;
3975            params!(Int32, Int32) => Operation::binary(move |_ecx, start, stop| {
3976                Ok(TableFuncPlan {
3977                    imp: TableFuncImpl::CallTable {
3978                        func: TableFunc::GenerateSeriesInt32,
3979                        exprs: vec![
3980                            start, stop,
3981                            HirScalarExpr::literal(Datum::Int32(1), SqlScalarType::Int32),
3982                        ],
3983                    },
3984                    column_names: vec!["generate_series".into()],
3985                })
3986            }) => ReturnType::set_of(Int32.into()), 1067;
3987            params!(Int64, Int64, Int64) => Operation::variadic(move |_ecx, exprs| {
3988                Ok(TableFuncPlan {
3989                    imp: TableFuncImpl::CallTable {
3990                        func: TableFunc::GenerateSeriesInt64,
3991                        exprs,
3992                    },
3993                    column_names: vec!["generate_series".into()],
3994                })
3995            }) => ReturnType::set_of(Int64.into()), 1068;
3996            params!(Int64, Int64) => Operation::binary(move |_ecx, start, stop| {
3997                Ok(TableFuncPlan {
3998                    imp: TableFuncImpl::CallTable {
3999                        func: TableFunc::GenerateSeriesInt64,
4000                        exprs: vec![
4001                            start, stop,
4002                            HirScalarExpr::literal(Datum::Int64(1), SqlScalarType::Int64),
4003                        ],
4004                    },
4005                    column_names: vec!["generate_series".into()],
4006                })
4007            }) => ReturnType::set_of(Int64.into()), 1069;
4008            params!(Timestamp, Timestamp, Interval) => Operation::variadic(move |_ecx, exprs| {
4009                Ok(TableFuncPlan {
4010                    imp: TableFuncImpl::CallTable {
4011                        func: TableFunc::GenerateSeriesTimestamp,
4012                        exprs,
4013                    },
4014                    column_names: vec!["generate_series".into()],
4015                })
4016            }) => ReturnType::set_of(Timestamp.into()), 938;
4017            params!(TimestampTz, TimestampTz, Interval) => Operation::variadic(move |_ecx, exprs| {
4018                Ok(TableFuncPlan {
4019                    imp: TableFuncImpl::CallTable {
4020                        func: TableFunc::GenerateSeriesTimestampTz,
4021                        exprs,
4022                    },
4023                    column_names: vec!["generate_series".into()],
4024                })
4025            }) => ReturnType::set_of(TimestampTz.into()), 939;
4026        },
4027
4028        "generate_subscripts" => Table {
4029            params!(ArrayAny, Int32) => Operation::variadic(move |_ecx, exprs| {
4030                Ok(TableFuncPlan {
4031                    imp: TableFuncImpl::CallTable {
4032                        func: TableFunc::GenerateSubscriptsArray,
4033                        exprs,
4034                    },
4035                    column_names: vec!["generate_subscripts".into()],
4036                })
4037            }) => ReturnType::set_of(Int32.into()), 1192;
4038        },
4039
4040        "jsonb_array_elements" => Table {
4041            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4042                Ok(TableFuncPlan {
4043                    imp: TableFuncImpl::CallTable {
4044                        func: TableFunc::JsonbArrayElements,
4045                        exprs: vec![jsonb],
4046                    },
4047                    column_names: vec!["value".into()],
4048                })
4049            }) => ReturnType::set_of(Jsonb.into()), 3219;
4050        },
4051        "jsonb_array_elements_text" => Table {
4052            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4053                Ok(TableFuncPlan {
4054                    imp: TableFuncImpl::CallTable {
4055                        func: TableFunc::JsonbArrayElementsStringify,
4056                        exprs: vec![jsonb],
4057                    },
4058                    column_names: vec!["value".into()],
4059                })
4060            }) => ReturnType::set_of(String.into()), 3465;
4061        },
4062        "jsonb_each" => Table {
4063            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4064                Ok(TableFuncPlan {
4065                    imp: TableFuncImpl::CallTable {
4066                        func: TableFunc::JsonbEach,
4067                        exprs: vec![jsonb],
4068                    },
4069                    column_names: vec!["key".into(), "value".into()],
4070                })
4071            }) => ReturnType::set_of(RecordAny), 3208;
4072        },
4073        "jsonb_each_text" => Table {
4074            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4075                Ok(TableFuncPlan {
4076                    imp: TableFuncImpl::CallTable {
4077                        func: TableFunc::JsonbEachStringify,
4078                        exprs: vec![jsonb],
4079                    },
4080                    column_names: vec!["key".into(), "value".into()],
4081                })
4082            }) => ReturnType::set_of(RecordAny), 3932;
4083        },
4084        "jsonb_object_keys" => Table {
4085            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4086                Ok(TableFuncPlan {
4087                    imp: TableFuncImpl::CallTable {
4088                        func: TableFunc::JsonbObjectKeys,
4089                        exprs: vec![jsonb],
4090                    },
4091                    column_names: vec!["jsonb_object_keys".into()],
4092                })
4093            }) => ReturnType::set_of(String.into()), 3931;
4094        },
4095        // Note that these implementations' input to `generate_series` is
4096        // contrived to match Flink's expected values. There are other,
4097        // equally valid windows we could generate.
4098        "date_bin_hopping" => Table {
4099            // (hop, width, timestamp)
4100            params!(Interval, Interval, Timestamp)
4101                => experimental_sql_impl_table_func(
4102                    &vars::ENABLE_DATE_BIN_HOPPING, "
4103                    SELECT *
4104                    FROM pg_catalog.generate_series(
4105                        pg_catalog.date_bin($1, $3 + $1, '1970-01-01') - $2, $3, $1
4106                    ) AS dbh(date_bin_hopping)
4107                ") => ReturnType::set_of(Timestamp.into()),
4108                oid::FUNC_MZ_DATE_BIN_HOPPING_UNIX_EPOCH_TS_OID;
4109            // (hop, width, timestamp)
4110            params!(Interval, Interval, TimestampTz)
4111                => experimental_sql_impl_table_func(
4112                    &vars::ENABLE_DATE_BIN_HOPPING, "
4113                    SELECT *
4114                    FROM pg_catalog.generate_series(
4115                        pg_catalog.date_bin($1, $3 + $1, '1970-01-01') - $2, $3, $1
4116                    ) AS dbh(date_bin_hopping)
4117                ") => ReturnType::set_of(TimestampTz.into()),
4118                oid::FUNC_MZ_DATE_BIN_HOPPING_UNIX_EPOCH_TSTZ_OID;
4119            // (hop, width, timestamp, origin)
4120            params!(Interval, Interval, Timestamp, Timestamp)
4121                => experimental_sql_impl_table_func(
4122                    &vars::ENABLE_DATE_BIN_HOPPING, "
4123                    SELECT *
4124                    FROM pg_catalog.generate_series(
4125                        pg_catalog.date_bin($1, $3 + $1, $4) - $2, $3, $1
4126                    ) AS dbh(date_bin_hopping)
4127                ") => ReturnType::set_of(Timestamp.into()),
4128                oid::FUNC_MZ_DATE_BIN_HOPPING_TS_OID;
4129            // (hop, width, timestamp, origin)
4130            params!(Interval, Interval, TimestampTz, TimestampTz)
4131                => experimental_sql_impl_table_func(
4132                    &vars::ENABLE_DATE_BIN_HOPPING, "
4133                    SELECT *
4134                    FROM pg_catalog.generate_series(
4135                        pg_catalog.date_bin($1, $3 + $1, $4) - $2, $3, $1
4136                    ) AS dbh(date_bin_hopping)
4137                ") => ReturnType::set_of(TimestampTz.into()),
4138                oid::FUNC_MZ_DATE_BIN_HOPPING_TSTZ_OID;
4139        },
4140        "encode" => Scalar {
4141            params!(Bytes, String) => BinaryFunc::from(func::Encode) => String, 1946;
4142        },
4143        "decode" => Scalar {
4144            params!(String, String) => BinaryFunc::from(func::Decode) => Bytes, 1947;
4145        },
4146        "regexp_split_to_array" => Scalar {
4147            params!(String, String) => VariadicFunc::from(variadic::RegexpSplitToArray)
4148                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 2767;
4149            params!(String, String, String) => VariadicFunc::from(variadic::RegexpSplitToArray)
4150                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 2768;
4151        },
4152        "regexp_split_to_table" => Table {
4153            params!(String, String) => sql_impl_table_func("
4154                SELECT unnest(regexp_split_to_array($1, $2))
4155            ") => ReturnType::set_of(String.into()), 2765;
4156            params!(String, String, String) => sql_impl_table_func("
4157                SELECT unnest(regexp_split_to_array($1, $2, $3))
4158            ") => ReturnType::set_of(String.into()), 2766;
4159        },
4160        "regexp_replace" => Scalar {
4161            params!(String, String, String)
4162                => VariadicFunc::from(variadic::RegexpReplace) => String, 2284;
4163            params!(String, String, String, String)
4164                => VariadicFunc::from(variadic::RegexpReplace) => String, 2285;
4165            // TODO: PostgreSQL supports additional five and six argument
4166            // forms of this function which allow controlling where to
4167            // start the replacement and how many replacements to make.
4168        },
4169        "regexp_matches" => Table {
4170            params!(String, String) => Operation::variadic(move |_ecx, exprs| {
4171                let column_names = vec!["regexp_matches".into()];
4172                Ok(TableFuncPlan {
4173                    imp: TableFuncImpl::CallTable {
4174                        func: TableFunc::RegexpMatches,
4175                        exprs: vec![exprs[0].clone(), exprs[1].clone()],
4176                    },
4177                    column_names,
4178                })
4179            }) => ReturnType::set_of(
4180                SqlScalarType::Array(Box::new(SqlScalarType::String)).into(),
4181            ), 2763;
4182            params!(String, String, String) => Operation::variadic(move |_ecx, exprs| {
4183                let column_names = vec!["regexp_matches".into()];
4184                Ok(TableFuncPlan {
4185                    imp: TableFuncImpl::CallTable {
4186                        func: TableFunc::RegexpMatches,
4187                        exprs: vec![exprs[0].clone(), exprs[1].clone(), exprs[2].clone()],
4188                    },
4189                    column_names,
4190                })
4191            }) => ReturnType::set_of(
4192                SqlScalarType::Array(Box::new(SqlScalarType::String)).into(),
4193            ), 2764;
4194        },
4195        "reverse" => Scalar {
4196            params!(String) => UnaryFunc::Reverse(func::Reverse) => String, 3062;
4197        }
4198    };
4199
4200    // Add side-effecting functions, which are defined in a separate module
4201    // using a restricted set of function definition features (e.g., no
4202    // overloads) to make them easier to plan.
4203    for sef_builtin in PG_CATALOG_SEF_BUILTINS.values() {
4204        builtins.insert(
4205            sef_builtin.name,
4206            Func::Scalar(vec![FuncImpl {
4207                oid: sef_builtin.oid,
4208                params: ParamList::Exact(
4209                    sef_builtin
4210                        .param_types
4211                        .iter()
4212                        .map(|t| ParamType::from(t.clone()))
4213                        .collect(),
4214                ),
4215                return_type: ReturnType::scalar(ParamType::from(
4216                    sef_builtin.return_type.scalar_type.clone(),
4217                )),
4218                op: Operation::variadic(|_ecx, _e| {
4219                    bail_unsupported!(format!("{} in this position", sef_builtin.name))
4220                }),
4221            }]),
4222        );
4223    }
4224
4225    builtins
4226});
4227
4228pub static INFORMATION_SCHEMA_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> =
4229    LazyLock::new(|| {
4230        use ParamType::*;
4231        builtins! {
4232            "_pg_expandarray" => Table {
4233                // See: https://github.com/postgres/postgres/blob/
4234                // 16e3ad5d143795b05a21dc887c2ab384cce4bcb8/
4235                // src/backend/catalog/information_schema.sql#L43
4236                params!(ArrayAny) => sql_impl_table_func("
4237                    SELECT
4238                        $1[s] AS x,
4239                        s - pg_catalog.array_lower($1, 1) + 1 AS n
4240                    FROM pg_catalog.generate_series(
4241                        pg_catalog.array_lower($1, 1),
4242                        pg_catalog.array_upper($1, 1),
4243                        1) as g(s)
4244                ") => ReturnType::set_of(RecordAny), oid::FUNC_PG_EXPAND_ARRAY;
4245            }
4246        }
4247    });
4248
4249pub static MZ_CATALOG_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
4250    use ParamType::*;
4251    use SqlScalarBaseType::*;
4252    builtins! {
4253        "constant_time_eq" => Scalar {
4254            params!(Bytes, Bytes) => BinaryFunc::from(func::ConstantTimeEqBytes)
4255                => Bool, oid::FUNC_CONSTANT_TIME_EQ_BYTES_OID;
4256            params!(String, String) => BinaryFunc::from(func::ConstantTimeEqString)
4257                => Bool, oid::FUNC_CONSTANT_TIME_EQ_STRING_OID;
4258        },
4259        // Note: this is the original version of the AVG(...) function, as it existed prior to
4260        // v0.66. We updated the internal type promotion used when summing values to increase
4261        // precision, but objects (e.g. materialized views) that already used the AVG(...) function
4262        // could not be changed. So we migrated all existing uses of the AVG(...) function to this
4263        // version.
4264        //
4265        // TODO(parkmycar): When objects no longer depend on this function we can safely delete it.
4266        "avg_internal_v1" => Scalar {
4267            params!(Int64) =>
4268                Operation::nullary(|_ecx| {
4269                    catalog_name_only!("avg_internal_v1")
4270                }) => Numeric,
4271                oid::FUNC_AVG_INTERNAL_V1_INT64_OID;
4272            params!(Int32) =>
4273                Operation::nullary(|_ecx| {
4274                    catalog_name_only!("avg_internal_v1")
4275                }) => Numeric,
4276                oid::FUNC_AVG_INTERNAL_V1_INT32_OID;
4277            params!(Int16) =>
4278                Operation::nullary(|_ecx| {
4279                    catalog_name_only!("avg_internal_v1")
4280                }) => Numeric,
4281                oid::FUNC_AVG_INTERNAL_V1_INT16_OID;
4282            params!(UInt64) =>
4283                Operation::nullary(|_ecx| {
4284                    catalog_name_only!("avg_internal_v1")
4285                }) => Numeric,
4286                oid::FUNC_AVG_INTERNAL_V1_UINT64_OID;
4287            params!(UInt32) =>
4288                Operation::nullary(|_ecx| {
4289                    catalog_name_only!("avg_internal_v1")
4290                }) => Numeric,
4291                oid::FUNC_AVG_INTERNAL_V1_UINT32_OID;
4292            params!(UInt16) =>
4293                Operation::nullary(|_ecx| {
4294                    catalog_name_only!("avg_internal_v1")
4295                }) => Numeric,
4296                oid::FUNC_AVG_INTERNAL_V1_UINT16_OID;
4297            params!(Float32) =>
4298                Operation::nullary(|_ecx| {
4299                    catalog_name_only!("avg_internal_v1")
4300                }) => Float64,
4301                oid::FUNC_AVG_INTERNAL_V1_FLOAT32_OID;
4302            params!(Float64) =>
4303                Operation::nullary(|_ecx| {
4304                    catalog_name_only!("avg_internal_v1")
4305                }) => Float64,
4306                oid::FUNC_AVG_INTERNAL_V1_FLOAT64_OID;
4307            params!(Interval) =>
4308                Operation::nullary(|_ecx| {
4309                    catalog_name_only!("avg_internal_v1")
4310                }) => Interval,
4311                oid::FUNC_AVG_INTERNAL_V1_INTERVAL_OID;
4312        },
4313        "csv_extract" => Table {
4314            params!(Int64, String) => Operation::binary(move |_ecx, ncols, input| {
4315                const MAX_EXTRACT_COLUMNS: i64 = 8192;
4316                const TOO_MANY_EXTRACT_COLUMNS: i64 = MAX_EXTRACT_COLUMNS + 1;
4317
4318                let ncols = match ncols.into_literal_int64() {
4319                    None | Some(i64::MIN..=0) => {
4320                        sql_bail!(
4321                            "csv_extract number of columns \
4322                             must be a positive integer literal"
4323                        );
4324                    },
4325                    Some(ncols @ 1..=MAX_EXTRACT_COLUMNS) => ncols,
4326                    Some(ncols @ TOO_MANY_EXTRACT_COLUMNS..) => {
4327                        return Err(PlanError::TooManyColumns {
4328                            max_num_columns: usize::try_from(MAX_EXTRACT_COLUMNS)
4329                                .unwrap_or(usize::MAX),
4330                            req_num_columns: usize::try_from(ncols)
4331                                .unwrap_or(usize::MAX),
4332                        });
4333                    },
4334                };
4335                let ncols = usize::try_from(ncols).expect("known to be greater than zero");
4336
4337                let column_names = (1..=ncols).map(|i| format!("column{}", i).into()).collect();
4338                Ok(TableFuncPlan {
4339                    imp: TableFuncImpl::CallTable {
4340                        func: TableFunc::CsvExtract(ncols),
4341                        exprs: vec![input],
4342                    },
4343                    column_names,
4344                })
4345            }) => ReturnType::set_of(RecordAny), oid::FUNC_CSV_EXTRACT_OID;
4346        },
4347        "concat_agg" => Aggregate {
4348            params!(Any) => Operation::unary(|_ecx, _e| {
4349                bail_unsupported!("concat_agg")
4350            }) => String, oid::FUNC_CONCAT_AGG_OID;
4351        },
4352        "crc32" => Scalar {
4353            params!(String) => UnaryFunc::Crc32String(func::Crc32String)
4354                => UInt32, oid::FUNC_CRC32_STRING_OID;
4355            params!(Bytes) => UnaryFunc::Crc32Bytes(func::Crc32Bytes)
4356                => UInt32, oid::FUNC_CRC32_BYTES_OID;
4357        },
4358        "datediff" => Scalar {
4359            params!(String, Timestamp, Timestamp)
4360                => VariadicFunc::from(variadic::DateDiffTimestamp)
4361                => Int64, oid::FUNC_DATEDIFF_TIMESTAMP;
4362            params!(String, TimestampTz, TimestampTz)
4363                => VariadicFunc::from(variadic::DateDiffTimestampTz)
4364                => Int64, oid::FUNC_DATEDIFF_TIMESTAMPTZ;
4365            params!(String, Date, Date) => VariadicFunc::from(variadic::DateDiffDate)
4366                => Int64, oid::FUNC_DATEDIFF_DATE;
4367            params!(String, Time, Time) => VariadicFunc::from(variadic::DateDiffTime)
4368                => Int64, oid::FUNC_DATEDIFF_TIME;
4369        },
4370        // We can't use the `privilege_fn!` macro because the macro relies on the object having an
4371        // OID, and clusters do not have OIDs.
4372        "has_cluster_privilege" => Scalar {
4373            params!(String, String, String) => sql_impl_func(
4374                "has_cluster_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4375            ) => Bool, oid::FUNC_HAS_CLUSTER_PRIVILEGE_TEXT_TEXT_TEXT_OID;
4376            params!(Oid, String, String) => sql_impl_func(&format!("
4377                CASE
4378                -- We must first check $2 to avoid a potentially
4379                -- null error message (an error itself).
4380                WHEN $2 IS NULL
4381                THEN NULL
4382                -- Validate the cluster name to return a proper error.
4383                WHEN NOT EXISTS (
4384                    SELECT name FROM mz_clusters WHERE name = $2)
4385                THEN mz_unsafe.mz_error_if_null(
4386                    NULL::boolean,
4387                    'error cluster \"' || $2 || '\" does not exist')
4388                -- Validate the privileges and other arguments.
4389                WHEN NOT mz_internal.mz_validate_privileges($3)
4390                OR $1 IS NULL
4391                OR $3 IS NULL
4392                OR $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
4393                THEN NULL
4394                ELSE COALESCE(
4395                    (
4396                        SELECT
4397                            bool_or(
4398                                mz_internal.mz_acl_item_contains_privilege(privilege, $3)
4399                            )
4400                                AS has_cluster_privilege
4401                        FROM
4402                            (
4403                                SELECT
4404                                    unnest(privileges)
4405                                FROM
4406                                    mz_clusters
4407                                WHERE
4408                                    mz_clusters.name = $2
4409                            )
4410                                AS user_privs (privilege)
4411                            LEFT JOIN mz_catalog.mz_roles ON
4412                                    mz_internal.mz_aclitem_grantee(privilege) = mz_roles.id
4413                        WHERE
4414                            mz_internal.mz_aclitem_grantee(privilege) = '{}'
4415                            OR pg_has_role($1, mz_roles.oid, 'USAGE')
4416                    ),
4417                    false
4418                )
4419                END
4420            ", RoleId::Public))
4421                => Bool, oid::FUNC_HAS_CLUSTER_PRIVILEGE_OID_TEXT_TEXT_OID;
4422            params!(String, String) => sql_impl_func(
4423                "has_cluster_privilege(current_user, $1, $2)",
4424            ) => Bool, oid::FUNC_HAS_CLUSTER_PRIVILEGE_TEXT_TEXT_OID;
4425        },
4426        "has_connection_privilege" => Scalar {
4427            params!(String, String, String) => sql_impl_func(
4428                "has_connection_privilege(\
4429                 mz_internal.mz_role_oid($1), \
4430                 mz_internal.mz_connection_oid($2), $3)",
4431            ) => Bool,
4432                oid::FUNC_HAS_CONNECTION_PRIVILEGE_TEXT_TEXT_TEXT_OID;
4433            params!(String, Oid, String) => sql_impl_func(
4434                "has_connection_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4435            ) => Bool,
4436                oid::FUNC_HAS_CONNECTION_PRIVILEGE_TEXT_OID_TEXT_OID;
4437            params!(Oid, String, String) => sql_impl_func(
4438                "has_connection_privilege($1, mz_internal.mz_connection_oid($2), $3)",
4439            ) => Bool,
4440                oid::FUNC_HAS_CONNECTION_PRIVILEGE_OID_TEXT_TEXT_OID;
4441            params!(Oid, Oid, String) => sql_impl_func(
4442                &privilege_fn!("has_connection_privilege", "mz_connections"),
4443            ) => Bool,
4444                oid::FUNC_HAS_CONNECTION_PRIVILEGE_OID_OID_TEXT_OID;
4445            params!(String, String) => sql_impl_func(
4446                "has_connection_privilege(current_user, $1, $2)",
4447            ) => Bool,
4448                oid::FUNC_HAS_CONNECTION_PRIVILEGE_TEXT_TEXT_OID;
4449            params!(Oid, String) => sql_impl_func(
4450                "has_connection_privilege(current_user, $1, $2)",
4451            ) => Bool,
4452                oid::FUNC_HAS_CONNECTION_PRIVILEGE_OID_TEXT_OID;
4453        },
4454        "has_role" => Scalar {
4455            params!(String, String, String)
4456                => sql_impl_func("pg_has_role($1, $2, $3)")
4457                => Bool, oid::FUNC_HAS_ROLE_TEXT_TEXT_TEXT_OID;
4458            params!(String, Oid, String)
4459                => sql_impl_func("pg_has_role($1, $2, $3)")
4460                => Bool, oid::FUNC_HAS_ROLE_TEXT_OID_TEXT_OID;
4461            params!(Oid, String, String)
4462                => sql_impl_func("pg_has_role($1, $2, $3)")
4463                => Bool, oid::FUNC_HAS_ROLE_OID_TEXT_TEXT_OID;
4464            params!(Oid, Oid, String)
4465                => sql_impl_func("pg_has_role($1, $2, $3)")
4466                => Bool, oid::FUNC_HAS_ROLE_OID_OID_TEXT_OID;
4467            params!(String, String)
4468                => sql_impl_func("pg_has_role($1, $2)")
4469                => Bool, oid::FUNC_HAS_ROLE_TEXT_TEXT_OID;
4470            params!(Oid, String)
4471                => sql_impl_func("pg_has_role($1, $2)")
4472                => Bool, oid::FUNC_HAS_ROLE_OID_TEXT_OID;
4473        },
4474        "has_secret_privilege" => Scalar {
4475            params!(String, String, String) => sql_impl_func(
4476                "has_secret_privilege(\
4477                 mz_internal.mz_role_oid($1), \
4478                 mz_internal.mz_secret_oid($2), $3)",
4479            ) => Bool,
4480                oid::FUNC_HAS_SECRET_PRIVILEGE_TEXT_TEXT_TEXT_OID;
4481            params!(String, Oid, String) => sql_impl_func(
4482                "has_secret_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4483            ) => Bool,
4484                oid::FUNC_HAS_SECRET_PRIVILEGE_TEXT_OID_TEXT_OID;
4485            params!(Oid, String, String) => sql_impl_func(
4486                "has_secret_privilege($1, mz_internal.mz_secret_oid($2), $3)",
4487            ) => Bool,
4488                oid::FUNC_HAS_SECRET_PRIVILEGE_OID_TEXT_TEXT_OID;
4489            params!(Oid, Oid, String) => sql_impl_func(
4490                &privilege_fn!("has_secret_privilege", "mz_secrets"),
4491            ) => Bool,
4492                oid::FUNC_HAS_SECRET_PRIVILEGE_OID_OID_TEXT_OID;
4493            params!(String, String) => sql_impl_func(
4494                "has_secret_privilege(current_user, $1, $2)",
4495            ) => Bool,
4496                oid::FUNC_HAS_SECRET_PRIVILEGE_TEXT_TEXT_OID;
4497            params!(Oid, String) => sql_impl_func(
4498                "has_secret_privilege(current_user, $1, $2)",
4499            ) => Bool,
4500                oid::FUNC_HAS_SECRET_PRIVILEGE_OID_TEXT_OID;
4501        },
4502        "has_system_privilege" => Scalar {
4503            params!(String, String) => sql_impl_func(
4504                "has_system_privilege(mz_internal.mz_role_oid($1), $2)",
4505            ) => Bool, oid::FUNC_HAS_SYSTEM_PRIVILEGE_TEXT_TEXT_OID;
4506            params!(Oid, String) => sql_impl_func(&format!("
4507                CASE
4508                -- We need to validate the privileges to return a proper error before
4509                -- anything else.
4510                WHEN NOT mz_internal.mz_validate_privileges($2)
4511                OR $1 IS NULL
4512                OR $2 IS NULL
4513                OR $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
4514                THEN NULL
4515                ELSE COALESCE(
4516                    (
4517                        SELECT
4518                            bool_or(
4519                                mz_internal.mz_acl_item_contains_privilege(privileges, $2)
4520                            )
4521                                AS has_system_privilege
4522                        FROM mz_catalog.mz_system_privileges
4523                        LEFT JOIN mz_catalog.mz_roles ON
4524                                mz_internal.mz_aclitem_grantee(privileges) = mz_roles.id
4525                        WHERE
4526                            mz_internal.mz_aclitem_grantee(privileges) = '{}'
4527                            OR pg_has_role($1, mz_roles.oid, 'USAGE')
4528                    ),
4529                    false
4530                )
4531                END
4532            ", RoleId::Public))
4533                => Bool, oid::FUNC_HAS_SYSTEM_PRIVILEGE_OID_TEXT_OID;
4534            params!(String) => sql_impl_func(
4535                "has_system_privilege(current_user, $1)",
4536            ) => Bool, oid::FUNC_HAS_SYSTEM_PRIVILEGE_TEXT_OID;
4537        },
4538        "has_type_privilege" => Scalar {
4539            params!(String, String, String) => sql_impl_func(
4540                "has_type_privilege(mz_internal.mz_role_oid($1), $2::regtype::oid, $3)",
4541            ) => Bool, 3138;
4542            params!(String, Oid, String) => sql_impl_func(
4543                "has_type_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4544            ) => Bool, 3139;
4545            params!(Oid, String, String) => sql_impl_func(
4546                "has_type_privilege($1, $2::regtype::oid, $3)",
4547            ) => Bool, 3140;
4548            params!(Oid, Oid, String) => sql_impl_func(
4549                &privilege_fn!("has_type_privilege", "mz_types"),
4550            ) => Bool, 3141;
4551            params!(String, String) => sql_impl_func(
4552                "has_type_privilege(current_user, $1, $2)",
4553            ) => Bool, 3142;
4554            params!(Oid, String) => sql_impl_func(
4555                "has_type_privilege(current_user, $1, $2)",
4556            ) => Bool, 3143;
4557        },
4558        "kafka_murmur2" => Scalar {
4559            params!(String) => UnaryFunc::KafkaMurmur2String(func::KafkaMurmur2String)
4560                => Int32, oid::FUNC_KAFKA_MURMUR2_STRING_OID;
4561            params!(Bytes) => UnaryFunc::KafkaMurmur2Bytes(func::KafkaMurmur2Bytes)
4562                => Int32, oid::FUNC_KAFKA_MURMUR2_BYTES_OID;
4563        },
4564        "list_agg" => Aggregate {
4565            params!(Any) => Operation::unary_ordered(|ecx, e, order_by| {
4566                if let SqlScalarType::Char {.. }  = ecx.scalar_type(&e) {
4567                    bail_unsupported!("list_agg on char");
4568                };
4569                // ListConcat excepts all inputs to be lists, so wrap all input datums into
4570                // lists.
4571                let e_arr = HirScalarExpr::call_variadic(
4572                    variadic::ListCreate { elem_type: ecx.scalar_type(&e) },
4573                    vec![e],
4574                );
4575                Ok((e_arr, AggregateFunc::ListConcat { order_by }))
4576            }) => ListAnyCompatible,  oid::FUNC_LIST_AGG_OID;
4577        },
4578        "list_append" => Scalar {
4579            vec![ListAnyCompatible, ListElementAnyCompatible]
4580                => BinaryFunc::from(func::ListElementConcat)
4581                => ListAnyCompatible, oid::FUNC_LIST_APPEND_OID;
4582        },
4583        "list_cat" => Scalar {
4584            vec![ListAnyCompatible, ListAnyCompatible]
4585                => BinaryFunc::from(func::ListListConcat)
4586                => ListAnyCompatible, oid::FUNC_LIST_CAT_OID;
4587        },
4588        "list_n_layers" => Scalar {
4589            vec![ListAny] => Operation::unary(|ecx, e| {
4590                ecx.require_feature_flag(&vars::ENABLE_LIST_N_LAYERS)?;
4591                let d = ecx.scalar_type(&e).unwrap_list_n_layers();
4592                match i32::try_from(d) {
4593                    Ok(d) => Ok(HirScalarExpr::literal(Datum::Int32(d), SqlScalarType::Int32)),
4594                    Err(_) => sql_bail!("list has more than {} layers", i32::MAX),
4595                }
4596
4597            }) => Int32, oid::FUNC_LIST_N_LAYERS_OID;
4598        },
4599        "list_length" => Scalar {
4600            vec![ListAny] => UnaryFunc::ListLength(func::ListLength)
4601                => Int32, oid::FUNC_LIST_LENGTH_OID;
4602        },
4603        "list_length_max" => Scalar {
4604            vec![ListAny, Plain(SqlScalarType::Int64)] => Operation::binary(|ecx, lhs, rhs| {
4605                ecx.require_feature_flag(&vars::ENABLE_LIST_LENGTH_MAX)?;
4606                let max_layer = ecx.scalar_type(&lhs).unwrap_list_n_layers();
4607                Ok(lhs.call_binary(rhs, BinaryFunc::from(func::ListLengthMax { max_layer })))
4608            }) => Int32, oid::FUNC_LIST_LENGTH_MAX_OID;
4609        },
4610        "list_prepend" => Scalar {
4611            vec![ListElementAnyCompatible, ListAnyCompatible]
4612                => BinaryFunc::from(func::ElementListConcat)
4613                => ListAnyCompatible, oid::FUNC_LIST_PREPEND_OID;
4614        },
4615        "list_remove" => Scalar {
4616            vec![ListAnyCompatible, ListElementAnyCompatible] => Operation::binary(|ecx, lhs, rhs| {
4617                ecx.require_feature_flag(&vars::ENABLE_LIST_REMOVE)?;
4618                Ok(lhs.call_binary(rhs, func::ListRemove))
4619            }) => ListAnyCompatible, oid::FUNC_LIST_REMOVE_OID;
4620        },
4621        "map_agg" => Aggregate {
4622            params!(String, Any) => Operation::binary_ordered(|ecx, key, val, order_by| {
4623                let (value_type, val) = match ecx.scalar_type(&val) {
4624                    // TODO(see <materialize#7572>): remove this
4625                    SqlScalarType::Char { length } => (
4626                        SqlScalarType::Char { length },
4627                        val.call_unary(UnaryFunc::PadChar(func::PadChar { length })),
4628                    ),
4629                    typ => (typ, val),
4630                };
4631
4632                let e = HirScalarExpr::call_variadic(
4633                    variadic::RecordCreate {
4634                        field_names: vec![ColumnName::from("key"), ColumnName::from("val")],
4635                    },
4636                    vec![key, val],
4637                );
4638
4639                Ok((e, AggregateFunc::MapAgg { order_by, value_type }))
4640            }) => MapAny, oid::FUNC_MAP_AGG;
4641        },
4642        "map_build" => Scalar {
4643            // TODO: support a function to construct maps that looks like...
4644            //
4645            // params!([String], Any...) => Operation::variadic(|ecx, exprs| {
4646            //
4647            // ...the challenge here is that we don't support constructing other
4648            // complex types from varidaic functions and instead use a SQL
4649            // keyword; however that doesn't work very well for map because the
4650            // intuitive syntax would be something akin to `MAP[key=>value]`,
4651            // but that doesn't work out of the box because `key=>value` looks
4652            // like an expression.
4653            params!(ListAny) => Operation::unary(|ecx, expr| {
4654                let ty = ecx.scalar_type(&expr);
4655
4656                // This is a fake error but should suffice given how exotic the
4657                // function is.
4658                let err = || {
4659                    Err(sql_err!(
4660                        "function map_build({}) does not exist",
4661                        ecx.humanize_sql_scalar_type(&ty.clone(), false)
4662                    ))
4663                };
4664
4665                // This function only accepts lists of records whose schema is
4666                // (text, T).
4667                let value_type = match &ty {
4668                    SqlScalarType::List { element_type, .. } => match &**element_type {
4669                        SqlScalarType::Record { fields, .. } if fields.len() == 2 => {
4670                            if fields[0].1.scalar_type != SqlScalarType::String {
4671                                return err();
4672                            }
4673
4674                            fields[1].1.scalar_type.clone()
4675                        }
4676                        _ => return err(),
4677                    },
4678                    _ => unreachable!("input guaranteed to be list"),
4679                };
4680
4681                Ok(expr.call_unary(UnaryFunc::MapBuildFromRecordList(
4682                    func::MapBuildFromRecordList { value_type },
4683                )))
4684            }) => MapAny, oid::FUNC_MAP_BUILD;
4685        },
4686        "map_length" => Scalar {
4687            params![MapAny] => UnaryFunc::MapLength(func::MapLength)
4688                => Int32, oid::FUNC_MAP_LENGTH_OID;
4689        },
4690        "mz_environment_id" => Scalar {
4691            params!() => UnmaterializableFunc::MzEnvironmentId
4692                => String, oid::FUNC_MZ_ENVIRONMENT_ID_OID;
4693        },
4694        "mz_is_superuser" => Scalar {
4695            params!() => UnmaterializableFunc::MzIsSuperuser
4696                => SqlScalarType::Bool, oid::FUNC_MZ_IS_SUPERUSER;
4697        },
4698        "mz_logical_timestamp" => Scalar {
4699            params!() => Operation::nullary(|_ecx| {
4700                sql_bail!("mz_logical_timestamp() has been renamed to mz_now()")
4701            }) => MzTimestamp, oid::FUNC_MZ_LOGICAL_TIMESTAMP_OID;
4702        },
4703        "mz_now" => Scalar {
4704            params!() => UnmaterializableFunc::MzNow => MzTimestamp, oid::FUNC_MZ_NOW_OID;
4705        },
4706        "mz_uptime" => Scalar {
4707            params!() => UnmaterializableFunc::MzUptime => Interval, oid::FUNC_MZ_UPTIME_OID;
4708        },
4709        "mz_version" => Scalar {
4710            params!() => UnmaterializableFunc::MzVersion => String, oid::FUNC_MZ_VERSION_OID;
4711        },
4712        "mz_version_num" => Scalar {
4713            params!() => UnmaterializableFunc::MzVersionNum => Int32, oid::FUNC_MZ_VERSION_NUM_OID;
4714        },
4715        "pretty_sql" => Scalar {
4716            params!(String, Int32) => BinaryFunc::from(func::PrettySql)
4717                => String, oid::FUNC_PRETTY_SQL;
4718            params!(String) => Operation::unary(|_ecx, s| {
4719                let w: i32 = mz_sql_pretty::DEFAULT_WIDTH.try_into().expect("must fit");
4720                let width = HirScalarExpr::literal(Datum::Int32(w), SqlScalarType::Int32);
4721                Ok(s.call_binary(width, func::PrettySql))
4722            }) => String, oid::FUNC_PRETTY_SQL_NOWIDTH;
4723        },
4724        "regexp_extract" => Table {
4725            params!(String, String) => Operation::binary(move |_ecx, regex, haystack| {
4726                let regex = match regex.into_literal_string() {
4727                    None => sql_bail!(
4728                        "regexp_extract requires a string \
4729                         literal as its first argument"
4730                    ),
4731                    Some(regex) => {
4732                        let opts = mz_expr::AnalyzedRegexOpts::default();
4733                        mz_expr::AnalyzedRegex::new(&regex, opts)
4734                            .map_err(|e| {
4735                                sql_err!("analyzing regex: {}", e)
4736                            })?
4737                    },
4738                };
4739                let column_names = regex
4740                    .capture_groups_iter()
4741                    .map(|cg| {
4742                        cg.name.clone().unwrap_or_else(|| format!("column{}", cg.index)).into()
4743                    })
4744                    .collect::<Vec<_>>();
4745                if column_names.is_empty(){
4746                    sql_bail!("regexp_extract must specify at least one capture group");
4747                }
4748                Ok(TableFuncPlan {
4749                    imp: TableFuncImpl::CallTable {
4750                        func: TableFunc::RegexpExtract(regex),
4751                        exprs: vec![haystack],
4752                    },
4753                    column_names,
4754                })
4755            }) => ReturnType::set_of(RecordAny), oid::FUNC_REGEXP_EXTRACT_OID;
4756        },
4757        mz_expr::REPEAT_ROW_NAME => Table {
4758            params!(Int64) => Operation::unary(move |ecx, n| {
4759                ecx.require_feature_flag(&vars::ENABLE_REPEAT_ROW)?;
4760                Ok(TableFuncPlan {
4761                    imp: TableFuncImpl::CallTable {
4762                        func: TableFunc::RepeatRow,
4763                        exprs: vec![n],
4764                    },
4765                    column_names: vec![]
4766                })
4767            }) => ReturnType::none(true), oid::FUNC_REPEAT_ROW_OID;
4768        },
4769        "repeat_row_non_negative" => Table {
4770            params!(Int64) => Operation::unary(move |ecx, n| {
4771                ecx.require_feature_flag(&vars::ENABLE_REPEAT_ROW_NON_NEGATIVE)?;
4772                Ok(TableFuncPlan {
4773                    imp: TableFuncImpl::CallTable {
4774                        func: TableFunc::RepeatRowNonNegative,
4775                        exprs: vec![n],
4776                    },
4777                    column_names: vec![]
4778                })
4779            }) => ReturnType::none(true), oid::FUNC_REPEAT_ROW_NON_NEGATIVE_OID;
4780        },
4781        "seahash" => Scalar {
4782            params!(String) => UnaryFunc::SeahashString(func::SeahashString)
4783                => UInt64, oid::FUNC_SEAHASH_STRING_OID;
4784            params!(Bytes) => UnaryFunc::SeahashBytes(func::SeahashBytes)
4785                => UInt64, oid::FUNC_SEAHASH_BYTES_OID;
4786        },
4787        "starts_with" => Scalar {
4788            params!(String, String) => BinaryFunc::from(func::StartsWith) => Bool, 3696;
4789        },
4790        "timezone_offset" => Scalar {
4791            params!(String, TimestampTz) => BinaryFunc::from(func::TimezoneOffset)
4792                => RecordAny, oid::FUNC_TIMEZONE_OFFSET;
4793        },
4794        "try_parse_monotonic_iso8601_timestamp" => Scalar {
4795            params!(String) => Operation::unary(move |_ecx, e| {
4796                Ok(e.call_unary(UnaryFunc::TryParseMonotonicIso8601Timestamp(
4797                    func::TryParseMonotonicIso8601Timestamp,
4798                )))
4799            }) => Timestamp, oid::FUNC_TRY_PARSE_MONOTONIC_ISO8601_TIMESTAMP;
4800        },
4801        "unnest" => Table {
4802            vec![ArrayAny] => Operation::unary(move |ecx, e| {
4803                let el_typ = ecx.scalar_type(&e).unwrap_array_element_type().clone();
4804                Ok(TableFuncPlan {
4805                    imp: TableFuncImpl::CallTable {
4806                        func: TableFunc::UnnestArray { el_typ },
4807                        exprs: vec![e],
4808                    },
4809                    column_names: vec!["unnest".into()],
4810                })
4811            }) =>
4812                // This return type should be equivalent to
4813                // "ArrayElementAny", but this would be its sole use.
4814                ReturnType::set_of(AnyElement), 2331;
4815            vec![ListAny] => Operation::unary(move |ecx, e| {
4816                let el_typ = ecx.scalar_type(&e).unwrap_list_element_type().clone();
4817                Ok(TableFuncPlan {
4818                    imp: TableFuncImpl::CallTable {
4819                        func: TableFunc::UnnestList { el_typ },
4820                        exprs: vec![e],
4821                    },
4822                    column_names: vec!["unnest".into()],
4823                })
4824            }) =>
4825                // This return type should be equivalent to
4826                // "ListElementAny", but this would be its sole use.
4827                ReturnType::set_of(Any), oid::FUNC_UNNEST_LIST_OID;
4828            vec![MapAny] => Operation::unary(move |ecx, e| {
4829                let value_type = ecx.scalar_type(&e).unwrap_map_value_type().clone();
4830                Ok(TableFuncPlan {
4831                    imp: TableFuncImpl::CallTable {
4832                        func: TableFunc::UnnestMap { value_type },
4833                        exprs: vec![e],
4834                    },
4835                    column_names: vec!["key".into(), "value".into()],
4836                })
4837            }) =>
4838                // This return type should be equivalent to
4839                // "ListElementAny", but this would be its sole use.
4840                ReturnType::set_of(Any), oid::FUNC_UNNEST_MAP_OID;
4841        }
4842    }
4843});
4844
4845pub static MZ_INTERNAL_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
4846    use ParamType::*;
4847    use SqlScalarBaseType::*;
4848    builtins! {
4849        "aclitem_grantor" => Scalar {
4850            params!(AclItem) => UnaryFunc::AclItemGrantor(func::AclItemGrantor)
4851                => Oid, oid::FUNC_ACL_ITEM_GRANTOR_OID;
4852        },
4853        "aclitem_grantee" => Scalar {
4854            params!(AclItem) => UnaryFunc::AclItemGrantee(func::AclItemGrantee)
4855                => Oid, oid::FUNC_ACL_ITEM_GRANTEE_OID;
4856        },
4857        "aclitem_privileges" => Scalar {
4858            params!(AclItem) => UnaryFunc::AclItemPrivileges(func::AclItemPrivileges)
4859                => String, oid::FUNC_ACL_ITEM_PRIVILEGES_OID;
4860        },
4861        "is_rbac_enabled" => Scalar {
4862            params!() => UnmaterializableFunc::IsRbacEnabled => Bool, oid::FUNC_IS_RBAC_ENABLED_OID;
4863        },
4864        "make_mz_aclitem" => Scalar {
4865            params!(String, String, String) => VariadicFunc::from(variadic::MakeMzAclItem)
4866                => MzAclItem, oid::FUNC_MAKE_MZ_ACL_ITEM_OID;
4867        },
4868        "mz_acl_item_contains_privilege" => Scalar {
4869            params!(MzAclItem, String)
4870                => BinaryFunc::from(func::MzAclItemContainsPrivilege)
4871                => Bool, oid::FUNC_MZ_ACL_ITEM_CONTAINS_PRIVILEGE_OID;
4872        },
4873        "mz_aclexplode" => Table {
4874            params!(SqlScalarType::Array(Box::new(SqlScalarType::MzAclItem)))
4875                => Operation::unary(move |_ecx, mz_aclitems| {
4876                Ok(TableFuncPlan {
4877                    imp: TableFuncImpl::CallTable {
4878                        func: TableFunc::MzAclExplode,
4879                        exprs: vec![mz_aclitems],
4880                    },
4881                    column_names: vec![
4882                        "grantor".into(), "grantee".into(),
4883                        "privilege_type".into(), "is_grantable".into(),
4884                    ],
4885                })
4886            }) => ReturnType::set_of(RecordAny), oid::FUNC_MZ_ACL_ITEM_EXPLODE_OID;
4887        },
4888        "mz_aclitem_grantor" => Scalar {
4889            params!(MzAclItem) => UnaryFunc::MzAclItemGrantor(func::MzAclItemGrantor)
4890                => String, oid::FUNC_MZ_ACL_ITEM_GRANTOR_OID;
4891        },
4892        "mz_aclitem_grantee" => Scalar {
4893            params!(MzAclItem) => UnaryFunc::MzAclItemGrantee(func::MzAclItemGrantee)
4894                => String, oid::FUNC_MZ_ACL_ITEM_GRANTEE_OID;
4895        },
4896        "mz_aclitem_privileges" => Scalar {
4897            params!(MzAclItem) => UnaryFunc::MzAclItemPrivileges(
4898                func::MzAclItemPrivileges,
4899            ) => String, oid::FUNC_MZ_ACL_ITEM_PRIVILEGES_OID;
4900        },
4901        // There is no regclass equivalent for roles to look up connections, so we
4902        // have this helper function instead.
4903        //
4904        // TODO: invent an OID alias for connections
4905        "mz_connection_oid" => Scalar {
4906            params!(String) => sql_impl_func("
4907                CASE
4908                WHEN $1 IS NULL THEN NULL
4909                ELSE (
4910                    mz_unsafe.mz_error_if_null(
4911                        (SELECT oid FROM mz_catalog.mz_objects
4912                         WHERE name = $1 AND type = 'connection'),
4913                        'connection \"' || $1 || '\" does not exist'
4914                    )
4915                )
4916                END
4917            ") => Oid, oid::FUNC_CONNECTION_OID_OID;
4918        },
4919        "mz_format_privileges" => Scalar {
4920            params!(String) => UnaryFunc::MzFormatPrivileges(func::MzFormatPrivileges)
4921                => SqlScalarType::Array(Box::new(SqlScalarType::String)),
4922                oid::FUNC_MZ_FORMAT_PRIVILEGES_OID;
4923        },
4924        "mz_name_rank" => Table {
4925            // Determines the id, rank of all objects that can be matched using
4926            // the provided args.
4927            params!(
4928                // Database
4929                String,
4930                // Schemas/search path
4931                ParamType::Plain(SqlScalarType::Array(Box::new(SqlScalarType::String))),
4932                // Item name
4933                String,
4934                // Get rank among particular OID alias (e.g. regclass)
4935                String
4936            ) =>
4937            // credit for using rank() to @def-
4938            sql_impl_table_func("
4939            -- The best ranked name is the one that belongs to the schema correlated with the lowest
4940            -- index in the search path
4941            SELECT id, name, count, min(schema_pref) OVER () = schema_pref AS best_ranked FROM (
4942                SELECT DISTINCT
4943                    o.id,
4944                    ARRAY[CASE WHEN s.database_id IS NULL THEN NULL ELSE d.name END, s.name, o.name]
4945                    AS name,
4946                    o.count,
4947                    pg_catalog.array_position($2, s.name) AS schema_pref
4948                FROM
4949                    (
4950                        SELECT
4951                            o.id,
4952                            o.schema_id,
4953                            o.name,
4954                            count(*)
4955                        FROM mz_catalog.mz_objects AS o
4956                        JOIN mz_internal.mz_object_oid_alias AS a
4957                            ON o.type = a.object_type
4958                        WHERE o.name = CAST($3 AS pg_catalog.text) AND a.oid_alias = $4
4959                        GROUP BY 1, 2, 3
4960                    )
4961                        AS o
4962                    JOIN mz_catalog.mz_schemas AS s ON o.schema_id = s.id
4963                    JOIN
4964                        unnest($2) AS search_schema (name)
4965                        ON search_schema.name = s.name
4966                    JOIN
4967                        (
4968                            SELECT id, name FROM mz_catalog.mz_databases
4969                            -- If the provided database does not exist, add a row for it so that it
4970                            -- can still join against ambient schemas.
4971                            UNION ALL
4972                            SELECT '', $1 WHERE $1 NOT IN (SELECT name FROM mz_catalog.mz_databases)
4973                        ) AS d
4974                        ON d.id = COALESCE(s.database_id, d.id)
4975                WHERE d.name = CAST($1 AS pg_catalog.text)
4976            );
4977            ") => ReturnType::set_of(RecordAny), oid::FUNC_MZ_NAME_RANK;
4978        },
4979        "mz_resolve_object_name" => Table {
4980            params!(String, String) =>
4981            // Normalize the input name, and for any NULL values (e.g. not database qualified), use
4982            // the defaults used during name resolution.
4983            sql_impl_table_func("
4984                SELECT
4985                    o.id, o.oid, o.schema_id, o.name, o.type, o.owner_id, o.privileges
4986                FROM
4987                    (SELECT mz_internal.mz_normalize_object_name($2))
4988                            AS normalized (n),
4989                    mz_internal.mz_name_rank(
4990                        COALESCE(n[1], pg_catalog.current_database()),
4991                        CASE
4992                            WHEN n[2] IS NULL
4993                                THEN pg_catalog.current_schemas(true)
4994                            ELSE
4995                                ARRAY[n[2]]
4996                        END,
4997                        n[3],
4998                        $1
4999                    ) AS r,
5000                    mz_catalog.mz_objects AS o
5001                WHERE r.id = o.id AND r.best_ranked;
5002            ") => ReturnType::set_of(RecordAny), oid::FUNC_MZ_RESOLVE_OBJECT_NAME;
5003        },
5004        // Returns the an array representing the minimal namespace a user must
5005        // provide to refer to an item whose name is the first argument.
5006        //
5007        // The first argument must be a fully qualified name (i.e. contain
5008        // database.schema.object), with each level of the namespace being an
5009        // element.
5010        //
5011        // The second argument represents the `GlobalId` of the resolved object.
5012        // This is a safeguard to ensure that the name we are resolving refers
5013        // to the expected entry. For example, this helps us disambiguate cases
5014        // where e.g. types and functions have the same name.
5015        "mz_minimal_name_qualification" => Scalar {
5016            params!(SqlScalarType::Array(Box::new(SqlScalarType::String)), String) => {
5017                sql_impl_func("(
5018                    SELECT
5019                    CASE
5020                        WHEN $1::pg_catalog.text[] IS NULL
5021                            THEN NULL
5022                    -- If DB doesn't match, requires full qual
5023                        WHEN $1[1] != pg_catalog.current_database()
5024                            THEN $1
5025                    -- If not in currently searchable schema, must be schema qualified
5026                        WHEN NOT $1[2] = ANY(pg_catalog.current_schemas(true))
5027                            THEN ARRAY[$1[2], $1[3]]
5028                    ELSE
5029                        minimal_name
5030                    END
5031                FROM (
5032                    -- Subquery so we return one null row in the cases where
5033                    -- there are no matches.
5034                    SELECT (
5035                        SELECT DISTINCT
5036                            CASE
5037                                -- If there is only one item with this name and it's rank 1,
5038                                -- it is uniquely nameable with just the final element
5039                                WHEN best_ranked AND count = 1
5040                                    THEN ARRAY[r.name[3]]
5041                                -- Otherwise, it is findable in the search path, so does not
5042                                -- need database qualification
5043                                ELSE
5044                                    ARRAY[r.name[2], r.name[3]]
5045                            END AS minimal_name
5046                        FROM mz_catalog.mz_objects AS o
5047                            JOIN mz_internal.mz_object_oid_alias AS a
5048                                ON o.type = a.object_type,
5049                            -- implied lateral to put the OID alias into scope
5050                            mz_internal.mz_name_rank(
5051                                pg_catalog.current_database(),
5052                                pg_catalog.current_schemas(true),
5053                                $1[3],
5054                                a.oid_alias
5055                            ) AS r
5056                        WHERE o.id = $2 AND r.id = $2
5057                    )
5058                )
5059            )")
5060            } => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5061                oid::FUNC_MZ_MINIMINAL_NAME_QUALIFICATION;
5062        },
5063        "mz_global_id_to_name" => Scalar {
5064            params!(String) => sql_impl_func("
5065            CASE
5066                WHEN $1 IS NULL THEN NULL
5067                ELSE (
5068                    SELECT array_to_string(minimal_name, '.')
5069                    FROM (
5070                        SELECT mz_unsafe.mz_error_if_null(
5071                            (
5072                                -- Return the fully-qualified name
5073                                SELECT DISTINCT ARRAY[qual.d, qual.s, item.name]
5074                                FROM
5075                                    mz_catalog.mz_objects AS item
5076                                JOIN
5077                                (
5078                                    SELECT
5079                                        d.name AS d,
5080                                        s.name AS s,
5081                                        s.id AS schema_id
5082                                    FROM
5083                                        mz_catalog.mz_schemas AS s
5084                                        LEFT JOIN
5085                                            (SELECT id, name FROM mz_catalog.mz_databases)
5086                                            AS d
5087                                            ON s.database_id = d.id
5088                                ) AS qual
5089                                ON qual.schema_id = item.schema_id
5090                                WHERE item.id = CAST($1 AS text)
5091                            ),
5092                            'global ID ' || $1 || ' does not exist'
5093                        )
5094                    ) AS n (fqn),
5095                    LATERAL (
5096                        -- Get the minimal qualification of the fully qualified name
5097                        SELECT mz_internal.mz_minimal_name_qualification(fqn, $1)
5098                    ) AS m (minimal_name)
5099                )
5100                END
5101            ") => String, oid::FUNC_MZ_GLOBAL_ID_TO_NAME;
5102        },
5103        "mz_normalize_object_name" => Scalar {
5104            params!(String) => sql_impl_func("
5105            (
5106                SELECT
5107                    CASE
5108                        WHEN $1 IS NULL OR ident IS NULL THEN NULL
5109                        WHEN pg_catalog.array_length(ident, 1) > 3
5110                            THEN mz_unsafe.mz_error_if_null(
5111                                NULL::pg_catalog.text[],
5112                                'improper relation name (too many dotted names): ' || $1
5113                            )
5114                        ELSE pg_catalog.array_cat(
5115                            pg_catalog.array_fill(
5116                                CAST(NULL AS pg_catalog.text),
5117                                ARRAY[3 - pg_catalog.array_length(ident, 1)]
5118                            ),
5119                            ident
5120                        )
5121                    END
5122                FROM (
5123                    SELECT pg_catalog.parse_ident($1) AS ident
5124                ) AS i
5125            )") => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5126                oid::FUNC_MZ_NORMALIZE_OBJECT_NAME;
5127        },
5128        "mz_normalize_schema_name" => Scalar {
5129            params!(String) => sql_impl_func("
5130             (
5131                SELECT
5132                    CASE
5133                        WHEN $1 IS NULL OR ident IS NULL THEN NULL
5134                        WHEN pg_catalog.array_length(ident, 1) > 2
5135                            THEN mz_unsafe.mz_error_if_null(
5136                                NULL::pg_catalog.text[],
5137                                'improper schema name (too many dotted names): ' || $1
5138                            )
5139                        ELSE pg_catalog.array_cat(
5140                            pg_catalog.array_fill(
5141                                CAST(NULL AS pg_catalog.text),
5142                                ARRAY[2 - pg_catalog.array_length(ident, 1)]
5143                            ),
5144                            ident
5145                        )
5146                    END
5147                FROM (
5148                    SELECT pg_catalog.parse_ident($1) AS ident
5149                ) AS i
5150            )") => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5151                oid::FUNC_MZ_NORMALIZE_SCHEMA_NAME;
5152        },
5153        "mz_render_typmod" => Scalar {
5154            params!(Oid, Int32) => BinaryFunc::from(func::MzRenderTypmod)
5155                => String, oid::FUNC_MZ_RENDER_TYPMOD_OID;
5156        },
5157        "mz_role_oid_memberships" => Scalar {
5158            params!() => UnmaterializableFunc::MzRoleOidMemberships
5159                => SqlScalarType::Map {
5160                    value_type: Box::new(SqlScalarType::Array(
5161                        Box::new(SqlScalarType::String),
5162                    )),
5163                    custom_id: None,
5164                }, oid::FUNC_MZ_ROLE_OID_MEMBERSHIPS;
5165        },
5166        "mz_session_role_memberships" => Scalar {
5167            params!() => UnmaterializableFunc::MzSessionRoleMemberships
5168                => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5169                oid::FUNC_MZ_SESSION_ROLE_MEMBERSHIPS_OID;
5170        },
5171        // There is no regclass equivalent for databases to look up
5172        // oids, so we have this helper function instead.
5173        "mz_database_oid" => Scalar {
5174            params!(String) => sql_impl_func("
5175                CASE
5176                WHEN $1 IS NULL THEN NULL
5177                ELSE (
5178                    mz_unsafe.mz_error_if_null(
5179                        (SELECT oid FROM mz_databases WHERE name = $1),
5180                        'database \"' || $1 || '\" does not exist'
5181                    )
5182                )
5183                END
5184            ") => Oid, oid::FUNC_DATABASE_OID_OID;
5185        },
5186        // There is no regclass equivalent for schemas to look up
5187        // oids, so we have this helper function instead.
5188        "mz_schema_oid" => Scalar {
5189            params!(String) => sql_impl_func("
5190            CASE
5191                WHEN $1 IS NULL THEN NULL
5192            ELSE
5193                mz_unsafe.mz_error_if_null(
5194                    (
5195                        SELECT
5196                            (
5197                                SELECT s.oid
5198                                FROM mz_catalog.mz_schemas AS s
5199                                LEFT JOIN mz_databases AS d ON s.database_id = d.id
5200                                WHERE
5201                                    (
5202                                        -- Filter to only schemas in the named database or the
5203                                        -- current database if no database was specified.
5204                                        d.name = COALESCE(n[1], pg_catalog.current_database())
5205                                        -- Always include all ambient schemas.
5206                                        OR s.database_id IS NULL
5207                                    )
5208                                    AND s.name = n[2]
5209                            )
5210                        FROM mz_internal.mz_normalize_schema_name($1) AS n
5211                    ),
5212                    'schema \"' || $1 || '\" does not exist'
5213                )
5214            END
5215            ") => Oid, oid::FUNC_SCHEMA_OID_OID;
5216        },
5217        // There is no regclass equivalent for roles to look up
5218        // oids, so we have this helper function instead.
5219        "mz_role_oid" => Scalar {
5220            params!(String) => sql_impl_func("
5221                CASE
5222                WHEN $1 IS NULL THEN NULL
5223                ELSE (
5224                    mz_unsafe.mz_error_if_null(
5225                        (SELECT oid FROM mz_catalog.mz_roles WHERE name = $1),
5226                        'role \"' || $1 || '\" does not exist'
5227                    )
5228                )
5229                END
5230            ") => Oid, oid::FUNC_ROLE_OID_OID;
5231        },
5232        // There is no regclass equivalent for roles to look up secrets, so we
5233        // have this helper function instead.
5234        //
5235        // TODO: invent an OID alias for secrets
5236        "mz_secret_oid" => Scalar {
5237            params!(String) => sql_impl_func("
5238                CASE
5239                WHEN $1 IS NULL THEN NULL
5240                ELSE (
5241                    mz_unsafe.mz_error_if_null(
5242                        (SELECT oid FROM mz_catalog.mz_objects WHERE name = $1 AND type = 'secret'),
5243                        'secret \"' || $1 || '\" does not exist'
5244                    )
5245                )
5246                END
5247            ") => Oid, oid::FUNC_SECRET_OID_OID;
5248        },
5249        // This ought to be exposed in `mz_catalog`, but its name is rather
5250        // confusing. It does not identify the SQL session, but the
5251        // invocation of this `environmentd` process.
5252        "mz_session_id" => Scalar {
5253            params!() => UnmaterializableFunc::MzSessionId => Uuid, oid::FUNC_MZ_SESSION_ID_OID;
5254        },
5255        "mz_type_name" => Scalar {
5256            params!(Oid) => UnaryFunc::MzTypeName(func::MzTypeName)
5257                => String, oid::FUNC_MZ_TYPE_NAME;
5258        },
5259        "mz_validate_privileges" => Scalar {
5260            params!(String) => UnaryFunc::MzValidatePrivileges(func::MzValidatePrivileges)
5261                => Bool, oid::FUNC_MZ_VALIDATE_PRIVILEGES_OID;
5262        },
5263        "mz_validate_role_privilege" => Scalar {
5264            params!(String) => UnaryFunc::MzValidateRolePrivilege(
5265                func::MzValidateRolePrivilege,
5266            ) => Bool, oid::FUNC_MZ_VALIDATE_ROLE_PRIVILEGE_OID;
5267        },
5268        "parse_catalog_acl_mode" => Scalar {
5269            params!(Jsonb) => UnaryFunc::ParseCatalogAclMode(func::ParseCatalogAclMode)
5270                => String, oid::FUNC_PARSE_CATALOG_ACL_MODE_OID;
5271        },
5272        "parse_catalog_audit_log_details" => Scalar {
5273            params!(Jsonb) => UnaryFunc::ParseCatalogAuditLogDetails(
5274                func::ParseCatalogAuditLogDetails,
5275            ) => Jsonb, oid::FUNC_PARSE_CATALOG_AUDIT_LOG_DETAILS_OID;
5276        },
5277        "parse_catalog_create_sql" => Scalar {
5278            params!(String) => UnaryFunc::ParseCatalogCreateSql(func::ParseCatalogCreateSql)
5279                => Jsonb, oid::FUNC_PARSE_CATALOG_CREATE_SQL_OID;
5280        },
5281        "parse_catalog_id" => Scalar {
5282            params!(Jsonb) => UnaryFunc::ParseCatalogId(func::ParseCatalogId)
5283                => String, oid::FUNC_PARSE_CATALOG_ID_OID;
5284        },
5285        "parse_catalog_privileges" => Scalar {
5286            params!(Jsonb) => UnaryFunc::ParseCatalogPrivileges(func::ParseCatalogPrivileges)
5287                => SqlScalarType::Array(Box::new(SqlScalarType::MzAclItem)),
5288                oid::FUNC_PARSE_CATALOG_PRIVILEGES_OID;
5289        },
5290        "redact_sql" => Scalar {
5291            params!(String) => UnaryFunc::RedactSql(func::RedactSql)
5292                => String, oid::FUNC_REDACT_SQL_OID;
5293        }
5294    }
5295});
5296
5297pub static MZ_UNSAFE_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
5298    use ParamType::*;
5299    use SqlScalarBaseType::*;
5300    builtins! {
5301        // `mz_all`/`mz_any` back the `ALL`/`ANY` subquery operators, whose
5302        // rewrite in `transform_ast` always feeds them a boolean comparison.
5303        // The parameter must stay `Bool`: `AggregateFunc::All`/`Any` render as
5304        // accumulable reduces whose accumulator only accepts boolean datums, so
5305        // a non-boolean argument would crash a compute worker at runtime rather
5306        // than being rejected here at plan time (database-issues#9298).
5307        "mz_all" => Aggregate {
5308            params!(Bool) => AggregateFunc::All => Bool, oid::FUNC_MZ_ALL_OID;
5309        },
5310        "mz_any" => Aggregate {
5311            params!(Bool) => AggregateFunc::Any => Bool, oid::FUNC_MZ_ANY_OID;
5312        },
5313        "mz_avg_promotion_internal_v1" => Scalar {
5314            // Promotes a numeric type to the smallest fractional type that
5315            // can represent it. This is primarily useful for the avg
5316            // aggregate function, so that the avg of an integer column does
5317            // not get truncated to an integer, which would be surprising to
5318            // users (#549).
5319            params!(Float32) => Operation::identity()
5320                => Float32, oid::FUNC_MZ_AVG_PROMOTION_F32_OID_INTERNAL_V1;
5321            params!(Float64) => Operation::identity()
5322                => Float64, oid::FUNC_MZ_AVG_PROMOTION_F64_OID_INTERNAL_V1;
5323            params!(Int16) => Operation::unary(|ecx, e| {
5324                typeconv::plan_cast(
5325                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5326                )
5327            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I16_OID_INTERNAL_V1;
5328            params!(Int32) => Operation::unary(|ecx, e| {
5329                typeconv::plan_cast(
5330                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5331                )
5332            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I32_OID_INTERNAL_V1;
5333            params!(UInt16) => Operation::unary(|ecx, e| {
5334                typeconv::plan_cast(
5335                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5336                )
5337            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U16_OID_INTERNAL_V1;
5338            params!(UInt32) => Operation::unary(|ecx, e| {
5339                typeconv::plan_cast(
5340                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5341                )
5342            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U32_OID_INTERNAL_V1;
5343        },
5344        "mz_avg_promotion" => Scalar {
5345            // Promotes a numeric type to the smallest fractional type that
5346            // can represent it. This is primarily useful for the avg
5347            // aggregate function, so that the avg of an integer column does
5348            // not get truncated to an integer, which would be surprising to
5349            // users (#549).
5350            params!(Float32) => Operation::identity()
5351                => Float32, oid::FUNC_MZ_AVG_PROMOTION_F32_OID;
5352            params!(Float64) => Operation::identity()
5353                => Float64, oid::FUNC_MZ_AVG_PROMOTION_F64_OID;
5354            params!(Int16) => Operation::unary(|ecx, e| {
5355                typeconv::plan_cast(
5356                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5357                )
5358            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I16_OID;
5359            params!(Int32) => Operation::unary(|ecx, e| {
5360                typeconv::plan_cast(
5361                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5362                )
5363            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I32_OID;
5364            params!(Int64) => Operation::unary(|ecx, e| {
5365                typeconv::plan_cast(
5366                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5367                )
5368            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I64_OID;
5369            params!(UInt16) => Operation::unary(|ecx, e| {
5370                typeconv::plan_cast(
5371                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5372                )
5373            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U16_OID;
5374            params!(UInt32) => Operation::unary(|ecx, e| {
5375                typeconv::plan_cast(
5376                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5377                )
5378            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U32_OID;
5379            params!(UInt64) => Operation::unary(|ecx, e| {
5380                typeconv::plan_cast(
5381                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5382                )
5383            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U64_OID;
5384            params!(Numeric) => Operation::unary(|ecx, e| {
5385                typeconv::plan_cast(
5386                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5387                )
5388            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_NUMERIC_OID;
5389        },
5390        "mz_error_if_null" => Scalar {
5391            // If the first argument is NULL, returns an EvalError::Internal whose error
5392            // message is the second argument.
5393            params!(Any, String) => VariadicFunc::from(variadic::ErrorIfNull)
5394                => Any, oid::FUNC_MZ_ERROR_IF_NULL_OID;
5395        },
5396        "generate_series_unoptimized" => Table {
5397            // An int64 `generate_series` the optimizer promises to leave as an
5398            // enumeration (see `TableFunc::GenerateSeriesUnoptimized`). For
5399            // tests that rely on the enumeration work actually happening; not
5400            // a supported surface.
5401            params!(Int64, Int64) => Operation::binary(move |_ecx, start, stop| {
5402                Ok(TableFuncPlan {
5403                    imp: TableFuncImpl::CallTable {
5404                        func: TableFunc::GenerateSeriesUnoptimized,
5405                        exprs: vec![
5406                            start, stop,
5407                            HirScalarExpr::literal(Datum::Int64(1), SqlScalarType::Int64),
5408                        ],
5409                    },
5410                    column_names: vec!["generate_series_unoptimized".into()],
5411                })
5412            }) => ReturnType::set_of(Int64.into()), oid::FUNC_MZ_GEN_SERIES_UNOPT_OID;
5413            params!(Int64, Int64, Int64) => Operation::variadic(move |_ecx, exprs| {
5414                Ok(TableFuncPlan {
5415                    imp: TableFuncImpl::CallTable {
5416                        func: TableFunc::GenerateSeriesUnoptimized,
5417                        exprs,
5418                    },
5419                    column_names: vec!["generate_series_unoptimized".into()],
5420                })
5421            }) => ReturnType::set_of(Int64.into()), oid::FUNC_MZ_GEN_SERIES_UNOPT_STEP_OID;
5422        },
5423        "mz_sleep" => Scalar {
5424            params!(Float64) => UnaryFunc::Sleep(func::Sleep)
5425                => TimestampTz, oid::FUNC_MZ_SLEEP_OID;
5426        },
5427        "mz_panic" => Scalar {
5428            params!(String) => UnaryFunc::Panic(func::Panic) => String, oid::FUNC_MZ_PANIC_OID;
5429        }
5430    }
5431});
5432
5433fn digest(algorithm: &'static str) -> Operation<HirScalarExpr> {
5434    Operation::unary(move |_ecx, input| {
5435        let algorithm = HirScalarExpr::literal(Datum::String(algorithm), SqlScalarType::String);
5436        Ok(input.call_binary(algorithm, BinaryFunc::from(func::DigestBytes)))
5437    })
5438}
5439
5440fn array_to_string(
5441    ecx: &ExprContext,
5442    exprs: Vec<HirScalarExpr>,
5443) -> Result<HirScalarExpr, PlanError> {
5444    let elem_type = match ecx.scalar_type(&exprs[0]) {
5445        SqlScalarType::Array(elem_type) => *elem_type,
5446        _ => unreachable!("array_to_string is guaranteed to receive array as first argument"),
5447    };
5448    Ok(HirScalarExpr::call_variadic(
5449        variadic::ArrayToString { elem_type },
5450        exprs,
5451    ))
5452}
5453
5454/// Correlates an operator with all of its implementations.
5455pub static OP_IMPLS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
5456    use BinaryFunc as BF;
5457    use ParamType::*;
5458    use SqlScalarBaseType::*;
5459    builtins! {
5460        // Literal OIDs collected from PG 13 using a version of this query
5461        // ```sql
5462        // SELECT
5463        //     oid,
5464        //     oprname,
5465        //     oprleft::regtype,
5466        //     oprright::regtype
5467        // FROM
5468        //     pg_operator
5469        // WHERE
5470        //     oprname IN (
5471        //         '+', '-', '*', '/', '%',
5472        //         '|', '&', '#', '~', '<<', '>>',
5473        //         '~~', '!~~'
5474        //     )
5475        // ORDER BY
5476        //     oprname;
5477        // ```
5478        // Values are also available through
5479        // https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_operator.dat
5480
5481        // ARITHMETIC
5482        "+" => Scalar {
5483            params!(Any) => Operation::new(|ecx, exprs, _params, _order_by| {
5484                // Unary plus has unusual compatibility requirements.
5485                //
5486                // In PostgreSQL, it is only defined for numeric types, so
5487                // `+$1` and `+'1'` get coerced to `Float64` per the usual
5488                // rules, but `+'1'::text` is rejected.
5489                //
5490                // In SQLite, unary plus can be applied to *any* type, and
5491                // is always the identity function.
5492                //
5493                // To try to be compatible with both PostgreSQL and SQlite,
5494                // we accept explicitly-typed arguments of any type, but try
5495                // to coerce unknown-type arguments as `Float64`.
5496                typeconv::plan_coerce(ecx, exprs.into_element(), &SqlScalarType::Float64)
5497            }) => Any, oid::OP_UNARY_PLUS_OID;
5498            params!(Int16, Int16) => BF::from(func::AddInt16) => Int16, 550;
5499            params!(Int32, Int32) => BF::from(func::AddInt32) => Int32, 551;
5500            params!(Int64, Int64) => BF::from(func::AddInt64) => Int64, 684;
5501            params!(UInt16, UInt16) => BF::from(func::AddUint16) => UInt16, oid::FUNC_ADD_UINT16;
5502            params!(UInt32, UInt32) => BF::from(func::AddUint32) => UInt32, oid::FUNC_ADD_UINT32;
5503            params!(UInt64, UInt64) => BF::from(func::AddUint64) => UInt64, oid::FUNC_ADD_UINT64;
5504            params!(Float32, Float32) => BF::from(func::AddFloat32) => Float32, 586;
5505            params!(Float64, Float64) => BF::from(func::AddFloat64) => Float64, 591;
5506            params!(Interval, Interval) => BF::from(func::AddInterval) => Interval, 1337;
5507            params!(Timestamp, Interval) => BF::from(func::AddTimestampInterval) => Timestamp, 2066;
5508            params!(Interval, Timestamp) => {
5509                Operation::binary(|_ecx, lhs, rhs| {
5510                    Ok(rhs.call_binary(lhs, func::AddTimestampInterval))
5511                })
5512            } => Timestamp, 2553;
5513            params!(TimestampTz, Interval)
5514                => BF::from(func::AddTimestampTzInterval) => TimestampTz, 1327;
5515            params!(Interval, TimestampTz) => {
5516                Operation::binary(|_ecx, lhs, rhs| {
5517                    Ok(rhs.call_binary(lhs, func::AddTimestampTzInterval))
5518                })
5519            } => TimestampTz, 2554;
5520            params!(Date, Interval) => BF::from(func::AddDateInterval) => Timestamp, 1076;
5521            params!(Interval, Date) => {
5522                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::AddDateInterval)))
5523            } => Timestamp, 2551;
5524            params!(Date, Time) => BF::from(func::AddDateTime) => Timestamp, 1360;
5525            params!(Time, Date) => {
5526                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::AddDateTime)))
5527            } => Timestamp, 1363;
5528            params!(Time, Interval) => BF::from(func::AddTimeInterval) => Time, 1800;
5529            params!(Interval, Time) => {
5530                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::AddTimeInterval)))
5531            } => Time, 1849;
5532            params!(Numeric, Numeric) => BF::from(func::AddNumeric) => Numeric, 1758;
5533            params!(RangeAny, RangeAny) => BF::from(func::RangeUnion) => RangeAny, 3898;
5534        },
5535        "-" => Scalar {
5536            params!(Int16) => UnaryFunc::NegInt16(func::NegInt16) => Int16, 559;
5537            params!(Int32) => UnaryFunc::NegInt32(func::NegInt32) => Int32, 558;
5538            params!(Int64) => UnaryFunc::NegInt64(func::NegInt64) => Int64, 484;
5539            params!(Float32) => UnaryFunc::NegFloat32(func::NegFloat32) => Float32, 584;
5540            params!(Float64) => UnaryFunc::NegFloat64(func::NegFloat64) => Float64, 585;
5541            params!(Numeric) => UnaryFunc::NegNumeric(func::NegNumeric) => Numeric, 17510;
5542            params!(Interval) => UnaryFunc::NegInterval(func::NegInterval) => Interval, 1336;
5543            params!(Int32, Int32) => BF::from(func::SubInt32) => Int32, 555;
5544            params!(Int64, Int64) => BF::from(func::SubInt64) => Int64, 685;
5545            params!(UInt16, UInt16) => BF::from(func::SubUint16) => UInt16, oid::FUNC_SUB_UINT16;
5546            params!(UInt32, UInt32) => BF::from(func::SubUint32) => UInt32, oid::FUNC_SUB_UINT32;
5547            params!(UInt64, UInt64) => BF::from(func::SubUint64) => UInt64, oid::FUNC_SUB_UINT64;
5548            params!(Float32, Float32) => BF::from(func::SubFloat32) => Float32, 587;
5549            params!(Float64, Float64) => BF::from(func::SubFloat64) => Float64, 592;
5550            params!(Numeric, Numeric) => BF::from(func::SubNumeric) => Numeric, 17590;
5551            params!(Interval, Interval) => BF::from(func::SubInterval) => Interval, 1338;
5552            params!(Timestamp, Timestamp) => BF::from(func::SubTimestamp) => Interval, 2067;
5553            params!(TimestampTz, TimestampTz) => BF::from(func::SubTimestampTz) => Interval, 1328;
5554            params!(Timestamp, Interval) => BF::from(func::SubTimestampInterval) => Timestamp, 2068;
5555            params!(TimestampTz, Interval)
5556                => BF::from(func::SubTimestampTzInterval) => TimestampTz, 1329;
5557            params!(Date, Date) => BF::from(func::SubDate) => Int32, 1099;
5558            params!(Date, Interval) => BF::from(func::SubDateInterval) => Timestamp, 1077;
5559            params!(Time, Time) => BF::from(func::SubTime) => Interval, 1399;
5560            params!(Time, Interval) => BF::from(func::SubTimeInterval) => Time, 1801;
5561            params!(Jsonb, Int64) => BF::from(func::JsonbDeleteInt64) => Jsonb, 3286;
5562            params!(Jsonb, String) => BF::from(func::JsonbDeleteString) => Jsonb, 3285;
5563            params!(RangeAny, RangeAny) => BF::from(func::RangeDifference) => RangeAny, 3899;
5564            // TODO(jamii) there should be corresponding overloads for
5565            // Array(Int64) and Array(String)
5566        },
5567        "*" => Scalar {
5568            params!(Int16, Int16) => BF::from(func::MulInt16) => Int16, 526;
5569            params!(Int32, Int32) => BF::from(func::MulInt32) => Int32, 514;
5570            params!(Int64, Int64) => BF::from(func::MulInt64) => Int64, 686;
5571            params!(UInt16, UInt16) => BF::from(func::MulUint16) => UInt16, oid::FUNC_MUL_UINT16;
5572            params!(UInt32, UInt32) => BF::from(func::MulUint32) => UInt32, oid::FUNC_MUL_UINT32;
5573            params!(UInt64, UInt64) => BF::from(func::MulUint64) => UInt64, oid::FUNC_MUL_UINT64;
5574            params!(Float32, Float32) => BF::from(func::MulFloat32) => Float32, 589;
5575            params!(Float64, Float64) => BF::from(func::MulFloat64) => Float64, 594;
5576            params!(Interval, Float64) => BF::from(func::MulInterval) => Interval, 1583;
5577            params!(Float64, Interval) => {
5578                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::MulInterval)))
5579            } => Interval, 1584;
5580            params!(Numeric, Numeric) => BF::from(func::MulNumeric) => Numeric, 1760;
5581            params!(RangeAny, RangeAny) => BF::from(func::RangeIntersection) => RangeAny, 3900;
5582        },
5583        "/" => Scalar {
5584            params!(Int16, Int16) => BF::from(func::DivInt16) => Int16, 527;
5585            params!(Int32, Int32) => BF::from(func::DivInt32) => Int32, 528;
5586            params!(Int64, Int64) => BF::from(func::DivInt64) => Int64, 687;
5587            params!(UInt16, UInt16) => BF::from(func::DivUint16) => UInt16, oid::FUNC_DIV_UINT16;
5588            params!(UInt32, UInt32) => BF::from(func::DivUint32) => UInt32, oid::FUNC_DIV_UINT32;
5589            params!(UInt64, UInt64) => BF::from(func::DivUint64) => UInt64, oid::FUNC_DIV_UINT64;
5590            params!(Float32, Float32) => BF::from(func::DivFloat32) => Float32, 588;
5591            params!(Float64, Float64) => BF::from(func::DivFloat64) => Float64, 593;
5592            params!(Interval, Float64) => BF::from(func::DivInterval) => Interval, 1585;
5593            params!(Numeric, Numeric) => BF::from(func::DivNumeric) => Numeric, 1761;
5594        },
5595        "%" => Scalar {
5596            params!(Int16, Int16) => BF::from(func::ModInt16) => Int16, 529;
5597            params!(Int32, Int32) => BF::from(func::ModInt32) => Int32, 530;
5598            params!(Int64, Int64) => BF::from(func::ModInt64) => Int64, 439;
5599            params!(UInt16, UInt16) => BF::from(func::ModUint16) => UInt16, oid::FUNC_MOD_UINT16;
5600            params!(UInt32, UInt32) => BF::from(func::ModUint32) => UInt32, oid::FUNC_MOD_UINT32;
5601            params!(UInt64, UInt64) => BF::from(func::ModUint64) => UInt64, oid::FUNC_MOD_UINT64;
5602            params!(Float32, Float32) => BF::from(func::ModFloat32) => Float32, oid::OP_MOD_F32_OID;
5603            params!(Float64, Float64) => BF::from(func::ModFloat64) => Float64, oid::OP_MOD_F64_OID;
5604            params!(Numeric, Numeric) => BF::from(func::ModNumeric) => Numeric, 1762;
5605        },
5606        "&" => Scalar {
5607            params!(Int16, Int16) => BF::from(func::BitAndInt16) => Int16, 1874;
5608            params!(Int32, Int32) => BF::from(func::BitAndInt32) => Int32, 1880;
5609            params!(Int64, Int64) => BF::from(func::BitAndInt64) => Int64, 1886;
5610            params!(UInt16, UInt16) => BF::from(func::BitAndUint16) => UInt16, oid::FUNC_AND_UINT16;
5611            params!(UInt32, UInt32) => BF::from(func::BitAndUint32) => UInt32, oid::FUNC_AND_UINT32;
5612            params!(UInt64, UInt64) => BF::from(func::BitAndUint64) => UInt64, oid::FUNC_AND_UINT64;
5613        },
5614        "|" => Scalar {
5615            params!(Int16, Int16) => BF::from(func::BitOrInt16) => Int16, 1875;
5616            params!(Int32, Int32) => BF::from(func::BitOrInt32) => Int32, 1881;
5617            params!(Int64, Int64) => BF::from(func::BitOrInt64) => Int64, 1887;
5618            params!(UInt16, UInt16) => BF::from(func::BitOrUint16) => UInt16, oid::FUNC_OR_UINT16;
5619            params!(UInt32, UInt32) => BF::from(func::BitOrUint32) => UInt32, oid::FUNC_OR_UINT32;
5620            params!(UInt64, UInt64) => BF::from(func::BitOrUint64) => UInt64, oid::FUNC_OR_UINT64;
5621        },
5622        "#" => Scalar {
5623            params!(Int16, Int16) => BF::from(func::BitXorInt16) => Int16, 1876;
5624            params!(Int32, Int32) => BF::from(func::BitXorInt32) => Int32, 1882;
5625            params!(Int64, Int64) => BF::from(func::BitXorInt64) => Int64, 1888;
5626            params!(UInt16, UInt16) => BF::from(func::BitXorUint16) => UInt16, oid::FUNC_XOR_UINT16;
5627            params!(UInt32, UInt32) => BF::from(func::BitXorUint32) => UInt32, oid::FUNC_XOR_UINT32;
5628            params!(UInt64, UInt64) => BF::from(func::BitXorUint64) => UInt64, oid::FUNC_XOR_UINT64;
5629        },
5630        "<<" => Scalar {
5631            params!(Int16, Int32) => BF::from(func::BitShiftLeftInt16) => Int16, 1878;
5632            params!(Int32, Int32) => BF::from(func::BitShiftLeftInt32) => Int32, 1884;
5633            params!(Int64, Int32) => BF::from(func::BitShiftLeftInt64) => Int64, 1890;
5634            params!(UInt16, UInt32) => BF::from(func::BitShiftLeftUint16)
5635                => UInt16, oid::FUNC_SHIFT_LEFT_UINT16;
5636            params!(UInt32, UInt32) => BF::from(func::BitShiftLeftUint32)
5637                => UInt32, oid::FUNC_SHIFT_LEFT_UINT32;
5638            params!(UInt64, UInt32) => BF::from(func::BitShiftLeftUint64)
5639                => UInt64, oid::FUNC_SHIFT_LEFT_UINT64;
5640            params!(RangeAny, RangeAny) => BF::from(func::RangeBefore) => Bool, 3893;
5641        },
5642        ">>" => Scalar {
5643            params!(Int16, Int32) => BF::from(func::BitShiftRightInt16) => Int16, 1879;
5644            params!(Int32, Int32) => BF::from(func::BitShiftRightInt32) => Int32, 1885;
5645            params!(Int64, Int32) => BF::from(func::BitShiftRightInt64) => Int64, 1891;
5646            params!(UInt16, UInt32) => BF::from(func::BitShiftRightUint16)
5647                => UInt16, oid::FUNC_SHIFT_RIGHT_UINT16;
5648            params!(UInt32, UInt32) => BF::from(func::BitShiftRightUint32)
5649                => UInt32, oid::FUNC_SHIFT_RIGHT_UINT32;
5650            params!(UInt64, UInt32) => BF::from(func::BitShiftRightUint64)
5651                => UInt64, oid::FUNC_SHIFT_RIGHT_UINT64;
5652            params!(RangeAny, RangeAny) => BF::from(func::RangeAfter) => Bool, 3894;
5653        },
5654
5655        // ILIKE
5656        "~~*" => Scalar {
5657            params!(String, String) => BF::from(func::IsLikeMatchCaseInsensitive) => Bool, 1627;
5658            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5659                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5660                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5661                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
5662                )
5663            }) => Bool, 1629;
5664        },
5665        "!~~*" => Scalar {
5666            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5667                Ok(lhs
5668                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
5669                    .call_unary(UnaryFunc::Not(func::Not)))
5670            }) => Bool, 1628;
5671            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5672                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5673                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5674                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
5675                    .call_unary(UnaryFunc::Not(func::Not))
5676                )
5677            }) => Bool, 1630;
5678        },
5679
5680
5681        // LIKE
5682        "~~" => Scalar {
5683            params!(String, String) => BF::from(func::IsLikeMatchCaseSensitive) => Bool, 1209;
5684            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5685                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5686                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5687                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
5688                )
5689            }) => Bool, 1211;
5690        },
5691        "!~~" => Scalar {
5692            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5693                Ok(lhs
5694                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
5695                    .call_unary(UnaryFunc::Not(func::Not)))
5696            }) => Bool, 1210;
5697            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5698                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5699                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5700                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
5701                    .call_unary(UnaryFunc::Not(func::Not))
5702                )
5703            }) => Bool, 1212;
5704        },
5705
5706        // REGEX
5707        "~" => Scalar {
5708            params!(Int16) => UnaryFunc::BitNotInt16(func::BitNotInt16) => Int16, 1877;
5709            params!(Int32) => UnaryFunc::BitNotInt32(func::BitNotInt32) => Int32, 1883;
5710            params!(Int64) => UnaryFunc::BitNotInt64(func::BitNotInt64) => Int64, 1889;
5711            params!(UInt16) => UnaryFunc::BitNotUint16(func::BitNotUint16)
5712                => UInt16, oid::FUNC_BIT_NOT_UINT16_OID;
5713            params!(UInt32) => UnaryFunc::BitNotUint32(func::BitNotUint32)
5714                => UInt32, oid::FUNC_BIT_NOT_UINT32_OID;
5715            params!(UInt64) => UnaryFunc::BitNotUint64(func::BitNotUint64)
5716                => UInt64, oid::FUNC_BIT_NOT_UINT64_OID;
5717            params!(String, String)
5718                => BinaryFunc::IsRegexpMatchCaseSensitive(
5719                    func::IsRegexpMatchCaseSensitive,
5720                ) => Bool, 641;
5721            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5722                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5723                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5724                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseSensitive(
5725                        func::IsRegexpMatchCaseSensitive,
5726                    )))
5727            }) => Bool, 1055;
5728        },
5729        "~*" => Scalar {
5730            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5731                Ok(lhs.call_binary(
5732                    rhs,
5733                    BinaryFunc::IsRegexpMatchCaseInsensitive(
5734                        func::IsRegexpMatchCaseInsensitive,
5735                    ),
5736                ))
5737            }) => Bool, 1228;
5738            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5739                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5740                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5741                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseInsensitive(
5742                        func::IsRegexpMatchCaseInsensitive,
5743                    )))
5744            }) => Bool, 1234;
5745        },
5746        "!~" => Scalar {
5747            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5748                Ok(lhs
5749                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseSensitive(
5750                        func::IsRegexpMatchCaseSensitive,
5751                    ))
5752                    .call_unary(UnaryFunc::Not(func::Not)))
5753            }) => Bool, 642;
5754            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5755                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5756                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5757                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseSensitive(
5758                        func::IsRegexpMatchCaseSensitive,
5759                    ))
5760                    .call_unary(UnaryFunc::Not(func::Not)))
5761            }) => Bool, 1056;
5762        },
5763        "!~*" => Scalar {
5764            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5765                Ok(lhs
5766                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseInsensitive(
5767                        func::IsRegexpMatchCaseInsensitive,
5768                    ))
5769                    .call_unary(UnaryFunc::Not(func::Not)))
5770            }) => Bool, 1229;
5771            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5772                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5773                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5774                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseInsensitive(
5775                        func::IsRegexpMatchCaseInsensitive,
5776                    ))
5777                    .call_unary(UnaryFunc::Not(func::Not)))
5778            }) => Bool, 1235;
5779        },
5780
5781        // CONCAT
5782        "||" => Scalar {
5783            params!(String, NonVecAny) => Operation::binary(|ecx, lhs, rhs| {
5784                let rhs = typeconv::plan_cast(
5785                    ecx,
5786                    CastContext::Explicit,
5787                    rhs,
5788                    &SqlScalarType::String,
5789                )?;
5790                Ok(lhs.call_binary(rhs, func::TextConcatBinary))
5791            }) => String, 2779;
5792            params!(NonVecAny, String) => Operation::binary(|ecx, lhs, rhs| {
5793                let lhs = typeconv::plan_cast(
5794                    ecx,
5795                    CastContext::Explicit,
5796                    lhs,
5797                    &SqlScalarType::String,
5798                )?;
5799                Ok(lhs.call_binary(rhs, func::TextConcatBinary))
5800            }) => String, 2780;
5801            params!(String, String) => BF::from(func::TextConcatBinary) => String, 654;
5802            params!(Jsonb, Jsonb) => BF::from(func::JsonbConcat) => Jsonb, 3284;
5803            params!(ArrayAnyCompatible, ArrayAnyCompatible)
5804                => BF::from(func::ArrayArrayConcat) => ArrayAnyCompatible, 375;
5805            params!(ListAnyCompatible, ListAnyCompatible)
5806                => BF::from(func::ListListConcat)
5807                => ListAnyCompatible, oid::OP_CONCAT_LIST_LIST_OID;
5808            params!(ListAnyCompatible, ListElementAnyCompatible)
5809                => BF::from(func::ListElementConcat)
5810                => ListAnyCompatible, oid::OP_CONCAT_LIST_ELEMENT_OID;
5811            params!(ListElementAnyCompatible, ListAnyCompatible)
5812                => BF::from(func::ElementListConcat)
5813                => ListAnyCompatible, oid::OP_CONCAT_ELEMENY_LIST_OID;
5814        },
5815
5816        // JSON, MAP, RANGE, LIST, ARRAY
5817        "->" => Scalar {
5818            params!(Jsonb, Int64) => BF::from(func::JsonbGetInt64) => Jsonb, 3212;
5819            params!(Jsonb, String) => BF::from(func::JsonbGetString) => Jsonb, 3211;
5820            params!(MapAny, String) => BF::from(func::MapGetValue)
5821                => Any, oid::OP_GET_VALUE_MAP_OID;
5822        },
5823        "->>" => Scalar {
5824            params!(Jsonb, Int64) => BF::from(func::JsonbGetInt64Stringify) => String, 3481;
5825            params!(Jsonb, String) => BF::from(func::JsonbGetStringStringify) => String, 3477;
5826        },
5827        "#>" => Scalar {
5828            params!(Jsonb, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5829                => BF::from(func::JsonbGetPath) => Jsonb, 3213;
5830        },
5831        "#>>" => Scalar {
5832            params!(Jsonb, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5833                => BF::from(func::JsonbGetPathStringify) => String, 3206;
5834        },
5835        "@>" => Scalar {
5836            params!(Jsonb, Jsonb) => BF::from(func::JsonbContainsJsonb) => Bool, 3246;
5837            params!(Jsonb, String) => Operation::binary(|_ecx, lhs, rhs| {
5838                Ok(lhs.call_binary(
5839                    rhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb)),
5840                    BinaryFunc::from(func::JsonbContainsJsonb),
5841                ))
5842            }) => Bool, oid::OP_CONTAINS_JSONB_STRING_OID;
5843            params!(String, Jsonb) => Operation::binary(|_ecx, lhs, rhs| {
5844                Ok(lhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb))
5845                      .call_binary(rhs, func::JsonbContainsJsonb))
5846            }) => Bool, oid::OP_CONTAINS_STRING_JSONB_OID;
5847            params!(MapAnyCompatible, MapAnyCompatible)
5848                => BF::from(func::MapContainsMap)
5849                => Bool, oid::OP_CONTAINS_MAP_MAP_OID;
5850            params!(RangeAny, AnyElement) => Operation::binary(|ecx, lhs, rhs| {
5851                let elem_type = ecx.scalar_type(&lhs).unwrap_range_element_type().clone();
5852                let f = match elem_type {
5853                    SqlScalarType::Int32 => BF::from(func::RangeContainsI32),
5854                    SqlScalarType::Int64 => BF::from(func::RangeContainsI64),
5855                    SqlScalarType::Date => BF::from(func::RangeContainsDate),
5856                    SqlScalarType::Numeric { .. } => BF::from(func::RangeContainsNumeric),
5857                    SqlScalarType::Timestamp { .. } => BF::from(func::RangeContainsTimestamp),
5858                    SqlScalarType::TimestampTz { .. } => BF::from(func::RangeContainsTimestampTz),
5859                    _ => bail_unsupported!(format!("range element type: {elem_type:?}")),
5860                };
5861                Ok(lhs.call_binary(rhs, f))
5862            }) => Bool, 3889;
5863            params!(RangeAny, RangeAny) => Operation::binary(|_ecx, lhs, rhs| {
5864                Ok(lhs.call_binary(rhs, BF::from(func::RangeContainsRange)))
5865            }) => Bool, 3890;
5866            params!(ArrayAny, ArrayAny) => Operation::binary(|_ecx, lhs, rhs| {
5867                Ok(lhs.call_binary(rhs, BF::from(func::ArrayContainsArray)))
5868            }) => Bool, 2751;
5869            params!(ListAny, ListAny) => Operation::binary(|_ecx, lhs, rhs| {
5870                Ok(lhs.call_binary(rhs, BF::from(func::ListContainsList)))
5871            }) => Bool, oid::OP_CONTAINS_LIST_LIST_OID;
5872        },
5873        "<@" => Scalar {
5874            params!(Jsonb, Jsonb) => Operation::binary(|_ecx, lhs, rhs| {
5875                Ok(rhs.call_binary(
5876                    lhs,
5877                    func::JsonbContainsJsonb
5878                ))
5879            }) => Bool, 3250;
5880            params!(Jsonb, String) => Operation::binary(|_ecx, lhs, rhs| {
5881                Ok(rhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb))
5882                      .call_binary(lhs, func::JsonbContainsJsonb))
5883            }) => Bool, oid::OP_CONTAINED_JSONB_STRING_OID;
5884            params!(String, Jsonb) => Operation::binary(|_ecx, lhs, rhs| {
5885                Ok(rhs.call_binary(
5886                    lhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb)),
5887                    func::JsonbContainsJsonb,
5888                ))
5889            }) => Bool, oid::OP_CONTAINED_STRING_JSONB_OID;
5890            params!(MapAnyCompatible, MapAnyCompatible) => Operation::binary(|_ecx, lhs, rhs| {
5891                Ok(rhs.call_binary(lhs, func::MapContainsMap))
5892            }) => Bool, oid::OP_CONTAINED_MAP_MAP_OID;
5893            params!(AnyElement, RangeAny) => Operation::binary(|ecx, lhs, rhs| {
5894                let elem_type = ecx.scalar_type(&rhs).unwrap_range_element_type().clone();
5895                let f = match elem_type {
5896                    SqlScalarType::Int32 => BF::from(func::RangeContainsI32Rev),
5897                    SqlScalarType::Int64 => BF::from(func::RangeContainsI64Rev),
5898                    SqlScalarType::Date => BF::from(func::RangeContainsDateRev),
5899                    SqlScalarType::Numeric { .. } => BF::from(func::RangeContainsNumericRev),
5900                    SqlScalarType::Timestamp { .. } => BF::from(func::RangeContainsTimestampRev),
5901                    SqlScalarType::TimestampTz { .. } => {
5902                        BF::from(func::RangeContainsTimestampTzRev)
5903                    }
5904                    _ => bail_unsupported!(format!("range element type: {elem_type:?}")),
5905                };
5906                Ok(rhs.call_binary(lhs, f))
5907            }) => Bool, 3891;
5908            params!(RangeAny, RangeAny) => Operation::binary(|_ecx, lhs, rhs| {
5909                Ok(rhs.call_binary(lhs, BF::from(func::RangeContainsRangeRev)))
5910            }) => Bool, 3892;
5911            params!(ArrayAny, ArrayAny) => Operation::binary(|_ecx, lhs, rhs| {
5912                Ok(lhs.call_binary(rhs, BF::from(func::ArrayContainsArrayRev)))
5913            }) => Bool, 2752;
5914            params!(ListAny, ListAny) => Operation::binary(|_ecx, lhs, rhs| {
5915                Ok(lhs.call_binary(rhs, BF::from(func::ListContainsListRev)))
5916            }) => Bool, oid::OP_IS_CONTAINED_LIST_LIST_OID;
5917        },
5918        "?" => Scalar {
5919            params!(Jsonb, String) => BF::from(func::JsonbContainsString) => Bool, 3247;
5920            params!(MapAny, String) => BF::from(func::MapContainsKey)
5921                => Bool, oid::OP_CONTAINS_KEY_MAP_OID;
5922        },
5923        "?&" => Scalar {
5924            params!(MapAny, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5925                => BF::from(func::MapContainsAllKeys)
5926                => Bool, oid::OP_CONTAINS_ALL_KEYS_MAP_OID;
5927        },
5928        "?|" => Scalar {
5929            params!(MapAny, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5930                => BF::from(func::MapContainsAnyKeys)
5931                => Bool, oid::OP_CONTAINS_ANY_KEYS_MAP_OID;
5932        },
5933        "&&" => Scalar {
5934            params!(RangeAny, RangeAny) => BF::from(func::RangeOverlaps) => Bool, 3888;
5935        },
5936        "&<" => Scalar {
5937            params!(RangeAny, RangeAny) => BF::from(func::RangeOverleft) => Bool, 3895;
5938        },
5939        "&>" => Scalar {
5940            params!(RangeAny, RangeAny) => BF::from(func::RangeOverright) => Bool, 3896;
5941        },
5942        "-|-" => Scalar {
5943            params!(RangeAny, RangeAny) => BF::from(func::RangeAdjacent) => Bool, 3897;
5944        },
5945
5946        // COMPARISON OPS
5947        "<" => Scalar {
5948            params!(Numeric, Numeric) => BF::from(func::Lt) => Bool, 1754;
5949            params!(Bool, Bool) => BF::from(func::Lt) => Bool, 58;
5950            params!(Int16, Int16) => BF::from(func::Lt) => Bool, 95;
5951            params!(Int32, Int32) => BF::from(func::Lt) => Bool, 97;
5952            params!(Int64, Int64) => BF::from(func::Lt) => Bool, 412;
5953            params!(UInt16, UInt16) => BF::from(func::Lt) => Bool, oid::FUNC_LT_UINT16_OID;
5954            params!(UInt32, UInt32) => BF::from(func::Lt) => Bool, oid::FUNC_LT_UINT32_OID;
5955            params!(UInt64, UInt64) => BF::from(func::Lt) => Bool, oid::FUNC_LT_UINT64_OID;
5956            params!(Float32, Float32) => BF::from(func::Lt) => Bool, 622;
5957            params!(Float64, Float64) => BF::from(func::Lt) => Bool, 672;
5958            params!(Oid, Oid) => BF::from(func::Lt) => Bool, 609;
5959            params!(Date, Date) => BF::from(func::Lt) => Bool, 1095;
5960            params!(Time, Time) => BF::from(func::Lt) => Bool, 1110;
5961            params!(Timestamp, Timestamp) => BF::from(func::Lt) => Bool, 2062;
5962            params!(TimestampTz, TimestampTz) => BF::from(func::Lt) => Bool, 1322;
5963            params!(Uuid, Uuid) => BF::from(func::Lt) => Bool, 2974;
5964            params!(Interval, Interval) => BF::from(func::Lt) => Bool, 1332;
5965            params!(Bytes, Bytes) => BF::from(func::Lt) => Bool, 1957;
5966            params!(String, String) => BF::from(func::Lt) => Bool, 664;
5967            params!(Char, Char) => BF::from(func::Lt) => Bool, 1058;
5968            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Lt) => Bool, 631;
5969            params!(PgLegacyName, PgLegacyName) => BF::from(func::Lt) => Bool, 660;
5970            params!(Jsonb, Jsonb) => BF::from(func::Lt) => Bool, 3242;
5971            params!(ArrayAny, ArrayAny) => BF::from(func::Lt) => Bool, 1072;
5972            params!(RecordAny, RecordAny) => BF::from(func::Lt) => Bool, 2990;
5973            params!(MzTimestamp, MzTimestamp) => BF::from(func::Lt)
5974                => Bool, oid::FUNC_MZ_TIMESTAMP_LT_MZ_TIMESTAMP_OID;
5975            params!(RangeAny, RangeAny) => BF::from(func::Lt) => Bool, 3884;
5976        },
5977        "<=" => Scalar {
5978            params!(Numeric, Numeric) => BF::from(func::Lte) => Bool, 1755;
5979            params!(Bool, Bool) => BF::from(func::Lte) => Bool, 1694;
5980            params!(Int16, Int16) => BF::from(func::Lte) => Bool, 522;
5981            params!(Int32, Int32) => BF::from(func::Lte) => Bool, 523;
5982            params!(Int64, Int64) => BF::from(func::Lte) => Bool, 414;
5983            params!(UInt16, UInt16) => BF::from(func::Lte) => Bool, oid::FUNC_LTE_UINT16_OID;
5984            params!(UInt32, UInt32) => BF::from(func::Lte) => Bool, oid::FUNC_LTE_UINT32_OID;
5985            params!(UInt64, UInt64) => BF::from(func::Lte) => Bool, oid::FUNC_LTE_UINT64_OID;
5986            params!(Float32, Float32) => BF::from(func::Lte) => Bool, 624;
5987            params!(Float64, Float64) => BF::from(func::Lte) => Bool, 673;
5988            params!(Oid, Oid) => BF::from(func::Lte) => Bool, 611;
5989            params!(Date, Date) => BF::from(func::Lte) => Bool, 1096;
5990            params!(Time, Time) => BF::from(func::Lte) => Bool, 1111;
5991            params!(Timestamp, Timestamp) => BF::from(func::Lte) => Bool, 2063;
5992            params!(TimestampTz, TimestampTz) => BF::from(func::Lte) => Bool, 1323;
5993            params!(Uuid, Uuid) => BF::from(func::Lte) => Bool, 2976;
5994            params!(Interval, Interval) => BF::from(func::Lte) => Bool, 1333;
5995            params!(Bytes, Bytes) => BF::from(func::Lte) => Bool, 1958;
5996            params!(String, String) => BF::from(func::Lte) => Bool, 665;
5997            params!(Char, Char) => BF::from(func::Lte) => Bool, 1059;
5998            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Lte) => Bool, 632;
5999            params!(PgLegacyName, PgLegacyName) => BF::from(func::Lte) => Bool, 661;
6000            params!(Jsonb, Jsonb) => BF::from(func::Lte) => Bool, 3244;
6001            params!(ArrayAny, ArrayAny) => BF::from(func::Lte) => Bool, 1074;
6002            params!(RecordAny, RecordAny) => BF::from(func::Lte) => Bool, 2992;
6003            params!(MzTimestamp, MzTimestamp) => BF::from(func::Lte)
6004                => Bool, oid::FUNC_MZ_TIMESTAMP_LTE_MZ_TIMESTAMP_OID;
6005            params!(RangeAny, RangeAny) => BF::from(func::Lte) => Bool, 3885;
6006        },
6007        ">" => Scalar {
6008            params!(Numeric, Numeric) => BF::from(func::Gt) => Bool, 1756;
6009            params!(Bool, Bool) => BF::from(func::Gt) => Bool, 59;
6010            params!(Int16, Int16) => BF::from(func::Gt) => Bool, 520;
6011            params!(Int32, Int32) => BF::from(func::Gt) => Bool, 521;
6012            params!(Int64, Int64) => BF::from(func::Gt) => Bool, 413;
6013            params!(UInt16, UInt16) => BF::from(func::Gt) => Bool, oid::FUNC_GT_UINT16_OID;
6014            params!(UInt32, UInt32) => BF::from(func::Gt) => Bool, oid::FUNC_GT_UINT32_OID;
6015            params!(UInt64, UInt64) => BF::from(func::Gt) => Bool, oid::FUNC_GT_UINT64_OID;
6016            params!(Float32, Float32) => BF::from(func::Gt) => Bool, 623;
6017            params!(Float64, Float64) => BF::from(func::Gt) => Bool, 674;
6018            params!(Oid, Oid) => BF::from(func::Gt) => Bool, 610;
6019            params!(Date, Date) => BF::from(func::Gt) => Bool, 1097;
6020            params!(Time, Time) => BF::from(func::Gt) => Bool, 1112;
6021            params!(Timestamp, Timestamp) => BF::from(func::Gt) => Bool, 2064;
6022            params!(TimestampTz, TimestampTz) => BF::from(func::Gt) => Bool, 1324;
6023            params!(Uuid, Uuid) => BF::from(func::Gt) => Bool, 2975;
6024            params!(Interval, Interval) => BF::from(func::Gt) => Bool, 1334;
6025            params!(Bytes, Bytes) => BF::from(func::Gt) => Bool, 1959;
6026            params!(String, String) => BF::from(func::Gt) => Bool, 666;
6027            params!(Char, Char) => BF::from(func::Gt) => Bool, 1060;
6028            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Gt) => Bool, 633;
6029            params!(PgLegacyName, PgLegacyName) => BF::from(func::Gt) => Bool, 662;
6030            params!(Jsonb, Jsonb) => BF::from(func::Gt) => Bool, 3243;
6031            params!(ArrayAny, ArrayAny) => BF::from(func::Gt) => Bool, 1073;
6032            params!(RecordAny, RecordAny) => BF::from(func::Gt) => Bool, 2991;
6033            params!(MzTimestamp, MzTimestamp) => BF::from(func::Gt)
6034                => Bool, oid::FUNC_MZ_TIMESTAMP_GT_MZ_TIMESTAMP_OID;
6035            params!(RangeAny, RangeAny) => BF::from(func::Gt) => Bool, 3887;
6036        },
6037        ">=" => Scalar {
6038            params!(Numeric, Numeric) => BF::from(func::Gte) => Bool, 1757;
6039            params!(Bool, Bool) => BF::from(func::Gte) => Bool, 1695;
6040            params!(Int16, Int16) => BF::from(func::Gte) => Bool, 524;
6041            params!(Int32, Int32) => BF::from(func::Gte) => Bool, 525;
6042            params!(Int64, Int64) => BF::from(func::Gte) => Bool, 415;
6043            params!(UInt16, UInt16) => BF::from(func::Gte) => Bool, oid::FUNC_GTE_UINT16_OID;
6044            params!(UInt32, UInt32) => BF::from(func::Gte) => Bool, oid::FUNC_GTE_UINT32_OID;
6045            params!(UInt64, UInt64) => BF::from(func::Gte) => Bool, oid::FUNC_GTE_UINT64_OID;
6046            params!(Float32, Float32) => BF::from(func::Gte) => Bool, 625;
6047            params!(Float64, Float64) => BF::from(func::Gte) => Bool, 675;
6048            params!(Oid, Oid) => BF::from(func::Gte) => Bool, 612;
6049            params!(Date, Date) => BF::from(func::Gte) => Bool, 1098;
6050            params!(Time, Time) => BF::from(func::Gte) => Bool, 1113;
6051            params!(Timestamp, Timestamp) => BF::from(func::Gte) => Bool, 2065;
6052            params!(TimestampTz, TimestampTz) => BF::from(func::Gte) => Bool, 1325;
6053            params!(Uuid, Uuid) => BF::from(func::Gte) => Bool, 2977;
6054            params!(Interval, Interval) => BF::from(func::Gte) => Bool, 1335;
6055            params!(Bytes, Bytes) => BF::from(func::Gte) => Bool, 1960;
6056            params!(String, String) => BF::from(func::Gte) => Bool, 667;
6057            params!(Char, Char) => BF::from(func::Gte) => Bool, 1061;
6058            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Gte) => Bool, 634;
6059            params!(PgLegacyName, PgLegacyName) => BF::from(func::Gte) => Bool, 663;
6060            params!(Jsonb, Jsonb) => BF::from(func::Gte) => Bool, 3245;
6061            params!(ArrayAny, ArrayAny) => BF::from(func::Gte) => Bool, 1075;
6062            params!(RecordAny, RecordAny) => BF::from(func::Gte) => Bool, 2993;
6063            params!(MzTimestamp, MzTimestamp) => BF::from(func::Gte)
6064                => Bool, oid::FUNC_MZ_TIMESTAMP_GTE_MZ_TIMESTAMP_OID;
6065            params!(RangeAny, RangeAny) => BF::from(func::Gte) => Bool, 3886;
6066        },
6067        // Warning!
6068        // - If you are writing functions here that do not simply use
6069        //   `BinaryFunc::Eq`, you will break row equality (used in e.g.
6070        //   DISTINCT operations and JOINs). In short, this is totally verboten.
6071        // - The implementation of `BinaryFunc::Eq` is byte equality on two
6072        //   datums, and we enforce that both inputs to the function are of the
6073        //   same type in planning. However, it's possible that we will perform
6074        //   equality on types not listed here (e.g. `Varchar`) due to decisions
6075        //   made in the optimizer.
6076        // - Null inputs are handled by `BinaryFunc::eval` checking `propagates_nulls`.
6077        "=" => Scalar {
6078            params!(Numeric, Numeric) => BF::from(func::Eq) => Bool, 1752;
6079            params!(Bool, Bool) => BF::from(func::Eq) => Bool, 91;
6080            params!(Int16, Int16) => BF::from(func::Eq) => Bool, 94;
6081            params!(Int32, Int32) => BF::from(func::Eq) => Bool, 96;
6082            params!(Int64, Int64) => BF::from(func::Eq) => Bool, 410;
6083            params!(UInt16, UInt16) => BF::from(func::Eq) => Bool, oid::FUNC_EQ_UINT16_OID;
6084            params!(UInt32, UInt32) => BF::from(func::Eq) => Bool, oid::FUNC_EQ_UINT32_OID;
6085            params!(UInt64, UInt64) => BF::from(func::Eq) => Bool, oid::FUNC_EQ_UINT64_OID;
6086            params!(Float32, Float32) => BF::from(func::Eq) => Bool, 620;
6087            params!(Float64, Float64) => BF::from(func::Eq) => Bool, 670;
6088            params!(Oid, Oid) => BF::from(func::Eq) => Bool, 607;
6089            params!(Date, Date) => BF::from(func::Eq) => Bool, 1093;
6090            params!(Time, Time) => BF::from(func::Eq) => Bool, 1108;
6091            params!(Timestamp, Timestamp) => BF::from(func::Eq) => Bool, 2060;
6092            params!(TimestampTz, TimestampTz) => BF::from(func::Eq) => Bool, 1320;
6093            params!(Uuid, Uuid) => BF::from(func::Eq) => Bool, 2972;
6094            params!(Interval, Interval) => BF::from(func::Eq) => Bool, 1330;
6095            params!(Bytes, Bytes) => BF::from(func::Eq) => Bool, 1955;
6096            params!(String, String) => BF::from(func::Eq) => Bool, 98;
6097            params!(Char, Char) => BF::from(func::Eq) => Bool, 1054;
6098            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Eq) => Bool, 92;
6099            params!(PgLegacyName, PgLegacyName) => BF::from(func::Eq) => Bool, 93;
6100            params!(Jsonb, Jsonb) => BF::from(func::Eq) => Bool, 3240;
6101            params!(ListAny, ListAny) => BF::from(func::Eq) => Bool, oid::FUNC_LIST_EQ_OID;
6102            params!(ArrayAny, ArrayAny) => BF::from(func::Eq) => Bool, 1070;
6103            params!(RecordAny, RecordAny) => BF::from(func::Eq) => Bool, 2988;
6104            params!(MzTimestamp, MzTimestamp) => BF::from(func::Eq)
6105                => Bool, oid::FUNC_MZ_TIMESTAMP_EQ_MZ_TIMESTAMP_OID;
6106            params!(RangeAny, RangeAny) => BF::from(func::Eq) => Bool, 3882;
6107            params!(MzAclItem, MzAclItem) => BF::from(func::Eq)
6108                => Bool, oid::FUNC_MZ_ACL_ITEM_EQ_MZ_ACL_ITEM_OID;
6109            params!(AclItem, AclItem) => BF::from(func::Eq) => Bool, 974;
6110        },
6111        "<>" => Scalar {
6112            params!(Numeric, Numeric) => BF::from(func::NotEq) => Bool, 1753;
6113            params!(Bool, Bool) => BF::from(func::NotEq) => Bool, 85;
6114            params!(Int16, Int16) => BF::from(func::NotEq) => Bool, 519;
6115            params!(Int32, Int32) => BF::from(func::NotEq) => Bool, 518;
6116            params!(Int64, Int64) => BF::from(func::NotEq) => Bool, 411;
6117            params!(UInt16, UInt16) => BF::from(func::NotEq) => Bool, oid::FUNC_NOT_EQ_UINT16_OID;
6118            params!(UInt32, UInt32) => BF::from(func::NotEq) => Bool, oid::FUNC_NOT_EQ_UINT32_OID;
6119            params!(UInt64, UInt64) => BF::from(func::NotEq) => Bool, oid::FUNC_NOT_EQ_UINT64_OID;
6120            params!(Float32, Float32) => BF::from(func::NotEq) => Bool, 621;
6121            params!(Float64, Float64) => BF::from(func::NotEq) => Bool, 671;
6122            params!(Oid, Oid) => BF::from(func::NotEq) => Bool, 608;
6123            params!(Date, Date) => BF::from(func::NotEq) => Bool, 1094;
6124            params!(Time, Time) => BF::from(func::NotEq) => Bool, 1109;
6125            params!(Timestamp, Timestamp) => BF::from(func::NotEq) => Bool, 2061;
6126            params!(TimestampTz, TimestampTz) => BF::from(func::NotEq) => Bool, 1321;
6127            params!(Uuid, Uuid) => BF::from(func::NotEq) => Bool, 2973;
6128            params!(Interval, Interval) => BF::from(func::NotEq) => Bool, 1331;
6129            params!(Bytes, Bytes) => BF::from(func::NotEq) => Bool, 1956;
6130            params!(String, String) => BF::from(func::NotEq) => Bool, 531;
6131            params!(Char, Char) => BF::from(func::NotEq) => Bool, 1057;
6132            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::NotEq) => Bool, 630;
6133            params!(PgLegacyName, PgLegacyName) => BF::from(func::NotEq) => Bool, 643;
6134            params!(Jsonb, Jsonb) => BF::from(func::NotEq) => Bool, 3241;
6135            params!(ArrayAny, ArrayAny) => BF::from(func::NotEq) => Bool, 1071;
6136            params!(RecordAny, RecordAny) => BF::from(func::NotEq) => Bool, 2989;
6137            params!(MzTimestamp, MzTimestamp) => BF::from(func::NotEq)
6138                => Bool, oid::FUNC_MZ_TIMESTAMP_NOT_EQ_MZ_TIMESTAMP_OID;
6139            params!(RangeAny, RangeAny) => BF::from(func::NotEq) => Bool, 3883;
6140            params!(MzAclItem, MzAclItem) => BF::from(func::NotEq)
6141                => Bool, oid::FUNC_MZ_ACL_ITEM_NOT_EQ_MZ_ACL_ITEM_OID;
6142        }
6143    }
6144});
6145
6146/// Resolves the operator to a set of function implementations.
6147pub fn resolve_op(op: &str) -> Result<&'static [FuncImpl<HirScalarExpr>], PlanError> {
6148    match OP_IMPLS.get(op) {
6149        Some(Func::Scalar(impls)) => Ok(impls),
6150        Some(_) => unreachable!("all operators must be scalar functions"),
6151        // TODO: these require sql arrays
6152        // JsonContainsAnyFields
6153        // JsonContainsAllFields
6154        // TODO: these require json paths
6155        // JsonGetPath
6156        // JsonGetPathAsText
6157        // JsonDeletePath
6158        // JsonContainsPath
6159        // JsonApplyPathPredicate
6160        None => bail_unsupported!(format!("[{}]", op)),
6161    }
6162}
6163
6164// Since ViewableVariables is unmaterializeable (which can't be eval'd) that
6165// depend on their arguments, implement directly with Hir.
6166fn current_settings(
6167    name: HirScalarExpr,
6168    missing_ok: HirScalarExpr,
6169) -> Result<HirScalarExpr, PlanError> {
6170    // MapGetValue returns Null if the key doesn't exist in the map.
6171    let expr = HirScalarExpr::call_binary(
6172        HirScalarExpr::call_unmaterializable(UnmaterializableFunc::ViewableVariables),
6173        HirScalarExpr::call_unary(name, UnaryFunc::Lower(func::Lower)),
6174        func::MapGetValue,
6175    );
6176    let expr = HirScalarExpr::if_then_else(
6177        missing_ok,
6178        expr.clone(),
6179        HirScalarExpr::call_variadic(
6180            variadic::ErrorIfNull,
6181            vec![
6182                expr,
6183                HirScalarExpr::literal(
6184                    Datum::String("unrecognized configuration parameter"),
6185                    SqlScalarType::String,
6186                ),
6187            ],
6188        ),
6189    );
6190    Ok(expr)
6191}