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                do_convert(cexpr, &target)?
1631            }
1632        };
1633        res_exprs.push(expr);
1634    }
1635
1636    Ok(res_exprs)
1637}
1638
1639/// Provides shorthand for converting `Vec<SqlScalarType>` into `Vec<ParamType>`.
1640macro_rules! params {
1641    ([$($p:expr),*], $v:ident...) => {
1642        ParamList::Variadic {
1643            leading: vec![$($p.into(),)*],
1644            trailing: $v.into(),
1645        }
1646    };
1647    ($v:ident...) => { ParamList::Variadic { leading: vec![], trailing: $v.into() } };
1648    ($($p:expr),*) => { ParamList::Exact(vec![$($p.into(),)*]) };
1649}
1650
1651macro_rules! impl_def {
1652    // Return type explicitly specified. This must be the case in situations
1653    // such as:
1654    // - Polymorphic functions: We have no way of understanding if the input
1655    //   type affects the return type, so you must tell us what the return type
1656    //   is.
1657    // - Explicitly defined Operations whose returned expression does not
1658    //   appropriately correlate to the function itself, e.g. returning a
1659    //   UnaryFunc from a FuncImpl that takes two parameters.
1660    // - Unimplemented/catalog-only functions
1661    ($params:expr, $op:expr, $return_type:expr, $oid:expr) => {{
1662        FuncImpl {
1663            oid: $oid,
1664            params: $params.into(),
1665            op: $op.into(),
1666            return_type: $return_type.into(),
1667        }
1668    }};
1669}
1670
1671/// Constructs builtin function map.
1672macro_rules! builtins {
1673    {
1674        $(
1675            $name:expr => $ty:ident {
1676                $($params:expr => $op:expr => $return_type:expr, $oid:expr;)+
1677            }
1678        ),+
1679    } => {{
1680
1681        let mut builtins = BTreeMap::new();
1682        $(
1683            let impls = vec![$(impl_def!($params, $op, $return_type, $oid)),+];
1684            let func = Func::$ty(impls);
1685            let expect_set_return = matches!(&func, Func::Table(_));
1686            for imp in func.func_impls() {
1687                assert_eq!(
1688                    imp.return_is_set, expect_set_return,
1689                    "wrong set return value for func with oid {}",
1690                    imp.oid
1691                );
1692            }
1693            let old = builtins.insert($name, func);
1694            mz_ore::assert_none!(old, "duplicate entry in builtins list");
1695        )+
1696        builtins
1697    }};
1698}
1699
1700#[derive(Debug)]
1701pub struct TableFuncPlan {
1702    pub imp: TableFuncImpl,
1703    pub column_names: Vec<ColumnName>,
1704}
1705
1706/// The implementation of a table function is either
1707/// 1. just a `CallTable` HIR node (in which case we can put `WITH ORDINALITY` into it when we
1708///    create the actual HIR node in `plan_table_function_internal`),
1709/// 2. or a general HIR expression. This happens when it's implemented as SQL, i.e., by a call to
1710///    `sql_impl_table_func_inner`.
1711///
1712/// TODO(ggevay, database-issues#9598): when a table function in 2. is used with WITH ORDINALITY or
1713/// ROWS FROM, we can't use the new implementation of WITH ORDINALITY. Depending on
1714/// `enable_with_ordinality_legacy_fallback`, we either fall back to the legacy implementation or
1715/// error out the query planning. The legacy WITH ORDINALITY implementation relies on the
1716/// row_number window function, and is mostly broken. It can give an incorrect ordering, and also
1717/// has an extreme performance problem in some cases. Discussed in
1718/// <https://github.com/MaterializeInc/database-issues/issues/4764#issuecomment-2854572614>
1719///
1720/// These table functions are somewhat exotic, and WITH ORDINALITY / ROWS FROM are also somewhat
1721/// exotic, so let's hope that the combination of these is so exotic that nobody will need it for
1722/// quite a while. Note that the SQL standard only allows WITH ORDINALITY on `unnest_...` functions,
1723/// of which none fall into the 2. category, so we are fine with these; it's only a Postgres
1724/// extension to support WITH ORDINALITY on arbitrary table functions. When this combination arises,
1725/// we emit a Sentry error, so that we'll know about it.
1726///
1727/// When we eventually need to fix this, a possible approach would be to write two SQL
1728/// implementations: one would be their current implementation, and the other would be
1729/// WITH ORDINALITY.
1730/// - This will be trivial for some table functions, e.g., those that end with an UNNEST just need a
1731///   WITH ORDINALITY on this UNNEST (e.g., `regexp_split_to_table`).
1732/// - `_pg_expandarray` and `date_bin_hopping` also look easy.
1733/// - `mz_name_rank` and `mz_resolve_object_name` look more complicated but hopefully solvable.
1734///
1735/// Another approach to fixing this would be to add an ORDER BY to the SQL definitions and then
1736/// move this ORDER BY into a row_number window function call. This would at least solve the
1737/// correctness problem, but not the performance problem.
1738#[derive(Debug)]
1739pub enum TableFuncImpl {
1740    CallTable {
1741        func: TableFunc,
1742        exprs: Vec<HirScalarExpr>,
1743    },
1744    Expr(HirRelationExpr),
1745}
1746
1747#[derive(Debug)]
1748pub enum Func {
1749    Scalar(Vec<FuncImpl<HirScalarExpr>>),
1750    Aggregate(Vec<FuncImpl<(HirScalarExpr, AggregateFunc)>>),
1751    Table(Vec<FuncImpl<TableFuncPlan>>),
1752    ScalarWindow(Vec<FuncImpl<ScalarWindowFunc>>),
1753    ValueWindow(Vec<FuncImpl<(HirScalarExpr, ValueWindowFunc)>>),
1754}
1755
1756impl Func {
1757    pub fn func_impls(&self) -> Vec<FuncImplCatalogDetails> {
1758        match self {
1759            Func::Scalar(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1760            Func::Aggregate(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1761            Func::Table(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1762            Func::ScalarWindow(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1763            Func::ValueWindow(impls) => impls.iter().map(|f| f.details()).collect::<Vec<_>>(),
1764        }
1765    }
1766
1767    pub fn class(&self) -> &str {
1768        match self {
1769            Func::Scalar(..) => "scalar",
1770            Func::Aggregate(..) => "aggregate",
1771            Func::Table(..) => "table",
1772            Func::ScalarWindow(..) => "window",
1773            Func::ValueWindow(..) => "window",
1774        }
1775    }
1776}
1777
1778/// Functions using this macro should be transformed/planned away before
1779/// reaching function selection code, but still need to be present in the
1780/// catalog during planning.
1781macro_rules! catalog_name_only {
1782    ($name:expr) => {
1783        panic!(
1784            "{} should be planned away before reaching function selection",
1785            $name
1786        )
1787    };
1788}
1789
1790/// Generates an (OID, OID, TEXT) SQL implementation for has_X_privilege style functions.
1791macro_rules! privilege_fn {
1792    ( $fn_name:expr, $catalog_tbl:expr ) => {{
1793        let fn_name = $fn_name;
1794        let catalog_tbl = $catalog_tbl;
1795        let public_role = RoleId::Public;
1796        format!(
1797            "
1798                    CASE
1799                    -- We need to validate the privileges to return a proper error before anything
1800                    -- else.
1801                    WHEN NOT mz_internal.mz_validate_privileges($3)
1802                    OR $1 IS NULL
1803                    OR $2 IS NULL
1804                    OR $3 IS NULL
1805                    OR $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
1806                    OR $2 NOT IN (SELECT oid FROM {catalog_tbl})
1807                    THEN NULL
1808                    ELSE COALESCE(
1809                        (
1810                            SELECT
1811                                bool_or(
1812                                    mz_internal.mz_acl_item_contains_privilege(privilege, $3)
1813                                )
1814                                    AS {fn_name}
1815                            FROM
1816                                (
1817                                    SELECT
1818                                        unnest(privileges)
1819                                    FROM
1820                                        {catalog_tbl}
1821                                    WHERE
1822                                        {catalog_tbl}.oid = $2
1823                                )
1824                                    AS user_privs (privilege)
1825                                LEFT JOIN mz_catalog.mz_roles ON
1826                                        mz_internal.mz_aclitem_grantee(privilege) = mz_roles.id
1827                            WHERE
1828                                mz_internal.mz_aclitem_grantee(privilege) = '{public_role}'
1829                                OR pg_has_role($1, mz_roles.oid, 'USAGE')
1830                        ),
1831                        false
1832                    )
1833                    END
1834                ",
1835        )
1836    }};
1837}
1838
1839/// Correlates a built-in function name to its implementations.
1840pub static PG_CATALOG_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
1841    use ParamType::*;
1842    use SqlScalarBaseType::*;
1843    let mut builtins = builtins! {
1844        // Literal OIDs collected from PG 13 using a version of this query
1845        // ```sql
1846        // SELECT oid, proname, proargtypes::regtype[]
1847        // FROM pg_proc
1848        // WHERE proname IN (
1849        //      'ascii', 'array_upper', 'jsonb_build_object'
1850        // );
1851        // ```
1852        // Values are also available through
1853        // https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_proc.dat
1854
1855        // Scalars.
1856        "abs" => Scalar {
1857            params!(Int16) => UnaryFunc::AbsInt16(func::AbsInt16) => Int16, 1398;
1858            params!(Int32) => UnaryFunc::AbsInt32(func::AbsInt32) => Int32, 1397;
1859            params!(Int64) => UnaryFunc::AbsInt64(func::AbsInt64) => Int64, 1396;
1860            params!(Numeric) => UnaryFunc::AbsNumeric(func::AbsNumeric) => Numeric, 1705;
1861            params!(Float32) => UnaryFunc::AbsFloat32(func::AbsFloat32) => Float32, 1394;
1862            params!(Float64) => UnaryFunc::AbsFloat64(func::AbsFloat64) => Float64, 1395;
1863        },
1864        "aclexplode" => Table {
1865            params!(SqlScalarType::Array(Box::new(
1866                SqlScalarType::AclItem,
1867            ))) => Operation::unary(move |_ecx, aclitems| {
1868                Ok(TableFuncPlan {
1869                    imp: TableFuncImpl::CallTable {
1870                        func: TableFunc::AclExplode,
1871                        exprs: vec![aclitems],
1872                    },
1873                    column_names: vec![
1874                        "grantor".into(), "grantee".into(),
1875                        "privilege_type".into(), "is_grantable".into(),
1876                    ],
1877                })
1878            }) => ReturnType::set_of(RecordAny), 1689;
1879        },
1880        "array_cat" => Scalar {
1881            params!(ArrayAnyCompatible, ArrayAnyCompatible) => Operation::binary(|_ecx, lhs, rhs| {
1882                Ok(lhs.call_binary(rhs, func::ArrayArrayConcat))
1883            }) => ArrayAnyCompatible, 383;
1884        },
1885        "array_fill" => Scalar {
1886            params!(AnyElement, SqlScalarType::Array(Box::new(SqlScalarType::Int32)))
1887                => Operation::binary(|ecx, elem, dims| {
1888                let elem_type = ecx.scalar_type(&elem);
1889
1890                let elem_type = match elem_type.array_of_self_elem_type() {
1891                    Ok(elem_type) => elem_type,
1892                    Err(elem_type) => bail_unsupported!(
1893                        // This will be used in error msgs, therefore
1894                        // we call with `postgres_compat` false.
1895                        format!("array_fill on {}", ecx.humanize_sql_scalar_type(&elem_type, false))
1896                    ),
1897                };
1898
1899                Ok(HirScalarExpr::call_variadic(
1900                    variadic::ArrayFill { elem_type },
1901                    vec![elem, dims]
1902                ))
1903            }) => ArrayAny, 1193;
1904            params!(
1905                AnyElement,
1906                SqlScalarType::Array(Box::new(SqlScalarType::Int32)),
1907                SqlScalarType::Array(Box::new(SqlScalarType::Int32))
1908            ) => Operation::variadic(|ecx, exprs| {
1909                let elem_type = ecx.scalar_type(&exprs[0]);
1910
1911                let elem_type = match elem_type.array_of_self_elem_type() {
1912                    Ok(elem_type) => elem_type,
1913                    Err(elem_type) => bail_unsupported!(
1914                        format!("array_fill on {}", ecx.humanize_sql_scalar_type(&elem_type, false))
1915                    ),
1916                };
1917
1918                Ok(HirScalarExpr::call_variadic(variadic::ArrayFill { elem_type }, exprs))
1919            }) => ArrayAny, 1286;
1920        },
1921        "array_length" => Scalar {
1922            params![ArrayAny, Int64] => BinaryFunc::from(func::ArrayLength) => Int32, 2176;
1923        },
1924        "array_lower" => Scalar {
1925            params!(ArrayAny, Int64) => BinaryFunc::from(func::ArrayLower) => Int32, 2091;
1926        },
1927        "array_position" => Scalar {
1928            params!(ArrayAnyCompatible, AnyCompatible)
1929                => VariadicFunc::from(variadic::ArrayPosition) => Int32, 3277;
1930            params!(ArrayAnyCompatible, AnyCompatible, Int32)
1931                => VariadicFunc::from(variadic::ArrayPosition) => Int32, 3278;
1932        },
1933        "array_remove" => Scalar {
1934            params!(ArrayAnyCompatible, AnyCompatible)
1935                => BinaryFunc::from(func::ArrayRemove)
1936                => ArrayAnyCompatible, 3167;
1937        },
1938        "array_to_string" => Scalar {
1939            params!(ArrayAny, String) => Operation::variadic(array_to_string) => String, 395;
1940            params!(ArrayAny, String, String)
1941                => Operation::variadic(array_to_string) => String, 384;
1942        },
1943        "array_upper" => Scalar {
1944            params!(ArrayAny, Int64) => BinaryFunc::from(func::ArrayUpper) => Int32, 2092;
1945        },
1946        "ascii" => Scalar {
1947            params!(String) => UnaryFunc::Ascii(func::Ascii) => Int32, 1620;
1948        },
1949        "avg" => Scalar {
1950            params!(Int64) => Operation::nullary(|_ecx| catalog_name_only!("avg")) => Numeric, 2100;
1951            params!(Int32) => Operation::nullary(|_ecx| catalog_name_only!("avg")) => Numeric, 2101;
1952            params!(Int16) => Operation::nullary(|_ecx| catalog_name_only!("avg")) => Numeric, 2102;
1953            params!(UInt64) =>
1954                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1955                => Numeric, oid::FUNC_AVG_UINT64_OID;
1956            params!(UInt32) =>
1957                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1958                => Numeric, oid::FUNC_AVG_UINT32_OID;
1959            params!(UInt16) =>
1960                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1961                => Numeric, oid::FUNC_AVG_UINT16_OID;
1962            params!(Float32) =>
1963                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1964                => Float64, 2104;
1965            params!(Float64) =>
1966                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1967                => Float64, 2105;
1968            params!(Interval) =>
1969                Operation::nullary(|_ecx| catalog_name_only!("avg"))
1970                => Interval, 2106;
1971        },
1972        "bit_count" => Scalar {
1973            params!(Bytes) => UnaryFunc::BitCountBytes(func::BitCountBytes) => Int64, 6163;
1974        },
1975        "bit_length" => Scalar {
1976            params!(Bytes) => UnaryFunc::BitLengthBytes(func::BitLengthBytes) => Int32, 1810;
1977            params!(String) => UnaryFunc::BitLengthString(func::BitLengthString) => Int32, 1811;
1978        },
1979        "btrim" => Scalar {
1980            params!(String) => UnaryFunc::TrimWhitespace(func::TrimWhitespace) => String, 885;
1981            params!(String, String) => BinaryFunc::from(func::Trim) => String, 884;
1982        },
1983        "cbrt" => Scalar {
1984            params!(Float64) => UnaryFunc::CbrtFloat64(func::CbrtFloat64) => Float64, 1345;
1985        },
1986        "ceil" => Scalar {
1987            params!(Float32) => UnaryFunc::CeilFloat32(func::CeilFloat32)
1988                => Float32, oid::FUNC_CEIL_F32_OID;
1989            params!(Float64) => UnaryFunc::CeilFloat64(func::CeilFloat64) => Float64, 2308;
1990            params!(Numeric) => UnaryFunc::CeilNumeric(func::CeilNumeric) => Numeric, 1711;
1991        },
1992        "ceiling" => Scalar {
1993            params!(Float32) => UnaryFunc::CeilFloat32(func::CeilFloat32)
1994                => Float32, oid::FUNC_CEILING_F32_OID;
1995            params!(Float64) => UnaryFunc::CeilFloat64(func::CeilFloat64) => Float64, 2320;
1996            params!(Numeric) => UnaryFunc::CeilNumeric(func::CeilNumeric) => Numeric, 2167;
1997        },
1998        "char_length" => Scalar {
1999            params!(String) => UnaryFunc::CharLength(func::CharLength) => Int32, 1381;
2000        },
2001        // SQL exactly matches PostgreSQL's implementation.
2002        "col_description" => Scalar {
2003            params!(Oid, Int32) => sql_impl_func(
2004                "(SELECT description
2005                    FROM pg_description
2006                    WHERE objoid = $1 AND classoid = 'pg_class'::regclass AND objsubid = $2)"
2007                ) => String, 1216;
2008        },
2009        "concat" => Scalar {
2010            params!(Any...) => Operation::variadic(|ecx, cexprs| {
2011                if cexprs.is_empty() {
2012                    sql_bail!("No function matches the given name and argument types. \
2013                    You might need to add explicit type casts.")
2014                }
2015                let mut exprs = vec![];
2016                for expr in cexprs {
2017                    exprs.push(match ecx.scalar_type(&expr) {
2018                        // concat uses nonstandard bool -> string casts
2019                        // to match historical baggage in PostgreSQL.
2020                        SqlScalarType::Bool => expr.call_unary(
2021                            UnaryFunc::CastBoolToStringNonstandard(
2022                                func::CastBoolToStringNonstandard,
2023                            ),
2024                        ),
2025                        // TODO(see <materialize#7572>): remove call to PadChar
2026                        SqlScalarType::Char { length } => {
2027                            expr.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
2028                        }
2029                        _ => typeconv::to_string(ecx, expr)?
2030                    });
2031                }
2032                Ok(HirScalarExpr::call_variadic(variadic::Concat, exprs))
2033            }) => String, 3058;
2034        },
2035        "concat_ws" => Scalar {
2036            params!([String], Any...) => Operation::variadic(|ecx, cexprs| {
2037                if cexprs.len() < 2 {
2038                    sql_bail!("No function matches the given name and argument types. \
2039                    You might need to add explicit type casts.")
2040                }
2041                let mut exprs = vec![];
2042                for expr in cexprs {
2043                    exprs.push(match ecx.scalar_type(&expr) {
2044                        // concat uses nonstandard bool -> string casts
2045                        // to match historical baggage in PostgreSQL.
2046                        SqlScalarType::Bool => expr.call_unary(
2047                            UnaryFunc::CastBoolToStringNonstandard(
2048                                func::CastBoolToStringNonstandard,
2049                            ),
2050                        ),
2051                        // TODO(see <materialize#7572>): remove call to PadChar
2052                        SqlScalarType::Char { length } => {
2053                            expr.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
2054                        }
2055                        _ => typeconv::to_string(ecx, expr)?
2056                    });
2057                }
2058                Ok(HirScalarExpr::call_variadic(variadic::ConcatWs, exprs))
2059            }) => String, 3059;
2060        },
2061        "convert_from" => Scalar {
2062            params!(Bytes, String) => BinaryFunc::from(func::ConvertFrom) => String, 1714;
2063        },
2064        "cos" => Scalar {
2065            params!(Float64) => UnaryFunc::Cos(func::Cos) => Float64, 1605;
2066        },
2067        "acos" => Scalar {
2068            params!(Float64) => UnaryFunc::Acos(func::Acos) => Float64, 1601;
2069        },
2070        "cosh" => Scalar {
2071            params!(Float64) => UnaryFunc::Cosh(func::Cosh) => Float64, 2463;
2072        },
2073        "acosh" => Scalar {
2074            params!(Float64) => UnaryFunc::Acosh(func::Acosh) => Float64, 2466;
2075        },
2076        "cot" => Scalar {
2077            params!(Float64) => UnaryFunc::Cot(func::Cot) => Float64, 1607;
2078        },
2079        "current_schema" => Scalar {
2080            // TODO: this should be `name`. This is tricky in Materialize
2081            // because `name` truncates to 63 characters but Materialize does
2082            // not have a limit on identifier length.
2083            params!() => UnmaterializableFunc::CurrentSchema => String, 1402;
2084        },
2085        "current_schemas" => Scalar {
2086            params!(Bool) => Operation::unary(|_ecx, e| {
2087                Ok(HirScalarExpr::if_then_else(
2088                     e,
2089                     HirScalarExpr::call_unmaterializable(
2090                         UnmaterializableFunc::CurrentSchemasWithSystem,
2091                     ),
2092                     HirScalarExpr::call_unmaterializable(
2093                         UnmaterializableFunc::CurrentSchemasWithoutSystem,
2094                     ),
2095                ))
2096                // TODO: this should be `name[]`. This is tricky in Materialize
2097                // because `name` truncates to 63 characters but Materialize
2098                // does not have a limit on identifier length.
2099            }) => SqlScalarType::Array(Box::new(SqlScalarType::String)), 1403;
2100        },
2101        "current_database" => Scalar {
2102            params!() => UnmaterializableFunc::CurrentDatabase => String, 861;
2103        },
2104        "current_catalog" => Scalar {
2105            params!() => UnmaterializableFunc::CurrentDatabase => String, oid::FUNC_CURRENT_CATALOG;
2106        },
2107        "current_setting" => Scalar {
2108            params!(String) => Operation::unary(|_ecx, name| {
2109                current_settings(name, HirScalarExpr::literal_false())
2110            }) => SqlScalarType::String, 2077;
2111            params!(String, Bool) => Operation::binary(|_ecx, name, missing_ok| {
2112                current_settings(name, missing_ok)
2113            }) => SqlScalarType::String, 3294;
2114        },
2115        "current_timestamp" => Scalar {
2116            params!() => UnmaterializableFunc::CurrentTimestamp
2117                => TimestampTz, oid::FUNC_CURRENT_TIMESTAMP_OID;
2118        },
2119        "current_user" => Scalar {
2120            params!() => UnmaterializableFunc::CurrentUser => String, 745;
2121        },
2122        "current_role" => Scalar {
2123            params!() => UnmaterializableFunc::CurrentUser => String, oid::FUNC_CURRENT_ROLE;
2124        },
2125        "user" => Scalar {
2126            params!() => UnmaterializableFunc::CurrentUser => String, oid::FUNC_USER;
2127        },
2128        "session_user" => Scalar {
2129            params!() => UnmaterializableFunc::SessionUser => String, 746;
2130        },
2131        "chr" => Scalar {
2132            params!(Int32) => UnaryFunc::Chr(func::Chr) => String, 1621;
2133        },
2134        "date" => Scalar {
2135            params!(String) => UnaryFunc::CastStringToDate(func::CastStringToDate)
2136                => Date, oid::FUNC_DATE_FROM_TEXT;
2137            params!(Timestamp) => UnaryFunc::CastTimestampToDate(func::CastTimestampToDate)
2138                => Date, 2029;
2139            params!(TimestampTz) => UnaryFunc::CastTimestampTzToDate(func::CastTimestampTzToDate)
2140                => Date, 1178;
2141        },
2142        "date_bin" => Scalar {
2143            params!(Interval, Timestamp) => Operation::binary(|ecx, stride, source| {
2144                ecx.require_feature_flag(&vars::ENABLE_BINARY_DATE_BIN)?;
2145                Ok(stride.call_binary(source, func::DateBinTimestamp))
2146            }) => Timestamp, oid::FUNC_MZ_DATE_BIN_UNIX_EPOCH_TS_OID;
2147            params!(Interval, TimestampTz) => Operation::binary(|ecx, stride, source| {
2148                ecx.require_feature_flag(&vars::ENABLE_BINARY_DATE_BIN)?;
2149                Ok(stride.call_binary(source, func::DateBinTimestampTz))
2150            }) => TimestampTz, oid::FUNC_MZ_DATE_BIN_UNIX_EPOCH_TSTZ_OID;
2151            params!(Interval, Timestamp, Timestamp)
2152                => VariadicFunc::from(variadic::DateBinTimestamp) => Timestamp, 6177;
2153            params!(Interval, TimestampTz, TimestampTz)
2154                => VariadicFunc::from(variadic::DateBinTimestampTz) => TimestampTz, 6178;
2155        },
2156        "extract" => Scalar {
2157            params!(String, Interval)
2158                => BinaryFunc::from(func::DatePartIntervalNumeric) => Numeric, 6204;
2159            params!(String, Time)
2160                => BinaryFunc::from(func::DatePartTimeNumeric) => Numeric, 6200;
2161            params!(String, Timestamp)
2162                => BinaryFunc::from(func::DatePartTimestampTimestampNumeric)
2163                => Numeric, 6202;
2164            params!(String, TimestampTz)
2165                => BinaryFunc::from(func::DatePartTimestampTimestampTzNumeric)
2166                => Numeric, 6203;
2167            params!(String, Date) => BinaryFunc::from(func::ExtractDateUnits) => Numeric, 6199;
2168        },
2169        "date_part" => Scalar {
2170            params!(String, Interval)
2171                => BinaryFunc::from(func::DatePartIntervalF64) => Float64, 1172;
2172            params!(String, Time)
2173                => BinaryFunc::from(func::DatePartTimeF64) => Float64, 1385;
2174            params!(String, Timestamp)
2175                => BinaryFunc::from(func::DatePartTimestampTimestampF64)
2176                => Float64, 2021;
2177            params!(String, TimestampTz)
2178                => BinaryFunc::from(func::DatePartTimestampTimestampTzF64)
2179                => Float64, 1171;
2180        },
2181        "date_trunc" => Scalar {
2182            params!(String, Timestamp)
2183                => BinaryFunc::from(func::DateTruncUnitsTimestamp) => Timestamp, 2020;
2184            params!(String, TimestampTz)
2185                => BinaryFunc::from(func::DateTruncUnitsTimestampTz)
2186                => TimestampTz, 1217;
2187            params!(String, Interval)
2188                => BinaryFunc::from(func::DateTruncInterval) => Interval, 1218;
2189        },
2190        "daterange" => Scalar {
2191            params!(Date, Date) => Operation::variadic(|_ecx, mut exprs| {
2192                exprs.push(HirScalarExpr::literal(
2193                    Datum::String("[)"), SqlScalarType::String,
2194                ));
2195                Ok(HirScalarExpr::call_variadic(
2196                    variadic::RangeCreate { elem_type: SqlScalarType::Date },
2197                    exprs,
2198                ))
2199            }) => SqlScalarType::Range {
2200                element_type: Box::new(SqlScalarType::Date),
2201            }, 3941;
2202            params!(Date, Date, String) => Operation::variadic(|_ecx, exprs| {
2203                Ok(HirScalarExpr::call_variadic(
2204                    variadic::RangeCreate { elem_type: SqlScalarType::Date },
2205                    exprs,
2206                ))
2207            }) => SqlScalarType::Range {
2208                element_type: Box::new(SqlScalarType::Date),
2209            }, 3942;
2210        },
2211        "degrees" => Scalar {
2212            params!(Float64) => UnaryFunc::Degrees(func::Degrees) => Float64, 1608;
2213        },
2214        "digest" => Scalar {
2215            params!(String, String) => BinaryFunc::from(func::DigestString)
2216                => Bytes, oid::FUNC_PG_DIGEST_STRING;
2217            params!(Bytes, String) => BinaryFunc::from(func::DigestBytes)
2218                => Bytes, oid::FUNC_PG_DIGEST_BYTES;
2219        },
2220        "exp" => Scalar {
2221            params!(Float64) => UnaryFunc::Exp(func::Exp) => Float64, 1347;
2222            params!(Numeric) => UnaryFunc::ExpNumeric(func::ExpNumeric) => Numeric, 1732;
2223        },
2224        "floor" => Scalar {
2225            params!(Float32) => UnaryFunc::FloorFloat32(func::FloorFloat32)
2226                => Float32, oid::FUNC_FLOOR_F32_OID;
2227            params!(Float64) => UnaryFunc::FloorFloat64(func::FloorFloat64) => Float64, 2309;
2228            params!(Numeric) => UnaryFunc::FloorNumeric(func::FloorNumeric) => Numeric, 1712;
2229        },
2230        "format_type" => Scalar {
2231            params!(Oid, Int32) => sql_impl_func(
2232                "CASE
2233                        WHEN $1 IS NULL THEN NULL
2234                        -- timestamp and timestamptz have the typmod in
2235                        -- a nonstandard location that requires special
2236                        -- handling.
2237                        WHEN $1 = 1114 AND $2 >= 0 THEN 'timestamp(' || $2 || ') without time zone'
2238                        WHEN $1 = 1184 AND $2 >= 0 THEN 'timestamp(' || $2 || ') with time zone'
2239                        ELSE coalesce(
2240                            (SELECT pg_catalog.concat(
2241                                coalesce(mz_internal.mz_type_name($1), name),
2242                                mz_internal.mz_render_typmod($1, $2))
2243                             FROM mz_catalog.mz_types WHERE oid = $1),
2244                            '???')
2245                    END"
2246            ) => String, 1081;
2247        },
2248        "get_bit" => Scalar {
2249            params!(Bytes, Int32) => BinaryFunc::from(func::GetBit) => Int32, 723;
2250        },
2251        "get_byte" => Scalar {
2252            params!(Bytes, Int32) => BinaryFunc::from(func::GetByte) => Int32, 721;
2253        },
2254        "pg_get_ruledef" => Scalar {
2255            params!(Oid) => sql_impl_func("NULL::pg_catalog.text") => String, 1573;
2256            params!(Oid, Bool) => sql_impl_func("NULL::pg_catalog.text") => String, 2504;
2257        },
2258        "has_schema_privilege" => Scalar {
2259            params!(String, String, String) => sql_impl_func(
2260                "has_schema_privilege(\
2261                 mz_internal.mz_role_oid($1), \
2262                 mz_internal.mz_schema_oid($2), $3)",
2263            ) => Bool, 2268;
2264            params!(String, Oid, String) => sql_impl_func(
2265                "has_schema_privilege(\
2266                 mz_internal.mz_role_oid($1), $2, $3)",
2267            ) => Bool, 2269;
2268            params!(Oid, String, String) => sql_impl_func(
2269                "has_schema_privilege(\
2270                 $1, mz_internal.mz_schema_oid($2), $3)",
2271            ) => Bool, 2270;
2272            params!(Oid, Oid, String) => sql_impl_func(
2273                &privilege_fn!(
2274                    "has_schema_privilege", "mz_schemas"
2275                ),
2276            ) => Bool, 2271;
2277            params!(String, String) => sql_impl_func(
2278                "has_schema_privilege(current_user, $1, $2)",
2279            ) => Bool, 2272;
2280            params!(Oid, String) => sql_impl_func(
2281                "has_schema_privilege(current_user, $1, $2)",
2282            ) => Bool, 2273;
2283        },
2284        "has_database_privilege" => Scalar {
2285            params!(String, String, String) => sql_impl_func(
2286                "has_database_privilege(\
2287                 mz_internal.mz_role_oid($1), \
2288                 mz_internal.mz_database_oid($2), $3)",
2289            ) => Bool, 2250;
2290            params!(String, Oid, String) => sql_impl_func(
2291                "has_database_privilege(\
2292                 mz_internal.mz_role_oid($1), $2, $3)",
2293            ) => Bool, 2251;
2294            params!(Oid, String, String) => sql_impl_func(
2295                "has_database_privilege(\
2296                 $1, mz_internal.mz_database_oid($2), $3)",
2297            ) => Bool, 2252;
2298            params!(Oid, Oid, String) => sql_impl_func(
2299                &privilege_fn!(
2300                    "has_database_privilege", "mz_databases"
2301                ),
2302            ) => Bool, 2253;
2303            params!(String, String) => sql_impl_func(
2304                "has_database_privilege(current_user, $1, $2)",
2305            ) => Bool, 2254;
2306            params!(Oid, String) => sql_impl_func(
2307                "has_database_privilege(current_user, $1, $2)",
2308            ) => Bool, 2255;
2309        },
2310        "has_table_privilege" => Scalar {
2311            params!(String, String, String) => sql_impl_func(
2312                "has_table_privilege(\
2313                 mz_internal.mz_role_oid($1), \
2314                 $2::regclass::oid, $3)",
2315            ) => Bool, 1922;
2316            params!(String, Oid, String) => sql_impl_func(
2317                "has_table_privilege(\
2318                 mz_internal.mz_role_oid($1), $2, $3)",
2319            ) => Bool, 1923;
2320            params!(Oid, String, String) => sql_impl_func(
2321                "has_table_privilege(\
2322                 $1, $2::regclass::oid, $3)",
2323            ) => Bool, 1924;
2324            params!(Oid, Oid, String) => sql_impl_func(
2325                &privilege_fn!(
2326                    "has_table_privilege", "mz_relations"
2327                ),
2328            ) => Bool, 1925;
2329            params!(String, String) => sql_impl_func(
2330                "has_table_privilege(current_user, $1, $2)",
2331            ) => Bool, 1926;
2332            params!(Oid, String) => sql_impl_func(
2333                "has_table_privilege(current_user, $1, $2)",
2334            ) => Bool, 1927;
2335        },
2336        "hmac" => Scalar {
2337            params!(String, String, String) => VariadicFunc::from(variadic::HmacString)
2338                => Bytes, oid::FUNC_PG_HMAC_STRING;
2339            params!(Bytes, Bytes, String) => VariadicFunc::from(variadic::HmacBytes)
2340                => Bytes, oid::FUNC_PG_HMAC_BYTES;
2341        },
2342        "initcap" => Scalar {
2343            params!(String) => UnaryFunc::Initcap(func::Initcap) => String, 872;
2344        },
2345        "int4range" => Scalar {
2346            params!(Int32, Int32) => Operation::variadic(|_ecx, mut exprs| {
2347                exprs.push(HirScalarExpr::literal(
2348                    Datum::String("[)"), SqlScalarType::String,
2349                ));
2350                Ok(HirScalarExpr::call_variadic(
2351                    variadic::RangeCreate { elem_type: SqlScalarType::Int32 },
2352                    exprs,
2353                ))
2354            }) => SqlScalarType::Range {
2355                element_type: Box::new(SqlScalarType::Int32),
2356            }, 3840;
2357            params!(Int32, Int32, String) => Operation::variadic(|_ecx, exprs| {
2358                Ok(HirScalarExpr::call_variadic(
2359                    variadic::RangeCreate { elem_type: SqlScalarType::Int32 },
2360                    exprs,
2361                ))
2362            }) => SqlScalarType::Range {
2363                element_type: Box::new(SqlScalarType::Int32),
2364            }, 3841;
2365        },
2366        "int8range" => Scalar {
2367            params!(Int64, Int64) => Operation::variadic(|_ecx, mut exprs| {
2368                exprs.push(HirScalarExpr::literal(
2369                    Datum::String("[)"), SqlScalarType::String,
2370                ));
2371                Ok(HirScalarExpr::call_variadic(
2372                    variadic::RangeCreate { elem_type: SqlScalarType::Int64 },
2373                    exprs,
2374                ))
2375            }) => SqlScalarType::Range {
2376                element_type: Box::new(SqlScalarType::Int64),
2377            }, 3945;
2378            params!(Int64, Int64, String) => Operation::variadic(|_ecx, exprs| {
2379                Ok(HirScalarExpr::call_variadic(
2380                    variadic::RangeCreate { elem_type: SqlScalarType::Int64 },
2381                    exprs,
2382                ))
2383            }) => SqlScalarType::Range {
2384                element_type: Box::new(SqlScalarType::Int64),
2385            }, 3946;
2386        },
2387        "isempty" => Scalar {
2388            params!(RangeAny) => UnaryFunc::RangeEmpty(func::RangeEmpty) => Bool, 3850;
2389        },
2390        "jsonb_array_length" => Scalar {
2391            params!(Jsonb) => UnaryFunc::JsonbArrayLength(func::JsonbArrayLength) => Int32, 3207;
2392        },
2393        "jsonb_build_array" => Scalar {
2394            params!() => VariadicFunc::from(variadic::JsonbBuildArray) => Jsonb, 3272;
2395            params!(Any...) => Operation::variadic(|ecx, exprs| {
2396                Ok(HirScalarExpr::call_variadic(
2397                    variadic::JsonbBuildArray,
2398                    exprs
2399                        .into_iter()
2400                        .map(|e| typeconv::to_jsonb(ecx, e))
2401                        .collect::<Result<Vec<_>, _>>()?,
2402                ))
2403            }) => Jsonb, 3271;
2404        },
2405        "jsonb_build_object" => Scalar {
2406            params!() => VariadicFunc::from(variadic::JsonbBuildObject) => Jsonb, 3274;
2407            params!(Any...) => Operation::variadic(|ecx, exprs| {
2408                if exprs.len() % 2 != 0 {
2409                    sql_bail!("argument list must have even number of elements")
2410                }
2411                let mut elems = Vec::with_capacity(exprs.len());
2412                for (key, val) in exprs.into_iter().tuples() {
2413                    elems.push(typeconv::to_string(ecx, key)?);
2414                    elems.push(typeconv::to_jsonb(ecx, val)?);
2415                }
2416                Ok(HirScalarExpr::call_variadic(variadic::JsonbBuildObject, elems))
2417            }) => Jsonb, 3273;
2418        },
2419        "jsonb_pretty" => Scalar {
2420            params!(Jsonb) => UnaryFunc::JsonbPretty(func::JsonbPretty) => String, 3306;
2421        },
2422        "jsonb_strip_nulls" => Scalar {
2423            params!(Jsonb) => UnaryFunc::JsonbStripNulls(func::JsonbStripNulls) => Jsonb, 3262;
2424        },
2425        "jsonb_typeof" => Scalar {
2426            params!(Jsonb) => UnaryFunc::JsonbTypeof(func::JsonbTypeof) => String, 3210;
2427        },
2428        "justify_days" => Scalar {
2429            params!(Interval) => UnaryFunc::JustifyDays(func::JustifyDays) => Interval, 1295;
2430        },
2431        "justify_hours" => Scalar {
2432            params!(Interval) => UnaryFunc::JustifyHours(func::JustifyHours) => Interval, 1175;
2433        },
2434        "justify_interval" => Scalar {
2435            params!(Interval) => UnaryFunc::JustifyInterval(func::JustifyInterval)
2436                => Interval, 2711;
2437        },
2438        "left" => Scalar {
2439            params!(String, Int32) => BinaryFunc::from(func::Left) => String, 3060;
2440        },
2441        "length" => Scalar {
2442            params!(Bytes) => UnaryFunc::ByteLengthBytes(func::ByteLengthBytes) => Int32, 2010;
2443            // bpcharlen is redundant with automatic coercion to string, 1318.
2444            params!(String) => UnaryFunc::CharLength(func::CharLength) => Int32, 1317;
2445            params!(Bytes, String) => BinaryFunc::from(func::EncodedBytesCharLength) => Int32, 1713;
2446        },
2447        "like_escape" => Scalar {
2448            params!(String, String) => BinaryFunc::from(func::LikeEscape) => String, 1637;
2449        },
2450        "ln" => Scalar {
2451            params!(Float64) => UnaryFunc::Ln(func::Ln) => Float64, 1341;
2452            params!(Numeric) => UnaryFunc::LnNumeric(func::LnNumeric) => Numeric, 1734;
2453        },
2454        "log10" => Scalar {
2455            params!(Float64) => UnaryFunc::Log10(func::Log10) => Float64, 1194;
2456            params!(Numeric) => UnaryFunc::Log10Numeric(func::Log10Numeric) => Numeric, 1481;
2457        },
2458        "log" => Scalar {
2459            params!(Float64) => UnaryFunc::Log10(func::Log10) => Float64, 1340;
2460            params!(Numeric) => UnaryFunc::Log10Numeric(func::Log10Numeric) => Numeric, 1741;
2461            params!(Numeric, Numeric) => BinaryFunc::from(func::LogBaseNumeric) => Numeric, 1736;
2462        },
2463        "lower" => Scalar {
2464            params!(String) => UnaryFunc::Lower(func::Lower) => String, 870;
2465            params!(RangeAny) => UnaryFunc::RangeLower(func::RangeLower) => AnyElement, 3848;
2466        },
2467        "lower_inc" => Scalar {
2468            params!(RangeAny) => UnaryFunc::RangeLowerInc(func::RangeLowerInc) => Bool, 3851;
2469        },
2470        "lower_inf" => Scalar {
2471            params!(RangeAny) => UnaryFunc::RangeLowerInf(func::RangeLowerInf) => Bool, 3853;
2472        },
2473        "lpad" => Scalar {
2474            params!(String, Int32) => VariadicFunc::from(variadic::PadLeading) => String, 879;
2475            params!(String, Int32, String) => VariadicFunc::from(variadic::PadLeading)
2476                => String, 873;
2477        },
2478        "ltrim" => Scalar {
2479            params!(String) => UnaryFunc::TrimLeadingWhitespace(
2480                func::TrimLeadingWhitespace,
2481            ) => String, 881;
2482            params!(String, String) => BinaryFunc::from(func::TrimLeading) => String, 875;
2483        },
2484        "makeaclitem" => Scalar {
2485            params!(Oid, Oid, String, Bool)
2486                => VariadicFunc::from(variadic::MakeAclItem) => AclItem, 1365;
2487        },
2488        "make_timestamp" => Scalar {
2489            params!(Int64, Int64, Int64, Int64, Int64, Float64)
2490                => VariadicFunc::from(variadic::MakeTimestamp) => Timestamp, 3461;
2491        },
2492        "md5" => Scalar {
2493            params!(String) => Operation::unary(move |_ecx, input| {
2494                let algorithm = HirScalarExpr::literal(Datum::String("md5"), SqlScalarType::String);
2495                let encoding = HirScalarExpr::literal(Datum::String("hex"), SqlScalarType::String);
2496                Ok(input
2497                    .call_binary(algorithm, func::DigestString)
2498                    .call_binary(encoding, func::Encode))
2499            }) => String, 2311;
2500            params!(Bytes) => Operation::unary(move |_ecx, input| {
2501                let algorithm = HirScalarExpr::literal(
2502                    Datum::String("md5"), SqlScalarType::String,
2503                );
2504                let encoding = HirScalarExpr::literal(
2505                    Datum::String("hex"), SqlScalarType::String,
2506                );
2507                Ok(input
2508                    .call_binary(algorithm, func::DigestBytes)
2509                    .call_binary(encoding, func::Encode))
2510            }) => String, 2321;
2511        },
2512        "mod" => Scalar {
2513            params!(Numeric, Numeric) =>
2514                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2515                => Numeric, 1728;
2516            params!(Int16, Int16) =>
2517                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2518                => Int16, 940;
2519            params!(Int32, Int32) =>
2520                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2521                => Int32, 941;
2522            params!(Int64, Int64) =>
2523                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2524                => Int64, 947;
2525            params!(UInt16, UInt16) =>
2526                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2527                => UInt16, oid::FUNC_MOD_UINT16_OID;
2528            params!(UInt32, UInt32) =>
2529                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2530                => UInt32, oid::FUNC_MOD_UINT32_OID;
2531            params!(UInt64, UInt64) =>
2532                Operation::nullary(|_ecx| catalog_name_only!("mod"))
2533                => UInt64, oid::FUNC_MOD_UINT64_OID;
2534        },
2535        "normalize" => Scalar {
2536            // Parser always provides two arguments (defaults second to "NFC" when omitted)
2537            params!(String, String) => BinaryFunc::Normalize(func::Normalize)
2538                => String, oid::FUNC_NORMALIZE_OID;
2539        },
2540        "now" => Scalar {
2541            params!() => UnmaterializableFunc::CurrentTimestamp => TimestampTz, 1299;
2542        },
2543        "numrange" => Scalar {
2544            params!(Numeric, Numeric) => Operation::variadic(|_ecx, mut exprs| {
2545                exprs.push(HirScalarExpr::literal(
2546                    Datum::String("[)"), SqlScalarType::String,
2547                ));
2548                Ok(HirScalarExpr::call_variadic(
2549                    variadic::RangeCreate {
2550                        elem_type: SqlScalarType::Numeric { max_scale: None },
2551                    },
2552                    exprs,
2553                ))
2554            }) => SqlScalarType::Range {
2555                element_type: Box::new(SqlScalarType::Numeric { max_scale: None }),
2556            }, 3844;
2557            params!(Numeric, Numeric, String) => Operation::variadic(|_ecx, exprs| {
2558                Ok(HirScalarExpr::call_variadic(
2559                    variadic::RangeCreate {
2560                        elem_type: SqlScalarType::Numeric { max_scale: None },
2561                    },
2562                    exprs,
2563                ))
2564            }) => SqlScalarType::Range {
2565                element_type: Box::new(SqlScalarType::Numeric { max_scale: None }),
2566            }, 3845;
2567        },
2568        "octet_length" => Scalar {
2569            params!(Bytes) => UnaryFunc::ByteLengthBytes(func::ByteLengthBytes) => Int32, 720;
2570            params!(String) => UnaryFunc::ByteLengthString(func::ByteLengthString) => Int32, 1374;
2571            params!(Char) => Operation::unary(|ecx, e| {
2572                let length = ecx.scalar_type(&e).unwrap_char_length();
2573                Ok(e.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
2574                    .call_unary(UnaryFunc::ByteLengthString(func::ByteLengthString))
2575                )
2576            }) => Int32, 1375;
2577        },
2578        // SQL closely matches PostgreSQL's implementation.
2579        // We don't yet support casting to regnamespace, so use our constant for
2580        // the oid of 'pg_catalog'.
2581        "obj_description" => Scalar {
2582            params!(Oid, String) => sql_impl_func(&format!(
2583                "(SELECT description FROM pg_description
2584                  WHERE objoid = $1
2585                    AND classoid = (
2586                      SELECT oid FROM pg_class WHERE relname = $2 AND relnamespace = {})
2587                    AND objsubid = 0)",
2588                oid::SCHEMA_PG_CATALOG_OID
2589            )) => String, 1215;
2590        },
2591        "pg_column_size" => Scalar {
2592            params!(Any) => UnaryFunc::PgColumnSize(func::PgColumnSize) => Int32, 1269;
2593        },
2594        "pg_size_pretty" => Scalar {
2595            params!(Numeric) => UnaryFunc::PgSizePretty(func::PgSizePretty) => String, 3166;
2596        },
2597        "mz_row_size" => Scalar {
2598            params!(Any) => Operation::unary(|ecx, e| {
2599                let s = ecx.scalar_type(&e);
2600                if !matches!(s, SqlScalarType::Record{..}) {
2601                    sql_bail!("mz_row_size requires a record type");
2602                }
2603                Ok(e.call_unary(UnaryFunc::MzRowSize(func::MzRowSize)))
2604            }) => Int32, oid::FUNC_MZ_ROW_SIZE;
2605        },
2606        "parse_ident" => Scalar {
2607            params!(String) => Operation::unary(|_ecx, ident| {
2608                Ok(ident.call_binary(HirScalarExpr::literal_true(), func::ParseIdent))
2609            }) => SqlScalarType::Array(Box::new(SqlScalarType::String)),
2610                oid::FUNC_PARSE_IDENT_DEFAULT_STRICT;
2611            params!(String, Bool) => BinaryFunc::from(func::ParseIdent)
2612                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 1268;
2613        },
2614        "pg_encoding_to_char" => Scalar {
2615            // Materialize only supports UT8-encoded databases. Return 'UTF8' if Postgres'
2616            // encoding id for UTF8 (6) is provided, otherwise return 'NULL'.
2617            params!(Int64) => sql_impl_func(
2618                "CASE WHEN $1 = 6 THEN 'UTF8' ELSE NULL END",
2619            ) => String, 1597;
2620        },
2621        "pg_backend_pid" => Scalar {
2622            params!() => UnmaterializableFunc::PgBackendPid => Int32, 2026;
2623        },
2624        // pg_get_constraintdef gives more info about a constraint within the `pg_constraint`
2625        // view. Certain meta commands rely on this function not throwing an error, but the
2626        // `pg_constraint` view is empty in materialize. Therefore we know any oid provided is
2627        // not a valid constraint, so we can return NULL which is what PostgreSQL does when
2628        // provided an invalid OID.
2629        "pg_get_constraintdef" => Scalar {
2630            params!(Oid) => Operation::unary(|_ecx, _oid|
2631                Ok(HirScalarExpr::literal_null(SqlScalarType::String))) => String, 1387;
2632            params!(Oid, Bool) => Operation::binary(|_ecx, _oid, _pretty|
2633                Ok(HirScalarExpr::literal_null(SqlScalarType::String))) => String, 2508;
2634        },
2635        // pg_get_indexdef reconstructs the creating command for an index. We only support
2636        // arrangement based indexes, so we can hardcode that in.
2637        // TODO(jkosh44): In order to include the index WITH options,
2638        // they will need to be saved somewhere in the catalog
2639        "pg_get_indexdef" => Scalar {
2640            params!(Oid) => sql_impl_func(
2641                "(SELECT 'CREATE INDEX ' || i.name
2642                    || ' ON ' || r.name
2643                    || ' USING arrangement (' || (
2644                    SELECT pg_catalog.string_agg(
2645                        cols.col_exp, ',' ORDER BY cols.index_position)
2646                    FROM (
2647                        SELECT c.name AS col_exp, ic.index_position
2648                        FROM mz_catalog.mz_index_columns AS ic
2649                        JOIN mz_catalog.mz_indexes AS i2
2650                            ON ic.index_id = i2.id
2651                        JOIN mz_catalog.mz_columns AS c
2652                            ON i2.on_id = c.id
2653                            AND ic.on_position = c.position
2654                        WHERE ic.index_id = i.id
2655                            AND ic.on_expression IS NULL
2656                        UNION
2657                        SELECT ic.on_expression AS col_exp,
2658                            ic.index_position
2659                        FROM mz_catalog.mz_index_columns AS ic
2660                        WHERE ic.index_id = i.id
2661                            AND ic.on_expression IS NOT NULL
2662                    ) AS cols
2663                ) || ')'
2664                FROM mz_catalog.mz_indexes AS i
2665                JOIN mz_catalog.mz_relations AS r
2666                    ON i.on_id = r.id
2667                WHERE i.oid = $1)"
2668            ) => String, 1643;
2669            // A position of 0 is treated as if no position was given.
2670            // Third parameter, pretty, is ignored.
2671            params!(Oid, Int32, Bool) => sql_impl_func(
2672                "(SELECT CASE
2673                    WHEN $2 = 0
2674                    THEN pg_catalog.pg_get_indexdef($1)
2675                    ELSE (
2676                        SELECT c.name
2677                        FROM mz_catalog.mz_indexes AS i
2678                        JOIN mz_catalog.mz_index_columns AS ic
2679                            ON i.id = ic.index_id
2680                        JOIN mz_catalog.mz_columns AS c
2681                            ON i.on_id = c.id
2682                            AND ic.on_position = c.position
2683                        WHERE i.oid = $1
2684                            AND ic.on_expression IS NULL
2685                            AND ic.index_position = $2
2686                        UNION
2687                        SELECT ic.on_expression
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                        WHERE i.oid = $1
2692                            AND ic.on_expression IS NOT NULL
2693                            AND ic.index_position = $2)
2694                    END)"
2695            ) => String, 2507;
2696        },
2697        // pg_get_viewdef returns the (query part of) the given view's definition.
2698        // We currently don't support pretty-printing (the `Bool`/`Int32` parameters).
2699        "pg_get_viewdef" => Scalar {
2700            params!(String) => sql_impl_func(
2701                "(SELECT definition FROM mz_catalog.mz_views WHERE name = $1)"
2702            ) => String, 1640;
2703            params!(Oid) => sql_impl_func(
2704                "(SELECT definition FROM mz_catalog.mz_views WHERE oid = $1)"
2705            ) => String, 1641;
2706            params!(String, Bool) => sql_impl_func(
2707                "(SELECT definition FROM mz_catalog.mz_views WHERE name = $1)"
2708            ) => String, 2505;
2709            params!(Oid, Bool) => sql_impl_func(
2710                "(SELECT definition FROM mz_catalog.mz_views WHERE oid = $1)"
2711            ) => String, 2506;
2712            params!(Oid, Int32) => sql_impl_func(
2713                "(SELECT definition FROM mz_catalog.mz_views WHERE oid = $1)"
2714            ) => String, 3159;
2715        },
2716        // pg_get_expr is meant to convert the textual version of
2717        // pg_node_tree data into parseable expressions. However, we don't
2718        // use the pg_get_expr structure anywhere and the equivalent columns
2719        // in Materialize (e.g. index expressions) are already stored as
2720        // parseable expressions. So, we offer this function in the catalog
2721        // for ORM support, but make no effort to provide its semantics,
2722        // e.g. this also means we drop the Oid argument on the floor.
2723        "pg_get_expr" => Scalar {
2724            params!(String, Oid) => Operation::binary(|_ecx, l, _r| Ok(l)) => String, 1716;
2725            params!(String, Oid, Bool) => Operation::variadic(
2726                move |_ecx, mut args| Ok(args.remove(0)),
2727            ) => String, 2509;
2728        },
2729        "pg_get_userbyid" => Scalar {
2730            params!(Oid) => sql_impl_func(
2731                "CASE \
2732                   WHEN $1 IS NULL THEN NULL \
2733                   ELSE COALESCE(\
2734                     (SELECT name FROM mz_catalog.mz_roles WHERE oid = $1),\
2735                     'unknown (OID=' || $1 || ')'\
2736                   ) \
2737                END"
2738            ) => String, 1642;
2739        },
2740        // The privilege param is validated but ignored. That's because we haven't implemented
2741        // NOINHERIT roles, so it has no effect on the result.
2742        //
2743        // In PostgreSQL, this should always return true for superusers. In Materialize it's
2744        // impossible to determine if a role is a superuser since it's specific to a session. So we
2745        // cannot copy PostgreSQL semantics there.
2746        "pg_has_role" => Scalar {
2747            params!(String, String, String) => sql_impl_func(
2748                "pg_has_role(\
2749                 mz_internal.mz_role_oid($1), \
2750                 mz_internal.mz_role_oid($2), $3)",
2751            ) => Bool, 2705;
2752            params!(String, Oid, String) => sql_impl_func(
2753                "pg_has_role(\
2754                 mz_internal.mz_role_oid($1), $2, $3)",
2755            ) => Bool, 2706;
2756            params!(Oid, String, String) => sql_impl_func(
2757                "pg_has_role(\
2758                 $1, mz_internal.mz_role_oid($2), $3)",
2759            ) => Bool, 2707;
2760            params!(Oid, Oid, String) => sql_impl_func(
2761                "CASE
2762                -- We need to validate the privilege to return a proper error before anything
2763                -- else.
2764                WHEN NOT mz_internal.mz_validate_role_privilege($3)
2765                OR $1 IS NULL
2766                OR $2 IS NULL
2767                OR $3 IS NULL
2768                THEN NULL
2769                WHEN $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
2770                OR $2 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
2771                THEN false
2772                ELSE $2::text IN (SELECT UNNEST(mz_internal.mz_role_oid_memberships() -> $1::text))
2773                END",
2774            ) => Bool, 2708;
2775            params!(String, String) => sql_impl_func(
2776                "pg_has_role(current_user, $1, $2)",
2777            ) => Bool, 2709;
2778            params!(Oid, String) => sql_impl_func(
2779                "pg_has_role(current_user, $1, $2)",
2780            ) => Bool, 2710;
2781        },
2782        // pg_is_in_recovery indicates whether a recovery is still in progress. Materialize does
2783        // not have a concept of recovery, so we default to always returning false.
2784        "pg_is_in_recovery" => Scalar {
2785            params!() => Operation::nullary(|_ecx| {
2786                Ok(HirScalarExpr::literal_false())
2787            }) => Bool, 3810;
2788        },
2789        "pg_postmaster_start_time" => Scalar {
2790            params!() => UnmaterializableFunc::PgPostmasterStartTime => TimestampTz, 2560;
2791        },
2792        "pg_relation_size" => Scalar {
2793            params!(RegClass, String) => sql_impl_func(
2794                "CASE WHEN $1 IS NULL OR $2 IS NULL \
2795                 THEN NULL ELSE -1::pg_catalog.int8 END",
2796            ) => Int64, 2332;
2797            params!(RegClass) => sql_impl_func(
2798                "CASE WHEN $1 IS NULL \
2799                 THEN NULL ELSE -1::pg_catalog.int8 END",
2800            ) => Int64, 2325;
2801        },
2802        "pg_stat_get_numscans" => Scalar {
2803            params!(Oid) => sql_impl_func(
2804                "CASE WHEN $1 IS NULL \
2805                 THEN NULL ELSE -1::pg_catalog.int8 END",
2806            ) => Int64, 1928;
2807        },
2808        "pg_table_is_visible" => Scalar {
2809            params!(Oid) => sql_impl_func(
2810                "(SELECT s.name = ANY(pg_catalog.current_schemas(true))
2811                     FROM mz_catalog.mz_objects o JOIN mz_catalog.mz_schemas s ON o.schema_id = s.id
2812                     WHERE o.oid = $1)"
2813            ) => Bool, 2079;
2814        },
2815        "pg_type_is_visible" => Scalar {
2816            params!(Oid) => sql_impl_func(
2817                "(SELECT s.name = ANY(pg_catalog.current_schemas(true))
2818                     FROM mz_catalog.mz_types t JOIN mz_catalog.mz_schemas s ON t.schema_id = s.id
2819                     WHERE t.oid = $1)"
2820            ) => Bool, 2080;
2821        },
2822        "pg_function_is_visible" => Scalar {
2823            params!(Oid) => sql_impl_func(
2824                "(SELECT s.name = ANY(pg_catalog.current_schemas(true))
2825                     FROM mz_catalog.mz_functions f
2826                     JOIN mz_catalog.mz_schemas s
2827                         ON f.schema_id = s.id
2828                     WHERE f.oid = $1)"
2829            ) => Bool, 2081;
2830        },
2831        // pg_tablespace_location indicates what path in the filesystem that a given tablespace is
2832        // located in. This concept does not make sense though in Materialize which is a cloud
2833        // native database, so we just return the null value.
2834        "pg_tablespace_location" => Scalar {
2835            params!(Oid) => Operation::unary(|_ecx, _e| {
2836                Ok(HirScalarExpr::literal_null(SqlScalarType::String))
2837            }) => String, 3778;
2838        },
2839        "pg_typeof" => Scalar {
2840            params!(Any) => Operation::new(|ecx, exprs, params, _order_by| {
2841                // pg_typeof reports the type *before* coercion.
2842                let name = match ecx.scalar_type(&exprs[0]) {
2843                    CoercibleScalarType::Uncoerced => "unknown".to_string(),
2844                    CoercibleScalarType::Record(_) => "record".to_string(),
2845                    CoercibleScalarType::Coerced(ty) => ecx.humanize_sql_scalar_type(&ty, true),
2846                };
2847
2848                // For consistency with other functions, verify that
2849                // coercion is possible, though we don't actually care about
2850                // the coerced results.
2851                coerce_args_to_types(ecx, exprs, params)?;
2852
2853                // TODO(benesch): make this function have return type
2854                // regtype, when we support that type. Document the function
2855                // at that point. For now, it's useful enough to have this
2856                // halfway version that returns a string.
2857                Ok(HirScalarExpr::literal(Datum::String(&name), SqlScalarType::String))
2858            }) => String, 1619;
2859        },
2860        "position" => Scalar {
2861            params!(String, String) => BinaryFunc::from(func::Position) => Int32, 849;
2862        },
2863        "pow" => Scalar {
2864            params!(Float64, Float64) =>
2865                Operation::nullary(|_ecx| catalog_name_only!("pow"))
2866                => Float64, 1346;
2867        },
2868        "power" => Scalar {
2869            params!(Float64, Float64) => BinaryFunc::from(func::Power) => Float64, 1368;
2870            params!(Numeric, Numeric) => BinaryFunc::from(func::PowerNumeric) => Numeric, 2169;
2871        },
2872        "quote_ident" => Scalar {
2873            params!(String) => UnaryFunc::QuoteIdent(func::QuoteIdent) => String, 1282;
2874        },
2875        "radians" => Scalar {
2876            params!(Float64) => UnaryFunc::Radians(func::Radians) => Float64, 1609;
2877        },
2878        "repeat" => Scalar {
2879            params!(String, Int32) => BinaryFunc::RepeatString(func::RepeatString) => String, 1622;
2880        },
2881        "regexp_match" => Scalar {
2882            params!(String, String) => VariadicFunc::from(variadic::RegexpMatch)
2883                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 3396;
2884            params!(String, String, String) => VariadicFunc::from(variadic::RegexpMatch)
2885                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 3397;
2886        },
2887        "replace" => Scalar {
2888            params!(String, String, String) => VariadicFunc::from(variadic::Replace)
2889                => String, 2087;
2890        },
2891        "right" => Scalar {
2892            params!(String, Int32) => BinaryFunc::from(func::Right) => String, 3061;
2893        },
2894        "round" => Scalar {
2895            params!(Float32) => UnaryFunc::RoundFloat32(func::RoundFloat32)
2896                => Float32, oid::FUNC_ROUND_F32_OID;
2897            params!(Float64) => UnaryFunc::RoundFloat64(func::RoundFloat64) => Float64, 1342;
2898            params!(Numeric) => UnaryFunc::RoundNumeric(func::RoundNumeric) => Numeric, 1708;
2899            params!(Numeric, Int32) => BinaryFunc::from(func::RoundNumericBinary) => Numeric, 1707;
2900        },
2901        "rtrim" => Scalar {
2902            params!(String) => UnaryFunc::TrimTrailingWhitespace(
2903                func::TrimTrailingWhitespace,
2904            ) => String, 882;
2905            params!(String, String) => BinaryFunc::from(func::TrimTrailing) => String, 876;
2906        },
2907        "sha224" => Scalar {
2908            params!(Bytes) => digest("sha224") => Bytes, 3419;
2909        },
2910        "sha256" => Scalar {
2911            params!(Bytes) => digest("sha256") => Bytes, 3420;
2912        },
2913        "sha384" => Scalar {
2914            params!(Bytes) => digest("sha384") => Bytes, 3421;
2915        },
2916        "sha512" => Scalar {
2917            params!(Bytes) => digest("sha512") => Bytes, 3422;
2918        },
2919        "sin" => Scalar {
2920            params!(Float64) => UnaryFunc::Sin(func::Sin) => Float64, 1604;
2921        },
2922        "asin" => Scalar {
2923            params!(Float64) => UnaryFunc::Asin(func::Asin) => Float64, 1600;
2924        },
2925        "sinh" => Scalar {
2926            params!(Float64) => UnaryFunc::Sinh(func::Sinh) => Float64, 2462;
2927        },
2928        "asinh" => Scalar {
2929            params!(Float64) => UnaryFunc::Asinh(func::Asinh) => Float64, 2465;
2930        },
2931        "strpos" => Scalar {
2932            params!(String, String) => BinaryFunc::from(func::Strpos) => Int32, 868;
2933        },
2934        "split_part" => Scalar {
2935            params!(String, String, Int32) => VariadicFunc::from(variadic::SplitPart)
2936                => String, 2088;
2937        },
2938        "stddev" => Scalar {
2939            params!(Float32) =>
2940                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2941                => Float64, 2157;
2942            params!(Float64) =>
2943                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2944                => Float64, 2158;
2945            params!(Int16) =>
2946                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2947                => Numeric, 2156;
2948            params!(Int32) =>
2949                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2950                => Numeric, 2155;
2951            params!(Int64) =>
2952                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2953                => Numeric, 2154;
2954            params!(UInt16) =>
2955                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2956                => Numeric, oid::FUNC_STDDEV_UINT16_OID;
2957            params!(UInt32) =>
2958                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2959                => Numeric, oid::FUNC_STDDEV_UINT32_OID;
2960            params!(UInt64) =>
2961                Operation::nullary(|_ecx| catalog_name_only!("stddev"))
2962                => Numeric, oid::FUNC_STDDEV_UINT64_OID;
2963        },
2964        "stddev_pop" => Scalar {
2965            params!(Float32) =>
2966                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2967                => Float64, 2727;
2968            params!(Float64) =>
2969                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2970                => Float64, 2728;
2971            params!(Int16) =>
2972                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2973                => Numeric, 2726;
2974            params!(Int32) =>
2975                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2976                => Numeric, 2725;
2977            params!(Int64) =>
2978                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2979                => Numeric, 2724;
2980            params!(UInt16) =>
2981                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2982                => Numeric, oid::FUNC_STDDEV_POP_UINT16_OID;
2983            params!(UInt32) =>
2984                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2985                => Numeric, oid::FUNC_STDDEV_POP_UINT32_OID;
2986            params!(UInt64) =>
2987                Operation::nullary(|_ecx| catalog_name_only!("stddev_pop"))
2988                => Numeric, oid::FUNC_STDDEV_POP_UINT64_OID;
2989        },
2990        "stddev_samp" => Scalar {
2991            params!(Float32) =>
2992                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
2993                => Float64, 2715;
2994            params!(Float64) =>
2995                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
2996                => Float64, 2716;
2997            params!(Int16) =>
2998                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
2999                => Numeric, 2714;
3000            params!(Int32) =>
3001                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3002                => Numeric, 2713;
3003            params!(Int64) =>
3004                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3005                => Numeric, 2712;
3006            params!(UInt16) =>
3007                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3008                => Numeric, oid::FUNC_STDDEV_SAMP_UINT16_OID;
3009            params!(UInt32) =>
3010                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3011                => Numeric, oid::FUNC_STDDEV_SAMP_UINT32_OID;
3012            params!(UInt64) =>
3013                Operation::nullary(|_ecx| catalog_name_only!("stddev_samp"))
3014                => Numeric, oid::FUNC_STDDEV_SAMP_UINT64_OID;
3015        },
3016        "substr" => Scalar {
3017            params!(String, Int32) => VariadicFunc::from(variadic::Substr) => String, 883;
3018            params!(String, Int32, Int32) => VariadicFunc::from(variadic::Substr) => String, 877;
3019        },
3020        "substring" => Scalar {
3021            params!(String, Int32) => VariadicFunc::from(variadic::Substr) => String, 937;
3022            params!(String, Int32, Int32) => VariadicFunc::from(variadic::Substr) => String, 936;
3023        },
3024        "sqrt" => Scalar {
3025            params!(Float64) => UnaryFunc::SqrtFloat64(func::SqrtFloat64) => Float64, 1344;
3026            params!(Numeric) => UnaryFunc::SqrtNumeric(func::SqrtNumeric) => Numeric, 1730;
3027        },
3028        "tan" => Scalar {
3029            params!(Float64) => UnaryFunc::Tan(func::Tan) => Float64, 1606;
3030        },
3031        "atan" => Scalar {
3032            params!(Float64) => UnaryFunc::Atan(func::Atan) => Float64, 1602;
3033        },
3034        "tanh" => Scalar {
3035            params!(Float64) => UnaryFunc::Tanh(func::Tanh) => Float64, 2464;
3036        },
3037        "atanh" => Scalar {
3038            params!(Float64) => UnaryFunc::Atanh(func::Atanh) => Float64, 2467;
3039        },
3040        "age" => Scalar {
3041            params!(Timestamp, Timestamp) => BinaryFunc::from(func::AgeTimestamp) => Interval, 2058;
3042            params!(TimestampTz, TimestampTz)
3043                => BinaryFunc::from(func::AgeTimestampTz) => Interval, 1199;
3044        },
3045        "timezone" => Scalar {
3046            params!(String, Timestamp)
3047                => BinaryFunc::TimezoneTimestampBinary(
3048                    func::TimezoneTimestampBinary,
3049                ) => TimestampTz, 2069;
3050            params!(String, TimestampTz)
3051                => BinaryFunc::TimezoneTimestampTzBinary(
3052                    func::TimezoneTimestampTzBinary,
3053                ) => Timestamp, 1159;
3054            // PG defines this as `text timetz`
3055            params!(String, Time) => Operation::binary(|ecx, lhs, rhs| {
3056                // NOTE: this overload is wrong. It should take and return a
3057                // `timetz`, which is a type we don't support because it has
3058                // inscrutable semantics (timezones are meaningless without a
3059                // date). This implementation attempted to extend those already
3060                // inscrutable semantics to the `time` type, which makes matters
3061                // even worse.
3062                //
3063                // This feature flag ensures we don't get *new* uses of this
3064                // function. At some point in the future, we should either
3065                // remove this overload entirely, after validating there are no
3066                // catalogs in production that rely on this overload, or we
3067                // should properly support the `timetz` type and adjust this
3068                // overload accordingly.
3069                ecx.require_feature_flag(&ENABLE_TIME_AT_TIME_ZONE)?;
3070                Ok(HirScalarExpr::call_variadic(
3071                    variadic::TimezoneTimeVariadic,
3072                    vec![
3073                        lhs,
3074                        rhs,
3075                        HirScalarExpr::call_unmaterializable(
3076                            UnmaterializableFunc::CurrentTimestamp,
3077                        ),
3078                    ],
3079                ))
3080            }) => Time, 2037;
3081            params!(Interval, Timestamp)
3082                => BinaryFunc::from(func::TimezoneIntervalTimestampBinary)
3083                => TimestampTz, 2070;
3084            params!(Interval, TimestampTz)
3085                => BinaryFunc::from(func::TimezoneIntervalTimestampTzBinary)
3086                => Timestamp, 1026;
3087            // PG defines this as `interval timetz`
3088            params!(Interval, Time)
3089                => BinaryFunc::from(func::TimezoneIntervalTimeBinary) => Time, 2038;
3090        },
3091        "to_char" => Scalar {
3092            params!(Timestamp, String)
3093                => BinaryFunc::from(func::ToCharTimestampFormat) => String, 2049;
3094            params!(TimestampTz, String)
3095                => BinaryFunc::from(func::ToCharTimestampTzFormat) => String, 1770;
3096        },
3097        // > Returns the value as json or jsonb. Arrays and composites
3098        // > are converted (recursively) to arrays and objects;
3099        // > otherwise, if there is a cast from the type to json, the
3100        // > cast function will be used to perform the conversion;
3101        // > otherwise, a scalar value is produced. For any scalar type
3102        // > other than a number, a Boolean, or a null value, the text
3103        // > representation will be used, in such a fashion that it is a
3104        // > valid json or jsonb value.
3105        //
3106        // https://www.postgresql.org/docs/current/functions-json.html
3107        "to_jsonb" => Scalar {
3108            params!(Any) => Operation::unary(|ecx, e| {
3109                // TODO(see <materialize#7572>): remove this
3110                let e = match ecx.scalar_type(&e) {
3111                    SqlScalarType::Char { length } => {
3112                        e.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
3113                    }
3114                    _ => e,
3115                };
3116                typeconv::to_jsonb(ecx, e)
3117            }) => Jsonb, 3787;
3118        },
3119        "to_timestamp" => Scalar {
3120            params!(Float64) => UnaryFunc::ToTimestamp(func::ToTimestamp) => TimestampTz, 1158;
3121        },
3122        "translate" => Scalar {
3123            params!(String, String, String) => VariadicFunc::from(variadic::Translate)
3124                => String, 878;
3125        },
3126        "trunc" => Scalar {
3127            params!(Float32) => UnaryFunc::TruncFloat32(func::TruncFloat32)
3128                => Float32, oid::FUNC_TRUNC_F32_OID;
3129            params!(Float64) => UnaryFunc::TruncFloat64(func::TruncFloat64) => Float64, 1343;
3130            params!(Numeric) => UnaryFunc::TruncNumeric(func::TruncNumeric) => Numeric, 1710;
3131        },
3132        "tsrange" => Scalar {
3133            params!(Timestamp, Timestamp) => Operation::variadic(|_ecx, mut exprs| {
3134                exprs.push(HirScalarExpr::literal(
3135                    Datum::String("[)"), SqlScalarType::String,
3136                ));
3137                Ok(HirScalarExpr::call_variadic(
3138                    variadic::RangeCreate {
3139                        elem_type: SqlScalarType::Timestamp { precision: None },
3140                    },
3141                    exprs,
3142                ))
3143            }) => SqlScalarType::Range {
3144                element_type: Box::new(SqlScalarType::Timestamp { precision: None }),
3145            }, 3933;
3146            params!(Timestamp, Timestamp, String) => Operation::variadic(|_ecx, exprs| {
3147                Ok(HirScalarExpr::call_variadic(
3148                    variadic::RangeCreate {
3149                        elem_type: SqlScalarType::Timestamp { precision: None },
3150                    },
3151                    exprs,
3152                ))
3153            }) => SqlScalarType::Range {
3154                element_type: Box::new(SqlScalarType::Timestamp { precision: None }),
3155            }, 3934;
3156        },
3157        "tstzrange" => Scalar {
3158            params!(TimestampTz, TimestampTz) => Operation::variadic(|_ecx, mut exprs| {
3159                exprs.push(HirScalarExpr::literal(
3160                    Datum::String("[)"), SqlScalarType::String,
3161                ));
3162                Ok(HirScalarExpr::call_variadic(
3163                    variadic::RangeCreate {
3164                        elem_type: SqlScalarType::TimestampTz { precision: None },
3165                    },
3166                    exprs,
3167                ))
3168            }) => SqlScalarType::Range {
3169                element_type: Box::new(SqlScalarType::TimestampTz { precision: None }),
3170            }, 3937;
3171            params!(TimestampTz, TimestampTz, String) => Operation::variadic(|_ecx, exprs| {
3172                Ok(HirScalarExpr::call_variadic(
3173                    variadic::RangeCreate {
3174                        elem_type: SqlScalarType::TimestampTz { precision: None },
3175                    },
3176                    exprs,
3177                ))
3178            }) => SqlScalarType::Range {
3179                element_type: Box::new(SqlScalarType::TimestampTz { precision: None }),
3180            }, 3938;
3181        },
3182        "upper" => Scalar {
3183            params!(String) => UnaryFunc::Upper(func::Upper) => String, 871;
3184            params!(RangeAny) => UnaryFunc::RangeUpper(func::RangeUpper) => AnyElement, 3849;
3185        },
3186        "upper_inc" => Scalar {
3187            params!(RangeAny) => UnaryFunc::RangeUpperInc(func::RangeUpperInc) => Bool, 3852;
3188        },
3189        "upper_inf" => Scalar {
3190            params!(RangeAny) => UnaryFunc::RangeUpperInf(func::RangeUpperInf) => Bool, 3854;
3191        },
3192        "uuid_generate_v5" => Scalar {
3193            params!(Uuid, String) => BinaryFunc::from(func::UuidGenerateV5)
3194                => Uuid, oid::FUNC_PG_UUID_GENERATE_V5;
3195        },
3196        "variance" => Scalar {
3197            params!(Float32) =>
3198                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3199                => Float64, 2151;
3200            params!(Float64) =>
3201                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3202                => Float64, 2152;
3203            params!(Int16) =>
3204                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3205                => Numeric, 2150;
3206            params!(Int32) =>
3207                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3208                => Numeric, 2149;
3209            params!(Int64) =>
3210                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3211                => Numeric, 2148;
3212            params!(UInt16) =>
3213                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3214                => Numeric, oid::FUNC_VARIANCE_UINT16_OID;
3215            params!(UInt32) =>
3216                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3217                => Numeric, oid::FUNC_VARIANCE_UINT32_OID;
3218            params!(UInt64) =>
3219                Operation::nullary(|_ecx| catalog_name_only!("variance"))
3220                => Numeric, oid::FUNC_VARIANCE_UINT64_OID;
3221        },
3222        "var_pop" => Scalar {
3223            params!(Float32) =>
3224                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3225                => Float64, 2721;
3226            params!(Float64) =>
3227                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3228                => Float64, 2722;
3229            params!(Int16) =>
3230                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3231                => Numeric, 2720;
3232            params!(Int32) =>
3233                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3234                => Numeric, 2719;
3235            params!(Int64) =>
3236                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3237                => Numeric, 2718;
3238            params!(UInt16) =>
3239                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3240                => Numeric, oid::FUNC_VAR_POP_UINT16_OID;
3241            params!(UInt32) =>
3242                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3243                => Numeric, oid::FUNC_VAR_POP_UINT32_OID;
3244            params!(UInt64) =>
3245                Operation::nullary(|_ecx| catalog_name_only!("var_pop"))
3246                => Numeric, oid::FUNC_VAR_POP_UINT64_OID;
3247        },
3248        "var_samp" => Scalar {
3249            params!(Float32) =>
3250                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3251                => Float64, 2644;
3252            params!(Float64) =>
3253                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3254                => Float64, 2645;
3255            params!(Int16) =>
3256                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3257                => Numeric, 2643;
3258            params!(Int32) =>
3259                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3260                => Numeric, 2642;
3261            params!(Int64) =>
3262                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3263                => Numeric, 2641;
3264            params!(UInt16) =>
3265                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3266                => Numeric, oid::FUNC_VAR_SAMP_UINT16_OID;
3267            params!(UInt32) =>
3268                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3269                => Numeric, oid::FUNC_VAR_SAMP_UINT32_OID;
3270            params!(UInt64) =>
3271                Operation::nullary(|_ecx| catalog_name_only!("var_samp"))
3272                => Numeric, oid::FUNC_VAR_SAMP_UINT64_OID;
3273        },
3274        "version" => Scalar {
3275            params!() => UnmaterializableFunc::Version => String, 89;
3276        },
3277
3278        // Internal conversion stubs.
3279        "aclitemin" => Scalar {
3280            params!(String) => Operation::variadic(|_ecx, _exprs| {
3281                bail_unsupported!("aclitemin")
3282            }) => AclItem, 1031;
3283        },
3284        "any_in" => Scalar {
3285            params!(String) => Operation::variadic(|_ecx, _exprs| {
3286                bail_unsupported!("any_in")
3287            }) => Any, 2294;
3288        },
3289        "anyarray_in" => Scalar {
3290            params!(String) => Operation::variadic(|_ecx, _exprs| {
3291                bail_unsupported!("anyarray_in")
3292            }) => ArrayAny, 2296;
3293        },
3294        "anycompatible_in" => Scalar {
3295            params!(String) => Operation::variadic(|_ecx, _exprs| {
3296                bail_unsupported!("anycompatible_in")
3297            }) => AnyCompatible, 5086;
3298        },
3299        "anycompatiblearray_in" => Scalar {
3300            params!(String) => Operation::variadic(|_ecx, _exprs| {
3301                bail_unsupported!("anycompatiblearray_in")
3302            }) => ArrayAnyCompatible, 5088;
3303        },
3304        "anycompatiblenonarray_in" => Scalar {
3305            params!(String) => Operation::variadic(|_ecx, _exprs| {
3306                bail_unsupported!("anycompatiblenonarray_in")
3307            }) => NonVecAnyCompatible, 5092;
3308        },
3309        "anycompatiblerange_in" => Scalar {
3310            params!(String, Oid, Int32) =>
3311                Operation::variadic(|_ecx, _exprs| {
3312                    bail_unsupported!("anycompatiblerange_in")
3313                }) => RangeAnyCompatible, 5094;
3314        },
3315        "anyelement_in" => Scalar {
3316            params!(String) => Operation::variadic(|_ecx, _exprs| {
3317                bail_unsupported!("anyelement_in")
3318            }) => AnyElement, 2312;
3319        },
3320        "anynonarray_in" => Scalar {
3321            params!(String) => Operation::variadic(|_ecx, _exprs| {
3322                bail_unsupported!("anynonarray_in")
3323            }) => NonVecAny, 2777;
3324        },
3325        "anyrange_in" => Scalar {
3326            params!(String, Oid, Int32) =>
3327                Operation::variadic(|_ecx, _exprs| {
3328                    bail_unsupported!("anyrange_in")
3329                }) => RangeAny, 3832;
3330        },
3331        "array_in" => Scalar {
3332            params!(String, Oid, Int32) =>
3333                Operation::variadic(|_ecx, _exprs| {
3334                    bail_unsupported!("array_in")
3335                }) => ArrayAny, 750;
3336        },
3337        "boolin" => Scalar {
3338            params!(String) => Operation::variadic(|_ecx, _exprs| {
3339                bail_unsupported!("boolin")
3340            }) => Bool, 1242;
3341        },
3342        "bpcharin" => Scalar {
3343            params!(String, Oid, Int32) =>
3344                Operation::variadic(|_ecx, _exprs| {
3345                    bail_unsupported!("bpcharin")
3346                }) => Char, 1044;
3347        },
3348        "byteain" => Scalar {
3349            params!(String) => Operation::variadic(|_ecx, _exprs| {
3350                bail_unsupported!("byteain")
3351            }) => Bytes, 1244;
3352        },
3353        "charin" => Scalar {
3354            params!(String) => Operation::variadic(|_ecx, _exprs| {
3355                bail_unsupported!("charin")
3356            }) => PgLegacyChar, 1245;
3357        },
3358        "date_in" => Scalar {
3359            params!(String) => Operation::variadic(|_ecx, _exprs| {
3360                bail_unsupported!("date_in")
3361            }) => Date, 1084;
3362        },
3363        "float4in" => Scalar {
3364            params!(String) => Operation::variadic(|_ecx, _exprs| {
3365                bail_unsupported!("float4in")
3366            }) => Float32, 200;
3367        },
3368        "float8in" => Scalar {
3369            params!(String) => Operation::variadic(|_ecx, _exprs| {
3370                bail_unsupported!("float8in")
3371            }) => Float64, 214;
3372        },
3373        "int2in" => Scalar {
3374            params!(String) => Operation::variadic(|_ecx, _exprs| {
3375                bail_unsupported!("int2in")
3376            }) => Int16, 38;
3377        },
3378        "int2vectorin" => Scalar {
3379            params!(String) => Operation::variadic(|_ecx, _exprs| {
3380                bail_unsupported!("int2vectorin")
3381            }) => Int2Vector, 40;
3382        },
3383        "int4in" => Scalar {
3384            params!(String) => Operation::variadic(|_ecx, _exprs| {
3385                bail_unsupported!("int4in")
3386            }) => Int32, 42;
3387        },
3388        "int8in" => Scalar {
3389            params!(String) => Operation::variadic(|_ecx, _exprs| {
3390                bail_unsupported!("int8in")
3391            }) => Int64, 460;
3392        },
3393        "internal_in" => Scalar {
3394            params!(String) => Operation::variadic(|_ecx, _exprs| {
3395                bail_unsupported!("internal_in")
3396            }) => Internal, 2304;
3397        },
3398        "interval_in" => Scalar {
3399            params!(String, Oid, Int32) =>
3400                Operation::variadic(|_ecx, _exprs| {
3401                    bail_unsupported!("interval_in")
3402                }) => Interval, 1160;
3403        },
3404        "jsonb_in" => Scalar {
3405            params!(String) => Operation::variadic(|_ecx, _exprs| {
3406                bail_unsupported!("jsonb_in")
3407            }) => Jsonb, 3806;
3408        },
3409        "namein" => Scalar {
3410            params!(String) => Operation::variadic(|_ecx, _exprs| {
3411                bail_unsupported!("namein")
3412            }) => PgLegacyName, 34;
3413        },
3414        "numeric_in" => Scalar {
3415            params!(String, Oid, Int32) =>
3416                Operation::variadic(|_ecx, _exprs| {
3417                    bail_unsupported!("numeric_in")
3418                }) => Numeric, 1701;
3419        },
3420        "oidin" => Scalar {
3421            params!(String) => Operation::variadic(|_ecx, _exprs| {
3422                bail_unsupported!("oidin")
3423            }) => Oid, 1798;
3424        },
3425        "range_in" => Scalar {
3426            params!(String, Oid, Int32) =>
3427                Operation::variadic(|_ecx, _exprs| {
3428                    bail_unsupported!("range_in")
3429                }) => RangeAny, 3834;
3430        },
3431        "record_in" => Scalar {
3432            params!(String, Oid, Int32) =>
3433                Operation::variadic(|_ecx, _exprs| {
3434                    bail_unsupported!("record_in")
3435                }) => RecordAny, 2290;
3436        },
3437        "regclassin" => Scalar {
3438            params!(String) => Operation::variadic(|_ecx, _exprs| {
3439                bail_unsupported!("regclassin")
3440            }) => RegClass, 2218;
3441        },
3442        "regprocin" => Scalar {
3443            params!(String) => Operation::variadic(|_ecx, _exprs| {
3444                bail_unsupported!("regprocin")
3445            }) => RegProc, 44;
3446        },
3447        "regtypein" => Scalar {
3448            params!(String) => Operation::variadic(|_ecx, _exprs| {
3449                bail_unsupported!("regtypein")
3450            }) => RegType, 2220;
3451        },
3452        "textin" => Scalar {
3453            params!(String) => Operation::variadic(|_ecx, _exprs| {
3454                bail_unsupported!("textin")
3455            }) => String, 46;
3456        },
3457        "time_in" => Scalar {
3458            params!(String, Oid, Int32) =>
3459                Operation::variadic(|_ecx, _exprs| {
3460                    bail_unsupported!("time_in")
3461                }) => Time, 1143;
3462        },
3463        "timestamp_in" => Scalar {
3464            params!(String, Oid, Int32) =>
3465                Operation::variadic(|_ecx, _exprs| {
3466                    bail_unsupported!("timestamp_in")
3467                }) => Timestamp, 1312;
3468        },
3469        "timestamptz_in" => Scalar {
3470            params!(String, Oid, Int32) =>
3471                Operation::variadic(|_ecx, _exprs| {
3472                    bail_unsupported!("timestamptz_in")
3473                }) => TimestampTz, 1150;
3474        },
3475        "varcharin" => Scalar {
3476            params!(String, Oid, Int32) =>
3477                Operation::variadic(|_ecx, _exprs| {
3478                    bail_unsupported!("varcharin")
3479                }) => VarChar, 1046;
3480        },
3481        "uuid_in" => Scalar {
3482            params!(String) => Operation::variadic(|_ecx, _exprs| {
3483                bail_unsupported!("uuid_in")
3484            }) => Uuid, 2952;
3485        },
3486        "boolrecv" => Scalar {
3487            params!(Internal) =>
3488                Operation::nullary(|_ecx| catalog_name_only!("boolrecv"))
3489                => Bool, 2436;
3490        },
3491        "textrecv" => Scalar {
3492            params!(Internal) =>
3493                Operation::nullary(|_ecx| catalog_name_only!("textrecv"))
3494                => String, 2414;
3495        },
3496        "anyarray_recv" => Scalar {
3497            params!(Internal) =>
3498                Operation::nullary(|_ecx| {
3499                    catalog_name_only!("anyarray_recv")
3500                }) => ArrayAny, 2502;
3501        },
3502        "bytearecv" => Scalar {
3503            params!(Internal) =>
3504                Operation::nullary(|_ecx| catalog_name_only!("bytearecv"))
3505                => Bytes, 2412;
3506        },
3507        "bpcharrecv" => Scalar {
3508            params!(Internal) =>
3509                Operation::nullary(|_ecx| {
3510                    catalog_name_only!("bpcharrecv")
3511                }) => Char, 2430;
3512        },
3513        "charrecv" => Scalar {
3514            params!(Internal) =>
3515                Operation::nullary(|_ecx| catalog_name_only!("charrecv"))
3516                => PgLegacyChar, 2434;
3517        },
3518        "date_recv" => Scalar {
3519            params!(Internal) =>
3520                Operation::nullary(|_ecx| catalog_name_only!("date_recv"))
3521                => Date, 2468;
3522        },
3523        "float4recv" => Scalar {
3524            params!(Internal) =>
3525                Operation::nullary(|_ecx| {
3526                    catalog_name_only!("float4recv")
3527                }) => Float32, 2424;
3528        },
3529        "float8recv" => Scalar {
3530            params!(Internal) =>
3531                Operation::nullary(|_ecx| {
3532                    catalog_name_only!("float8recv")
3533                }) => Float64, 2426;
3534        },
3535        "int4recv" => Scalar {
3536            params!(Internal) =>
3537                Operation::nullary(|_ecx| catalog_name_only!("int4recv"))
3538                => Int32, 2406;
3539        },
3540        "int8recv" => Scalar {
3541            params!(Internal) =>
3542                Operation::nullary(|_ecx| catalog_name_only!("int8recv"))
3543                => Int64, 2408;
3544        },
3545        "interval_recv" => Scalar {
3546            params!(Internal) =>
3547                Operation::nullary(|_ecx| {
3548                    catalog_name_only!("interval_recv")
3549                }) => Interval, 2478;
3550        },
3551        "jsonb_recv" => Scalar {
3552            params!(Internal) =>
3553                Operation::nullary(|_ecx| {
3554                    catalog_name_only!("jsonb_recv")
3555                }) => Jsonb, 3805;
3556        },
3557        "namerecv" => Scalar {
3558            params!(Internal) =>
3559                Operation::nullary(|_ecx| catalog_name_only!("namerecv"))
3560                => PgLegacyName, 2422;
3561        },
3562        "numeric_recv" => Scalar {
3563            params!(Internal) =>
3564                Operation::nullary(|_ecx| {
3565                    catalog_name_only!("numeric_recv")
3566                }) => Numeric, 2460;
3567        },
3568        "oidrecv" => Scalar {
3569            params!(Internal) =>
3570                Operation::nullary(|_ecx| catalog_name_only!("oidrecv"))
3571                => Oid, 2418;
3572        },
3573        "record_recv" => Scalar {
3574            params!(Internal) =>
3575                Operation::nullary(|_ecx| {
3576                    catalog_name_only!("recordrerecord_recvcv")
3577                }) => RecordAny, 2402;
3578        },
3579        "regclassrecv" => Scalar {
3580            params!(Internal) =>
3581                Operation::nullary(|_ecx| {
3582                    catalog_name_only!("regclassrecv")
3583                }) => RegClass, 2452;
3584        },
3585        "regprocrecv" => Scalar {
3586            params!(Internal) =>
3587                Operation::nullary(|_ecx| {
3588                    catalog_name_only!("regprocrecv")
3589                }) => RegProc, 2444;
3590        },
3591        "regtyperecv" => Scalar {
3592            params!(Internal) =>
3593                Operation::nullary(|_ecx| {
3594                    catalog_name_only!("regtyperecv")
3595                }) => RegType, 2454;
3596        },
3597        "int2recv" => Scalar {
3598            params!(Internal) =>
3599                Operation::nullary(|_ecx| catalog_name_only!("int2recv"))
3600                => Int16, 2404;
3601        },
3602        "time_recv" => Scalar {
3603            params!(Internal) =>
3604                Operation::nullary(|_ecx| catalog_name_only!("time_recv"))
3605                => Time, 2470;
3606        },
3607        "timestamp_recv" => Scalar {
3608            params!(Internal) =>
3609                Operation::nullary(|_ecx| {
3610                    catalog_name_only!("timestamp_recv")
3611                }) => Timestamp, 2474;
3612        },
3613        "timestamptz_recv" => Scalar {
3614            params!(Internal) =>
3615                Operation::nullary(|_ecx| {
3616                    catalog_name_only!("timestamptz_recv")
3617                }) => TimestampTz, 2476;
3618        },
3619        "uuid_recv" => Scalar {
3620            params!(Internal) =>
3621                Operation::nullary(|_ecx| catalog_name_only!("uuid_recv"))
3622                => Uuid, 2961;
3623        },
3624        "varcharrecv" => Scalar {
3625            params!(Internal) =>
3626                Operation::nullary(|_ecx| {
3627                    catalog_name_only!("varcharrecv")
3628                }) => VarChar, 2432;
3629        },
3630        "int2vectorrecv" => Scalar {
3631            params!(Internal) =>
3632                Operation::nullary(|_ecx| {
3633                    catalog_name_only!("int2vectorrecv")
3634                }) => Int2Vector, 2410;
3635        },
3636        "anycompatiblearray_recv" => Scalar {
3637            params!(Internal) =>
3638                Operation::nullary(|_ecx| {
3639                    catalog_name_only!("anycompatiblearray_recv")
3640                }) => ArrayAnyCompatible, 5090;
3641        },
3642        "array_recv" => Scalar {
3643            params!(Internal) =>
3644                Operation::nullary(|_ecx| {
3645                    catalog_name_only!("array_recv")
3646                }) => ArrayAny, 2400;
3647        },
3648        "range_recv" => Scalar {
3649            params!(Internal) =>
3650                Operation::nullary(|_ecx| {
3651                    catalog_name_only!("range_recv")
3652                }) => RangeAny, 3836;
3653        },
3654
3655
3656        // Aggregates.
3657        "array_agg" => Aggregate {
3658            params!(NonVecAny) => Operation::unary_ordered(|ecx, e, order_by| {
3659                let elem_type = ecx.scalar_type(&e);
3660
3661                let elem_type = match elem_type.array_of_self_elem_type() {
3662                    Ok(elem_type) => elem_type,
3663                    Err(elem_type) => bail_unsupported!(
3664                        format!("array_agg on {}", ecx.humanize_sql_scalar_type(&elem_type, false))
3665                    ),
3666                };
3667
3668                // ArrayConcat excepts all inputs to be arrays, so wrap all input datums into
3669                // arrays.
3670                let e_arr = HirScalarExpr::call_variadic(
3671                    variadic::ArrayCreate { elem_type },
3672                    vec![e],
3673                );
3674                Ok((e_arr, AggregateFunc::ArrayConcat { order_by }))
3675            }) => ArrayAny, 2335;
3676            params!(ArrayAny) => Operation::unary(|_ecx, _e| {
3677                bail_unsupported!("array_agg on arrays")
3678            }) => ArrayAny, 4053;
3679        },
3680        "bool_and" => Aggregate {
3681            params!(Bool) =>
3682                Operation::nullary(|_ecx| catalog_name_only!("bool_and"))
3683                => Bool, 2517;
3684        },
3685        "bool_or" => Aggregate {
3686            params!(Bool) => Operation::nullary(|_ecx| catalog_name_only!("bool_or")) => Bool, 2518;
3687        },
3688        "count" => Aggregate {
3689            params!() => Operation::nullary(|_ecx| {
3690                // COUNT(*) is equivalent to COUNT(true).
3691                // This is mirrored in `AggregateExpr::is_count_asterisk`, so if you modify this,
3692                // then attend to that code also (in both HIR and MIR).
3693                Ok((HirScalarExpr::literal_true(), AggregateFunc::Count))
3694            }) => Int64, 2803;
3695            params!(Any) => AggregateFunc::Count => Int64, 2147;
3696        },
3697        "max" => Aggregate {
3698            params!(Bool) => AggregateFunc::MaxBool => Bool, oid::FUNC_MAX_BOOL_OID;
3699            params!(Int16) => AggregateFunc::MaxInt16 => Int16, 2117;
3700            params!(Int32) => AggregateFunc::MaxInt32 => Int32, 2116;
3701            params!(Int64) => AggregateFunc::MaxInt64 => Int64, 2115;
3702            params!(UInt16) => AggregateFunc::MaxUInt16 => UInt16, oid::FUNC_MAX_UINT16_OID;
3703            params!(UInt32) => AggregateFunc::MaxUInt32 => UInt32, oid::FUNC_MAX_UINT32_OID;
3704            params!(UInt64) => AggregateFunc::MaxUInt64 => UInt64, oid::FUNC_MAX_UINT64_OID;
3705            params!(MzTimestamp) => AggregateFunc::MaxMzTimestamp
3706                => MzTimestamp, oid::FUNC_MAX_MZ_TIMESTAMP_OID;
3707            params!(Float32) => AggregateFunc::MaxFloat32 => Float32, 2119;
3708            params!(Float64) => AggregateFunc::MaxFloat64 => Float64, 2120;
3709            params!(String) => AggregateFunc::MaxString => String, 2129;
3710            // TODO(see <materialize#7572>): make this its own function
3711            params!(Char) => AggregateFunc::MaxString => Char, 2244;
3712            params!(Date) => AggregateFunc::MaxDate => Date, 2122;
3713            params!(Timestamp) => AggregateFunc::MaxTimestamp => Timestamp, 2126;
3714            params!(TimestampTz) => AggregateFunc::MaxTimestampTz => TimestampTz, 2127;
3715            params!(Numeric) => AggregateFunc::MaxNumeric => Numeric, oid::FUNC_MAX_NUMERIC_OID;
3716            params!(Interval) => AggregateFunc::MaxInterval => Interval, 2128;
3717            params!(Time) => AggregateFunc::MaxTime => Time, 2123;
3718        },
3719        "min" => Aggregate {
3720            params!(Bool) => AggregateFunc::MinBool => Bool, oid::FUNC_MIN_BOOL_OID;
3721            params!(Int16) => AggregateFunc::MinInt16 => Int16, 2133;
3722            params!(Int32) => AggregateFunc::MinInt32 => Int32, 2132;
3723            params!(Int64) => AggregateFunc::MinInt64 => Int64, 2131;
3724            params!(UInt16) => AggregateFunc::MinUInt16 => UInt16, oid::FUNC_MIN_UINT16_OID;
3725            params!(UInt32) => AggregateFunc::MinUInt32 => UInt32, oid::FUNC_MIN_UINT32_OID;
3726            params!(UInt64) => AggregateFunc::MinUInt64 => UInt64, oid::FUNC_MIN_UINT64_OID;
3727            params!(MzTimestamp) => AggregateFunc::MinMzTimestamp
3728                => MzTimestamp, oid::FUNC_MIN_MZ_TIMESTAMP_OID;
3729            params!(Float32) => AggregateFunc::MinFloat32 => Float32, 2135;
3730            params!(Float64) => AggregateFunc::MinFloat64 => Float64, 2136;
3731            params!(String) => AggregateFunc::MinString => String, 2145;
3732            // TODO(see <materialize#7572>): make this its own function
3733            params!(Char) => AggregateFunc::MinString => Char, 2245;
3734            params!(Date) => AggregateFunc::MinDate => Date, 2138;
3735            params!(Timestamp) => AggregateFunc::MinTimestamp => Timestamp, 2142;
3736            params!(TimestampTz) => AggregateFunc::MinTimestampTz => TimestampTz, 2143;
3737            params!(Numeric) => AggregateFunc::MinNumeric => Numeric, oid::FUNC_MIN_NUMERIC_OID;
3738            params!(Interval) => AggregateFunc::MinInterval => Interval, 2144;
3739            params!(Time) => AggregateFunc::MinTime => Time, 2139;
3740        },
3741        "jsonb_agg" => Aggregate {
3742            params!(Any) => Operation::unary_ordered(|ecx, e, order_by| {
3743                // TODO(see <materialize#7572>): remove this
3744                let e = match ecx.scalar_type(&e) {
3745                    SqlScalarType::Char { length } => {
3746                        e.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
3747                    }
3748                    _ => e,
3749                };
3750                // `AggregateFunc::JsonbAgg` filters out `Datum::Null` (it
3751                // needs to have *some* identity input), but the semantics
3752                // of the SQL function require that `Datum::Null` is treated
3753                // as `Datum::JsonbNull`. This call to `coalesce` converts
3754                // between the two semantics.
3755                let json_null = HirScalarExpr::literal(Datum::JsonNull, SqlScalarType::Jsonb);
3756                let e = HirScalarExpr::call_variadic(
3757                    variadic::Coalesce,
3758                    vec![typeconv::to_jsonb(ecx, e)?, json_null],
3759                );
3760                Ok((e, AggregateFunc::JsonbAgg { order_by }))
3761            }) => Jsonb, 3267;
3762        },
3763        "jsonb_object_agg" => Aggregate {
3764            params!(Any, Any) => Operation::binary_ordered(|ecx, key, val, order_by| {
3765                // TODO(see <materialize#7572>): remove this
3766                let key = match ecx.scalar_type(&key) {
3767                    SqlScalarType::Char { length } => {
3768                        key.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
3769                    }
3770                    _ => key,
3771                };
3772                let val = match ecx.scalar_type(&val) {
3773                    SqlScalarType::Char { length } => {
3774                        val.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
3775                    }
3776                    _ => val,
3777                };
3778
3779                let json_null = HirScalarExpr::literal(Datum::JsonNull, SqlScalarType::Jsonb);
3780                let key = typeconv::to_string(ecx, key)?;
3781                // `AggregateFunc::JsonbObjectAgg` uses the same underlying
3782                // implementation as `AggregateFunc::MapAgg`, so it's our
3783                // responsibility to apply the JSON-specific behavior of casting
3784                // SQL nulls to JSON nulls; otherwise the produced `Datum::Map`
3785                // can contain `Datum::Null` values that are not valid for the
3786                // `SqlScalarType::Jsonb` type.
3787                let val = HirScalarExpr::call_variadic(
3788                    variadic::Coalesce,
3789                    vec![typeconv::to_jsonb(ecx, val)?, json_null],
3790                );
3791                let e = HirScalarExpr::call_variadic(
3792                    variadic::RecordCreate {
3793                        field_names: vec![ColumnName::from("key"), ColumnName::from("val")],
3794                    },
3795                    vec![key, val],
3796                );
3797                Ok((e, AggregateFunc::JsonbObjectAgg { order_by }))
3798            }) => Jsonb, 3270;
3799        },
3800        "string_agg" => Aggregate {
3801            params!(String, String) => Operation::binary_ordered(|_ecx, value, sep, order_by| {
3802                let e = HirScalarExpr::call_variadic(
3803                    variadic::RecordCreate {
3804                        field_names: vec![ColumnName::from("value"), ColumnName::from("sep")],
3805                    },
3806                    vec![value, sep],
3807                );
3808                Ok((e, AggregateFunc::StringAgg { order_by }))
3809            }) => String, 3538;
3810            params!(Bytes, Bytes) =>
3811                Operation::binary(|_ecx, _l, _r| {
3812                    bail_unsupported!("string_agg on BYTEA")
3813                }) => Bytes, 3545;
3814        },
3815        "string_to_array" => Scalar {
3816            params!(String, String) => VariadicFunc::from(variadic::StringToArray)
3817                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 376;
3818            params!(String, String, String) => VariadicFunc::from(variadic::StringToArray)
3819                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 394;
3820        },
3821        "sum" => Aggregate {
3822            params!(Int16) => AggregateFunc::SumInt16 => Int64, 2109;
3823            params!(Int32) => AggregateFunc::SumInt32 => Int64, 2108;
3824            params!(Int64) => AggregateFunc::SumInt64 => Numeric, 2107;
3825            params!(UInt16) => AggregateFunc::SumUInt16 => UInt64, oid::FUNC_SUM_UINT16_OID;
3826            params!(UInt32) => AggregateFunc::SumUInt32 => UInt64, oid::FUNC_SUM_UINT32_OID;
3827            params!(UInt64) => AggregateFunc::SumUInt64 => Numeric, oid::FUNC_SUM_UINT64_OID;
3828            params!(Float32) => AggregateFunc::SumFloat32 => Float32, 2110;
3829            params!(Float64) => AggregateFunc::SumFloat64 => Float64, 2111;
3830            params!(Numeric) => AggregateFunc::SumNumeric => Numeric, 2114;
3831            params!(Interval) => Operation::unary(|_ecx, _e| {
3832                // Explicitly providing this unsupported overload
3833                // prevents `sum(NULL)` from choosing the `Float64`
3834                // implementation, so that we match PostgreSQL's behavior.
3835                // Plus we will one day want to support this overload.
3836                bail_unsupported!("sum(interval)");
3837            }) => Interval, 2113;
3838        },
3839
3840        // Scalar window functions.
3841        "row_number" => ScalarWindow {
3842            params!() => ScalarWindowFunc::RowNumber => Int64, 3100;
3843        },
3844        "rank" => ScalarWindow {
3845            params!() => ScalarWindowFunc::Rank => Int64, 3101;
3846        },
3847        "dense_rank" => ScalarWindow {
3848            params!() => ScalarWindowFunc::DenseRank => Int64, 3102;
3849        },
3850        "lag" => ValueWindow {
3851            // All args are encoded into a single record to be handled later
3852            params!(AnyElement) => Operation::unary(|ecx, e| {
3853                let typ = ecx.scalar_type(&e);
3854                let e = HirScalarExpr::call_variadic(
3855                    variadic::RecordCreate {
3856                        field_names: vec![
3857                            ColumnName::from("expr"),
3858                            ColumnName::from("offset"),
3859                            ColumnName::from("default"),
3860                        ],
3861                    },
3862                    vec![
3863                        e,
3864                        HirScalarExpr::literal(Datum::Int32(1), SqlScalarType::Int32),
3865                        HirScalarExpr::literal_null(typ),
3866                    ],
3867                );
3868                Ok((e, ValueWindowFunc::Lag))
3869            }) => AnyElement, 3106;
3870            params!(AnyElement, Int32) => Operation::binary(|ecx, e, offset| {
3871                let typ = ecx.scalar_type(&e);
3872                let e = HirScalarExpr::call_variadic(
3873                    variadic::RecordCreate {
3874                        field_names: vec![
3875                            ColumnName::from("expr"),
3876                            ColumnName::from("offset"),
3877                            ColumnName::from("default"),
3878                        ],
3879                    },
3880                    vec![e, offset, HirScalarExpr::literal_null(typ)],
3881                );
3882                Ok((e, ValueWindowFunc::Lag))
3883            }) => AnyElement, 3107;
3884            params!(AnyCompatible, Int32, AnyCompatible) => Operation::variadic(|_ecx, exprs| {
3885                let e = HirScalarExpr::call_variadic(
3886                    variadic::RecordCreate {
3887                        field_names: vec![
3888                            ColumnName::from("expr"),
3889                            ColumnName::from("offset"),
3890                            ColumnName::from("default"),
3891                        ],
3892                    },
3893                    exprs,
3894                );
3895                Ok((e, ValueWindowFunc::Lag))
3896            }) => AnyCompatible, 3108;
3897        },
3898        "lead" => ValueWindow {
3899            // All args are encoded into a single record to be handled later
3900            params!(AnyElement) => Operation::unary(|ecx, e| {
3901                let typ = ecx.scalar_type(&e);
3902                let e = HirScalarExpr::call_variadic(
3903                    variadic::RecordCreate {
3904                        field_names: vec![
3905                            ColumnName::from("expr"),
3906                            ColumnName::from("offset"),
3907                            ColumnName::from("default"),
3908                        ],
3909                    },
3910                    vec![
3911                        e,
3912                        HirScalarExpr::literal(Datum::Int32(1), SqlScalarType::Int32),
3913                        HirScalarExpr::literal_null(typ),
3914                    ],
3915                );
3916                Ok((e, ValueWindowFunc::Lead))
3917            }) => AnyElement, 3109;
3918            params!(AnyElement, Int32) => Operation::binary(|ecx, e, offset| {
3919                let typ = ecx.scalar_type(&e);
3920                let e = HirScalarExpr::call_variadic(
3921                    variadic::RecordCreate {
3922                        field_names: vec![
3923                            ColumnName::from("expr"),
3924                            ColumnName::from("offset"),
3925                            ColumnName::from("default"),
3926                        ],
3927                    },
3928                    vec![e, offset, HirScalarExpr::literal_null(typ)],
3929                );
3930                Ok((e, ValueWindowFunc::Lead))
3931            }) => AnyElement, 3110;
3932            params!(AnyCompatible, Int32, AnyCompatible) => Operation::variadic(|_ecx, exprs| {
3933                let e = HirScalarExpr::call_variadic(
3934                    variadic::RecordCreate {
3935                        field_names: vec![
3936                            ColumnName::from("expr"),
3937                            ColumnName::from("offset"),
3938                            ColumnName::from("default"),
3939                        ],
3940                    },
3941                    exprs,
3942                );
3943                Ok((e, ValueWindowFunc::Lead))
3944            }) => AnyCompatible, 3111;
3945        },
3946        "first_value" => ValueWindow {
3947            params!(AnyElement) => ValueWindowFunc::FirstValue => AnyElement, 3112;
3948        },
3949        "last_value" => ValueWindow {
3950            params!(AnyElement) => ValueWindowFunc::LastValue => AnyElement, 3113;
3951        },
3952
3953        // Table functions.
3954        "generate_series" => Table {
3955            params!(Int32, Int32, Int32) => Operation::variadic(move |_ecx, exprs| {
3956                Ok(TableFuncPlan {
3957                    imp: TableFuncImpl::CallTable {
3958                        func: TableFunc::GenerateSeriesInt32,
3959                        exprs,
3960                    },
3961                    column_names: vec!["generate_series".into()],
3962                })
3963            }) => ReturnType::set_of(Int32.into()), 1066;
3964            params!(Int32, Int32) => Operation::binary(move |_ecx, start, stop| {
3965                Ok(TableFuncPlan {
3966                    imp: TableFuncImpl::CallTable {
3967                        func: TableFunc::GenerateSeriesInt32,
3968                        exprs: vec![
3969                            start, stop,
3970                            HirScalarExpr::literal(Datum::Int32(1), SqlScalarType::Int32),
3971                        ],
3972                    },
3973                    column_names: vec!["generate_series".into()],
3974                })
3975            }) => ReturnType::set_of(Int32.into()), 1067;
3976            params!(Int64, Int64, Int64) => Operation::variadic(move |_ecx, exprs| {
3977                Ok(TableFuncPlan {
3978                    imp: TableFuncImpl::CallTable {
3979                        func: TableFunc::GenerateSeriesInt64,
3980                        exprs,
3981                    },
3982                    column_names: vec!["generate_series".into()],
3983                })
3984            }) => ReturnType::set_of(Int64.into()), 1068;
3985            params!(Int64, Int64) => Operation::binary(move |_ecx, start, stop| {
3986                Ok(TableFuncPlan {
3987                    imp: TableFuncImpl::CallTable {
3988                        func: TableFunc::GenerateSeriesInt64,
3989                        exprs: vec![
3990                            start, stop,
3991                            HirScalarExpr::literal(Datum::Int64(1), SqlScalarType::Int64),
3992                        ],
3993                    },
3994                    column_names: vec!["generate_series".into()],
3995                })
3996            }) => ReturnType::set_of(Int64.into()), 1069;
3997            params!(Timestamp, Timestamp, Interval) => Operation::variadic(move |_ecx, exprs| {
3998                Ok(TableFuncPlan {
3999                    imp: TableFuncImpl::CallTable {
4000                        func: TableFunc::GenerateSeriesTimestamp,
4001                        exprs,
4002                    },
4003                    column_names: vec!["generate_series".into()],
4004                })
4005            }) => ReturnType::set_of(Timestamp.into()), 938;
4006            params!(TimestampTz, TimestampTz, Interval) => Operation::variadic(move |_ecx, exprs| {
4007                Ok(TableFuncPlan {
4008                    imp: TableFuncImpl::CallTable {
4009                        func: TableFunc::GenerateSeriesTimestampTz,
4010                        exprs,
4011                    },
4012                    column_names: vec!["generate_series".into()],
4013                })
4014            }) => ReturnType::set_of(TimestampTz.into()), 939;
4015        },
4016
4017        "generate_subscripts" => Table {
4018            params!(ArrayAny, Int32) => Operation::variadic(move |_ecx, exprs| {
4019                Ok(TableFuncPlan {
4020                    imp: TableFuncImpl::CallTable {
4021                        func: TableFunc::GenerateSubscriptsArray,
4022                        exprs,
4023                    },
4024                    column_names: vec!["generate_subscripts".into()],
4025                })
4026            }) => ReturnType::set_of(Int32.into()), 1192;
4027        },
4028
4029        "jsonb_array_elements" => Table {
4030            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4031                Ok(TableFuncPlan {
4032                    imp: TableFuncImpl::CallTable {
4033                        func: TableFunc::JsonbArrayElements,
4034                        exprs: vec![jsonb],
4035                    },
4036                    column_names: vec!["value".into()],
4037                })
4038            }) => ReturnType::set_of(Jsonb.into()), 3219;
4039        },
4040        "jsonb_array_elements_text" => Table {
4041            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4042                Ok(TableFuncPlan {
4043                    imp: TableFuncImpl::CallTable {
4044                        func: TableFunc::JsonbArrayElementsStringify,
4045                        exprs: vec![jsonb],
4046                    },
4047                    column_names: vec!["value".into()],
4048                })
4049            }) => ReturnType::set_of(String.into()), 3465;
4050        },
4051        "jsonb_each" => Table {
4052            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4053                Ok(TableFuncPlan {
4054                    imp: TableFuncImpl::CallTable {
4055                        func: TableFunc::JsonbEach,
4056                        exprs: vec![jsonb],
4057                    },
4058                    column_names: vec!["key".into(), "value".into()],
4059                })
4060            }) => ReturnType::set_of(RecordAny), 3208;
4061        },
4062        "jsonb_each_text" => Table {
4063            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4064                Ok(TableFuncPlan {
4065                    imp: TableFuncImpl::CallTable {
4066                        func: TableFunc::JsonbEachStringify,
4067                        exprs: vec![jsonb],
4068                    },
4069                    column_names: vec!["key".into(), "value".into()],
4070                })
4071            }) => ReturnType::set_of(RecordAny), 3932;
4072        },
4073        "jsonb_object_keys" => Table {
4074            params!(Jsonb) => Operation::unary(move |_ecx, jsonb| {
4075                Ok(TableFuncPlan {
4076                    imp: TableFuncImpl::CallTable {
4077                        func: TableFunc::JsonbObjectKeys,
4078                        exprs: vec![jsonb],
4079                    },
4080                    column_names: vec!["jsonb_object_keys".into()],
4081                })
4082            }) => ReturnType::set_of(String.into()), 3931;
4083        },
4084        // Note that these implementations' input to `generate_series` is
4085        // contrived to match Flink's expected values. There are other,
4086        // equally valid windows we could generate.
4087        "date_bin_hopping" => Table {
4088            // (hop, width, timestamp)
4089            params!(Interval, Interval, Timestamp)
4090                => experimental_sql_impl_table_func(
4091                    &vars::ENABLE_DATE_BIN_HOPPING, "
4092                    SELECT *
4093                    FROM pg_catalog.generate_series(
4094                        pg_catalog.date_bin($1, $3 + $1, '1970-01-01') - $2, $3, $1
4095                    ) AS dbh(date_bin_hopping)
4096                ") => ReturnType::set_of(Timestamp.into()),
4097                oid::FUNC_MZ_DATE_BIN_HOPPING_UNIX_EPOCH_TS_OID;
4098            // (hop, width, timestamp)
4099            params!(Interval, Interval, TimestampTz)
4100                => experimental_sql_impl_table_func(
4101                    &vars::ENABLE_DATE_BIN_HOPPING, "
4102                    SELECT *
4103                    FROM pg_catalog.generate_series(
4104                        pg_catalog.date_bin($1, $3 + $1, '1970-01-01') - $2, $3, $1
4105                    ) AS dbh(date_bin_hopping)
4106                ") => ReturnType::set_of(TimestampTz.into()),
4107                oid::FUNC_MZ_DATE_BIN_HOPPING_UNIX_EPOCH_TSTZ_OID;
4108            // (hop, width, timestamp, origin)
4109            params!(Interval, Interval, Timestamp, Timestamp)
4110                => experimental_sql_impl_table_func(
4111                    &vars::ENABLE_DATE_BIN_HOPPING, "
4112                    SELECT *
4113                    FROM pg_catalog.generate_series(
4114                        pg_catalog.date_bin($1, $3 + $1, $4) - $2, $3, $1
4115                    ) AS dbh(date_bin_hopping)
4116                ") => ReturnType::set_of(Timestamp.into()),
4117                oid::FUNC_MZ_DATE_BIN_HOPPING_TS_OID;
4118            // (hop, width, timestamp, origin)
4119            params!(Interval, Interval, TimestampTz, TimestampTz)
4120                => experimental_sql_impl_table_func(
4121                    &vars::ENABLE_DATE_BIN_HOPPING, "
4122                    SELECT *
4123                    FROM pg_catalog.generate_series(
4124                        pg_catalog.date_bin($1, $3 + $1, $4) - $2, $3, $1
4125                    ) AS dbh(date_bin_hopping)
4126                ") => ReturnType::set_of(TimestampTz.into()),
4127                oid::FUNC_MZ_DATE_BIN_HOPPING_TSTZ_OID;
4128        },
4129        "encode" => Scalar {
4130            params!(Bytes, String) => BinaryFunc::from(func::Encode) => String, 1946;
4131        },
4132        "decode" => Scalar {
4133            params!(String, String) => BinaryFunc::from(func::Decode) => Bytes, 1947;
4134        },
4135        "regexp_split_to_array" => Scalar {
4136            params!(String, String) => VariadicFunc::from(variadic::RegexpSplitToArray)
4137                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 2767;
4138            params!(String, String, String) => VariadicFunc::from(variadic::RegexpSplitToArray)
4139                => SqlScalarType::Array(Box::new(SqlScalarType::String)), 2768;
4140        },
4141        "regexp_split_to_table" => Table {
4142            params!(String, String) => sql_impl_table_func("
4143                SELECT unnest(regexp_split_to_array($1, $2))
4144            ") => ReturnType::set_of(String.into()), 2765;
4145            params!(String, String, String) => sql_impl_table_func("
4146                SELECT unnest(regexp_split_to_array($1, $2, $3))
4147            ") => ReturnType::set_of(String.into()), 2766;
4148        },
4149        "regexp_replace" => Scalar {
4150            params!(String, String, String)
4151                => VariadicFunc::from(variadic::RegexpReplace) => String, 2284;
4152            params!(String, String, String, String)
4153                => VariadicFunc::from(variadic::RegexpReplace) => String, 2285;
4154            // TODO: PostgreSQL supports additional five and six argument
4155            // forms of this function which allow controlling where to
4156            // start the replacement and how many replacements to make.
4157        },
4158        "regexp_matches" => Table {
4159            params!(String, String) => Operation::variadic(move |_ecx, exprs| {
4160                let column_names = vec!["regexp_matches".into()];
4161                Ok(TableFuncPlan {
4162                    imp: TableFuncImpl::CallTable {
4163                        func: TableFunc::RegexpMatches,
4164                        exprs: vec![exprs[0].clone(), exprs[1].clone()],
4165                    },
4166                    column_names,
4167                })
4168            }) => ReturnType::set_of(
4169                SqlScalarType::Array(Box::new(SqlScalarType::String)).into(),
4170            ), 2763;
4171            params!(String, String, String) => Operation::variadic(move |_ecx, exprs| {
4172                let column_names = vec!["regexp_matches".into()];
4173                Ok(TableFuncPlan {
4174                    imp: TableFuncImpl::CallTable {
4175                        func: TableFunc::RegexpMatches,
4176                        exprs: vec![exprs[0].clone(), exprs[1].clone(), exprs[2].clone()],
4177                    },
4178                    column_names,
4179                })
4180            }) => ReturnType::set_of(
4181                SqlScalarType::Array(Box::new(SqlScalarType::String)).into(),
4182            ), 2764;
4183        },
4184        "reverse" => Scalar {
4185            params!(String) => UnaryFunc::Reverse(func::Reverse) => String, 3062;
4186        }
4187    };
4188
4189    // Add side-effecting functions, which are defined in a separate module
4190    // using a restricted set of function definition features (e.g., no
4191    // overloads) to make them easier to plan.
4192    for sef_builtin in PG_CATALOG_SEF_BUILTINS.values() {
4193        builtins.insert(
4194            sef_builtin.name,
4195            Func::Scalar(vec![FuncImpl {
4196                oid: sef_builtin.oid,
4197                params: ParamList::Exact(
4198                    sef_builtin
4199                        .param_types
4200                        .iter()
4201                        .map(|t| ParamType::from(t.clone()))
4202                        .collect(),
4203                ),
4204                return_type: ReturnType::scalar(ParamType::from(
4205                    sef_builtin.return_type.scalar_type.clone(),
4206                )),
4207                op: Operation::variadic(|_ecx, _e| {
4208                    bail_unsupported!(format!("{} in this position", sef_builtin.name))
4209                }),
4210            }]),
4211        );
4212    }
4213
4214    builtins
4215});
4216
4217pub static INFORMATION_SCHEMA_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> =
4218    LazyLock::new(|| {
4219        use ParamType::*;
4220        builtins! {
4221            "_pg_expandarray" => Table {
4222                // See: https://github.com/postgres/postgres/blob/
4223                // 16e3ad5d143795b05a21dc887c2ab384cce4bcb8/
4224                // src/backend/catalog/information_schema.sql#L43
4225                params!(ArrayAny) => sql_impl_table_func("
4226                    SELECT
4227                        $1[s] AS x,
4228                        s - pg_catalog.array_lower($1, 1) + 1 AS n
4229                    FROM pg_catalog.generate_series(
4230                        pg_catalog.array_lower($1, 1),
4231                        pg_catalog.array_upper($1, 1),
4232                        1) as g(s)
4233                ") => ReturnType::set_of(RecordAny), oid::FUNC_PG_EXPAND_ARRAY;
4234            }
4235        }
4236    });
4237
4238pub static MZ_CATALOG_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
4239    use ParamType::*;
4240    use SqlScalarBaseType::*;
4241    builtins! {
4242        "constant_time_eq" => Scalar {
4243            params!(Bytes, Bytes) => BinaryFunc::from(func::ConstantTimeEqBytes)
4244                => Bool, oid::FUNC_CONSTANT_TIME_EQ_BYTES_OID;
4245            params!(String, String) => BinaryFunc::from(func::ConstantTimeEqString)
4246                => Bool, oid::FUNC_CONSTANT_TIME_EQ_STRING_OID;
4247        },
4248        // Note: this is the original version of the AVG(...) function, as it existed prior to
4249        // v0.66. We updated the internal type promotion used when summing values to increase
4250        // precision, but objects (e.g. materialized views) that already used the AVG(...) function
4251        // could not be changed. So we migrated all existing uses of the AVG(...) function to this
4252        // version.
4253        //
4254        // TODO(parkmycar): When objects no longer depend on this function we can safely delete it.
4255        "avg_internal_v1" => Scalar {
4256            params!(Int64) =>
4257                Operation::nullary(|_ecx| {
4258                    catalog_name_only!("avg_internal_v1")
4259                }) => Numeric,
4260                oid::FUNC_AVG_INTERNAL_V1_INT64_OID;
4261            params!(Int32) =>
4262                Operation::nullary(|_ecx| {
4263                    catalog_name_only!("avg_internal_v1")
4264                }) => Numeric,
4265                oid::FUNC_AVG_INTERNAL_V1_INT32_OID;
4266            params!(Int16) =>
4267                Operation::nullary(|_ecx| {
4268                    catalog_name_only!("avg_internal_v1")
4269                }) => Numeric,
4270                oid::FUNC_AVG_INTERNAL_V1_INT16_OID;
4271            params!(UInt64) =>
4272                Operation::nullary(|_ecx| {
4273                    catalog_name_only!("avg_internal_v1")
4274                }) => Numeric,
4275                oid::FUNC_AVG_INTERNAL_V1_UINT64_OID;
4276            params!(UInt32) =>
4277                Operation::nullary(|_ecx| {
4278                    catalog_name_only!("avg_internal_v1")
4279                }) => Numeric,
4280                oid::FUNC_AVG_INTERNAL_V1_UINT32_OID;
4281            params!(UInt16) =>
4282                Operation::nullary(|_ecx| {
4283                    catalog_name_only!("avg_internal_v1")
4284                }) => Numeric,
4285                oid::FUNC_AVG_INTERNAL_V1_UINT16_OID;
4286            params!(Float32) =>
4287                Operation::nullary(|_ecx| {
4288                    catalog_name_only!("avg_internal_v1")
4289                }) => Float64,
4290                oid::FUNC_AVG_INTERNAL_V1_FLOAT32_OID;
4291            params!(Float64) =>
4292                Operation::nullary(|_ecx| {
4293                    catalog_name_only!("avg_internal_v1")
4294                }) => Float64,
4295                oid::FUNC_AVG_INTERNAL_V1_FLOAT64_OID;
4296            params!(Interval) =>
4297                Operation::nullary(|_ecx| {
4298                    catalog_name_only!("avg_internal_v1")
4299                }) => Interval,
4300                oid::FUNC_AVG_INTERNAL_V1_INTERVAL_OID;
4301        },
4302        "csv_extract" => Table {
4303            params!(Int64, String) => Operation::binary(move |_ecx, ncols, input| {
4304                const MAX_EXTRACT_COLUMNS: i64 = 8192;
4305                const TOO_MANY_EXTRACT_COLUMNS: i64 = MAX_EXTRACT_COLUMNS + 1;
4306
4307                let ncols = match ncols.into_literal_int64() {
4308                    None | Some(i64::MIN..=0) => {
4309                        sql_bail!(
4310                            "csv_extract number of columns \
4311                             must be a positive integer literal"
4312                        );
4313                    },
4314                    Some(ncols @ 1..=MAX_EXTRACT_COLUMNS) => ncols,
4315                    Some(ncols @ TOO_MANY_EXTRACT_COLUMNS..) => {
4316                        return Err(PlanError::TooManyColumns {
4317                            max_num_columns: usize::try_from(MAX_EXTRACT_COLUMNS)
4318                                .unwrap_or(usize::MAX),
4319                            req_num_columns: usize::try_from(ncols)
4320                                .unwrap_or(usize::MAX),
4321                        });
4322                    },
4323                };
4324                let ncols = usize::try_from(ncols).expect("known to be greater than zero");
4325
4326                let column_names = (1..=ncols).map(|i| format!("column{}", i).into()).collect();
4327                Ok(TableFuncPlan {
4328                    imp: TableFuncImpl::CallTable {
4329                        func: TableFunc::CsvExtract(ncols),
4330                        exprs: vec![input],
4331                    },
4332                    column_names,
4333                })
4334            }) => ReturnType::set_of(RecordAny), oid::FUNC_CSV_EXTRACT_OID;
4335        },
4336        "concat_agg" => Aggregate {
4337            params!(Any) => Operation::unary(|_ecx, _e| {
4338                bail_unsupported!("concat_agg")
4339            }) => String, oid::FUNC_CONCAT_AGG_OID;
4340        },
4341        "crc32" => Scalar {
4342            params!(String) => UnaryFunc::Crc32String(func::Crc32String)
4343                => UInt32, oid::FUNC_CRC32_STRING_OID;
4344            params!(Bytes) => UnaryFunc::Crc32Bytes(func::Crc32Bytes)
4345                => UInt32, oid::FUNC_CRC32_BYTES_OID;
4346        },
4347        "datediff" => Scalar {
4348            params!(String, Timestamp, Timestamp)
4349                => VariadicFunc::from(variadic::DateDiffTimestamp)
4350                => Int64, oid::FUNC_DATEDIFF_TIMESTAMP;
4351            params!(String, TimestampTz, TimestampTz)
4352                => VariadicFunc::from(variadic::DateDiffTimestampTz)
4353                => Int64, oid::FUNC_DATEDIFF_TIMESTAMPTZ;
4354            params!(String, Date, Date) => VariadicFunc::from(variadic::DateDiffDate)
4355                => Int64, oid::FUNC_DATEDIFF_DATE;
4356            params!(String, Time, Time) => VariadicFunc::from(variadic::DateDiffTime)
4357                => Int64, oid::FUNC_DATEDIFF_TIME;
4358        },
4359        // We can't use the `privilege_fn!` macro because the macro relies on the object having an
4360        // OID, and clusters do not have OIDs.
4361        "has_cluster_privilege" => Scalar {
4362            params!(String, String, String) => sql_impl_func(
4363                "has_cluster_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4364            ) => Bool, oid::FUNC_HAS_CLUSTER_PRIVILEGE_TEXT_TEXT_TEXT_OID;
4365            params!(Oid, String, String) => sql_impl_func(&format!("
4366                CASE
4367                -- We must first check $2 to avoid a potentially
4368                -- null error message (an error itself).
4369                WHEN $2 IS NULL
4370                THEN NULL
4371                -- Validate the cluster name to return a proper error.
4372                WHEN NOT EXISTS (
4373                    SELECT name FROM mz_clusters WHERE name = $2)
4374                THEN mz_unsafe.mz_error_if_null(
4375                    NULL::boolean,
4376                    'error cluster \"' || $2 || '\" does not exist')
4377                -- Validate the privileges and other arguments.
4378                WHEN NOT mz_internal.mz_validate_privileges($3)
4379                OR $1 IS NULL
4380                OR $3 IS NULL
4381                OR $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
4382                THEN NULL
4383                ELSE COALESCE(
4384                    (
4385                        SELECT
4386                            bool_or(
4387                                mz_internal.mz_acl_item_contains_privilege(privilege, $3)
4388                            )
4389                                AS has_cluster_privilege
4390                        FROM
4391                            (
4392                                SELECT
4393                                    unnest(privileges)
4394                                FROM
4395                                    mz_clusters
4396                                WHERE
4397                                    mz_clusters.name = $2
4398                            )
4399                                AS user_privs (privilege)
4400                            LEFT JOIN mz_catalog.mz_roles ON
4401                                    mz_internal.mz_aclitem_grantee(privilege) = mz_roles.id
4402                        WHERE
4403                            mz_internal.mz_aclitem_grantee(privilege) = '{}'
4404                            OR pg_has_role($1, mz_roles.oid, 'USAGE')
4405                    ),
4406                    false
4407                )
4408                END
4409            ", RoleId::Public))
4410                => Bool, oid::FUNC_HAS_CLUSTER_PRIVILEGE_OID_TEXT_TEXT_OID;
4411            params!(String, String) => sql_impl_func(
4412                "has_cluster_privilege(current_user, $1, $2)",
4413            ) => Bool, oid::FUNC_HAS_CLUSTER_PRIVILEGE_TEXT_TEXT_OID;
4414        },
4415        "has_connection_privilege" => Scalar {
4416            params!(String, String, String) => sql_impl_func(
4417                "has_connection_privilege(\
4418                 mz_internal.mz_role_oid($1), \
4419                 mz_internal.mz_connection_oid($2), $3)",
4420            ) => Bool,
4421                oid::FUNC_HAS_CONNECTION_PRIVILEGE_TEXT_TEXT_TEXT_OID;
4422            params!(String, Oid, String) => sql_impl_func(
4423                "has_connection_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4424            ) => Bool,
4425                oid::FUNC_HAS_CONNECTION_PRIVILEGE_TEXT_OID_TEXT_OID;
4426            params!(Oid, String, String) => sql_impl_func(
4427                "has_connection_privilege($1, mz_internal.mz_connection_oid($2), $3)",
4428            ) => Bool,
4429                oid::FUNC_HAS_CONNECTION_PRIVILEGE_OID_TEXT_TEXT_OID;
4430            params!(Oid, Oid, String) => sql_impl_func(
4431                &privilege_fn!("has_connection_privilege", "mz_connections"),
4432            ) => Bool,
4433                oid::FUNC_HAS_CONNECTION_PRIVILEGE_OID_OID_TEXT_OID;
4434            params!(String, String) => sql_impl_func(
4435                "has_connection_privilege(current_user, $1, $2)",
4436            ) => Bool,
4437                oid::FUNC_HAS_CONNECTION_PRIVILEGE_TEXT_TEXT_OID;
4438            params!(Oid, String) => sql_impl_func(
4439                "has_connection_privilege(current_user, $1, $2)",
4440            ) => Bool,
4441                oid::FUNC_HAS_CONNECTION_PRIVILEGE_OID_TEXT_OID;
4442        },
4443        "has_role" => Scalar {
4444            params!(String, String, String)
4445                => sql_impl_func("pg_has_role($1, $2, $3)")
4446                => Bool, oid::FUNC_HAS_ROLE_TEXT_TEXT_TEXT_OID;
4447            params!(String, Oid, String)
4448                => sql_impl_func("pg_has_role($1, $2, $3)")
4449                => Bool, oid::FUNC_HAS_ROLE_TEXT_OID_TEXT_OID;
4450            params!(Oid, String, String)
4451                => sql_impl_func("pg_has_role($1, $2, $3)")
4452                => Bool, oid::FUNC_HAS_ROLE_OID_TEXT_TEXT_OID;
4453            params!(Oid, Oid, String)
4454                => sql_impl_func("pg_has_role($1, $2, $3)")
4455                => Bool, oid::FUNC_HAS_ROLE_OID_OID_TEXT_OID;
4456            params!(String, String)
4457                => sql_impl_func("pg_has_role($1, $2)")
4458                => Bool, oid::FUNC_HAS_ROLE_TEXT_TEXT_OID;
4459            params!(Oid, String)
4460                => sql_impl_func("pg_has_role($1, $2)")
4461                => Bool, oid::FUNC_HAS_ROLE_OID_TEXT_OID;
4462        },
4463        "has_secret_privilege" => Scalar {
4464            params!(String, String, String) => sql_impl_func(
4465                "has_secret_privilege(\
4466                 mz_internal.mz_role_oid($1), \
4467                 mz_internal.mz_secret_oid($2), $3)",
4468            ) => Bool,
4469                oid::FUNC_HAS_SECRET_PRIVILEGE_TEXT_TEXT_TEXT_OID;
4470            params!(String, Oid, String) => sql_impl_func(
4471                "has_secret_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4472            ) => Bool,
4473                oid::FUNC_HAS_SECRET_PRIVILEGE_TEXT_OID_TEXT_OID;
4474            params!(Oid, String, String) => sql_impl_func(
4475                "has_secret_privilege($1, mz_internal.mz_secret_oid($2), $3)",
4476            ) => Bool,
4477                oid::FUNC_HAS_SECRET_PRIVILEGE_OID_TEXT_TEXT_OID;
4478            params!(Oid, Oid, String) => sql_impl_func(
4479                &privilege_fn!("has_secret_privilege", "mz_secrets"),
4480            ) => Bool,
4481                oid::FUNC_HAS_SECRET_PRIVILEGE_OID_OID_TEXT_OID;
4482            params!(String, String) => sql_impl_func(
4483                "has_secret_privilege(current_user, $1, $2)",
4484            ) => Bool,
4485                oid::FUNC_HAS_SECRET_PRIVILEGE_TEXT_TEXT_OID;
4486            params!(Oid, String) => sql_impl_func(
4487                "has_secret_privilege(current_user, $1, $2)",
4488            ) => Bool,
4489                oid::FUNC_HAS_SECRET_PRIVILEGE_OID_TEXT_OID;
4490        },
4491        "has_system_privilege" => Scalar {
4492            params!(String, String) => sql_impl_func(
4493                "has_system_privilege(mz_internal.mz_role_oid($1), $2)",
4494            ) => Bool, oid::FUNC_HAS_SYSTEM_PRIVILEGE_TEXT_TEXT_OID;
4495            params!(Oid, String) => sql_impl_func(&format!("
4496                CASE
4497                -- We need to validate the privileges to return a proper error before
4498                -- anything else.
4499                WHEN NOT mz_internal.mz_validate_privileges($2)
4500                OR $1 IS NULL
4501                OR $2 IS NULL
4502                OR $1 NOT IN (SELECT oid FROM mz_catalog.mz_roles)
4503                THEN NULL
4504                ELSE COALESCE(
4505                    (
4506                        SELECT
4507                            bool_or(
4508                                mz_internal.mz_acl_item_contains_privilege(privileges, $2)
4509                            )
4510                                AS has_system_privilege
4511                        FROM mz_catalog.mz_system_privileges
4512                        LEFT JOIN mz_catalog.mz_roles ON
4513                                mz_internal.mz_aclitem_grantee(privileges) = mz_roles.id
4514                        WHERE
4515                            mz_internal.mz_aclitem_grantee(privileges) = '{}'
4516                            OR pg_has_role($1, mz_roles.oid, 'USAGE')
4517                    ),
4518                    false
4519                )
4520                END
4521            ", RoleId::Public))
4522                => Bool, oid::FUNC_HAS_SYSTEM_PRIVILEGE_OID_TEXT_OID;
4523            params!(String) => sql_impl_func(
4524                "has_system_privilege(current_user, $1)",
4525            ) => Bool, oid::FUNC_HAS_SYSTEM_PRIVILEGE_TEXT_OID;
4526        },
4527        "has_type_privilege" => Scalar {
4528            params!(String, String, String) => sql_impl_func(
4529                "has_type_privilege(mz_internal.mz_role_oid($1), $2::regtype::oid, $3)",
4530            ) => Bool, 3138;
4531            params!(String, Oid, String) => sql_impl_func(
4532                "has_type_privilege(mz_internal.mz_role_oid($1), $2, $3)",
4533            ) => Bool, 3139;
4534            params!(Oid, String, String) => sql_impl_func(
4535                "has_type_privilege($1, $2::regtype::oid, $3)",
4536            ) => Bool, 3140;
4537            params!(Oid, Oid, String) => sql_impl_func(
4538                &privilege_fn!("has_type_privilege", "mz_types"),
4539            ) => Bool, 3141;
4540            params!(String, String) => sql_impl_func(
4541                "has_type_privilege(current_user, $1, $2)",
4542            ) => Bool, 3142;
4543            params!(Oid, String) => sql_impl_func(
4544                "has_type_privilege(current_user, $1, $2)",
4545            ) => Bool, 3143;
4546        },
4547        "kafka_murmur2" => Scalar {
4548            params!(String) => UnaryFunc::KafkaMurmur2String(func::KafkaMurmur2String)
4549                => Int32, oid::FUNC_KAFKA_MURMUR2_STRING_OID;
4550            params!(Bytes) => UnaryFunc::KafkaMurmur2Bytes(func::KafkaMurmur2Bytes)
4551                => Int32, oid::FUNC_KAFKA_MURMUR2_BYTES_OID;
4552        },
4553        "list_agg" => Aggregate {
4554            params!(Any) => Operation::unary_ordered(|ecx, e, order_by| {
4555                if let SqlScalarType::Char {.. }  = ecx.scalar_type(&e) {
4556                    bail_unsupported!("list_agg on char");
4557                };
4558                // ListConcat excepts all inputs to be lists, so wrap all input datums into
4559                // lists.
4560                let e_arr = HirScalarExpr::call_variadic(
4561                    variadic::ListCreate { elem_type: ecx.scalar_type(&e) },
4562                    vec![e],
4563                );
4564                Ok((e_arr, AggregateFunc::ListConcat { order_by }))
4565            }) => ListAnyCompatible,  oid::FUNC_LIST_AGG_OID;
4566        },
4567        "list_append" => Scalar {
4568            vec![ListAnyCompatible, ListElementAnyCompatible]
4569                => BinaryFunc::from(func::ListElementConcat)
4570                => ListAnyCompatible, oid::FUNC_LIST_APPEND_OID;
4571        },
4572        "list_cat" => Scalar {
4573            vec![ListAnyCompatible, ListAnyCompatible]
4574                => BinaryFunc::from(func::ListListConcat)
4575                => ListAnyCompatible, oid::FUNC_LIST_CAT_OID;
4576        },
4577        "list_n_layers" => Scalar {
4578            vec![ListAny] => Operation::unary(|ecx, e| {
4579                ecx.require_feature_flag(&vars::ENABLE_LIST_N_LAYERS)?;
4580                let d = ecx.scalar_type(&e).unwrap_list_n_layers();
4581                match i32::try_from(d) {
4582                    Ok(d) => Ok(HirScalarExpr::literal(Datum::Int32(d), SqlScalarType::Int32)),
4583                    Err(_) => sql_bail!("list has more than {} layers", i32::MAX),
4584                }
4585
4586            }) => Int32, oid::FUNC_LIST_N_LAYERS_OID;
4587        },
4588        "list_length" => Scalar {
4589            vec![ListAny] => UnaryFunc::ListLength(func::ListLength)
4590                => Int32, oid::FUNC_LIST_LENGTH_OID;
4591        },
4592        "list_length_max" => Scalar {
4593            vec![ListAny, Plain(SqlScalarType::Int64)] => Operation::binary(|ecx, lhs, rhs| {
4594                ecx.require_feature_flag(&vars::ENABLE_LIST_LENGTH_MAX)?;
4595                let max_layer = ecx.scalar_type(&lhs).unwrap_list_n_layers();
4596                Ok(lhs.call_binary(rhs, BinaryFunc::from(func::ListLengthMax { max_layer })))
4597            }) => Int32, oid::FUNC_LIST_LENGTH_MAX_OID;
4598        },
4599        "list_prepend" => Scalar {
4600            vec![ListElementAnyCompatible, ListAnyCompatible]
4601                => BinaryFunc::from(func::ElementListConcat)
4602                => ListAnyCompatible, oid::FUNC_LIST_PREPEND_OID;
4603        },
4604        "list_remove" => Scalar {
4605            vec![ListAnyCompatible, ListElementAnyCompatible] => Operation::binary(|ecx, lhs, rhs| {
4606                ecx.require_feature_flag(&vars::ENABLE_LIST_REMOVE)?;
4607                Ok(lhs.call_binary(rhs, func::ListRemove))
4608            }) => ListAnyCompatible, oid::FUNC_LIST_REMOVE_OID;
4609        },
4610        "map_agg" => Aggregate {
4611            params!(String, Any) => Operation::binary_ordered(|ecx, key, val, order_by| {
4612                let (value_type, val) = match ecx.scalar_type(&val) {
4613                    // TODO(see <materialize#7572>): remove this
4614                    SqlScalarType::Char { length } => (
4615                        SqlScalarType::Char { length },
4616                        val.call_unary(UnaryFunc::PadChar(func::PadChar { length })),
4617                    ),
4618                    typ => (typ, val),
4619                };
4620
4621                let e = HirScalarExpr::call_variadic(
4622                    variadic::RecordCreate {
4623                        field_names: vec![ColumnName::from("key"), ColumnName::from("val")],
4624                    },
4625                    vec![key, val],
4626                );
4627
4628                Ok((e, AggregateFunc::MapAgg { order_by, value_type }))
4629            }) => MapAny, oid::FUNC_MAP_AGG;
4630        },
4631        "map_build" => Scalar {
4632            // TODO: support a function to construct maps that looks like...
4633            //
4634            // params!([String], Any...) => Operation::variadic(|ecx, exprs| {
4635            //
4636            // ...the challenge here is that we don't support constructing other
4637            // complex types from varidaic functions and instead use a SQL
4638            // keyword; however that doesn't work very well for map because the
4639            // intuitive syntax would be something akin to `MAP[key=>value]`,
4640            // but that doesn't work out of the box because `key=>value` looks
4641            // like an expression.
4642            params!(ListAny) => Operation::unary(|ecx, expr| {
4643                let ty = ecx.scalar_type(&expr);
4644
4645                // This is a fake error but should suffice given how exotic the
4646                // function is.
4647                let err = || {
4648                    Err(sql_err!(
4649                        "function map_build({}) does not exist",
4650                        ecx.humanize_sql_scalar_type(&ty.clone(), false)
4651                    ))
4652                };
4653
4654                // This function only accepts lists of records whose schema is
4655                // (text, T).
4656                let value_type = match &ty {
4657                    SqlScalarType::List { element_type, .. } => match &**element_type {
4658                        SqlScalarType::Record { fields, .. } if fields.len() == 2 => {
4659                            if fields[0].1.scalar_type != SqlScalarType::String {
4660                                return err();
4661                            }
4662
4663                            fields[1].1.scalar_type.clone()
4664                        }
4665                        _ => return err(),
4666                    },
4667                    _ => unreachable!("input guaranteed to be list"),
4668                };
4669
4670                Ok(expr.call_unary(UnaryFunc::MapBuildFromRecordList(
4671                    func::MapBuildFromRecordList { value_type },
4672                )))
4673            }) => MapAny, oid::FUNC_MAP_BUILD;
4674        },
4675        "map_length" => Scalar {
4676            params![MapAny] => UnaryFunc::MapLength(func::MapLength)
4677                => Int32, oid::FUNC_MAP_LENGTH_OID;
4678        },
4679        "mz_environment_id" => Scalar {
4680            params!() => UnmaterializableFunc::MzEnvironmentId
4681                => String, oid::FUNC_MZ_ENVIRONMENT_ID_OID;
4682        },
4683        "mz_is_superuser" => Scalar {
4684            params!() => UnmaterializableFunc::MzIsSuperuser
4685                => SqlScalarType::Bool, oid::FUNC_MZ_IS_SUPERUSER;
4686        },
4687        "mz_logical_timestamp" => Scalar {
4688            params!() => Operation::nullary(|_ecx| {
4689                sql_bail!("mz_logical_timestamp() has been renamed to mz_now()")
4690            }) => MzTimestamp, oid::FUNC_MZ_LOGICAL_TIMESTAMP_OID;
4691        },
4692        "mz_now" => Scalar {
4693            params!() => UnmaterializableFunc::MzNow => MzTimestamp, oid::FUNC_MZ_NOW_OID;
4694        },
4695        "mz_uptime" => Scalar {
4696            params!() => UnmaterializableFunc::MzUptime => Interval, oid::FUNC_MZ_UPTIME_OID;
4697        },
4698        "mz_version" => Scalar {
4699            params!() => UnmaterializableFunc::MzVersion => String, oid::FUNC_MZ_VERSION_OID;
4700        },
4701        "mz_version_num" => Scalar {
4702            params!() => UnmaterializableFunc::MzVersionNum => Int32, oid::FUNC_MZ_VERSION_NUM_OID;
4703        },
4704        "pretty_sql" => Scalar {
4705            params!(String, Int32) => BinaryFunc::from(func::PrettySql)
4706                => String, oid::FUNC_PRETTY_SQL;
4707            params!(String) => Operation::unary(|_ecx, s| {
4708                let w: i32 = mz_sql_pretty::DEFAULT_WIDTH.try_into().expect("must fit");
4709                let width = HirScalarExpr::literal(Datum::Int32(w), SqlScalarType::Int32);
4710                Ok(s.call_binary(width, func::PrettySql))
4711            }) => String, oid::FUNC_PRETTY_SQL_NOWIDTH;
4712        },
4713        "regexp_extract" => Table {
4714            params!(String, String) => Operation::binary(move |_ecx, regex, haystack| {
4715                let regex = match regex.into_literal_string() {
4716                    None => sql_bail!(
4717                        "regexp_extract requires a string \
4718                         literal as its first argument"
4719                    ),
4720                    Some(regex) => {
4721                        let opts = mz_expr::AnalyzedRegexOpts::default();
4722                        mz_expr::AnalyzedRegex::new(&regex, opts)
4723                            .map_err(|e| {
4724                                sql_err!("analyzing regex: {}", e)
4725                            })?
4726                    },
4727                };
4728                let column_names = regex
4729                    .capture_groups_iter()
4730                    .map(|cg| {
4731                        cg.name.clone().unwrap_or_else(|| format!("column{}", cg.index)).into()
4732                    })
4733                    .collect::<Vec<_>>();
4734                if column_names.is_empty(){
4735                    sql_bail!("regexp_extract must specify at least one capture group");
4736                }
4737                Ok(TableFuncPlan {
4738                    imp: TableFuncImpl::CallTable {
4739                        func: TableFunc::RegexpExtract(regex),
4740                        exprs: vec![haystack],
4741                    },
4742                    column_names,
4743                })
4744            }) => ReturnType::set_of(RecordAny), oid::FUNC_REGEXP_EXTRACT_OID;
4745        },
4746        mz_expr::REPEAT_ROW_NAME => Table {
4747            params!(Int64) => Operation::unary(move |ecx, n| {
4748                ecx.require_feature_flag(&vars::ENABLE_REPEAT_ROW)?;
4749                Ok(TableFuncPlan {
4750                    imp: TableFuncImpl::CallTable {
4751                        func: TableFunc::RepeatRow,
4752                        exprs: vec![n],
4753                    },
4754                    column_names: vec![]
4755                })
4756            }) => ReturnType::none(true), oid::FUNC_REPEAT_ROW_OID;
4757        },
4758        "repeat_row_non_negative" => Table {
4759            params!(Int64) => Operation::unary(move |ecx, n| {
4760                ecx.require_feature_flag(&vars::ENABLE_REPEAT_ROW_NON_NEGATIVE)?;
4761                Ok(TableFuncPlan {
4762                    imp: TableFuncImpl::CallTable {
4763                        func: TableFunc::RepeatRowNonNegative,
4764                        exprs: vec![n],
4765                    },
4766                    column_names: vec![]
4767                })
4768            }) => ReturnType::none(true), oid::FUNC_REPEAT_ROW_NON_NEGATIVE_OID;
4769        },
4770        "seahash" => Scalar {
4771            params!(String) => UnaryFunc::SeahashString(func::SeahashString)
4772                => UInt64, oid::FUNC_SEAHASH_STRING_OID;
4773            params!(Bytes) => UnaryFunc::SeahashBytes(func::SeahashBytes)
4774                => UInt64, oid::FUNC_SEAHASH_BYTES_OID;
4775        },
4776        "starts_with" => Scalar {
4777            params!(String, String) => BinaryFunc::from(func::StartsWith) => Bool, 3696;
4778        },
4779        "timezone_offset" => Scalar {
4780            params!(String, TimestampTz) => BinaryFunc::from(func::TimezoneOffset)
4781                => RecordAny, oid::FUNC_TIMEZONE_OFFSET;
4782        },
4783        "try_parse_monotonic_iso8601_timestamp" => Scalar {
4784            params!(String) => Operation::unary(move |_ecx, e| {
4785                Ok(e.call_unary(UnaryFunc::TryParseMonotonicIso8601Timestamp(
4786                    func::TryParseMonotonicIso8601Timestamp,
4787                )))
4788            }) => Timestamp, oid::FUNC_TRY_PARSE_MONOTONIC_ISO8601_TIMESTAMP;
4789        },
4790        "unnest" => Table {
4791            vec![ArrayAny] => Operation::unary(move |ecx, e| {
4792                let el_typ = ecx.scalar_type(&e).unwrap_array_element_type().clone();
4793                Ok(TableFuncPlan {
4794                    imp: TableFuncImpl::CallTable {
4795                        func: TableFunc::UnnestArray { el_typ },
4796                        exprs: vec![e],
4797                    },
4798                    column_names: vec!["unnest".into()],
4799                })
4800            }) =>
4801                // This return type should be equivalent to
4802                // "ArrayElementAny", but this would be its sole use.
4803                ReturnType::set_of(AnyElement), 2331;
4804            vec![ListAny] => Operation::unary(move |ecx, e| {
4805                let el_typ = ecx.scalar_type(&e).unwrap_list_element_type().clone();
4806                Ok(TableFuncPlan {
4807                    imp: TableFuncImpl::CallTable {
4808                        func: TableFunc::UnnestList { el_typ },
4809                        exprs: vec![e],
4810                    },
4811                    column_names: vec!["unnest".into()],
4812                })
4813            }) =>
4814                // This return type should be equivalent to
4815                // "ListElementAny", but this would be its sole use.
4816                ReturnType::set_of(Any), oid::FUNC_UNNEST_LIST_OID;
4817            vec![MapAny] => Operation::unary(move |ecx, e| {
4818                let value_type = ecx.scalar_type(&e).unwrap_map_value_type().clone();
4819                Ok(TableFuncPlan {
4820                    imp: TableFuncImpl::CallTable {
4821                        func: TableFunc::UnnestMap { value_type },
4822                        exprs: vec![e],
4823                    },
4824                    column_names: vec!["key".into(), "value".into()],
4825                })
4826            }) =>
4827                // This return type should be equivalent to
4828                // "ListElementAny", but this would be its sole use.
4829                ReturnType::set_of(Any), oid::FUNC_UNNEST_MAP_OID;
4830        }
4831    }
4832});
4833
4834pub static MZ_INTERNAL_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
4835    use ParamType::*;
4836    use SqlScalarBaseType::*;
4837    builtins! {
4838        "aclitem_grantor" => Scalar {
4839            params!(AclItem) => UnaryFunc::AclItemGrantor(func::AclItemGrantor)
4840                => Oid, oid::FUNC_ACL_ITEM_GRANTOR_OID;
4841        },
4842        "aclitem_grantee" => Scalar {
4843            params!(AclItem) => UnaryFunc::AclItemGrantee(func::AclItemGrantee)
4844                => Oid, oid::FUNC_ACL_ITEM_GRANTEE_OID;
4845        },
4846        "aclitem_privileges" => Scalar {
4847            params!(AclItem) => UnaryFunc::AclItemPrivileges(func::AclItemPrivileges)
4848                => String, oid::FUNC_ACL_ITEM_PRIVILEGES_OID;
4849        },
4850        "is_rbac_enabled" => Scalar {
4851            params!() => UnmaterializableFunc::IsRbacEnabled => Bool, oid::FUNC_IS_RBAC_ENABLED_OID;
4852        },
4853        "make_mz_aclitem" => Scalar {
4854            params!(String, String, String) => VariadicFunc::from(variadic::MakeMzAclItem)
4855                => MzAclItem, oid::FUNC_MAKE_MZ_ACL_ITEM_OID;
4856        },
4857        "mz_acl_item_contains_privilege" => Scalar {
4858            params!(MzAclItem, String)
4859                => BinaryFunc::from(func::MzAclItemContainsPrivilege)
4860                => Bool, oid::FUNC_MZ_ACL_ITEM_CONTAINS_PRIVILEGE_OID;
4861        },
4862        "mz_aclexplode" => Table {
4863            params!(SqlScalarType::Array(Box::new(SqlScalarType::MzAclItem)))
4864                => Operation::unary(move |_ecx, mz_aclitems| {
4865                Ok(TableFuncPlan {
4866                    imp: TableFuncImpl::CallTable {
4867                        func: TableFunc::MzAclExplode,
4868                        exprs: vec![mz_aclitems],
4869                    },
4870                    column_names: vec![
4871                        "grantor".into(), "grantee".into(),
4872                        "privilege_type".into(), "is_grantable".into(),
4873                    ],
4874                })
4875            }) => ReturnType::set_of(RecordAny), oid::FUNC_MZ_ACL_ITEM_EXPLODE_OID;
4876        },
4877        "mz_aclitem_grantor" => Scalar {
4878            params!(MzAclItem) => UnaryFunc::MzAclItemGrantor(func::MzAclItemGrantor)
4879                => String, oid::FUNC_MZ_ACL_ITEM_GRANTOR_OID;
4880        },
4881        "mz_aclitem_grantee" => Scalar {
4882            params!(MzAclItem) => UnaryFunc::MzAclItemGrantee(func::MzAclItemGrantee)
4883                => String, oid::FUNC_MZ_ACL_ITEM_GRANTEE_OID;
4884        },
4885        "mz_aclitem_privileges" => Scalar {
4886            params!(MzAclItem) => UnaryFunc::MzAclItemPrivileges(
4887                func::MzAclItemPrivileges,
4888            ) => String, oid::FUNC_MZ_ACL_ITEM_PRIVILEGES_OID;
4889        },
4890        // There is no regclass equivalent for roles to look up connections, so we
4891        // have this helper function instead.
4892        //
4893        // TODO: invent an OID alias for connections
4894        "mz_connection_oid" => Scalar {
4895            params!(String) => sql_impl_func("
4896                CASE
4897                WHEN $1 IS NULL THEN NULL
4898                ELSE (
4899                    mz_unsafe.mz_error_if_null(
4900                        (SELECT oid FROM mz_catalog.mz_objects
4901                         WHERE name = $1 AND type = 'connection'),
4902                        'connection \"' || $1 || '\" does not exist'
4903                    )
4904                )
4905                END
4906            ") => Oid, oid::FUNC_CONNECTION_OID_OID;
4907        },
4908        "mz_format_privileges" => Scalar {
4909            params!(String) => UnaryFunc::MzFormatPrivileges(func::MzFormatPrivileges)
4910                => SqlScalarType::Array(Box::new(SqlScalarType::String)),
4911                oid::FUNC_MZ_FORMAT_PRIVILEGES_OID;
4912        },
4913        "mz_name_rank" => Table {
4914            // Determines the id, rank of all objects that can be matched using
4915            // the provided args.
4916            params!(
4917                // Database
4918                String,
4919                // Schemas/search path
4920                ParamType::Plain(SqlScalarType::Array(Box::new(SqlScalarType::String))),
4921                // Item name
4922                String,
4923                // Get rank among particular OID alias (e.g. regclass)
4924                String
4925            ) =>
4926            // credit for using rank() to @def-
4927            sql_impl_table_func("
4928            -- The best ranked name is the one that belongs to the schema correlated with the lowest
4929            -- index in the search path
4930            SELECT id, name, count, min(schema_pref) OVER () = schema_pref AS best_ranked FROM (
4931                SELECT DISTINCT
4932                    o.id,
4933                    ARRAY[CASE WHEN s.database_id IS NULL THEN NULL ELSE d.name END, s.name, o.name]
4934                    AS name,
4935                    o.count,
4936                    pg_catalog.array_position($2, s.name) AS schema_pref
4937                FROM
4938                    (
4939                        SELECT
4940                            o.id,
4941                            o.schema_id,
4942                            o.name,
4943                            count(*)
4944                        FROM mz_catalog.mz_objects AS o
4945                        JOIN mz_internal.mz_object_oid_alias AS a
4946                            ON o.type = a.object_type
4947                        WHERE o.name = CAST($3 AS pg_catalog.text) AND a.oid_alias = $4
4948                        GROUP BY 1, 2, 3
4949                    )
4950                        AS o
4951                    JOIN mz_catalog.mz_schemas AS s ON o.schema_id = s.id
4952                    JOIN
4953                        unnest($2) AS search_schema (name)
4954                        ON search_schema.name = s.name
4955                    JOIN
4956                        (
4957                            SELECT id, name FROM mz_catalog.mz_databases
4958                            -- If the provided database does not exist, add a row for it so that it
4959                            -- can still join against ambient schemas.
4960                            UNION ALL
4961                            SELECT '', $1 WHERE $1 NOT IN (SELECT name FROM mz_catalog.mz_databases)
4962                        ) AS d
4963                        ON d.id = COALESCE(s.database_id, d.id)
4964                WHERE d.name = CAST($1 AS pg_catalog.text)
4965            );
4966            ") => ReturnType::set_of(RecordAny), oid::FUNC_MZ_NAME_RANK;
4967        },
4968        "mz_resolve_object_name" => Table {
4969            params!(String, String) =>
4970            // Normalize the input name, and for any NULL values (e.g. not database qualified), use
4971            // the defaults used during name resolution.
4972            sql_impl_table_func("
4973                SELECT
4974                    o.id, o.oid, o.schema_id, o.name, o.type, o.owner_id, o.privileges
4975                FROM
4976                    (SELECT mz_internal.mz_normalize_object_name($2))
4977                            AS normalized (n),
4978                    mz_internal.mz_name_rank(
4979                        COALESCE(n[1], pg_catalog.current_database()),
4980                        CASE
4981                            WHEN n[2] IS NULL
4982                                THEN pg_catalog.current_schemas(true)
4983                            ELSE
4984                                ARRAY[n[2]]
4985                        END,
4986                        n[3],
4987                        $1
4988                    ) AS r,
4989                    mz_catalog.mz_objects AS o
4990                WHERE r.id = o.id AND r.best_ranked;
4991            ") => ReturnType::set_of(RecordAny), oid::FUNC_MZ_RESOLVE_OBJECT_NAME;
4992        },
4993        // Returns the an array representing the minimal namespace a user must
4994        // provide to refer to an item whose name is the first argument.
4995        //
4996        // The first argument must be a fully qualified name (i.e. contain
4997        // database.schema.object), with each level of the namespace being an
4998        // element.
4999        //
5000        // The second argument represents the `GlobalId` of the resolved object.
5001        // This is a safeguard to ensure that the name we are resolving refers
5002        // to the expected entry. For example, this helps us disambiguate cases
5003        // where e.g. types and functions have the same name.
5004        "mz_minimal_name_qualification" => Scalar {
5005            params!(SqlScalarType::Array(Box::new(SqlScalarType::String)), String) => {
5006                sql_impl_func("(
5007                    SELECT
5008                    CASE
5009                        WHEN $1::pg_catalog.text[] IS NULL
5010                            THEN NULL
5011                    -- If DB doesn't match, requires full qual
5012                        WHEN $1[1] != pg_catalog.current_database()
5013                            THEN $1
5014                    -- If not in currently searchable schema, must be schema qualified
5015                        WHEN NOT $1[2] = ANY(pg_catalog.current_schemas(true))
5016                            THEN ARRAY[$1[2], $1[3]]
5017                    ELSE
5018                        minimal_name
5019                    END
5020                FROM (
5021                    -- Subquery so we return one null row in the cases where
5022                    -- there are no matches.
5023                    SELECT (
5024                        SELECT DISTINCT
5025                            CASE
5026                                -- If there is only one item with this name and it's rank 1,
5027                                -- it is uniquely nameable with just the final element
5028                                WHEN best_ranked AND count = 1
5029                                    THEN ARRAY[r.name[3]]
5030                                -- Otherwise, it is findable in the search path, so does not
5031                                -- need database qualification
5032                                ELSE
5033                                    ARRAY[r.name[2], r.name[3]]
5034                            END AS minimal_name
5035                        FROM mz_catalog.mz_objects AS o
5036                            JOIN mz_internal.mz_object_oid_alias AS a
5037                                ON o.type = a.object_type,
5038                            -- implied lateral to put the OID alias into scope
5039                            mz_internal.mz_name_rank(
5040                                pg_catalog.current_database(),
5041                                pg_catalog.current_schemas(true),
5042                                $1[3],
5043                                a.oid_alias
5044                            ) AS r
5045                        WHERE o.id = $2 AND r.id = $2
5046                    )
5047                )
5048            )")
5049            } => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5050                oid::FUNC_MZ_MINIMINAL_NAME_QUALIFICATION;
5051        },
5052        "mz_global_id_to_name" => Scalar {
5053            params!(String) => sql_impl_func("
5054            CASE
5055                WHEN $1 IS NULL THEN NULL
5056                ELSE (
5057                    SELECT array_to_string(minimal_name, '.')
5058                    FROM (
5059                        SELECT mz_unsafe.mz_error_if_null(
5060                            (
5061                                -- Return the fully-qualified name
5062                                SELECT DISTINCT ARRAY[qual.d, qual.s, item.name]
5063                                FROM
5064                                    mz_catalog.mz_objects AS item
5065                                JOIN
5066                                (
5067                                    SELECT
5068                                        d.name AS d,
5069                                        s.name AS s,
5070                                        s.id AS schema_id
5071                                    FROM
5072                                        mz_catalog.mz_schemas AS s
5073                                        LEFT JOIN
5074                                            (SELECT id, name FROM mz_catalog.mz_databases)
5075                                            AS d
5076                                            ON s.database_id = d.id
5077                                ) AS qual
5078                                ON qual.schema_id = item.schema_id
5079                                WHERE item.id = CAST($1 AS text)
5080                            ),
5081                            'global ID ' || $1 || ' does not exist'
5082                        )
5083                    ) AS n (fqn),
5084                    LATERAL (
5085                        -- Get the minimal qualification of the fully qualified name
5086                        SELECT mz_internal.mz_minimal_name_qualification(fqn, $1)
5087                    ) AS m (minimal_name)
5088                )
5089                END
5090            ") => String, oid::FUNC_MZ_GLOBAL_ID_TO_NAME;
5091        },
5092        "mz_normalize_object_name" => Scalar {
5093            params!(String) => sql_impl_func("
5094            (
5095                SELECT
5096                    CASE
5097                        WHEN $1 IS NULL OR ident IS NULL THEN NULL
5098                        WHEN pg_catalog.array_length(ident, 1) > 3
5099                            THEN mz_unsafe.mz_error_if_null(
5100                                NULL::pg_catalog.text[],
5101                                'improper relation name (too many dotted names): ' || $1
5102                            )
5103                        ELSE pg_catalog.array_cat(
5104                            pg_catalog.array_fill(
5105                                CAST(NULL AS pg_catalog.text),
5106                                ARRAY[3 - pg_catalog.array_length(ident, 1)]
5107                            ),
5108                            ident
5109                        )
5110                    END
5111                FROM (
5112                    SELECT pg_catalog.parse_ident($1) AS ident
5113                ) AS i
5114            )") => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5115                oid::FUNC_MZ_NORMALIZE_OBJECT_NAME;
5116        },
5117        "mz_normalize_schema_name" => Scalar {
5118            params!(String) => sql_impl_func("
5119             (
5120                SELECT
5121                    CASE
5122                        WHEN $1 IS NULL OR ident IS NULL THEN NULL
5123                        WHEN pg_catalog.array_length(ident, 1) > 2
5124                            THEN mz_unsafe.mz_error_if_null(
5125                                NULL::pg_catalog.text[],
5126                                'improper schema name (too many dotted names): ' || $1
5127                            )
5128                        ELSE pg_catalog.array_cat(
5129                            pg_catalog.array_fill(
5130                                CAST(NULL AS pg_catalog.text),
5131                                ARRAY[2 - pg_catalog.array_length(ident, 1)]
5132                            ),
5133                            ident
5134                        )
5135                    END
5136                FROM (
5137                    SELECT pg_catalog.parse_ident($1) AS ident
5138                ) AS i
5139            )") => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5140                oid::FUNC_MZ_NORMALIZE_SCHEMA_NAME;
5141        },
5142        "mz_render_typmod" => Scalar {
5143            params!(Oid, Int32) => BinaryFunc::from(func::MzRenderTypmod)
5144                => String, oid::FUNC_MZ_RENDER_TYPMOD_OID;
5145        },
5146        "mz_role_oid_memberships" => Scalar {
5147            params!() => UnmaterializableFunc::MzRoleOidMemberships
5148                => SqlScalarType::Map {
5149                    value_type: Box::new(SqlScalarType::Array(
5150                        Box::new(SqlScalarType::String),
5151                    )),
5152                    custom_id: None,
5153                }, oid::FUNC_MZ_ROLE_OID_MEMBERSHIPS;
5154        },
5155        "mz_session_role_memberships" => Scalar {
5156            params!() => UnmaterializableFunc::MzSessionRoleMemberships
5157                => SqlScalarType::Array(Box::new(SqlScalarType::String)),
5158                oid::FUNC_MZ_SESSION_ROLE_MEMBERSHIPS_OID;
5159        },
5160        // There is no regclass equivalent for databases to look up
5161        // oids, so we have this helper function instead.
5162        "mz_database_oid" => Scalar {
5163            params!(String) => sql_impl_func("
5164                CASE
5165                WHEN $1 IS NULL THEN NULL
5166                ELSE (
5167                    mz_unsafe.mz_error_if_null(
5168                        (SELECT oid FROM mz_databases WHERE name = $1),
5169                        'database \"' || $1 || '\" does not exist'
5170                    )
5171                )
5172                END
5173            ") => Oid, oid::FUNC_DATABASE_OID_OID;
5174        },
5175        // There is no regclass equivalent for schemas to look up
5176        // oids, so we have this helper function instead.
5177        "mz_schema_oid" => Scalar {
5178            params!(String) => sql_impl_func("
5179            CASE
5180                WHEN $1 IS NULL THEN NULL
5181            ELSE
5182                mz_unsafe.mz_error_if_null(
5183                    (
5184                        SELECT
5185                            (
5186                                SELECT s.oid
5187                                FROM mz_catalog.mz_schemas AS s
5188                                LEFT JOIN mz_databases AS d ON s.database_id = d.id
5189                                WHERE
5190                                    (
5191                                        -- Filter to only schemas in the named database or the
5192                                        -- current database if no database was specified.
5193                                        d.name = COALESCE(n[1], pg_catalog.current_database())
5194                                        -- Always include all ambient schemas.
5195                                        OR s.database_id IS NULL
5196                                    )
5197                                    AND s.name = n[2]
5198                            )
5199                        FROM mz_internal.mz_normalize_schema_name($1) AS n
5200                    ),
5201                    'schema \"' || $1 || '\" does not exist'
5202                )
5203            END
5204            ") => Oid, oid::FUNC_SCHEMA_OID_OID;
5205        },
5206        // There is no regclass equivalent for roles to look up
5207        // oids, so we have this helper function instead.
5208        "mz_role_oid" => Scalar {
5209            params!(String) => sql_impl_func("
5210                CASE
5211                WHEN $1 IS NULL THEN NULL
5212                ELSE (
5213                    mz_unsafe.mz_error_if_null(
5214                        (SELECT oid FROM mz_catalog.mz_roles WHERE name = $1),
5215                        'role \"' || $1 || '\" does not exist'
5216                    )
5217                )
5218                END
5219            ") => Oid, oid::FUNC_ROLE_OID_OID;
5220        },
5221        // There is no regclass equivalent for roles to look up secrets, so we
5222        // have this helper function instead.
5223        //
5224        // TODO: invent an OID alias for secrets
5225        "mz_secret_oid" => Scalar {
5226            params!(String) => sql_impl_func("
5227                CASE
5228                WHEN $1 IS NULL THEN NULL
5229                ELSE (
5230                    mz_unsafe.mz_error_if_null(
5231                        (SELECT oid FROM mz_catalog.mz_objects WHERE name = $1 AND type = 'secret'),
5232                        'secret \"' || $1 || '\" does not exist'
5233                    )
5234                )
5235                END
5236            ") => Oid, oid::FUNC_SECRET_OID_OID;
5237        },
5238        // This ought to be exposed in `mz_catalog`, but its name is rather
5239        // confusing. It does not identify the SQL session, but the
5240        // invocation of this `environmentd` process.
5241        "mz_session_id" => Scalar {
5242            params!() => UnmaterializableFunc::MzSessionId => Uuid, oid::FUNC_MZ_SESSION_ID_OID;
5243        },
5244        "mz_type_name" => Scalar {
5245            params!(Oid) => UnaryFunc::MzTypeName(func::MzTypeName)
5246                => String, oid::FUNC_MZ_TYPE_NAME;
5247        },
5248        "mz_validate_privileges" => Scalar {
5249            params!(String) => UnaryFunc::MzValidatePrivileges(func::MzValidatePrivileges)
5250                => Bool, oid::FUNC_MZ_VALIDATE_PRIVILEGES_OID;
5251        },
5252        "mz_validate_role_privilege" => Scalar {
5253            params!(String) => UnaryFunc::MzValidateRolePrivilege(
5254                func::MzValidateRolePrivilege,
5255            ) => Bool, oid::FUNC_MZ_VALIDATE_ROLE_PRIVILEGE_OID;
5256        },
5257        "parse_catalog_acl_mode" => Scalar {
5258            params!(Jsonb) => UnaryFunc::ParseCatalogAclMode(func::ParseCatalogAclMode)
5259                => String, oid::FUNC_PARSE_CATALOG_ACL_MODE_OID;
5260        },
5261        "parse_catalog_create_sql" => Scalar {
5262            params!(String) => UnaryFunc::ParseCatalogCreateSql(func::ParseCatalogCreateSql)
5263                => Jsonb, oid::FUNC_PARSE_CATALOG_CREATE_SQL_OID;
5264        },
5265        "parse_catalog_id" => Scalar {
5266            params!(Jsonb) => UnaryFunc::ParseCatalogId(func::ParseCatalogId)
5267                => String, oid::FUNC_PARSE_CATALOG_ID_OID;
5268        },
5269        "parse_catalog_privileges" => Scalar {
5270            params!(Jsonb) => UnaryFunc::ParseCatalogPrivileges(func::ParseCatalogPrivileges)
5271                => SqlScalarType::Array(Box::new(SqlScalarType::MzAclItem)),
5272                oid::FUNC_PARSE_CATALOG_PRIVILEGES_OID;
5273        },
5274        "redact_sql" => Scalar {
5275            params!(String) => UnaryFunc::RedactSql(func::RedactSql)
5276                => String, oid::FUNC_REDACT_SQL_OID;
5277        }
5278    }
5279});
5280
5281pub static MZ_UNSAFE_BUILTINS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
5282    use ParamType::*;
5283    use SqlScalarBaseType::*;
5284    builtins! {
5285        "mz_all" => Aggregate {
5286            params!(Any) => AggregateFunc::All => Bool, oid::FUNC_MZ_ALL_OID;
5287        },
5288        "mz_any" => Aggregate {
5289            params!(Any) => AggregateFunc::Any => Bool, oid::FUNC_MZ_ANY_OID;
5290        },
5291        "mz_avg_promotion_internal_v1" => Scalar {
5292            // Promotes a numeric type to the smallest fractional type that
5293            // can represent it. This is primarily useful for the avg
5294            // aggregate function, so that the avg of an integer column does
5295            // not get truncated to an integer, which would be surprising to
5296            // users (#549).
5297            params!(Float32) => Operation::identity()
5298                => Float32, oid::FUNC_MZ_AVG_PROMOTION_F32_OID_INTERNAL_V1;
5299            params!(Float64) => Operation::identity()
5300                => Float64, oid::FUNC_MZ_AVG_PROMOTION_F64_OID_INTERNAL_V1;
5301            params!(Int16) => Operation::unary(|ecx, e| {
5302                typeconv::plan_cast(
5303                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5304                )
5305            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I16_OID_INTERNAL_V1;
5306            params!(Int32) => Operation::unary(|ecx, e| {
5307                typeconv::plan_cast(
5308                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5309                )
5310            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I32_OID_INTERNAL_V1;
5311            params!(UInt16) => Operation::unary(|ecx, e| {
5312                typeconv::plan_cast(
5313                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5314                )
5315            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U16_OID_INTERNAL_V1;
5316            params!(UInt32) => Operation::unary(|ecx, e| {
5317                typeconv::plan_cast(
5318                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5319                )
5320            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U32_OID_INTERNAL_V1;
5321        },
5322        "mz_avg_promotion" => Scalar {
5323            // Promotes a numeric type to the smallest fractional type that
5324            // can represent it. This is primarily useful for the avg
5325            // aggregate function, so that the avg of an integer column does
5326            // not get truncated to an integer, which would be surprising to
5327            // users (#549).
5328            params!(Float32) => Operation::identity()
5329                => Float32, oid::FUNC_MZ_AVG_PROMOTION_F32_OID;
5330            params!(Float64) => Operation::identity()
5331                => Float64, oid::FUNC_MZ_AVG_PROMOTION_F64_OID;
5332            params!(Int16) => Operation::unary(|ecx, e| {
5333                typeconv::plan_cast(
5334                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5335                )
5336            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I16_OID;
5337            params!(Int32) => Operation::unary(|ecx, e| {
5338                typeconv::plan_cast(
5339                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5340                )
5341            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I32_OID;
5342            params!(Int64) => Operation::unary(|ecx, e| {
5343                typeconv::plan_cast(
5344                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5345                )
5346            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_I64_OID;
5347            params!(UInt16) => Operation::unary(|ecx, e| {
5348                typeconv::plan_cast(
5349                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5350                )
5351            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U16_OID;
5352            params!(UInt32) => Operation::unary(|ecx, e| {
5353                typeconv::plan_cast(
5354                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5355                )
5356            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U32_OID;
5357            params!(UInt64) => Operation::unary(|ecx, e| {
5358                typeconv::plan_cast(
5359                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5360                )
5361            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_U64_OID;
5362            params!(Numeric) => Operation::unary(|ecx, e| {
5363                typeconv::plan_cast(
5364                    ecx, CastContext::Explicit, e, &SqlScalarType::Numeric {max_scale: None},
5365                )
5366            }) => Numeric, oid::FUNC_MZ_AVG_PROMOTION_NUMERIC_OID;
5367        },
5368        "mz_error_if_null" => Scalar {
5369            // If the first argument is NULL, returns an EvalError::Internal whose error
5370            // message is the second argument.
5371            params!(Any, String) => VariadicFunc::from(variadic::ErrorIfNull)
5372                => Any, oid::FUNC_MZ_ERROR_IF_NULL_OID;
5373        },
5374        "mz_sleep" => Scalar {
5375            params!(Float64) => UnaryFunc::Sleep(func::Sleep)
5376                => TimestampTz, oid::FUNC_MZ_SLEEP_OID;
5377        },
5378        "mz_panic" => Scalar {
5379            params!(String) => UnaryFunc::Panic(func::Panic) => String, oid::FUNC_MZ_PANIC_OID;
5380        }
5381    }
5382});
5383
5384fn digest(algorithm: &'static str) -> Operation<HirScalarExpr> {
5385    Operation::unary(move |_ecx, input| {
5386        let algorithm = HirScalarExpr::literal(Datum::String(algorithm), SqlScalarType::String);
5387        Ok(input.call_binary(algorithm, BinaryFunc::from(func::DigestBytes)))
5388    })
5389}
5390
5391fn array_to_string(
5392    ecx: &ExprContext,
5393    exprs: Vec<HirScalarExpr>,
5394) -> Result<HirScalarExpr, PlanError> {
5395    let elem_type = match ecx.scalar_type(&exprs[0]) {
5396        SqlScalarType::Array(elem_type) => *elem_type,
5397        _ => unreachable!("array_to_string is guaranteed to receive array as first argument"),
5398    };
5399    Ok(HirScalarExpr::call_variadic(
5400        variadic::ArrayToString { elem_type },
5401        exprs,
5402    ))
5403}
5404
5405/// Correlates an operator with all of its implementations.
5406pub static OP_IMPLS: LazyLock<BTreeMap<&'static str, Func>> = LazyLock::new(|| {
5407    use BinaryFunc as BF;
5408    use ParamType::*;
5409    use SqlScalarBaseType::*;
5410    builtins! {
5411        // Literal OIDs collected from PG 13 using a version of this query
5412        // ```sql
5413        // SELECT
5414        //     oid,
5415        //     oprname,
5416        //     oprleft::regtype,
5417        //     oprright::regtype
5418        // FROM
5419        //     pg_operator
5420        // WHERE
5421        //     oprname IN (
5422        //         '+', '-', '*', '/', '%',
5423        //         '|', '&', '#', '~', '<<', '>>',
5424        //         '~~', '!~~'
5425        //     )
5426        // ORDER BY
5427        //     oprname;
5428        // ```
5429        // Values are also available through
5430        // https://github.com/postgres/postgres/blob/master/src/include/catalog/pg_operator.dat
5431
5432        // ARITHMETIC
5433        "+" => Scalar {
5434            params!(Any) => Operation::new(|ecx, exprs, _params, _order_by| {
5435                // Unary plus has unusual compatibility requirements.
5436                //
5437                // In PostgreSQL, it is only defined for numeric types, so
5438                // `+$1` and `+'1'` get coerced to `Float64` per the usual
5439                // rules, but `+'1'::text` is rejected.
5440                //
5441                // In SQLite, unary plus can be applied to *any* type, and
5442                // is always the identity function.
5443                //
5444                // To try to be compatible with both PostgreSQL and SQlite,
5445                // we accept explicitly-typed arguments of any type, but try
5446                // to coerce unknown-type arguments as `Float64`.
5447                typeconv::plan_coerce(ecx, exprs.into_element(), &SqlScalarType::Float64)
5448            }) => Any, oid::OP_UNARY_PLUS_OID;
5449            params!(Int16, Int16) => BF::from(func::AddInt16) => Int16, 550;
5450            params!(Int32, Int32) => BF::from(func::AddInt32) => Int32, 551;
5451            params!(Int64, Int64) => BF::from(func::AddInt64) => Int64, 684;
5452            params!(UInt16, UInt16) => BF::from(func::AddUint16) => UInt16, oid::FUNC_ADD_UINT16;
5453            params!(UInt32, UInt32) => BF::from(func::AddUint32) => UInt32, oid::FUNC_ADD_UINT32;
5454            params!(UInt64, UInt64) => BF::from(func::AddUint64) => UInt64, oid::FUNC_ADD_UINT64;
5455            params!(Float32, Float32) => BF::from(func::AddFloat32) => Float32, 586;
5456            params!(Float64, Float64) => BF::from(func::AddFloat64) => Float64, 591;
5457            params!(Interval, Interval) => BF::from(func::AddInterval) => Interval, 1337;
5458            params!(Timestamp, Interval) => BF::from(func::AddTimestampInterval) => Timestamp, 2066;
5459            params!(Interval, Timestamp) => {
5460                Operation::binary(|_ecx, lhs, rhs| {
5461                    Ok(rhs.call_binary(lhs, func::AddTimestampInterval))
5462                })
5463            } => Timestamp, 2553;
5464            params!(TimestampTz, Interval)
5465                => BF::from(func::AddTimestampTzInterval) => TimestampTz, 1327;
5466            params!(Interval, TimestampTz) => {
5467                Operation::binary(|_ecx, lhs, rhs| {
5468                    Ok(rhs.call_binary(lhs, func::AddTimestampTzInterval))
5469                })
5470            } => TimestampTz, 2554;
5471            params!(Date, Interval) => BF::from(func::AddDateInterval) => Timestamp, 1076;
5472            params!(Interval, Date) => {
5473                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::AddDateInterval)))
5474            } => Timestamp, 2551;
5475            params!(Date, Time) => BF::from(func::AddDateTime) => Timestamp, 1360;
5476            params!(Time, Date) => {
5477                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::AddDateTime)))
5478            } => Timestamp, 1363;
5479            params!(Time, Interval) => BF::from(func::AddTimeInterval) => Time, 1800;
5480            params!(Interval, Time) => {
5481                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::AddTimeInterval)))
5482            } => Time, 1849;
5483            params!(Numeric, Numeric) => BF::from(func::AddNumeric) => Numeric, 1758;
5484            params!(RangeAny, RangeAny) => BF::from(func::RangeUnion) => RangeAny, 3898;
5485        },
5486        "-" => Scalar {
5487            params!(Int16) => UnaryFunc::NegInt16(func::NegInt16) => Int16, 559;
5488            params!(Int32) => UnaryFunc::NegInt32(func::NegInt32) => Int32, 558;
5489            params!(Int64) => UnaryFunc::NegInt64(func::NegInt64) => Int64, 484;
5490            params!(Float32) => UnaryFunc::NegFloat32(func::NegFloat32) => Float32, 584;
5491            params!(Float64) => UnaryFunc::NegFloat64(func::NegFloat64) => Float64, 585;
5492            params!(Numeric) => UnaryFunc::NegNumeric(func::NegNumeric) => Numeric, 17510;
5493            params!(Interval) => UnaryFunc::NegInterval(func::NegInterval) => Interval, 1336;
5494            params!(Int32, Int32) => BF::from(func::SubInt32) => Int32, 555;
5495            params!(Int64, Int64) => BF::from(func::SubInt64) => Int64, 685;
5496            params!(UInt16, UInt16) => BF::from(func::SubUint16) => UInt16, oid::FUNC_SUB_UINT16;
5497            params!(UInt32, UInt32) => BF::from(func::SubUint32) => UInt32, oid::FUNC_SUB_UINT32;
5498            params!(UInt64, UInt64) => BF::from(func::SubUint64) => UInt64, oid::FUNC_SUB_UINT64;
5499            params!(Float32, Float32) => BF::from(func::SubFloat32) => Float32, 587;
5500            params!(Float64, Float64) => BF::from(func::SubFloat64) => Float64, 592;
5501            params!(Numeric, Numeric) => BF::from(func::SubNumeric) => Numeric, 17590;
5502            params!(Interval, Interval) => BF::from(func::SubInterval) => Interval, 1338;
5503            params!(Timestamp, Timestamp) => BF::from(func::SubTimestamp) => Interval, 2067;
5504            params!(TimestampTz, TimestampTz) => BF::from(func::SubTimestampTz) => Interval, 1328;
5505            params!(Timestamp, Interval) => BF::from(func::SubTimestampInterval) => Timestamp, 2068;
5506            params!(TimestampTz, Interval)
5507                => BF::from(func::SubTimestampTzInterval) => TimestampTz, 1329;
5508            params!(Date, Date) => BF::from(func::SubDate) => Int32, 1099;
5509            params!(Date, Interval) => BF::from(func::SubDateInterval) => Timestamp, 1077;
5510            params!(Time, Time) => BF::from(func::SubTime) => Interval, 1399;
5511            params!(Time, Interval) => BF::from(func::SubTimeInterval) => Time, 1801;
5512            params!(Jsonb, Int64) => BF::from(func::JsonbDeleteInt64) => Jsonb, 3286;
5513            params!(Jsonb, String) => BF::from(func::JsonbDeleteString) => Jsonb, 3285;
5514            params!(RangeAny, RangeAny) => BF::from(func::RangeDifference) => RangeAny, 3899;
5515            // TODO(jamii) there should be corresponding overloads for
5516            // Array(Int64) and Array(String)
5517        },
5518        "*" => Scalar {
5519            params!(Int16, Int16) => BF::from(func::MulInt16) => Int16, 526;
5520            params!(Int32, Int32) => BF::from(func::MulInt32) => Int32, 514;
5521            params!(Int64, Int64) => BF::from(func::MulInt64) => Int64, 686;
5522            params!(UInt16, UInt16) => BF::from(func::MulUint16) => UInt16, oid::FUNC_MUL_UINT16;
5523            params!(UInt32, UInt32) => BF::from(func::MulUint32) => UInt32, oid::FUNC_MUL_UINT32;
5524            params!(UInt64, UInt64) => BF::from(func::MulUint64) => UInt64, oid::FUNC_MUL_UINT64;
5525            params!(Float32, Float32) => BF::from(func::MulFloat32) => Float32, 589;
5526            params!(Float64, Float64) => BF::from(func::MulFloat64) => Float64, 594;
5527            params!(Interval, Float64) => BF::from(func::MulInterval) => Interval, 1583;
5528            params!(Float64, Interval) => {
5529                Operation::binary(|_ecx, lhs, rhs| Ok(rhs.call_binary(lhs, func::MulInterval)))
5530            } => Interval, 1584;
5531            params!(Numeric, Numeric) => BF::from(func::MulNumeric) => Numeric, 1760;
5532            params!(RangeAny, RangeAny) => BF::from(func::RangeIntersection) => RangeAny, 3900;
5533        },
5534        "/" => Scalar {
5535            params!(Int16, Int16) => BF::from(func::DivInt16) => Int16, 527;
5536            params!(Int32, Int32) => BF::from(func::DivInt32) => Int32, 528;
5537            params!(Int64, Int64) => BF::from(func::DivInt64) => Int64, 687;
5538            params!(UInt16, UInt16) => BF::from(func::DivUint16) => UInt16, oid::FUNC_DIV_UINT16;
5539            params!(UInt32, UInt32) => BF::from(func::DivUint32) => UInt32, oid::FUNC_DIV_UINT32;
5540            params!(UInt64, UInt64) => BF::from(func::DivUint64) => UInt64, oid::FUNC_DIV_UINT64;
5541            params!(Float32, Float32) => BF::from(func::DivFloat32) => Float32, 588;
5542            params!(Float64, Float64) => BF::from(func::DivFloat64) => Float64, 593;
5543            params!(Interval, Float64) => BF::from(func::DivInterval) => Interval, 1585;
5544            params!(Numeric, Numeric) => BF::from(func::DivNumeric) => Numeric, 1761;
5545        },
5546        "%" => Scalar {
5547            params!(Int16, Int16) => BF::from(func::ModInt16) => Int16, 529;
5548            params!(Int32, Int32) => BF::from(func::ModInt32) => Int32, 530;
5549            params!(Int64, Int64) => BF::from(func::ModInt64) => Int64, 439;
5550            params!(UInt16, UInt16) => BF::from(func::ModUint16) => UInt16, oid::FUNC_MOD_UINT16;
5551            params!(UInt32, UInt32) => BF::from(func::ModUint32) => UInt32, oid::FUNC_MOD_UINT32;
5552            params!(UInt64, UInt64) => BF::from(func::ModUint64) => UInt64, oid::FUNC_MOD_UINT64;
5553            params!(Float32, Float32) => BF::from(func::ModFloat32) => Float32, oid::OP_MOD_F32_OID;
5554            params!(Float64, Float64) => BF::from(func::ModFloat64) => Float64, oid::OP_MOD_F64_OID;
5555            params!(Numeric, Numeric) => BF::from(func::ModNumeric) => Numeric, 1762;
5556        },
5557        "&" => Scalar {
5558            params!(Int16, Int16) => BF::from(func::BitAndInt16) => Int16, 1874;
5559            params!(Int32, Int32) => BF::from(func::BitAndInt32) => Int32, 1880;
5560            params!(Int64, Int64) => BF::from(func::BitAndInt64) => Int64, 1886;
5561            params!(UInt16, UInt16) => BF::from(func::BitAndUint16) => UInt16, oid::FUNC_AND_UINT16;
5562            params!(UInt32, UInt32) => BF::from(func::BitAndUint32) => UInt32, oid::FUNC_AND_UINT32;
5563            params!(UInt64, UInt64) => BF::from(func::BitAndUint64) => UInt64, oid::FUNC_AND_UINT64;
5564        },
5565        "|" => Scalar {
5566            params!(Int16, Int16) => BF::from(func::BitOrInt16) => Int16, 1875;
5567            params!(Int32, Int32) => BF::from(func::BitOrInt32) => Int32, 1881;
5568            params!(Int64, Int64) => BF::from(func::BitOrInt64) => Int64, 1887;
5569            params!(UInt16, UInt16) => BF::from(func::BitOrUint16) => UInt16, oid::FUNC_OR_UINT16;
5570            params!(UInt32, UInt32) => BF::from(func::BitOrUint32) => UInt32, oid::FUNC_OR_UINT32;
5571            params!(UInt64, UInt64) => BF::from(func::BitOrUint64) => UInt64, oid::FUNC_OR_UINT64;
5572        },
5573        "#" => Scalar {
5574            params!(Int16, Int16) => BF::from(func::BitXorInt16) => Int16, 1876;
5575            params!(Int32, Int32) => BF::from(func::BitXorInt32) => Int32, 1882;
5576            params!(Int64, Int64) => BF::from(func::BitXorInt64) => Int64, 1888;
5577            params!(UInt16, UInt16) => BF::from(func::BitXorUint16) => UInt16, oid::FUNC_XOR_UINT16;
5578            params!(UInt32, UInt32) => BF::from(func::BitXorUint32) => UInt32, oid::FUNC_XOR_UINT32;
5579            params!(UInt64, UInt64) => BF::from(func::BitXorUint64) => UInt64, oid::FUNC_XOR_UINT64;
5580        },
5581        "<<" => Scalar {
5582            params!(Int16, Int32) => BF::from(func::BitShiftLeftInt16) => Int16, 1878;
5583            params!(Int32, Int32) => BF::from(func::BitShiftLeftInt32) => Int32, 1884;
5584            params!(Int64, Int32) => BF::from(func::BitShiftLeftInt64) => Int64, 1890;
5585            params!(UInt16, UInt32) => BF::from(func::BitShiftLeftUint16)
5586                => UInt16, oid::FUNC_SHIFT_LEFT_UINT16;
5587            params!(UInt32, UInt32) => BF::from(func::BitShiftLeftUint32)
5588                => UInt32, oid::FUNC_SHIFT_LEFT_UINT32;
5589            params!(UInt64, UInt32) => BF::from(func::BitShiftLeftUint64)
5590                => UInt64, oid::FUNC_SHIFT_LEFT_UINT64;
5591            params!(RangeAny, RangeAny) => BF::from(func::RangeBefore) => Bool, 3893;
5592        },
5593        ">>" => Scalar {
5594            params!(Int16, Int32) => BF::from(func::BitShiftRightInt16) => Int16, 1879;
5595            params!(Int32, Int32) => BF::from(func::BitShiftRightInt32) => Int32, 1885;
5596            params!(Int64, Int32) => BF::from(func::BitShiftRightInt64) => Int64, 1891;
5597            params!(UInt16, UInt32) => BF::from(func::BitShiftRightUint16)
5598                => UInt16, oid::FUNC_SHIFT_RIGHT_UINT16;
5599            params!(UInt32, UInt32) => BF::from(func::BitShiftRightUint32)
5600                => UInt32, oid::FUNC_SHIFT_RIGHT_UINT32;
5601            params!(UInt64, UInt32) => BF::from(func::BitShiftRightUint64)
5602                => UInt64, oid::FUNC_SHIFT_RIGHT_UINT64;
5603            params!(RangeAny, RangeAny) => BF::from(func::RangeAfter) => Bool, 3894;
5604        },
5605
5606        // ILIKE
5607        "~~*" => Scalar {
5608            params!(String, String) => BF::from(func::IsLikeMatchCaseInsensitive) => Bool, 1627;
5609            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5610                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5611                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5612                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
5613                )
5614            }) => Bool, 1629;
5615        },
5616        "!~~*" => Scalar {
5617            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5618                Ok(lhs
5619                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
5620                    .call_unary(UnaryFunc::Not(func::Not)))
5621            }) => Bool, 1628;
5622            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5623                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5624                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5625                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseInsensitive))
5626                    .call_unary(UnaryFunc::Not(func::Not))
5627                )
5628            }) => Bool, 1630;
5629        },
5630
5631
5632        // LIKE
5633        "~~" => Scalar {
5634            params!(String, String) => BF::from(func::IsLikeMatchCaseSensitive) => Bool, 1209;
5635            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5636                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5637                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5638                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
5639                )
5640            }) => Bool, 1211;
5641        },
5642        "!~~" => Scalar {
5643            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5644                Ok(lhs
5645                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
5646                    .call_unary(UnaryFunc::Not(func::Not)))
5647            }) => Bool, 1210;
5648            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5649                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5650                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5651                    .call_binary(rhs, BF::from(func::IsLikeMatchCaseSensitive))
5652                    .call_unary(UnaryFunc::Not(func::Not))
5653                )
5654            }) => Bool, 1212;
5655        },
5656
5657        // REGEX
5658        "~" => Scalar {
5659            params!(Int16) => UnaryFunc::BitNotInt16(func::BitNotInt16) => Int16, 1877;
5660            params!(Int32) => UnaryFunc::BitNotInt32(func::BitNotInt32) => Int32, 1883;
5661            params!(Int64) => UnaryFunc::BitNotInt64(func::BitNotInt64) => Int64, 1889;
5662            params!(UInt16) => UnaryFunc::BitNotUint16(func::BitNotUint16)
5663                => UInt16, oid::FUNC_BIT_NOT_UINT16_OID;
5664            params!(UInt32) => UnaryFunc::BitNotUint32(func::BitNotUint32)
5665                => UInt32, oid::FUNC_BIT_NOT_UINT32_OID;
5666            params!(UInt64) => UnaryFunc::BitNotUint64(func::BitNotUint64)
5667                => UInt64, oid::FUNC_BIT_NOT_UINT64_OID;
5668            params!(String, String)
5669                => BinaryFunc::IsRegexpMatchCaseSensitive(
5670                    func::IsRegexpMatchCaseSensitive,
5671                ) => Bool, 641;
5672            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5673                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5674                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5675                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseSensitive(
5676                        func::IsRegexpMatchCaseSensitive,
5677                    )))
5678            }) => Bool, 1055;
5679        },
5680        "~*" => Scalar {
5681            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5682                Ok(lhs.call_binary(
5683                    rhs,
5684                    BinaryFunc::IsRegexpMatchCaseInsensitive(
5685                        func::IsRegexpMatchCaseInsensitive,
5686                    ),
5687                ))
5688            }) => Bool, 1228;
5689            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5690                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5691                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5692                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseInsensitive(
5693                        func::IsRegexpMatchCaseInsensitive,
5694                    )))
5695            }) => Bool, 1234;
5696        },
5697        "!~" => Scalar {
5698            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5699                Ok(lhs
5700                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseSensitive(
5701                        func::IsRegexpMatchCaseSensitive,
5702                    ))
5703                    .call_unary(UnaryFunc::Not(func::Not)))
5704            }) => Bool, 642;
5705            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5706                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5707                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5708                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseSensitive(
5709                        func::IsRegexpMatchCaseSensitive,
5710                    ))
5711                    .call_unary(UnaryFunc::Not(func::Not)))
5712            }) => Bool, 1056;
5713        },
5714        "!~*" => Scalar {
5715            params!(String, String) => Operation::binary(|_ecx, lhs, rhs| {
5716                Ok(lhs
5717                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseInsensitive(
5718                        func::IsRegexpMatchCaseInsensitive,
5719                    ))
5720                    .call_unary(UnaryFunc::Not(func::Not)))
5721            }) => Bool, 1229;
5722            params!(Char, String) => Operation::binary(|ecx, lhs, rhs| {
5723                let length = ecx.scalar_type(&lhs).unwrap_char_length();
5724                Ok(lhs.call_unary(UnaryFunc::PadChar(func::PadChar { length }))
5725                    .call_binary(rhs, BinaryFunc::IsRegexpMatchCaseInsensitive(
5726                        func::IsRegexpMatchCaseInsensitive,
5727                    ))
5728                    .call_unary(UnaryFunc::Not(func::Not)))
5729            }) => Bool, 1235;
5730        },
5731
5732        // CONCAT
5733        "||" => Scalar {
5734            params!(String, NonVecAny) => Operation::binary(|ecx, lhs, rhs| {
5735                let rhs = typeconv::plan_cast(
5736                    ecx,
5737                    CastContext::Explicit,
5738                    rhs,
5739                    &SqlScalarType::String,
5740                )?;
5741                Ok(lhs.call_binary(rhs, func::TextConcatBinary))
5742            }) => String, 2779;
5743            params!(NonVecAny, String) => Operation::binary(|ecx, lhs, rhs| {
5744                let lhs = typeconv::plan_cast(
5745                    ecx,
5746                    CastContext::Explicit,
5747                    lhs,
5748                    &SqlScalarType::String,
5749                )?;
5750                Ok(lhs.call_binary(rhs, func::TextConcatBinary))
5751            }) => String, 2780;
5752            params!(String, String) => BF::from(func::TextConcatBinary) => String, 654;
5753            params!(Jsonb, Jsonb) => BF::from(func::JsonbConcat) => Jsonb, 3284;
5754            params!(ArrayAnyCompatible, ArrayAnyCompatible)
5755                => BF::from(func::ArrayArrayConcat) => ArrayAnyCompatible, 375;
5756            params!(ListAnyCompatible, ListAnyCompatible)
5757                => BF::from(func::ListListConcat)
5758                => ListAnyCompatible, oid::OP_CONCAT_LIST_LIST_OID;
5759            params!(ListAnyCompatible, ListElementAnyCompatible)
5760                => BF::from(func::ListElementConcat)
5761                => ListAnyCompatible, oid::OP_CONCAT_LIST_ELEMENT_OID;
5762            params!(ListElementAnyCompatible, ListAnyCompatible)
5763                => BF::from(func::ElementListConcat)
5764                => ListAnyCompatible, oid::OP_CONCAT_ELEMENY_LIST_OID;
5765        },
5766
5767        // JSON, MAP, RANGE, LIST, ARRAY
5768        "->" => Scalar {
5769            params!(Jsonb, Int64) => BF::from(func::JsonbGetInt64) => Jsonb, 3212;
5770            params!(Jsonb, String) => BF::from(func::JsonbGetString) => Jsonb, 3211;
5771            params!(MapAny, String) => BF::from(func::MapGetValue)
5772                => Any, oid::OP_GET_VALUE_MAP_OID;
5773        },
5774        "->>" => Scalar {
5775            params!(Jsonb, Int64) => BF::from(func::JsonbGetInt64Stringify) => String, 3481;
5776            params!(Jsonb, String) => BF::from(func::JsonbGetStringStringify) => String, 3477;
5777        },
5778        "#>" => Scalar {
5779            params!(Jsonb, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5780                => BF::from(func::JsonbGetPath) => Jsonb, 3213;
5781        },
5782        "#>>" => Scalar {
5783            params!(Jsonb, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5784                => BF::from(func::JsonbGetPathStringify) => String, 3206;
5785        },
5786        "@>" => Scalar {
5787            params!(Jsonb, Jsonb) => BF::from(func::JsonbContainsJsonb) => Bool, 3246;
5788            params!(Jsonb, String) => Operation::binary(|_ecx, lhs, rhs| {
5789                Ok(lhs.call_binary(
5790                    rhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb)),
5791                    BinaryFunc::from(func::JsonbContainsJsonb),
5792                ))
5793            }) => Bool, oid::OP_CONTAINS_JSONB_STRING_OID;
5794            params!(String, Jsonb) => Operation::binary(|_ecx, lhs, rhs| {
5795                Ok(lhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb))
5796                      .call_binary(rhs, func::JsonbContainsJsonb))
5797            }) => Bool, oid::OP_CONTAINS_STRING_JSONB_OID;
5798            params!(MapAnyCompatible, MapAnyCompatible)
5799                => BF::from(func::MapContainsMap)
5800                => Bool, oid::OP_CONTAINS_MAP_MAP_OID;
5801            params!(RangeAny, AnyElement) => Operation::binary(|ecx, lhs, rhs| {
5802                let elem_type = ecx.scalar_type(&lhs).unwrap_range_element_type().clone();
5803                let f = match elem_type {
5804                    SqlScalarType::Int32 => BF::from(func::RangeContainsI32),
5805                    SqlScalarType::Int64 => BF::from(func::RangeContainsI64),
5806                    SqlScalarType::Date => BF::from(func::RangeContainsDate),
5807                    SqlScalarType::Numeric { .. } => BF::from(func::RangeContainsNumeric),
5808                    SqlScalarType::Timestamp { .. } => BF::from(func::RangeContainsTimestamp),
5809                    SqlScalarType::TimestampTz { .. } => BF::from(func::RangeContainsTimestampTz),
5810                    _ => bail_unsupported!(format!("range element type: {elem_type:?}")),
5811                };
5812                Ok(lhs.call_binary(rhs, f))
5813            }) => Bool, 3889;
5814            params!(RangeAny, RangeAny) => Operation::binary(|_ecx, lhs, rhs| {
5815                Ok(lhs.call_binary(rhs, BF::from(func::RangeContainsRange)))
5816            }) => Bool, 3890;
5817            params!(ArrayAny, ArrayAny) => Operation::binary(|_ecx, lhs, rhs| {
5818                Ok(lhs.call_binary(rhs, BF::from(func::ArrayContainsArray)))
5819            }) => Bool, 2751;
5820            params!(ListAny, ListAny) => Operation::binary(|_ecx, lhs, rhs| {
5821                Ok(lhs.call_binary(rhs, BF::from(func::ListContainsList)))
5822            }) => Bool, oid::OP_CONTAINS_LIST_LIST_OID;
5823        },
5824        "<@" => Scalar {
5825            params!(Jsonb, Jsonb) => Operation::binary(|_ecx, lhs, rhs| {
5826                Ok(rhs.call_binary(
5827                    lhs,
5828                    func::JsonbContainsJsonb
5829                ))
5830            }) => Bool, 3250;
5831            params!(Jsonb, String) => Operation::binary(|_ecx, lhs, rhs| {
5832                Ok(rhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb))
5833                      .call_binary(lhs, func::JsonbContainsJsonb))
5834            }) => Bool, oid::OP_CONTAINED_JSONB_STRING_OID;
5835            params!(String, Jsonb) => Operation::binary(|_ecx, lhs, rhs| {
5836                Ok(rhs.call_binary(
5837                    lhs.call_unary(UnaryFunc::CastStringToJsonb(func::CastStringToJsonb)),
5838                    func::JsonbContainsJsonb,
5839                ))
5840            }) => Bool, oid::OP_CONTAINED_STRING_JSONB_OID;
5841            params!(MapAnyCompatible, MapAnyCompatible) => Operation::binary(|_ecx, lhs, rhs| {
5842                Ok(rhs.call_binary(lhs, func::MapContainsMap))
5843            }) => Bool, oid::OP_CONTAINED_MAP_MAP_OID;
5844            params!(AnyElement, RangeAny) => Operation::binary(|ecx, lhs, rhs| {
5845                let elem_type = ecx.scalar_type(&rhs).unwrap_range_element_type().clone();
5846                let f = match elem_type {
5847                    SqlScalarType::Int32 => BF::from(func::RangeContainsI32Rev),
5848                    SqlScalarType::Int64 => BF::from(func::RangeContainsI64Rev),
5849                    SqlScalarType::Date => BF::from(func::RangeContainsDateRev),
5850                    SqlScalarType::Numeric { .. } => BF::from(func::RangeContainsNumericRev),
5851                    SqlScalarType::Timestamp { .. } => BF::from(func::RangeContainsTimestampRev),
5852                    SqlScalarType::TimestampTz { .. } => {
5853                        BF::from(func::RangeContainsTimestampTzRev)
5854                    }
5855                    _ => bail_unsupported!(format!("range element type: {elem_type:?}")),
5856                };
5857                Ok(rhs.call_binary(lhs, f))
5858            }) => Bool, 3891;
5859            params!(RangeAny, RangeAny) => Operation::binary(|_ecx, lhs, rhs| {
5860                Ok(rhs.call_binary(lhs, BF::from(func::RangeContainsRangeRev)))
5861            }) => Bool, 3892;
5862            params!(ArrayAny, ArrayAny) => Operation::binary(|_ecx, lhs, rhs| {
5863                Ok(lhs.call_binary(rhs, BF::from(func::ArrayContainsArrayRev)))
5864            }) => Bool, 2752;
5865            params!(ListAny, ListAny) => Operation::binary(|_ecx, lhs, rhs| {
5866                Ok(lhs.call_binary(rhs, BF::from(func::ListContainsListRev)))
5867            }) => Bool, oid::OP_IS_CONTAINED_LIST_LIST_OID;
5868        },
5869        "?" => Scalar {
5870            params!(Jsonb, String) => BF::from(func::JsonbContainsString) => Bool, 3247;
5871            params!(MapAny, String) => BF::from(func::MapContainsKey)
5872                => Bool, oid::OP_CONTAINS_KEY_MAP_OID;
5873        },
5874        "?&" => Scalar {
5875            params!(MapAny, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5876                => BF::from(func::MapContainsAllKeys)
5877                => Bool, oid::OP_CONTAINS_ALL_KEYS_MAP_OID;
5878        },
5879        "?|" => Scalar {
5880            params!(MapAny, SqlScalarType::Array(Box::new(SqlScalarType::String)))
5881                => BF::from(func::MapContainsAnyKeys)
5882                => Bool, oid::OP_CONTAINS_ANY_KEYS_MAP_OID;
5883        },
5884        "&&" => Scalar {
5885            params!(RangeAny, RangeAny) => BF::from(func::RangeOverlaps) => Bool, 3888;
5886        },
5887        "&<" => Scalar {
5888            params!(RangeAny, RangeAny) => BF::from(func::RangeOverleft) => Bool, 3895;
5889        },
5890        "&>" => Scalar {
5891            params!(RangeAny, RangeAny) => BF::from(func::RangeOverright) => Bool, 3896;
5892        },
5893        "-|-" => Scalar {
5894            params!(RangeAny, RangeAny) => BF::from(func::RangeAdjacent) => Bool, 3897;
5895        },
5896
5897        // COMPARISON OPS
5898        "<" => Scalar {
5899            params!(Numeric, Numeric) => BF::from(func::Lt) => Bool, 1754;
5900            params!(Bool, Bool) => BF::from(func::Lt) => Bool, 58;
5901            params!(Int16, Int16) => BF::from(func::Lt) => Bool, 95;
5902            params!(Int32, Int32) => BF::from(func::Lt) => Bool, 97;
5903            params!(Int64, Int64) => BF::from(func::Lt) => Bool, 412;
5904            params!(UInt16, UInt16) => BF::from(func::Lt) => Bool, oid::FUNC_LT_UINT16_OID;
5905            params!(UInt32, UInt32) => BF::from(func::Lt) => Bool, oid::FUNC_LT_UINT32_OID;
5906            params!(UInt64, UInt64) => BF::from(func::Lt) => Bool, oid::FUNC_LT_UINT64_OID;
5907            params!(Float32, Float32) => BF::from(func::Lt) => Bool, 622;
5908            params!(Float64, Float64) => BF::from(func::Lt) => Bool, 672;
5909            params!(Oid, Oid) => BF::from(func::Lt) => Bool, 609;
5910            params!(Date, Date) => BF::from(func::Lt) => Bool, 1095;
5911            params!(Time, Time) => BF::from(func::Lt) => Bool, 1110;
5912            params!(Timestamp, Timestamp) => BF::from(func::Lt) => Bool, 2062;
5913            params!(TimestampTz, TimestampTz) => BF::from(func::Lt) => Bool, 1322;
5914            params!(Uuid, Uuid) => BF::from(func::Lt) => Bool, 2974;
5915            params!(Interval, Interval) => BF::from(func::Lt) => Bool, 1332;
5916            params!(Bytes, Bytes) => BF::from(func::Lt) => Bool, 1957;
5917            params!(String, String) => BF::from(func::Lt) => Bool, 664;
5918            params!(Char, Char) => BF::from(func::Lt) => Bool, 1058;
5919            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Lt) => Bool, 631;
5920            params!(PgLegacyName, PgLegacyName) => BF::from(func::Lt) => Bool, 660;
5921            params!(Jsonb, Jsonb) => BF::from(func::Lt) => Bool, 3242;
5922            params!(ArrayAny, ArrayAny) => BF::from(func::Lt) => Bool, 1072;
5923            params!(RecordAny, RecordAny) => BF::from(func::Lt) => Bool, 2990;
5924            params!(MzTimestamp, MzTimestamp) => BF::from(func::Lt)
5925                => Bool, oid::FUNC_MZ_TIMESTAMP_LT_MZ_TIMESTAMP_OID;
5926            params!(RangeAny, RangeAny) => BF::from(func::Lt) => Bool, 3884;
5927        },
5928        "<=" => Scalar {
5929            params!(Numeric, Numeric) => BF::from(func::Lte) => Bool, 1755;
5930            params!(Bool, Bool) => BF::from(func::Lte) => Bool, 1694;
5931            params!(Int16, Int16) => BF::from(func::Lte) => Bool, 522;
5932            params!(Int32, Int32) => BF::from(func::Lte) => Bool, 523;
5933            params!(Int64, Int64) => BF::from(func::Lte) => Bool, 414;
5934            params!(UInt16, UInt16) => BF::from(func::Lte) => Bool, oid::FUNC_LTE_UINT16_OID;
5935            params!(UInt32, UInt32) => BF::from(func::Lte) => Bool, oid::FUNC_LTE_UINT32_OID;
5936            params!(UInt64, UInt64) => BF::from(func::Lte) => Bool, oid::FUNC_LTE_UINT64_OID;
5937            params!(Float32, Float32) => BF::from(func::Lte) => Bool, 624;
5938            params!(Float64, Float64) => BF::from(func::Lte) => Bool, 673;
5939            params!(Oid, Oid) => BF::from(func::Lte) => Bool, 611;
5940            params!(Date, Date) => BF::from(func::Lte) => Bool, 1096;
5941            params!(Time, Time) => BF::from(func::Lte) => Bool, 1111;
5942            params!(Timestamp, Timestamp) => BF::from(func::Lte) => Bool, 2063;
5943            params!(TimestampTz, TimestampTz) => BF::from(func::Lte) => Bool, 1323;
5944            params!(Uuid, Uuid) => BF::from(func::Lte) => Bool, 2976;
5945            params!(Interval, Interval) => BF::from(func::Lte) => Bool, 1333;
5946            params!(Bytes, Bytes) => BF::from(func::Lte) => Bool, 1958;
5947            params!(String, String) => BF::from(func::Lte) => Bool, 665;
5948            params!(Char, Char) => BF::from(func::Lte) => Bool, 1059;
5949            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Lte) => Bool, 632;
5950            params!(PgLegacyName, PgLegacyName) => BF::from(func::Lte) => Bool, 661;
5951            params!(Jsonb, Jsonb) => BF::from(func::Lte) => Bool, 3244;
5952            params!(ArrayAny, ArrayAny) => BF::from(func::Lte) => Bool, 1074;
5953            params!(RecordAny, RecordAny) => BF::from(func::Lte) => Bool, 2992;
5954            params!(MzTimestamp, MzTimestamp) => BF::from(func::Lte)
5955                => Bool, oid::FUNC_MZ_TIMESTAMP_LTE_MZ_TIMESTAMP_OID;
5956            params!(RangeAny, RangeAny) => BF::from(func::Lte) => Bool, 3885;
5957        },
5958        ">" => Scalar {
5959            params!(Numeric, Numeric) => BF::from(func::Gt) => Bool, 1756;
5960            params!(Bool, Bool) => BF::from(func::Gt) => Bool, 59;
5961            params!(Int16, Int16) => BF::from(func::Gt) => Bool, 520;
5962            params!(Int32, Int32) => BF::from(func::Gt) => Bool, 521;
5963            params!(Int64, Int64) => BF::from(func::Gt) => Bool, 413;
5964            params!(UInt16, UInt16) => BF::from(func::Gt) => Bool, oid::FUNC_GT_UINT16_OID;
5965            params!(UInt32, UInt32) => BF::from(func::Gt) => Bool, oid::FUNC_GT_UINT32_OID;
5966            params!(UInt64, UInt64) => BF::from(func::Gt) => Bool, oid::FUNC_GT_UINT64_OID;
5967            params!(Float32, Float32) => BF::from(func::Gt) => Bool, 623;
5968            params!(Float64, Float64) => BF::from(func::Gt) => Bool, 674;
5969            params!(Oid, Oid) => BF::from(func::Gt) => Bool, 610;
5970            params!(Date, Date) => BF::from(func::Gt) => Bool, 1097;
5971            params!(Time, Time) => BF::from(func::Gt) => Bool, 1112;
5972            params!(Timestamp, Timestamp) => BF::from(func::Gt) => Bool, 2064;
5973            params!(TimestampTz, TimestampTz) => BF::from(func::Gt) => Bool, 1324;
5974            params!(Uuid, Uuid) => BF::from(func::Gt) => Bool, 2975;
5975            params!(Interval, Interval) => BF::from(func::Gt) => Bool, 1334;
5976            params!(Bytes, Bytes) => BF::from(func::Gt) => Bool, 1959;
5977            params!(String, String) => BF::from(func::Gt) => Bool, 666;
5978            params!(Char, Char) => BF::from(func::Gt) => Bool, 1060;
5979            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Gt) => Bool, 633;
5980            params!(PgLegacyName, PgLegacyName) => BF::from(func::Gt) => Bool, 662;
5981            params!(Jsonb, Jsonb) => BF::from(func::Gt) => Bool, 3243;
5982            params!(ArrayAny, ArrayAny) => BF::from(func::Gt) => Bool, 1073;
5983            params!(RecordAny, RecordAny) => BF::from(func::Gt) => Bool, 2991;
5984            params!(MzTimestamp, MzTimestamp) => BF::from(func::Gt)
5985                => Bool, oid::FUNC_MZ_TIMESTAMP_GT_MZ_TIMESTAMP_OID;
5986            params!(RangeAny, RangeAny) => BF::from(func::Gt) => Bool, 3887;
5987        },
5988        ">=" => Scalar {
5989            params!(Numeric, Numeric) => BF::from(func::Gte) => Bool, 1757;
5990            params!(Bool, Bool) => BF::from(func::Gte) => Bool, 1695;
5991            params!(Int16, Int16) => BF::from(func::Gte) => Bool, 524;
5992            params!(Int32, Int32) => BF::from(func::Gte) => Bool, 525;
5993            params!(Int64, Int64) => BF::from(func::Gte) => Bool, 415;
5994            params!(UInt16, UInt16) => BF::from(func::Gte) => Bool, oid::FUNC_GTE_UINT16_OID;
5995            params!(UInt32, UInt32) => BF::from(func::Gte) => Bool, oid::FUNC_GTE_UINT32_OID;
5996            params!(UInt64, UInt64) => BF::from(func::Gte) => Bool, oid::FUNC_GTE_UINT64_OID;
5997            params!(Float32, Float32) => BF::from(func::Gte) => Bool, 625;
5998            params!(Float64, Float64) => BF::from(func::Gte) => Bool, 675;
5999            params!(Oid, Oid) => BF::from(func::Gte) => Bool, 612;
6000            params!(Date, Date) => BF::from(func::Gte) => Bool, 1098;
6001            params!(Time, Time) => BF::from(func::Gte) => Bool, 1113;
6002            params!(Timestamp, Timestamp) => BF::from(func::Gte) => Bool, 2065;
6003            params!(TimestampTz, TimestampTz) => BF::from(func::Gte) => Bool, 1325;
6004            params!(Uuid, Uuid) => BF::from(func::Gte) => Bool, 2977;
6005            params!(Interval, Interval) => BF::from(func::Gte) => Bool, 1335;
6006            params!(Bytes, Bytes) => BF::from(func::Gte) => Bool, 1960;
6007            params!(String, String) => BF::from(func::Gte) => Bool, 667;
6008            params!(Char, Char) => BF::from(func::Gte) => Bool, 1061;
6009            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Gte) => Bool, 634;
6010            params!(PgLegacyName, PgLegacyName) => BF::from(func::Gte) => Bool, 663;
6011            params!(Jsonb, Jsonb) => BF::from(func::Gte) => Bool, 3245;
6012            params!(ArrayAny, ArrayAny) => BF::from(func::Gte) => Bool, 1075;
6013            params!(RecordAny, RecordAny) => BF::from(func::Gte) => Bool, 2993;
6014            params!(MzTimestamp, MzTimestamp) => BF::from(func::Gte)
6015                => Bool, oid::FUNC_MZ_TIMESTAMP_GTE_MZ_TIMESTAMP_OID;
6016            params!(RangeAny, RangeAny) => BF::from(func::Gte) => Bool, 3886;
6017        },
6018        // Warning!
6019        // - If you are writing functions here that do not simply use
6020        //   `BinaryFunc::Eq`, you will break row equality (used in e.g.
6021        //   DISTINCT operations and JOINs). In short, this is totally verboten.
6022        // - The implementation of `BinaryFunc::Eq` is byte equality on two
6023        //   datums, and we enforce that both inputs to the function are of the
6024        //   same type in planning. However, it's possible that we will perform
6025        //   equality on types not listed here (e.g. `Varchar`) due to decisions
6026        //   made in the optimizer.
6027        // - Null inputs are handled by `BinaryFunc::eval` checking `propagates_nulls`.
6028        "=" => Scalar {
6029            params!(Numeric, Numeric) => BF::from(func::Eq) => Bool, 1752;
6030            params!(Bool, Bool) => BF::from(func::Eq) => Bool, 91;
6031            params!(Int16, Int16) => BF::from(func::Eq) => Bool, 94;
6032            params!(Int32, Int32) => BF::from(func::Eq) => Bool, 96;
6033            params!(Int64, Int64) => BF::from(func::Eq) => Bool, 410;
6034            params!(UInt16, UInt16) => BF::from(func::Eq) => Bool, oid::FUNC_EQ_UINT16_OID;
6035            params!(UInt32, UInt32) => BF::from(func::Eq) => Bool, oid::FUNC_EQ_UINT32_OID;
6036            params!(UInt64, UInt64) => BF::from(func::Eq) => Bool, oid::FUNC_EQ_UINT64_OID;
6037            params!(Float32, Float32) => BF::from(func::Eq) => Bool, 620;
6038            params!(Float64, Float64) => BF::from(func::Eq) => Bool, 670;
6039            params!(Oid, Oid) => BF::from(func::Eq) => Bool, 607;
6040            params!(Date, Date) => BF::from(func::Eq) => Bool, 1093;
6041            params!(Time, Time) => BF::from(func::Eq) => Bool, 1108;
6042            params!(Timestamp, Timestamp) => BF::from(func::Eq) => Bool, 2060;
6043            params!(TimestampTz, TimestampTz) => BF::from(func::Eq) => Bool, 1320;
6044            params!(Uuid, Uuid) => BF::from(func::Eq) => Bool, 2972;
6045            params!(Interval, Interval) => BF::from(func::Eq) => Bool, 1330;
6046            params!(Bytes, Bytes) => BF::from(func::Eq) => Bool, 1955;
6047            params!(String, String) => BF::from(func::Eq) => Bool, 98;
6048            params!(Char, Char) => BF::from(func::Eq) => Bool, 1054;
6049            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::Eq) => Bool, 92;
6050            params!(PgLegacyName, PgLegacyName) => BF::from(func::Eq) => Bool, 93;
6051            params!(Jsonb, Jsonb) => BF::from(func::Eq) => Bool, 3240;
6052            params!(ListAny, ListAny) => BF::from(func::Eq) => Bool, oid::FUNC_LIST_EQ_OID;
6053            params!(ArrayAny, ArrayAny) => BF::from(func::Eq) => Bool, 1070;
6054            params!(RecordAny, RecordAny) => BF::from(func::Eq) => Bool, 2988;
6055            params!(MzTimestamp, MzTimestamp) => BF::from(func::Eq)
6056                => Bool, oid::FUNC_MZ_TIMESTAMP_EQ_MZ_TIMESTAMP_OID;
6057            params!(RangeAny, RangeAny) => BF::from(func::Eq) => Bool, 3882;
6058            params!(MzAclItem, MzAclItem) => BF::from(func::Eq)
6059                => Bool, oid::FUNC_MZ_ACL_ITEM_EQ_MZ_ACL_ITEM_OID;
6060            params!(AclItem, AclItem) => BF::from(func::Eq) => Bool, 974;
6061        },
6062        "<>" => Scalar {
6063            params!(Numeric, Numeric) => BF::from(func::NotEq) => Bool, 1753;
6064            params!(Bool, Bool) => BF::from(func::NotEq) => Bool, 85;
6065            params!(Int16, Int16) => BF::from(func::NotEq) => Bool, 519;
6066            params!(Int32, Int32) => BF::from(func::NotEq) => Bool, 518;
6067            params!(Int64, Int64) => BF::from(func::NotEq) => Bool, 411;
6068            params!(UInt16, UInt16) => BF::from(func::NotEq) => Bool, oid::FUNC_NOT_EQ_UINT16_OID;
6069            params!(UInt32, UInt32) => BF::from(func::NotEq) => Bool, oid::FUNC_NOT_EQ_UINT32_OID;
6070            params!(UInt64, UInt64) => BF::from(func::NotEq) => Bool, oid::FUNC_NOT_EQ_UINT64_OID;
6071            params!(Float32, Float32) => BF::from(func::NotEq) => Bool, 621;
6072            params!(Float64, Float64) => BF::from(func::NotEq) => Bool, 671;
6073            params!(Oid, Oid) => BF::from(func::NotEq) => Bool, 608;
6074            params!(Date, Date) => BF::from(func::NotEq) => Bool, 1094;
6075            params!(Time, Time) => BF::from(func::NotEq) => Bool, 1109;
6076            params!(Timestamp, Timestamp) => BF::from(func::NotEq) => Bool, 2061;
6077            params!(TimestampTz, TimestampTz) => BF::from(func::NotEq) => Bool, 1321;
6078            params!(Uuid, Uuid) => BF::from(func::NotEq) => Bool, 2973;
6079            params!(Interval, Interval) => BF::from(func::NotEq) => Bool, 1331;
6080            params!(Bytes, Bytes) => BF::from(func::NotEq) => Bool, 1956;
6081            params!(String, String) => BF::from(func::NotEq) => Bool, 531;
6082            params!(Char, Char) => BF::from(func::NotEq) => Bool, 1057;
6083            params!(PgLegacyChar, PgLegacyChar) => BF::from(func::NotEq) => Bool, 630;
6084            params!(PgLegacyName, PgLegacyName) => BF::from(func::NotEq) => Bool, 643;
6085            params!(Jsonb, Jsonb) => BF::from(func::NotEq) => Bool, 3241;
6086            params!(ArrayAny, ArrayAny) => BF::from(func::NotEq) => Bool, 1071;
6087            params!(RecordAny, RecordAny) => BF::from(func::NotEq) => Bool, 2989;
6088            params!(MzTimestamp, MzTimestamp) => BF::from(func::NotEq)
6089                => Bool, oid::FUNC_MZ_TIMESTAMP_NOT_EQ_MZ_TIMESTAMP_OID;
6090            params!(RangeAny, RangeAny) => BF::from(func::NotEq) => Bool, 3883;
6091            params!(MzAclItem, MzAclItem) => BF::from(func::NotEq)
6092                => Bool, oid::FUNC_MZ_ACL_ITEM_NOT_EQ_MZ_ACL_ITEM_OID;
6093        }
6094    }
6095});
6096
6097/// Resolves the operator to a set of function implementations.
6098pub fn resolve_op(op: &str) -> Result<&'static [FuncImpl<HirScalarExpr>], PlanError> {
6099    match OP_IMPLS.get(op) {
6100        Some(Func::Scalar(impls)) => Ok(impls),
6101        Some(_) => unreachable!("all operators must be scalar functions"),
6102        // TODO: these require sql arrays
6103        // JsonContainsAnyFields
6104        // JsonContainsAllFields
6105        // TODO: these require json paths
6106        // JsonGetPath
6107        // JsonGetPathAsText
6108        // JsonDeletePath
6109        // JsonContainsPath
6110        // JsonApplyPathPredicate
6111        None => bail_unsupported!(format!("[{}]", op)),
6112    }
6113}
6114
6115// Since ViewableVariables is unmaterializeable (which can't be eval'd) that
6116// depend on their arguments, implement directly with Hir.
6117fn current_settings(
6118    name: HirScalarExpr,
6119    missing_ok: HirScalarExpr,
6120) -> Result<HirScalarExpr, PlanError> {
6121    // MapGetValue returns Null if the key doesn't exist in the map.
6122    let expr = HirScalarExpr::call_binary(
6123        HirScalarExpr::call_unmaterializable(UnmaterializableFunc::ViewableVariables),
6124        HirScalarExpr::call_unary(name, UnaryFunc::Lower(func::Lower)),
6125        func::MapGetValue,
6126    );
6127    let expr = HirScalarExpr::if_then_else(
6128        missing_ok,
6129        expr.clone(),
6130        HirScalarExpr::call_variadic(
6131            variadic::ErrorIfNull,
6132            vec![
6133                expr,
6134                HirScalarExpr::literal(
6135                    Datum::String("unrecognized configuration parameter"),
6136                    SqlScalarType::String,
6137                ),
6138            ],
6139        ),
6140    );
6141    Ok(expr)
6142}