Skip to main content

mz_sql/
func.rs

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