Skip to main content

mz_sql/
func.rs

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