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