Skip to main content

mz_sql/plan/
typeconv.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//! Maintains a catalog of valid casts between [`mz_repr::SqlScalarType`]s, as well as
11//! other cast-related functions.
12
13use std::cell::RefCell;
14use std::collections::BTreeMap;
15use std::sync::LazyLock;
16
17use dynfmt::{Format, SimpleCurlyFormat};
18use itertools::Itertools;
19use mz_expr::func;
20use mz_expr::func::variadic::{JsonbBuildObject, RecordCreate};
21use mz_expr::func::{CastArrayToJsonb, CastListToJsonb};
22use mz_ore::soft_panic_or_log;
23use mz_repr::{
24    ColumnName, Datum, Row, SqlColumnType, SqlRelationType, SqlScalarBaseType, SqlScalarType,
25};
26use mz_sql_parser::ast::CastFailureMode;
27
28use crate::catalog::TypeCategory;
29use crate::plan::error::PlanError;
30use crate::plan::hir::{
31    AbstractColumnType, CoercibleScalarExpr, CoercibleScalarType, HirScalarExpr, NameMetadata,
32    UnaryFunc,
33};
34use crate::plan::query::{ExprContext, QueryContext};
35use crate::plan::scope::Scope;
36
37/// Like func::sql_impl_func, but for casts.
38fn sql_impl_cast(expr: &'static str) -> CastTemplate {
39    let invoke = crate::func::sql_impl(expr);
40    CastTemplate::strict_only(move |ecx, _ccx, from_type, _to_type| {
41        // Oddly, this needs to be able to gracefully fail so we can detect unmet dependencies.
42        let mut out = invoke(ecx, vec![from_type.clone()]).ok()?;
43        Some(move |e| {
44            out.splice_parameters(&[e], 0);
45            out
46        })
47    })
48}
49
50fn sql_impl_cast_per_context(casts: &[(CastContext, &'static str)]) -> CastTemplate {
51    let casts: BTreeMap<CastContext, _> = casts
52        .iter()
53        .map(|(ccx, expr)| (ccx.clone(), crate::func::sql_impl(expr)))
54        .collect();
55    CastTemplate::strict_only(move |ecx, ccx, from_type, _to_type| {
56        let invoke = &casts[&ccx];
57        let r = invoke(ecx, vec![from_type.clone()]);
58        let mut out = r.ok()?;
59        Some(move |e| {
60            out.splice_parameters(&[e], 0);
61            out
62        })
63    })
64}
65
66/// A cast is a function that takes a `ScalarExpr` to another `ScalarExpr`.
67type Cast = Box<dyn FnOnce(HirScalarExpr) -> HirScalarExpr>;
68
69/// A cast template is a function that produces a `Cast` given a concrete input
70/// and output type and a failure mode. A template can return `None` to
71/// indicate that it is incapable of producing a cast for the specified types,
72/// or for the specified failure mode.
73///
74/// Cast templates are used to share code for similar casts, where the input or
75/// output type is of one "category" of type. For example, a single cast
76/// template handles converting from strings to any list type. Without cast
77/// templates, we'd have to enumerate every possible list -> list conversion,
78/// which is impractical.
79struct CastTemplate(
80    Box<
81        dyn Fn(
82                &ExprContext,
83                CastContext,
84                CastFailureMode,
85                &SqlScalarType,
86                &SqlScalarType,
87            ) -> Option<Cast>
88            + Send
89            + Sync,
90    >,
91);
92
93impl CastTemplate {
94    /// Builds a template from a builder of the erroring cast.
95    ///
96    /// Under [`CastFailureMode::NullFallback`] the builder is run once on a
97    /// placeholder of the input type, and its result must be that placeholder
98    /// wrapped in zero or more `CallUnary` nodes with nothing else in between.
99    /// The template then emits those stages with each one that could error
100    /// wrapped in `TryCast`, so a failure at any stage yields NULL, while a
101    /// stage that cannot error is emitted as is and keeps its nullability.
102    ///
103    /// A builder whose result has any other shape, for example one that
104    /// compares or branches on its input, makes `TRY_CAST` report that the
105    /// cast cannot fall back to NULL, even when the strict cast cannot error.
106    /// `test_try_cast_wraps_every_stage_of_every_cast` in `mz_adapter` plans
107    /// every entry of the cast table in both modes and fails on such a
108    /// builder. A template whose builder has another shape on purpose uses
109    /// [`CastTemplate::strict_only`].
110    fn new<T, C>(t: T) -> CastTemplate
111    where
112        T: Fn(&ExprContext, CastContext, &SqlScalarType, &SqlScalarType) -> Option<C>
113            + Send
114            + Sync
115            + 'static,
116        C: FnOnce(HirScalarExpr) -> HirScalarExpr + 'static,
117    {
118        CastTemplate(Box::new(move |ecx, ccx, mode, from_ty, to_ty| {
119            let cast = t(ecx, ccx, from_ty, to_ty)?;
120            match mode {
121                CastFailureMode::Error => Some(Box::new(cast)),
122                CastFailureMode::NullFallback => {
123                    let stages = extract_unary_stages_for_try_cast(from_ty, cast)?;
124                    Some(Box::new(move |mut expr: HirScalarExpr| {
125                        for stage in stages {
126                            expr = expr.call_unary(UnaryFunc::try_cast_if_fallible(stage));
127                        }
128                        expr
129                    }))
130                }
131            }
132        }))
133    }
134
135    /// Builds a template that supports only [`CastFailureMode::Error`].
136    ///
137    /// For casts implemented in SQL: their failures come from `mz_error_if_null`
138    /// calls and from subquery cardinality, neither of which a scalar-level
139    /// wrapper can turn into NULL.
140    fn strict_only<T, C>(t: T) -> CastTemplate
141    where
142        T: Fn(&ExprContext, CastContext, &SqlScalarType, &SqlScalarType) -> Option<C>
143            + Send
144            + Sync
145            + 'static,
146        C: FnOnce(HirScalarExpr) -> HirScalarExpr + 'static,
147    {
148        CastTemplate(Box::new(move |ecx, ccx, mode, from_ty, to_ty| match mode {
149            CastFailureMode::Error => Some(Box::new(t(ecx, ccx, from_ty, to_ty)?)),
150            CastFailureMode::NullFallback => None,
151        }))
152    }
153}
154
155/// The unary functions a strict cast from `from` applies to its input,
156/// innermost first, or `None` if the cast has any other shape. See
157/// [`CastTemplate::new`] for the shape a cast must have.
158///
159/// Every stage after the first must propagate NULLs, so that the NULL a
160/// failed stage produces flows through the rest of the chain unchanged:
161/// `f(g(e))` matches `try[f](try[g](e))` only when `f` returns NULL on a NULL
162/// input. A violation is reported as a planner bug rather than refused
163/// silently, since every cast function propagates NULLs and a new one that
164/// does not is a bug in that function.
165fn extract_unary_stages_for_try_cast(
166    from: &SqlScalarType,
167    cast: impl FnOnce(HirScalarExpr) -> HirScalarExpr,
168) -> Option<Vec<UnaryFunc>> {
169    // Templates never inspect their input, so a dummy of the input type serves
170    // to discover the stages they apply. Built directly because
171    // `HirScalarExpr::literal` asserts that the datum inhabits the type, which
172    // `Datum::Dummy` never does.
173    let sentinel = HirScalarExpr::Literal(
174        Row::pack([Datum::Dummy]),
175        from.clone().nullable(false),
176        NameMetadata::default(),
177    );
178    let mut expr = cast(sentinel.clone());
179    let mut stages = vec![];
180    loop {
181        match expr {
182            HirScalarExpr::CallUnary {
183                func, expr: inner, ..
184            } => {
185                stages.push(func);
186                expr = *inner;
187            }
188            other if other == sentinel => break,
189            _ => return None,
190        }
191    }
192    stages.reverse();
193    if let Some(stage) = stages
194        .iter()
195        .skip(1)
196        .find(|stage| !stage.propagates_nulls())
197    {
198        soft_panic_or_log!("cast stage {stage} does not propagate NULLs; TRY_CAST cannot wrap it");
199        return None;
200    }
201    Some(stages)
202}
203
204impl From<UnaryFunc> for CastTemplate {
205    fn from(u: UnaryFunc) -> CastTemplate {
206        CastTemplate::new(move |_ecx, _ccx, _from, _to| {
207            let u = u.clone();
208            Some(move |expr: HirScalarExpr| expr.call_unary(u))
209        })
210    }
211}
212
213impl<const N: usize> From<[UnaryFunc; N]> for CastTemplate {
214    fn from(funcs: [UnaryFunc; N]) -> CastTemplate {
215        CastTemplate::new(move |_ecx, _ccx, _from, _to| {
216            let funcs = funcs.clone();
217            Some(move |mut expr: HirScalarExpr| {
218                for func in funcs {
219                    expr = expr.call_unary(func.clone());
220                }
221                expr
222            })
223        })
224    }
225}
226
227/// STRING to REG*
228///
229/// A reg* type represents a specific type of object by oid.
230///
231/// Casting from a string to a reg*:
232/// - Accepts `-`, the absent reference, as OID 0 in all cases, matching
233///   PostgreSQL's `parseDashOrOid`.
234/// - Accepts a string that looks like an OID and converts the value to the
235///   specified reg* type. This is available in all cases except explicitly
236///   casting text values to regclass (e.g. `SELECT '2'::text::regclass`)
237/// - Resolves non-OID-appearing strings to objects. If this string resolves to
238///   more than one OID (e.g. functions), it errors.
239///
240/// The below code provides a template to accomplish this for various reg*
241/// types. Arguments in order are:
242/// - 0: type catalog name this is casting to
243/// - 1: the category of this reg for the error message
244/// - 2: Whether or not to permit passing through numeric values as OIDs
245const STRING_REG_CAST_TEMPLATE: &str = "
246(SELECT
247CASE
248    WHEN $1 IS NULL THEN NULL
249-- Handle the absent reference, which the reg* text output spells `-`. PostgreSQL's
250-- `parseDashOrOid` accepts it for every reg* type, including text to regclass.
251    WHEN $1 = '-' THEN 0::pg_catalog.oid::pg_catalog.{0}
252-- Handle OID-like input, if available via {2}
253    WHEN {2} AND pg_catalog.substring($1, 1, 1) BETWEEN '0' AND '9' THEN
254        $1::pg_catalog.oid::pg_catalog.{0}
255    ELSE (
256    -- String case; look up that the item exists
257        SELECT o.oid
258        FROM mz_unsafe.mz_error_if_null(
259            (
260                -- We need to ensure a distinct here in the case of e.g. functions,
261                -- where multiple items share a GlobalId.
262                SELECT DISTINCT id AS name_id
263                FROM mz_internal.mz_resolve_object_name('{0}', $1)
264            ),
265            -- TODO: Support the correct error code for does not exist (42883).
266            '{1} \"' || $1 || '\" does not exist'
267        ) AS i (name_id),
268        -- Lateral lets us error separately from DNE case
269        LATERAL (
270            SELECT
271                CASE
272            -- Handle too many OIDs
273                WHEN mz_catalog.list_length(mz_catalog.list_agg(oid)) > 1 THEN
274                    mz_unsafe.mz_error_if_null(
275                        NULL::pg_catalog.{0},
276                        'more than one {1} named \"' || $1 || '\"'
277                    )
278            -- Resolve object name's OID if we know there is only one
279                ELSE
280                    CAST(mz_catalog.list_agg(oid)[1] AS pg_catalog.{0})
281                END
282            FROM mz_catalog.mz_objects
283            WHERE id = name_id
284            GROUP BY id
285        ) AS o (oid)
286    )
287END)";
288
289static STRING_TO_REGCLASS_EXPLICIT: LazyLock<String> = LazyLock::new(|| {
290    SimpleCurlyFormat
291        .format(STRING_REG_CAST_TEMPLATE, ["regclass", "relation", "false"])
292        .unwrap()
293        .to_string()
294});
295
296static STRING_TO_REGCLASS_COERCED: LazyLock<String> = LazyLock::new(|| {
297    SimpleCurlyFormat
298        .format(STRING_REG_CAST_TEMPLATE, ["regclass", "relation", "true"])
299        .unwrap()
300        .to_string()
301});
302
303static STRING_TO_REGPROC: LazyLock<String> = LazyLock::new(|| {
304    SimpleCurlyFormat
305        .format(STRING_REG_CAST_TEMPLATE, ["regproc", "function", "true"])
306        .unwrap()
307        .to_string()
308});
309
310static STRING_TO_REGTYPE: LazyLock<String> = LazyLock::new(|| {
311    SimpleCurlyFormat
312        .format(STRING_REG_CAST_TEMPLATE, ["regtype", "type", "true"])
313        .unwrap()
314        .to_string()
315});
316
317// OID 0 renders as `-` to match PostgreSQL's `regprocout`, `regclassout` and
318// `regtypeout`, which all spell the absent reference that way. A nonzero OID
319// that names nothing still renders as its digits, also as PostgreSQL does.
320const REG_STRING_CAST_TEMPLATE: &str = "(
321SELECT
322    CASE
323      WHEN CAST($1 AS pg_catalog.oid) = 0::pg_catalog.oid THEN '-'
324      ELSE
325        COALESCE(mz_internal.mz_global_id_to_name(o.id), CAST($1 AS pg_catalog.oid)::pg_catalog.text)
326    END
327    AS text
328FROM
329  (
330        SELECT
331          (
332            SELECT DISTINCT id
333            FROM
334              mz_catalog.mz_objects AS o
335                JOIN
336                  mz_internal.mz_object_oid_alias AS a
337                  ON o.type = a.object_type
338            WHERE
339              oid = CAST($1 AS pg_catalog.oid)
340                AND
341              a.oid_alias = '{0}'
342          )
343      )
344      AS o
345)";
346
347static REGCLASS_TO_STRING: LazyLock<String> = LazyLock::new(|| {
348    SimpleCurlyFormat
349        .format(REG_STRING_CAST_TEMPLATE, ["regclass"])
350        .unwrap()
351        .to_string()
352});
353
354static REGPROC_TO_STRING: LazyLock<String> = LazyLock::new(|| {
355    SimpleCurlyFormat
356        .format(REG_STRING_CAST_TEMPLATE, ["regproc"])
357        .unwrap()
358        .to_string()
359});
360
361static REGTYPE_TO_STRING: LazyLock<String> = LazyLock::new(|| {
362    SimpleCurlyFormat
363        .format(REG_STRING_CAST_TEMPLATE, ["regtype"])
364        .unwrap()
365        .to_string()
366});
367
368/// Describes the context of a cast.
369///
370/// n.b. this type derived `Ord, PartialOrd` and the ordering of these values
371/// has semantics meaning; casts are only permitted when the caller's cast
372/// context is geq the ccx we store, which is the minimum required context.
373#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
374pub enum CastContext {
375    /// Implicit casts are "no-brainer" casts that apply automatically in
376    /// expressions. They are typically lossless, such as `SqlScalarType::Int32` to
377    /// `SqlScalarType::Int64`.
378    Implicit,
379    /// Assignment casts are "reasonable" casts that make sense to apply
380    /// automatically in `INSERT` statements, but are surprising enough that
381    /// they don't apply implicitly in expressions.
382    Assignment,
383    /// Explicit casts are casts that are possible but may be surprising, like
384    /// casting `SqlScalarType::Json` to `SqlScalarType::Int32`, and therefore they do
385    /// not happen unless explicitly requested by the user with a cast operator.
386    Explicit,
387    /// Coerced casts permit different behavior when a type is coerced from a
388    /// string literal vs. a value of type `pg_catalog::text`.
389    ///
390    /// The only call site that should pass this value in to this module is
391    /// string coercion.
392    Coerced,
393}
394
395/// The implementation of a cast.
396struct CastImpl {
397    template: CastTemplate,
398    context: CastContext,
399}
400
401macro_rules! casts(
402    {
403        $(
404            $from_to:expr => $cast_context:ident: $cast_template:expr
405        ),+
406    } => {{
407        let mut m = BTreeMap::new();
408        $(
409            m.insert($from_to, CastImpl {
410                template: $cast_template.into(),
411                context: CastContext::$cast_context,
412            });
413        )+
414        m
415    }};
416);
417
418static VALID_CASTS: LazyLock<BTreeMap<(SqlScalarBaseType, SqlScalarBaseType), CastImpl>> =
419    LazyLock::new(|| {
420        use SqlScalarBaseType::*;
421        use UnaryFunc::*;
422
423        casts! {
424            // BOOL
425            (Bool, Int32) => Explicit: CastBoolToInt32(func::CastBoolToInt32),
426            (Bool, Int64) => Explicit: CastBoolToInt64(func::CastBoolToInt64),
427            (Bool, String) => Assignment: CastBoolToString(func::CastBoolToString),
428
429            //INT16
430            (Int16, Int32) => Implicit: CastInt16ToInt32(func::CastInt16ToInt32),
431            (Int16, Int64) => Implicit: CastInt16ToInt64(func::CastInt16ToInt64),
432            (Int16, UInt16) => Assignment: CastInt16ToUint16(func::CastInt16ToUint16),
433            (Int16, UInt32) => Assignment: CastInt16ToUint32(func::CastInt16ToUint32),
434            (Int16, UInt64) => Assignment: CastInt16ToUint64(func::CastInt16ToUint64),
435            (Int16, Float32) => Implicit: CastInt16ToFloat32(func::CastInt16ToFloat32),
436            (Int16, Float64) => Implicit: CastInt16ToFloat64(func::CastInt16ToFloat64),
437            (Int16, Numeric) => Implicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
438                let s = to_type.unwrap_numeric_max_scale();
439                let f = CastInt16ToNumeric(func::CastInt16ToNumeric(s));
440                Some(move |e: HirScalarExpr| e.call_unary(f))
441            }),
442            (Int16, Oid) => Implicit: [
443                CastInt16ToInt32(func::CastInt16ToInt32),
444                CastInt32ToOid(func::CastInt32ToOid),
445            ],
446            (Int16, RegClass) => Implicit: [
447                CastInt16ToInt32(func::CastInt16ToInt32),
448                CastInt32ToOid(func::CastInt32ToOid),
449                CastOidToRegClass(func::CastOidToRegClass),
450            ],
451            (Int16, RegProc) => Implicit: [
452                CastInt16ToInt32(func::CastInt16ToInt32),
453                CastInt32ToOid(func::CastInt32ToOid),
454                CastOidToRegProc(func::CastOidToRegProc),
455            ],
456            (Int16, RegType) => Implicit: [
457                CastInt16ToInt32(func::CastInt16ToInt32),
458                CastInt32ToOid(func::CastInt32ToOid),
459                CastOidToRegType(func::CastOidToRegType),
460            ],
461            (Int16, String) => Assignment: CastInt16ToString(func::CastInt16ToString),
462
463            //INT32
464            (Int32, Bool) => Explicit: CastInt32ToBool(func::CastInt32ToBool),
465            (Int32, Oid) => Implicit: CastInt32ToOid(func::CastInt32ToOid),
466            (Int32, RegClass) => Implicit: [
467                CastInt32ToOid(func::CastInt32ToOid),
468                CastOidToRegClass(func::CastOidToRegClass),
469            ],
470            (Int32, RegProc) => Implicit: [
471                CastInt32ToOid(func::CastInt32ToOid),
472                CastOidToRegProc(func::CastOidToRegProc),
473            ],
474            (Int32, RegType) => Implicit: [
475                CastInt32ToOid(func::CastInt32ToOid),
476                CastOidToRegType(func::CastOidToRegType),
477            ],
478            (Int32, PgLegacyChar) => Explicit:
479                CastInt32ToPgLegacyChar(func::CastInt32ToPgLegacyChar),
480            (Int32, Int16) => Assignment: CastInt32ToInt16(func::CastInt32ToInt16),
481            (Int32, Int64) => Implicit: CastInt32ToInt64(func::CastInt32ToInt64),
482            (Int32, UInt16) => Assignment: CastInt32ToUint16(func::CastInt32ToUint16),
483            (Int32, UInt32) => Assignment: CastInt32ToUint32(func::CastInt32ToUint32),
484            (Int32, UInt64) => Assignment: CastInt32ToUint64(func::CastInt32ToUint64),
485            (Int32, Float32) => Implicit: CastInt32ToFloat32(func::CastInt32ToFloat32),
486            (Int32, Float64) => Implicit: CastInt32ToFloat64(func::CastInt32ToFloat64),
487            (Int32, Numeric) => Implicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
488                let s = to_type.unwrap_numeric_max_scale();
489                let f = CastInt32ToNumeric(func::CastInt32ToNumeric(s));
490                Some(move |e: HirScalarExpr| e.call_unary(f))
491            }),
492            (Int32, String) => Assignment: CastInt32ToString(func::CastInt32ToString),
493
494            // INT64
495            (Int64, Bool) => Explicit: CastInt64ToBool(func::CastInt64ToBool),
496            (Int64, Int16) => Assignment: CastInt64ToInt16(func::CastInt64ToInt16),
497            (Int64, Int32) => Assignment: CastInt64ToInt32(func::CastInt64ToInt32),
498            (Int64, UInt16) => Assignment: CastInt64ToUint16(func::CastInt64ToUint16),
499            (Int64, UInt32) => Assignment: CastInt64ToUint32(func::CastInt64ToUint32),
500            (Int64, UInt64) => Assignment: CastInt64ToUint64(func::CastInt64ToUint64),
501            (Int64, Numeric) => Implicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
502                let s = to_type.unwrap_numeric_max_scale();
503                let f = CastInt64ToNumeric(func::CastInt64ToNumeric(s));
504                Some(move |e: HirScalarExpr| e.call_unary(f))
505            }),
506            (Int64, Float32) => Implicit: CastInt64ToFloat32(func::CastInt64ToFloat32),
507            (Int64, Float64) => Implicit: CastInt64ToFloat64(func::CastInt64ToFloat64),
508            (Int64, Oid) => Implicit: CastInt64ToOid(func::CastInt64ToOid),
509            (Int64, RegClass) => Implicit: [
510                CastInt64ToOid(func::CastInt64ToOid),
511                CastOidToRegClass(func::CastOidToRegClass),
512            ],
513            (Int64, RegProc) => Implicit: [
514                CastInt64ToOid(func::CastInt64ToOid),
515                CastOidToRegProc(func::CastOidToRegProc),
516            ],
517            (Int64, RegType) => Implicit: [
518                CastInt64ToOid(func::CastInt64ToOid),
519                CastOidToRegType(func::CastOidToRegType),
520            ],
521            (Int64, String) => Assignment: CastInt64ToString(func::CastInt64ToString),
522
523            // UINT16
524            (UInt16, UInt32) => Implicit: CastUint16ToUint32(func::CastUint16ToUint32),
525            (UInt16, UInt64) => Implicit: CastUint16ToUint64(func::CastUint16ToUint64),
526            (UInt16, Int16) => Assignment: CastUint16ToInt16(func::CastUint16ToInt16),
527            (UInt16, Int32) => Implicit: CastUint16ToInt32(func::CastUint16ToInt32),
528            (UInt16, Int64) => Implicit: CastUint16ToInt64(func::CastUint16ToInt64),
529            (UInt16, Numeric) => Implicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
530                let s = to_type.unwrap_numeric_max_scale();
531                let f = CastUint16ToNumeric(func::CastUint16ToNumeric(s));
532                Some(move |e: HirScalarExpr| e.call_unary(f))
533            }),
534            (UInt16, Float32) => Implicit: CastUint16ToFloat32(func::CastUint16ToFloat32),
535            (UInt16, Float64) => Implicit: CastUint16ToFloat64(func::CastUint16ToFloat64),
536            (UInt16, String) => Assignment: CastUint16ToString(func::CastUint16ToString),
537
538            // UINT32
539            (UInt32, UInt16) => Assignment: CastUint32ToUint16(func::CastUint32ToUint16),
540            (UInt32, UInt64) => Implicit: CastUint32ToUint64(func::CastUint32ToUint64),
541            (UInt32, Int16) => Assignment: CastUint32ToInt16(func::CastUint32ToInt16),
542            (UInt32, Int32) => Assignment: CastUint32ToInt32(func::CastUint32ToInt32),
543            (UInt32, Int64) => Implicit: CastUint32ToInt64(func::CastUint32ToInt64),
544            (UInt32, Numeric) => Implicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
545                let s = to_type.unwrap_numeric_max_scale();
546                let f = CastUint32ToNumeric(func::CastUint32ToNumeric(s));
547                Some(move |e: HirScalarExpr| e.call_unary(f))
548            }),
549            (UInt32, Float32) => Implicit: CastUint32ToFloat32(func::CastUint32ToFloat32),
550            (UInt32, Float64) => Implicit: CastUint32ToFloat64(func::CastUint32ToFloat64),
551            (UInt32, String) => Assignment: CastUint32ToString(func::CastUint32ToString),
552
553            // UINT64
554            (UInt64, UInt16) => Assignment: CastUint64ToUint16(func::CastUint64ToUint16),
555            (UInt64, UInt32) => Assignment: CastUint64ToUint32(func::CastUint64ToUint32),
556            (UInt64, Int16) => Assignment: CastUint64ToInt16(func::CastUint64ToInt16),
557            (UInt64, Int32) => Assignment: CastUint64ToInt32(func::CastUint64ToInt32),
558            (UInt64, Int64) => Assignment: CastUint64ToInt64(func::CastUint64ToInt64),
559            (UInt64, Numeric) => Implicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
560                let s = to_type.unwrap_numeric_max_scale();
561                let f = CastUint64ToNumeric(func::CastUint64ToNumeric(s));
562                Some(move |e: HirScalarExpr| e.call_unary(f))
563            }),
564            (UInt64, Float32) => Implicit: CastUint64ToFloat32(func::CastUint64ToFloat32),
565            (UInt64, Float64) => Implicit: CastUint64ToFloat64(func::CastUint64ToFloat64),
566            (UInt64, String) => Assignment: CastUint64ToString(func::CastUint64ToString),
567
568            // MZ_TIMESTAMP
569            (MzTimestamp, String) => Assignment:
570                CastMzTimestampToString(func::CastMzTimestampToString),
571            (MzTimestamp, Timestamp) => Assignment:
572                CastMzTimestampToTimestamp(func::CastMzTimestampToTimestamp),
573            (MzTimestamp, TimestampTz) => Assignment:
574                CastMzTimestampToTimestampTz(
575                    func::CastMzTimestampToTimestampTz,
576                ),
577            (String, MzTimestamp) => Assignment:
578                CastStringToMzTimestamp(func::CastStringToMzTimestamp),
579            (UInt64, MzTimestamp) => Implicit:
580                CastUint64ToMzTimestamp(func::CastUint64ToMzTimestamp),
581            (UInt32, MzTimestamp) => Implicit:
582                CastUint32ToMzTimestamp(func::CastUint32ToMzTimestamp),
583            (Int64, MzTimestamp) => Implicit:
584                CastInt64ToMzTimestamp(func::CastInt64ToMzTimestamp),
585            (Int32, MzTimestamp) => Implicit:
586                CastInt32ToMzTimestamp(func::CastInt32ToMzTimestamp),
587            (Numeric, MzTimestamp) => Implicit:
588                CastNumericToMzTimestamp(func::CastNumericToMzTimestamp),
589            (Timestamp, MzTimestamp) => Implicit:
590                CastTimestampToMzTimestamp(func::CastTimestampToMzTimestamp),
591            (TimestampTz, MzTimestamp) => Implicit:
592                CastTimestampTzToMzTimestamp(
593                    func::CastTimestampTzToMzTimestamp,
594                ),
595            (Date, MzTimestamp) => Implicit:
596                CastDateToMzTimestamp(func::CastDateToMzTimestamp),
597
598            // OID
599            (Oid, Int32) => Assignment: CastOidToInt32(func::CastOidToInt32),
600            (Oid, Int64) => Assignment: CastOidToInt64(func::CastOidToInt64),
601            (Oid, String) => Explicit: CastOidToString(func::CastOidToString),
602            (Oid, RegClass) => Implicit: CastOidToRegClass(func::CastOidToRegClass),
603            (Oid, RegProc) => Implicit: CastOidToRegProc(func::CastOidToRegProc),
604            (Oid, RegType) => Implicit: CastOidToRegType(func::CastOidToRegType),
605
606            // REGCLASS
607            (RegClass, Oid) => Implicit: CastRegClassToOid(func::CastRegClassToOid),
608            (RegClass, String) => Explicit: sql_impl_cast(&REGCLASS_TO_STRING),
609
610            // REGPROC
611            (RegProc, Oid) => Implicit: CastRegProcToOid(func::CastRegProcToOid),
612            (RegProc, String) => Explicit: sql_impl_cast(&REGPROC_TO_STRING),
613
614            // REGTYPE
615            (RegType, Oid) => Implicit: CastRegTypeToOid(func::CastRegTypeToOid),
616            (RegType, String) => Explicit: sql_impl_cast(&REGTYPE_TO_STRING),
617
618            // FLOAT32
619            (Float32, Int16) => Assignment: CastFloat32ToInt16(func::CastFloat32ToInt16),
620            (Float32, Int32) => Assignment: CastFloat32ToInt32(func::CastFloat32ToInt32),
621            (Float32, Int64) => Assignment: CastFloat32ToInt64(func::CastFloat32ToInt64),
622            (Float32, UInt16) => Assignment: CastFloat32ToUint16(func::CastFloat32ToUint16),
623            (Float32, UInt32) => Assignment: CastFloat32ToUint32(func::CastFloat32ToUint32),
624            (Float32, UInt64) => Assignment: CastFloat32ToUint64(func::CastFloat32ToUint64),
625            (Float32, Float64) => Implicit: CastFloat32ToFloat64(func::CastFloat32ToFloat64),
626            (Float32, Numeric) => Assignment: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
627                let s = to_type.unwrap_numeric_max_scale();
628                let f = CastFloat32ToNumeric(func::CastFloat32ToNumeric(s));
629                Some(move |e: HirScalarExpr| e.call_unary(f))
630            }),
631            (Float32, String) => Assignment: CastFloat32ToString(func::CastFloat32ToString),
632
633            // FLOAT64
634            (Float64, Int16) => Assignment: CastFloat64ToInt16(func::CastFloat64ToInt16),
635            (Float64, Int32) => Assignment: CastFloat64ToInt32(func::CastFloat64ToInt32),
636            (Float64, Int64) => Assignment: CastFloat64ToInt64(func::CastFloat64ToInt64),
637            (Float64, UInt16) => Assignment: CastFloat64ToUint16(func::CastFloat64ToUint16),
638            (Float64, UInt32) => Assignment: CastFloat64ToUint32(func::CastFloat64ToUint32),
639            (Float64, UInt64) => Assignment: CastFloat64ToUint64(func::CastFloat64ToUint64),
640            (Float64, Float32) => Assignment: CastFloat64ToFloat32(func::CastFloat64ToFloat32),
641            (Float64, Numeric) => Assignment: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
642                let s = to_type.unwrap_numeric_max_scale();
643                let f = CastFloat64ToNumeric(func::CastFloat64ToNumeric(s));
644                Some(move |e: HirScalarExpr| e.call_unary(f))
645            }),
646            (Float64, String) => Assignment: CastFloat64ToString(func::CastFloat64ToString),
647
648            // DATE
649            (Date, Timestamp) => Implicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
650                let p = to_type.unwrap_timestamp_precision();
651                let f = CastDateToTimestamp(func::CastDateToTimestamp(p));
652                Some(move |e: HirScalarExpr| e.call_unary(f))
653            }),
654            (Date, TimestampTz) => Implicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
655                let p = to_type.unwrap_timestamp_precision();
656                let f = CastDateToTimestampTz(func::CastDateToTimestampTz(p));
657                Some(move |e: HirScalarExpr| e.call_unary(f))
658            }),
659            (Date, String) => Assignment: CastDateToString(func::CastDateToString),
660
661            // TIME
662            (Time, Interval) => Implicit: CastTimeToInterval(func::CastTimeToInterval),
663            (Time, String) => Assignment: CastTimeToString(func::CastTimeToString),
664
665            // TIMESTAMP
666            (Timestamp, Date) => Assignment: CastTimestampToDate(func::CastTimestampToDate),
667            (Timestamp, TimestampTz) => Implicit: CastTemplate::new(
668                |_ecx, _ccx, from_type, to_type|
669            {
670                let from = from_type.unwrap_timestamp_precision();
671                let to = to_type.unwrap_timestamp_precision();
672                let f = CastTimestampToTimestampTz(
673                    func::CastTimestampToTimestampTz { from, to },
674                );
675                Some(move |e: HirScalarExpr| e.call_unary(f))
676            }),
677            (Timestamp, Timestamp) => Assignment: CastTemplate::new(
678                |_ecx, _ccx, from_type, to_type|
679            {
680                let from = from_type.unwrap_timestamp_precision();
681                let to = to_type.unwrap_timestamp_precision();
682                let f = AdjustTimestampPrecision(
683                    func::AdjustTimestampPrecision { from, to },
684                );
685                Some(move |e: HirScalarExpr| e.call_unary(f))
686            }),
687            (Timestamp, Time) => Assignment: CastTimestampToTime(func::CastTimestampToTime),
688            (Timestamp, String) => Assignment: CastTimestampToString(func::CastTimestampToString),
689
690            // TIMESTAMPTZ
691            (TimestampTz, Date) => Assignment: CastTimestampTzToDate(func::CastTimestampTzToDate),
692            (TimestampTz, Timestamp) => Assignment: CastTemplate::new(
693                |_ecx, _ccx, from_type, to_type|
694            {
695                let from = from_type.unwrap_timestamp_precision();
696                let to = to_type.unwrap_timestamp_precision();
697                let f = CastTimestampTzToTimestamp(
698                    func::CastTimestampTzToTimestamp { from, to },
699                );
700                Some(move |e: HirScalarExpr| e.call_unary(f))
701            }),
702            (TimestampTz, TimestampTz) => Assignment: CastTemplate::new(
703                |_ecx, _ccx, from_type, to_type|
704            {
705                let from = from_type.unwrap_timestamp_precision();
706                let to = to_type.unwrap_timestamp_precision();
707                let f = AdjustTimestampTzPrecision(
708                    func::AdjustTimestampTzPrecision { from, to },
709                );
710                Some(move |e: HirScalarExpr| e.call_unary(f))
711            }),
712            (TimestampTz, Time) => Assignment:
713                CastTimestampTzToTime(func::CastTimestampTzToTime),
714            (TimestampTz, String) => Assignment:
715                CastTimestampTzToString(func::CastTimestampTzToString),
716
717            // INTERVAL
718            (Interval, Time) => Assignment: CastIntervalToTime(func::CastIntervalToTime),
719            (Interval, String) => Assignment: CastIntervalToString(func::CastIntervalToString),
720
721            // BYTES
722            (Bytes, String) => Assignment: CastBytesToString(func::CastBytesToString),
723
724            // STRING
725            (String, Bool) => Explicit: CastStringToBool(func::CastStringToBool),
726            (String, Int16) => Explicit: CastStringToInt16(func::CastStringToInt16),
727            (String, Int32) => Explicit: CastStringToInt32(func::CastStringToInt32),
728            (String, Int64) => Explicit: CastStringToInt64(func::CastStringToInt64),
729            (String, UInt16) => Explicit: CastStringToUint16(func::CastStringToUint16),
730            (String, UInt32) => Explicit: CastStringToUint32(func::CastStringToUint32),
731            (String, UInt64) => Explicit: CastStringToUint64(func::CastStringToUint64),
732            (String, Oid) => Explicit: CastStringToOid(func::CastStringToOid),
733
734            // STRING to REG*
735            // A reg* type represents a specific type of object by oid.
736            // Converting from string to reg* does a lookup of the object name
737            // in the corresponding mz_catalog table and expects exactly one object to match it.
738            // You can also specify (in postgres) a string that's a valid
739            // int4 and it'll happily cast it (without verifying that the int4 matches
740            // an object oid).
741            // TODO: Support the correct error code for does not exist (42883).
742            (String, RegClass) => Explicit: sql_impl_cast_per_context(
743                &[
744                    (CastContext::Explicit, &STRING_TO_REGCLASS_EXPLICIT),
745                    (CastContext::Coerced, &STRING_TO_REGCLASS_COERCED)
746                ]
747            ),
748            (String, RegProc) => Explicit: sql_impl_cast(&STRING_TO_REGPROC),
749            (String, RegType) => Explicit: sql_impl_cast(&STRING_TO_REGTYPE),
750
751            (String, Float32) => Explicit: CastStringToFloat32(func::CastStringToFloat32),
752            (String, Float64) => Explicit: CastStringToFloat64(func::CastStringToFloat64),
753            (String, Numeric) => Explicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
754                let s = to_type.unwrap_numeric_max_scale();
755                let f = CastStringToNumeric(func::CastStringToNumeric(s));
756                Some(move |e: HirScalarExpr| e.call_unary(f))
757            }),
758            (String, Date) => Explicit: CastStringToDate(func::CastStringToDate),
759            (String, Time) => Explicit: CastStringToTime(func::CastStringToTime),
760            (String, Timestamp) => Explicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
761                let p = to_type.unwrap_timestamp_precision();
762                let f = CastStringToTimestamp(func::CastStringToTimestamp(p));
763                Some(move |e: HirScalarExpr| e.call_unary(f))
764            }),
765            (String, TimestampTz) => Explicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
766                let p = to_type.unwrap_timestamp_precision();
767                let f = CastStringToTimestampTz(
768                    func::CastStringToTimestampTz(p),
769                );
770                Some(move |e: HirScalarExpr| e.call_unary(f))
771            }),
772            (String, Interval) => Explicit: CastStringToInterval(func::CastStringToInterval),
773            (String, Bytes) => Explicit: CastStringToBytes(func::CastStringToBytes),
774            (String, Jsonb) => Explicit: CastStringToJsonb(func::CastStringToJsonb),
775            (String, Uuid) => Explicit: CastStringToUuid(func::CastStringToUuid),
776            (String, Array) => Explicit: CastTemplate::new(|ecx, ccx, from_type, to_type| {
777                let return_ty = to_type.clone();
778                let to_el_type = to_type.unwrap_array_element_type();
779                let cast_expr = plan_hypothetical_cast(ecx, ccx, from_type, to_el_type)?;
780                Some(|e: HirScalarExpr| {
781                    e.call_unary(UnaryFunc::CastStringToArray(
782                        func::CastStringToArray {
783                            return_ty,
784                            cast_expr: Box::new(cast_expr),
785                        },
786                    ))
787                })
788            }),
789            (String, List) => Explicit: CastTemplate::new(|ecx, ccx, from_type, to_type| {
790                let return_ty = to_type.clone();
791                let to_el_type = to_type.unwrap_list_element_type();
792                let cast_expr = plan_hypothetical_cast(ecx, ccx, from_type, to_el_type)?;
793                Some(|e: HirScalarExpr| {
794                    e.call_unary(UnaryFunc::CastStringToList(
795                        func::CastStringToList {
796                            return_ty,
797                            cast_expr: Box::new(cast_expr),
798                        },
799                    ))
800                })
801            }),
802            (String, Map) => Explicit: CastTemplate::new(|ecx, ccx, from_type, to_type| {
803                let return_ty = to_type.clone();
804                let to_val_type = to_type.unwrap_map_value_type();
805                let cast_expr = plan_hypothetical_cast(ecx, ccx, from_type, to_val_type)?;
806                Some(|e: HirScalarExpr| {
807                    e.call_unary(UnaryFunc::CastStringToMap(
808                        func::CastStringToMap {
809                            return_ty,
810                            cast_expr: Box::new(cast_expr),
811                        },
812                    ))
813                })
814            }),
815            (String, Range) => Explicit: CastTemplate::new(|ecx, ccx, from_type, to_type| {
816                let return_ty = to_type.clone();
817                let to_el_type = to_type.unwrap_range_element_type();
818                let cast_expr = plan_hypothetical_cast(ecx, ccx, from_type, to_el_type)?;
819                Some(|e: HirScalarExpr| {
820                    e.call_unary(UnaryFunc::CastStringToRange(
821                        func::CastStringToRange {
822                            return_ty,
823                            cast_expr: Box::new(cast_expr),
824                        },
825                    ))
826                })
827            }),
828            (String, Int2Vector) => Explicit: CastStringToInt2Vector(func::CastStringToInt2Vector),
829            (String, Char) => Implicit: CastTemplate::new(|_ecx, ccx, _from_type, to_type| {
830                let length = to_type.unwrap_char_length();
831                let fail_on_len = ccx != CastContext::Explicit;
832                let f = CastStringToChar(func::CastStringToChar {
833                    length, fail_on_len,
834                });
835                Some(move |e: HirScalarExpr| e.call_unary(f))
836            }),
837            (String, VarChar) => Implicit: CastTemplate::new(|_ecx, ccx, _from_type, to_type| {
838                let length = to_type.unwrap_varchar_max_length();
839                let fail_on_len = ccx != CastContext::Explicit;
840                let f = CastStringToVarChar(func::CastStringToVarChar {
841                    length, fail_on_len,
842                });
843                Some(move |e: HirScalarExpr| e.call_unary(f))
844            }),
845            (String, PgLegacyChar) => Assignment:
846                CastStringToPgLegacyChar(func::CastStringToPgLegacyChar),
847            // CHAR
848            (Char, String) => Implicit: CastCharToString(func::CastCharToString),
849            (Char, Char) => Implicit: CastTemplate::new(|_ecx, ccx, _from_type, to_type| {
850                let length = to_type.unwrap_char_length();
851                let fail_on_len = ccx != CastContext::Explicit;
852                let f = CastStringToChar(func::CastStringToChar {
853                    length, fail_on_len,
854                });
855                Some(move |e: HirScalarExpr| e.call_unary(f))
856            }),
857            (Char, VarChar) => Implicit: CastTemplate::new(|_ecx, ccx, _from_type, to_type| {
858                let length = to_type.unwrap_varchar_max_length();
859                let fail_on_len = ccx != CastContext::Explicit;
860                let f = CastStringToVarChar(func::CastStringToVarChar {
861                    length, fail_on_len,
862                });
863                Some(move |e: HirScalarExpr| e.call_unary(f))
864            }),
865            (Char, PgLegacyChar) => Assignment:
866                CastStringToPgLegacyChar(func::CastStringToPgLegacyChar),
867
868            // VARCHAR
869            (VarChar, String) => Implicit: CastVarCharToString(func::CastVarCharToString),
870            (VarChar, Char) => Implicit: CastTemplate::new(|_ecx, ccx, _from_type, to_type| {
871                let length = to_type.unwrap_char_length();
872                let fail_on_len = ccx != CastContext::Explicit;
873                let f = CastStringToChar(func::CastStringToChar {
874                    length, fail_on_len,
875                });
876                Some(move |e: HirScalarExpr| e.call_unary(f))
877            }),
878            (VarChar, VarChar) => Implicit: CastTemplate::new(|_ecx, ccx, _from_type, to_type| {
879                let length = to_type.unwrap_varchar_max_length();
880                let fail_on_len = ccx != CastContext::Explicit;
881                let f = CastStringToVarChar(func::CastStringToVarChar {
882                    length, fail_on_len,
883                });
884                Some(move |e: HirScalarExpr| e.call_unary(f))
885            }),
886            (VarChar, PgLegacyChar) => Assignment:
887                CastStringToPgLegacyChar(func::CastStringToPgLegacyChar),
888
889            // PG LEGACY CHAR
890            (PgLegacyChar, String) => Implicit:
891                CastPgLegacyCharToString(func::CastPgLegacyCharToString),
892            (PgLegacyChar, Char) => Assignment:
893                CastPgLegacyCharToChar(func::CastPgLegacyCharToChar),
894            (PgLegacyChar, VarChar) => Assignment:
895                CastPgLegacyCharToVarChar(func::CastPgLegacyCharToVarChar),
896            (PgLegacyChar, Int32) => Explicit:
897                CastPgLegacyCharToInt32(func::CastPgLegacyCharToInt32),
898
899            // PG LEGACY NAME
900            // Under the hood VarChars and Name's are just Strings, so we
901            // can re-use existing methods
902            // on Strings and VarChars instead of defining new ones.
903            (PgLegacyName, String) => Implicit: CastVarCharToString(func::CastVarCharToString),
904            (PgLegacyName, Char) => Assignment: CastTemplate::new(|_ecx, ccx, _from_type, to_type| {
905                let length = to_type.unwrap_char_length();
906                let fail_on_len = ccx != CastContext::Explicit;
907                let f = CastStringToChar(func::CastStringToChar {
908                    length, fail_on_len,
909                });
910                Some(move |e: HirScalarExpr| e.call_unary(f))
911            }),
912            (PgLegacyName, VarChar) => Assignment: CastTemplate::new(
913                |_ecx, ccx, _from_type, to_type|
914            {
915                let length = to_type.unwrap_varchar_max_length();
916                let fail_on_len = ccx != CastContext::Explicit;
917                let f = CastStringToVarChar(func::CastStringToVarChar {
918                    length, fail_on_len,
919                });
920                Some(move |e: HirScalarExpr| e.call_unary(f))
921            }),
922            (String, PgLegacyName) => Implicit:
923                CastStringToPgLegacyName(func::CastStringToPgLegacyName),
924            (Char, PgLegacyName) => Implicit:
925                CastStringToPgLegacyName(func::CastStringToPgLegacyName),
926            (VarChar, PgLegacyName) => Implicit:
927                CastStringToPgLegacyName(func::CastStringToPgLegacyName),
928
929            // RECORD
930            (Record, String) => Assignment: CastTemplate::new(|_ecx, _ccx, from_type, _to_type| {
931                let ty = from_type.clone();
932                Some(|e: HirScalarExpr| {
933                    e.call_unary(CastRecordToString(
934                        func::CastRecordToString { ty },
935                    ))
936                })
937            }),
938            (Record, Record) => Implicit: CastTemplate::new(|ecx, ccx, from_type, to_type| {
939                let from_fields = from_type.unwrap_record_element_type();
940                let to_fields = to_type.unwrap_record_element_type();
941                if from_fields.len() != to_fields.len() {
942                    return None;
943                }
944
945                if let (
946                    l @ SqlScalarType::Record {
947                        custom_id: Some(..), ..
948                    },
949                    r,
950                ) = (from_type, to_type)
951                {
952                    // Changing `from`'s custom_id requires at least
953                    // Assignment context
954                    if ccx == CastContext::Implicit && l != r {
955                        return None;
956                    }
957                }
958
959                let cast_exprs = from_fields
960                    .iter()
961                    .zip_eq(to_fields)
962                    .map(|(f, t)| plan_hypothetical_cast(ecx, ccx, f, t))
963                    .collect::<Option<Box<_>>>()?;
964                let to = to_type.clone();
965                Some(|e: HirScalarExpr| {
966                    e.call_unary(CastRecord1ToRecord2(
967                        func::CastRecord1ToRecord2 {
968                            return_ty: to,
969                            cast_exprs,
970                        },
971                    ))
972                })
973            }),
974
975            // ARRAY
976            (Array, String) => Assignment: CastTemplate::new(|_ecx, _ccx, from_type, _to_type| {
977                let ty = from_type.clone();
978                Some(|e: HirScalarExpr| {
979                    e.call_unary(CastArrayToString(
980                        func::CastArrayToString { ty },
981                    ))
982                })
983            }),
984            (Array, List) => Explicit: CastArrayToListOneDim(func::CastArrayToListOneDim),
985            (Array, Array) => Explicit: CastTemplate::new(|ecx, ccx, from_type, to_type| {
986                let inner_from_type = from_type.unwrap_array_element_type();
987                let inner_to_type = to_type.unwrap_array_element_type();
988                let cast_expr = plan_hypothetical_cast(
989                    ecx, ccx, inner_from_type, inner_to_type,
990                )?;
991                let return_ty = to_type.clone();
992
993                Some(move |e: HirScalarExpr| {
994                    e.call_unary(CastArrayToArray(func::CastArrayToArray {
995                        return_ty,
996                        cast_expr: Box::new(cast_expr),
997                    }))
998                })
999            }),
1000
1001            // INT2VECTOR
1002            (Int2Vector, Array) => Implicit: CastTemplate::new(|ecx, ccx, _from_type, to_type| {
1003                let inner_to_type = to_type.unwrap_array_element_type();
1004                // Int2Vector elements are Int16, so if the target element type
1005                // differs we need to also cast the array elements after the
1006                // Int2Vector -> Array(Int16) conversion. (Postgres seems to do the same.)
1007                let element_cast = if inner_to_type != &SqlScalarType::Int16 {
1008                    let cast_expr = plan_hypothetical_cast(
1009                        ecx, ccx, &SqlScalarType::Int16, inner_to_type
1010                    )?;
1011                    Some((to_type.clone(), cast_expr))
1012                } else {
1013                    None
1014                };
1015                Some(move |e: HirScalarExpr| {
1016                    let arr = e.call_unary(
1017                        UnaryFunc::CastInt2VectorToArray(func::CastInt2VectorToArray)
1018                    );
1019                    match element_cast {
1020                        Some((return_ty, cast_expr)) => {
1021                            arr.call_unary(CastArrayToArray(
1022                                func::CastArrayToArray { return_ty, cast_expr: Box::new(cast_expr) }
1023                            ))
1024                        }
1025                        None => arr,
1026                    }
1027                })
1028            }),
1029            (Int2Vector, String) => Explicit: CastInt2VectorToString(func::CastInt2VectorToString),
1030
1031            // LIST
1032            (List, String) => Assignment: CastTemplate::new(|_ecx, _ccx, from_type, _to_type| {
1033                let ty = from_type.clone();
1034                Some(|e: HirScalarExpr| {
1035                    e.call_unary(CastListToString(
1036                        func::CastListToString { ty },
1037                    ))
1038                })
1039            }),
1040            (List, List) => Implicit: CastTemplate::new(|ecx, ccx, from_type, to_type| {
1041
1042                if let (
1043                    l @ SqlScalarType::List {
1044                        custom_id: Some(..), ..
1045                    },
1046                    r,
1047                ) = (from_type, to_type)
1048                {
1049                    // Changing `from`'s custom_id requires at least
1050                    // Assignment context
1051                    if ccx == CastContext::Implicit && !l.base_eq(r) {
1052                        return None;
1053                    }
1054                }
1055
1056                let return_ty = to_type.clone();
1057                let from_el_type = from_type.unwrap_list_element_type();
1058                let to_el_type = to_type.unwrap_list_element_type();
1059                let cast_expr = plan_hypothetical_cast(
1060                    ecx, ccx, from_el_type, to_el_type,
1061                )?;
1062                Some(|e: HirScalarExpr| {
1063                    e.call_unary(UnaryFunc::CastList1ToList2(
1064                        func::CastList1ToList2 {
1065                            return_ty,
1066                            cast_expr: Box::new(cast_expr),
1067                        },
1068                    ))
1069                })
1070            }),
1071
1072            // MAP
1073            (Map, String) => Assignment: CastTemplate::new(|_ecx, _ccx, from_type, _to_type| {
1074                let ty = from_type.clone();
1075                Some(|e: HirScalarExpr| e.call_unary(CastMapToString(func::CastMapToString { ty })))
1076            }),
1077
1078            // JSONB
1079            (Jsonb, Bool) => Explicit: CastJsonbToBool(func::CastJsonbToBool),
1080            (Jsonb, Int16) => Explicit: CastJsonbToInt16(func::CastJsonbToInt16),
1081            (Jsonb, Int32) => Explicit: CastJsonbToInt32(func::CastJsonbToInt32),
1082            (Jsonb, Int64) => Explicit: CastJsonbToInt64(func::CastJsonbToInt64),
1083            (Jsonb, Float32) => Explicit: CastJsonbToFloat32(func::CastJsonbToFloat32),
1084            (Jsonb, Float64) => Explicit: CastJsonbToFloat64(func::CastJsonbToFloat64),
1085            (Jsonb, Numeric) => Explicit: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
1086                let s = to_type.unwrap_numeric_max_scale();
1087                let f = CastJsonbToNumeric(func::CastJsonbToNumeric(s));
1088                Some(move |e: HirScalarExpr| e.call_unary(f))
1089            }),
1090            (Jsonb, String) => Assignment: CastJsonbToString(func::CastJsonbToString),
1091
1092            // UUID
1093            (Uuid, String) => Assignment: CastUuidToString(func::CastUuidToString),
1094
1095            // Numeric
1096            (Numeric, Numeric) => Assignment: CastTemplate::new(|_ecx, _ccx, _from_type, to_type| {
1097                let scale = to_type.unwrap_numeric_max_scale();
1098                Some(move |e: HirScalarExpr| match scale {
1099                    None => e,
1100                    Some(scale) => e.call_unary(
1101                        UnaryFunc::AdjustNumericScale(
1102                            func::AdjustNumericScale(scale),
1103                        ),
1104                    ),
1105                })
1106            }),
1107            (Numeric, Float32) => Implicit: CastNumericToFloat32(func::CastNumericToFloat32),
1108            (Numeric, Float64) => Implicit: CastNumericToFloat64(func::CastNumericToFloat64),
1109            (Numeric, Int16) => Assignment: CastNumericToInt16(func::CastNumericToInt16),
1110            (Numeric, Int32) => Assignment: CastNumericToInt32(func::CastNumericToInt32),
1111            (Numeric, Int64) => Assignment: CastNumericToInt64(func::CastNumericToInt64),
1112            (Numeric, UInt16) => Assignment: CastNumericToUint16(func::CastNumericToUint16),
1113            (Numeric, UInt32) => Assignment: CastNumericToUint32(func::CastNumericToUint32),
1114            (Numeric, UInt64) => Assignment: CastNumericToUint64(func::CastNumericToUint64),
1115            (Numeric, String) => Assignment: CastNumericToString(func::CastNumericToString),
1116
1117            // Range
1118            (Range, String) => Assignment: CastTemplate::new(|_ecx, _ccx, from_type, _to_type| {
1119                let ty = from_type.clone();
1120                Some(|e: HirScalarExpr| {
1121                    e.call_unary(CastRangeToString(
1122                        func::CastRangeToString { ty },
1123                    ))
1124                })
1125            }),
1126
1127            // MzAclItem
1128            (MzAclItem, String) => Explicit: sql_impl_cast("(
1129                SELECT
1130                    (CASE
1131                        WHEN grantee_role_id = 'p' THEN ''
1132                        ELSE COALESCE(grantee_role.name, grantee_role_id)
1133                    END)
1134                    || '='
1135                    || mz_internal.mz_aclitem_privileges($1)
1136                    || '/'
1137                    || COALESCE(grantor_role.name, grantor_role_id)
1138                FROM
1139                    (SELECT mz_internal.mz_aclitem_grantee($1) AS grantee_role_id),
1140                    (SELECT mz_internal.mz_aclitem_grantor($1) AS grantor_role_id)
1141                LEFT JOIN mz_catalog.mz_roles AS grantee_role ON grantee_role_id = grantee_role.id
1142                LEFT JOIN mz_catalog.mz_roles AS grantor_role ON grantor_role_id = grantor_role.id
1143            )"),
1144            (MzAclItem, AclItem) => Explicit: sql_impl_cast("(
1145                SELECT makeaclitem(
1146                    (CASE mz_internal.mz_aclitem_grantee($1)
1147                        WHEN 'p' THEN 0
1148                        ELSE (SELECT oid FROM mz_catalog.mz_roles
1149                            WHERE id = mz_internal.mz_aclitem_grantee($1))
1150                    END),
1151                    (SELECT oid FROM mz_catalog.mz_roles
1152                        WHERE id = mz_internal.mz_aclitem_grantor($1)),
1153                    (SELECT array_to_string(
1154                        mz_internal.mz_format_privileges(
1155                            mz_internal.mz_aclitem_privileges($1)
1156                        ), ',')),
1157                    -- GRANT OPTION isn't implemented so we hardcode false.
1158                    false
1159                )
1160            )"),
1161
1162            // AclItem
1163            (AclItem, String) => Explicit: sql_impl_cast("(
1164                SELECT
1165                    (CASE grantee_oid
1166                        WHEN 0 THEN ''
1167                        ELSE COALESCE(grantee_role.name, grantee_oid::text)
1168                    END)
1169                    || '='
1170                    || mz_internal.aclitem_privileges($1)
1171                    || '/'
1172                    || COALESCE(grantor_role.name, grantor_oid::text)
1173                FROM
1174                    (SELECT mz_internal.aclitem_grantee($1) AS grantee_oid),
1175                    (SELECT mz_internal.aclitem_grantor($1) AS grantor_oid)
1176                LEFT JOIN mz_catalog.mz_roles AS grantee_role ON grantee_oid = grantee_role.oid
1177                LEFT JOIN mz_catalog.mz_roles AS grantor_role ON grantor_oid = grantor_role.oid
1178            )"),
1179            (AclItem, MzAclItem) => Explicit: sql_impl_cast("(
1180                SELECT mz_internal.make_mz_aclitem(
1181                    (CASE mz_internal.aclitem_grantee($1)
1182                        WHEN 0 THEN 'p'
1183                        ELSE (SELECT id FROM mz_catalog.mz_roles
1184                            WHERE oid = mz_internal.aclitem_grantee($1))
1185                    END),
1186                    (SELECT id FROM mz_catalog.mz_roles
1187                        WHERE oid = mz_internal.aclitem_grantor($1)),
1188                    (SELECT array_to_string(
1189                        mz_internal.mz_format_privileges(
1190                            mz_internal.aclitem_privileges($1)
1191                        ), ','))
1192                )
1193            )")
1194        }
1195    });
1196
1197/// Error returned when a cast is not available or not allowed.
1198#[derive(Debug)]
1199pub enum CastError {
1200    /// No cast exists between the given types (or not in this cast context).
1201    InvalidCast {
1202        ccx: CastContext,
1203        from: String,
1204        to: String,
1205    },
1206    /// Cast would apply but is disallowed (e.g. range over an unsupported element type).
1207    UnsupportedRangeElementType { element_type_name: String },
1208    /// The cast exists but has no null-fallback form, so `TRY_CAST` cannot use it.
1209    NullFallbackUnsupported { from: String, to: String },
1210}
1211
1212impl CastError {
1213    /// Convert to a [`PlanError`] for use in planning; requires the expression name for context.
1214    pub fn into_plan_error(self, name: String) -> PlanError {
1215        match self {
1216            CastError::InvalidCast { ccx, from, to } => PlanError::InvalidCast {
1217                name,
1218                ccx,
1219                from,
1220                to,
1221            },
1222            CastError::UnsupportedRangeElementType { element_type_name } => {
1223                PlanError::UnsupportedRangeElementType { element_type_name }
1224            }
1225            CastError::NullFallbackUnsupported { from, to } => {
1226                PlanError::TryCastUnsupported { from, to }
1227            }
1228        }
1229    }
1230}
1231
1232/// Get casts directly between two [`SqlScalarType`]s, with control over the
1233/// allowed [`CastContext`].
1234///
1235/// Returns `Err` when the cast is not supported: either no cast exists
1236/// ([`CastError::InvalidCast`]) or the cast is disallowed
1237/// ([`CastError::UnsupportedRangeElementType`], e.g. range over float/uint).
1238fn get_cast(
1239    ecx: &ExprContext,
1240    ccx: CastContext,
1241    mode: CastFailureMode,
1242    from: &SqlScalarType,
1243    to: &SqlScalarType,
1244) -> Result<Cast, CastError> {
1245    use CastContext::*;
1246
1247    // The identity cast cannot fail, so it is the same in every failure mode.
1248    if from == to || (ccx == Implicit && from.base_eq(to)) {
1249        return Ok(Box::new(|expr| expr));
1250    }
1251
1252    // Reject casts to range types with unsupported element types at plan time.
1253    if let SqlScalarType::Range { element_type } = to {
1254        validate_range_element_type(ecx, element_type)?;
1255    }
1256
1257    let imp = match VALID_CASTS.get(&(from.into(), to.into())) {
1258        Some(imp) => imp,
1259        None => {
1260            return Err(CastError::InvalidCast {
1261                ccx,
1262                from: ecx.humanize_sql_scalar_type(from, false),
1263                to: ecx.humanize_sql_scalar_type(to, false),
1264            });
1265        }
1266    };
1267    let template = if ccx >= imp.context {
1268        Some(&imp.template)
1269    } else {
1270        None
1271    };
1272    let build = |mode| template.and_then(|template| (template.0)(ecx, ccx, mode, from, to));
1273    match build(mode) {
1274        Some(cast) => Ok(cast),
1275        // A template declines a failure mode by returning `None`, the same way
1276        // it declines a type pair. Tell the two apart so that the error names
1277        // the actual problem.
1278        None if mode == CastFailureMode::NullFallback
1279            && build(CastFailureMode::Error).is_some() =>
1280        {
1281            Err(CastError::NullFallbackUnsupported {
1282                from: ecx.humanize_sql_scalar_type(from, false),
1283                to: ecx.humanize_sql_scalar_type(to, false),
1284            })
1285        }
1286        None => Err(CastError::InvalidCast {
1287            ccx,
1288            from: ecx.humanize_sql_scalar_type(from, false),
1289            to: ecx.humanize_sql_scalar_type(to, false),
1290        }),
1291    }
1292}
1293
1294/// The base type pairs that have an entry in the cast table, for tests that
1295/// want to prove they exercised every one.
1296pub fn valid_cast_base_type_pairs() -> impl Iterator<Item = (SqlScalarBaseType, SqlScalarBaseType)>
1297{
1298    VALID_CASTS.keys().copied()
1299}
1300
1301/// Converts an expression to `SqlScalarType::String`.
1302///
1303/// Most types have an explicit cast to string, but a few do not: `mz_aclitem`
1304/// has no text cast at all, and the `reg*` casts are implemented as subqueries
1305/// that `plan_cast` rejects in contexts that disallow subqueries (e.g. a
1306/// `RETURNING` clause or a `to_jsonb` element cast). We surface those as a
1307/// planning error rather than panicking.
1308pub fn to_string(ecx: &ExprContext, expr: HirScalarExpr) -> Result<HirScalarExpr, PlanError> {
1309    plan_cast(ecx, CastContext::Explicit, expr, &SqlScalarType::String)
1310}
1311
1312/// Converts an expression to `SqlScalarType::Jsonb`.
1313///
1314/// The rules are as follows:
1315///   * `SqlScalarType::Boolean`s become JSON booleans.
1316///   * All numeric types are converted to `Float64`s, then become JSON numbers.
1317///   * Records are converted to a JSON object where the record's field names
1318///     are the keys of the object, and the record's fields are recursively
1319///     converted to JSON by `to_jsonb`.
1320///   * Other types are converted to strings by their usual cast function and
1321///     become JSON strings. That cast can fail (see `to_string`), in which case
1322///     we propagate the planning error instead of panicking.
1323pub fn to_jsonb(ecx: &ExprContext, expr: HirScalarExpr) -> Result<HirScalarExpr, PlanError> {
1324    use SqlScalarType::*;
1325
1326    Ok(match ecx.scalar_type(&expr) {
1327        Bool | Jsonb | Numeric { .. } => {
1328            expr.call_unary(UnaryFunc::CastJsonbableToJsonb(func::CastJsonbableToJsonb))
1329        }
1330        Int16 | Int32 | Int64 | UInt16 | UInt32 | UInt64 | Float32 | Float64 => plan_cast(
1331            ecx,
1332            CastContext::Explicit,
1333            expr,
1334            &Numeric { max_scale: None },
1335        )
1336        .expect("cast known to exist")
1337        .call_unary(UnaryFunc::CastJsonbableToJsonb(func::CastJsonbableToJsonb)),
1338        Record { fields, .. } => {
1339            let mut exprs = vec![];
1340            for (i, (name, _ty)) in fields.iter().enumerate() {
1341                exprs.push(HirScalarExpr::literal(
1342                    Datum::String(name),
1343                    SqlScalarType::String,
1344                ));
1345                exprs.push(to_jsonb(
1346                    ecx,
1347                    expr.clone()
1348                        .call_unary(UnaryFunc::RecordGet(func::RecordGet(i))),
1349                )?);
1350            }
1351            HirScalarExpr::call_variadic(JsonbBuildObject, exprs)
1352        }
1353        ref ty @ List {
1354            ref element_type, ..
1355        }
1356        | ref ty @ Array(ref element_type) => {
1357            // Construct a new expression context with one column whose type
1358            // is the container's element type.
1359            let qcx = QueryContext::root(ecx.qcx.scx, ecx.qcx.lifetime);
1360            let ecx = ExprContext {
1361                qcx: &qcx,
1362                name: "to_jsonb",
1363                scope: &Scope::empty(),
1364                relation_type: &SqlRelationType::new(vec![element_type.clone().nullable(true)]),
1365                allow_aggregates: false,
1366                allow_subqueries: false,
1367                allow_parameters: false,
1368                allow_windows: false,
1369            };
1370
1371            // Create an element-casting expression by calling `to_jsonb` on
1372            // an expression that references the first column in a row.
1373            let cast_element = to_jsonb(&ecx, HirScalarExpr::column(0))?;
1374            let cast_element = cast_element
1375                .lower_uncorrelated(ecx.catalog().system_vars())
1376                .expect("to_jsonb does not produce correlated expressions on uncorrelated input");
1377
1378            // The `Cast{Array|List}ToJsonb` functions take the element-casting
1379            // expression as an argument and evaluate the expression against
1380            // each element of the container at runtime.
1381            let func = match ty {
1382                List { .. } => UnaryFunc::CastListToJsonb(CastListToJsonb {
1383                    cast_element: Box::new(cast_element),
1384                }),
1385                Array { .. } => UnaryFunc::CastArrayToJsonb(CastArrayToJsonb {
1386                    cast_element: Box::new(cast_element),
1387                }),
1388                _ => unreachable!("validated above"),
1389            };
1390
1391            expr.call_unary(func)
1392        }
1393        Date
1394        | Time
1395        | Timestamp { .. }
1396        | TimestampTz { .. }
1397        | Interval
1398        | PgLegacyChar
1399        | PgLegacyName
1400        | Bytes
1401        | String
1402        | Char { .. }
1403        | VarChar { .. }
1404        | Uuid
1405        | Oid
1406        | Map { .. }
1407        | RegProc
1408        | RegType
1409        | RegClass
1410        | Int2Vector
1411        | MzTimestamp
1412        | Range { .. }
1413        | MzAclItem
1414        | AclItem => to_string(ecx, expr)?
1415            .call_unary(UnaryFunc::CastJsonbableToJsonb(func::CastJsonbableToJsonb)),
1416    })
1417}
1418
1419/// Guesses the most-common type among a set of [`SqlScalarType`]s that all members
1420/// can be cast to. Returns `None` if a common type cannot be deduced.
1421///
1422/// Note that this function implements the type-determination components of
1423/// Postgres' ["`UNION`, `CASE`, and Related Constructs"][union-type-conv] type
1424/// conversion.
1425///
1426/// [union-type-conv]: https://www.postgresql.org/docs/12/typeconv-union-case.html
1427pub fn guess_best_common_type(
1428    ecx: &ExprContext,
1429    types: &[CoercibleScalarType],
1430) -> Result<SqlScalarType, PlanError> {
1431    // This function is a translation of `select_common_type` in PostgreSQL with
1432    // the addition of our near match logic, which supports Materialize
1433    // non-linear type promotions.
1434    // https://github.com/postgres/postgres/blob/d1b307eef/
1435    //   src/backend/parser/parse_coerce.c#L1288-L1308
1436
1437    // If every type is a literal record with the same number of fields, the
1438    // best common type is a record with that number of fields. We recursively
1439    // guess the best type for each field.
1440    if let Some(CoercibleScalarType::Record(field_tys)) = types.first() {
1441        if types
1442            .iter()
1443            .all(|t| matches!(t, CoercibleScalarType::Record(fts) if field_tys.len() == fts.len()))
1444        {
1445            let mut fields = vec![];
1446            for i in 0..field_tys.len() {
1447                let name = ColumnName::from(format!("f{}", fields.len() + 1));
1448                let mut guesses = vec![];
1449                let mut nullable = false;
1450                for ty in types {
1451                    let field_ty = match ty {
1452                        CoercibleScalarType::Record(fts) => fts[i].clone(),
1453                        _ => unreachable!(),
1454                    };
1455                    if field_ty.nullable() {
1456                        nullable = true;
1457                    }
1458                    guesses.push(field_ty.scalar_type());
1459                }
1460                let guess = guess_best_common_type(ecx, &guesses)?;
1461                fields.push((name, guess.nullable(nullable)));
1462            }
1463            return Ok(SqlScalarType::Record {
1464                fields: fields.into(),
1465                custom_id: None,
1466            });
1467        }
1468    }
1469
1470    // Remove unknown types, and collect them.
1471    let mut types: Vec<_> = types.into_iter().filter_map(|v| v.as_coerced()).collect();
1472
1473    // In the case of mixed ints and uints, replace uints with their near match
1474    let contains_int = types.iter().any(|t| {
1475        matches!(
1476            t,
1477            SqlScalarType::Int16 | SqlScalarType::Int32 | SqlScalarType::Int64
1478        )
1479    });
1480
1481    for t in types.iter_mut() {
1482        if contains_int
1483            && matches!(
1484                t,
1485                SqlScalarType::UInt16 | SqlScalarType::UInt32 | SqlScalarType::UInt64
1486            )
1487        {
1488            *t = t.near_match().expect("unsigned ints have near matches")
1489        }
1490    }
1491
1492    let mut types = types.iter();
1493
1494    let mut candidate = match types.next() {
1495        // If no known types, fall back to `String`.
1496        None => return Ok(SqlScalarType::String),
1497        // Start by guessing the first type.
1498        Some(t) => t,
1499    };
1500
1501    let preferred_type = TypeCategory::from_type(candidate).preferred_type();
1502
1503    for typ in types {
1504        if TypeCategory::from_type(candidate) != TypeCategory::from_type(typ) {
1505            // The next type is in a different category; give up.
1506            sql_bail!(
1507                "{} types {} and {} cannot be matched",
1508                ecx.name,
1509                ecx.humanize_sql_scalar_type(candidate, false),
1510                ecx.humanize_sql_scalar_type(typ, false),
1511            );
1512        };
1513
1514        // If this type is the preferred type, make it the candidate.
1515        if preferred_type.as_ref() != Some(candidate)
1516            && can_cast(ecx, CastContext::Implicit, candidate, typ)
1517            && !can_cast(ecx, CastContext::Implicit, typ, candidate)
1518        {
1519            // The current candidate is not the preferred type for its category
1520            // and the next type is implicitly convertible to the current
1521            // candidate, but not vice-versa, so take the next type as the new
1522            // candidate.
1523            candidate = typ;
1524        }
1525    }
1526    Ok(candidate.without_modifiers())
1527}
1528
1529pub fn plan_coerce<'a>(
1530    ecx: &'a ExprContext,
1531    e: CoercibleScalarExpr,
1532    coerce_to: &SqlScalarType,
1533) -> Result<HirScalarExpr, PlanError> {
1534    plan_coerce_with_failure_mode(ecx, e, coerce_to, CastFailureMode::Error)
1535}
1536
1537/// Like [`plan_coerce`], but the casts that coerce a literal to `coerce_to`
1538/// use the given failure mode. `TRY_CAST('abc' AS int4)` reaches its cast
1539/// through this coercion, so the mode has to apply here too.
1540pub fn plan_coerce_with_failure_mode<'a>(
1541    ecx: &'a ExprContext,
1542    e: CoercibleScalarExpr,
1543    coerce_to: &SqlScalarType,
1544    mode: CastFailureMode,
1545) -> Result<HirScalarExpr, PlanError> {
1546    use CoercibleScalarExpr::*;
1547
1548    Ok(match e {
1549        Coerced(e) => e,
1550
1551        LiteralNull => HirScalarExpr::literal_null(coerce_to.clone()),
1552
1553        LiteralString(s) => {
1554            let lit = HirScalarExpr::literal(Datum::String(&s), SqlScalarType::String);
1555            // Per PostgreSQL, string literal explicitly casts to the base type.
1556            // The caller is responsible for applying any desired modifiers
1557            // (with either implicit or explicit semantics) via a separate call
1558            // to `plan_cast`.
1559            let coerce_to_base = &coerce_to.without_modifiers();
1560            plan_cast_with_failure_mode(ecx, CastContext::Coerced, mode, lit, coerce_to_base)?
1561        }
1562
1563        LiteralRecord(exprs) => {
1564            let arity = exprs.len();
1565            // Under `NullFallback` the target field types are not pushed into
1566            // the literal. Fields are typed on their own, so every field
1567            // conversion happens inside the record cast that follows, where
1568            // the `TryCast` wrapper covers it, and a bad field yields NULL for
1569            // the whole record rather than a record with a NULL field. This
1570            // mirrors how `plan_cast` treats collection constructors.
1571            let coercions = match (coerce_to, mode) {
1572                (SqlScalarType::Record { fields, .. }, CastFailureMode::Error)
1573                    if fields.len() == arity =>
1574                {
1575                    fields
1576                        .iter()
1577                        .map(|(_name, ty)| &ty.scalar_type)
1578                        .cloned()
1579                        .collect()
1580                }
1581                _ => vec![SqlScalarType::String; exprs.len()],
1582            };
1583            let mut out = vec![];
1584            for (e, coerce_to) in exprs.into_iter().zip_eq(coercions) {
1585                out.push(plan_coerce_with_failure_mode(ecx, e, &coerce_to, mode)?);
1586            }
1587            HirScalarExpr::call_variadic(
1588                RecordCreate {
1589                    field_names: (0..arity)
1590                        .map(|i| ColumnName::from(format!("f{}", i + 1)))
1591                        .collect(),
1592                },
1593                out,
1594            )
1595        }
1596
1597        Parameter(n) => {
1598            let prev = ecx.param_types().borrow_mut().insert(n, coerce_to.clone());
1599            if let Some(prev) = prev {
1600                if prev != *coerce_to {
1601                    sql_bail!(
1602                        "there are contradicting constraints for \
1603                         the type of parameter ${}: \
1604                         should be both {} and {}",
1605                        n,
1606                        ecx.humanize_sql_scalar_type(&prev, false),
1607                        ecx.humanize_sql_scalar_type(coerce_to, false),
1608                    );
1609                }
1610            }
1611            HirScalarExpr::parameter(n)
1612        }
1613    })
1614}
1615
1616/// Returns an error if the given type is not a supported range element type
1617/// (PostgreSQL built-in range types: int32, int64, date, numeric, timestamp,
1618/// timestamptz).
1619fn validate_range_element_type(
1620    ecx: &ExprContext,
1621    element_type: &SqlScalarType,
1622) -> Result<(), CastError> {
1623    let allowed = matches!(
1624        element_type,
1625        SqlScalarType::Int32
1626            | SqlScalarType::Int64
1627            | SqlScalarType::Date
1628            | SqlScalarType::Numeric { .. }
1629            | SqlScalarType::Timestamp { .. }
1630            | SqlScalarType::TimestampTz { .. }
1631    );
1632    if allowed {
1633        Ok(())
1634    } else {
1635        Err(CastError::UnsupportedRangeElementType {
1636            element_type_name: ecx.humanize_sql_scalar_type(element_type, false),
1637        })
1638    }
1639}
1640
1641/// Similar to `plan_cast`, but for situations where you only know the type of
1642/// the input expression (`from`) and not the expression itself. The returned
1643/// expression refers to the first column of some imaginary row, where the first
1644/// column is assumed to have type `from`.
1645///
1646/// If casting from `from` to `to` is not possible, returns `None`.
1647pub fn plan_hypothetical_cast(
1648    ecx: &ExprContext,
1649    ccx: CastContext,
1650    from: &SqlScalarType,
1651    to: &SqlScalarType,
1652) -> Option<mz_expr::MirScalarExpr> {
1653    plan_hypothetical_cast_with_failure_mode(ecx, ccx, CastFailureMode::Error, from, to)
1654}
1655
1656/// Similar to `plan_hypothetical_cast`, but with a `CastFailureMode` to specify behavior.
1657pub fn plan_hypothetical_cast_with_failure_mode(
1658    ecx: &ExprContext,
1659    ccx: CastContext,
1660    mode: CastFailureMode,
1661    from: &SqlScalarType,
1662    to: &SqlScalarType,
1663) -> Option<mz_expr::MirScalarExpr> {
1664    // Reconstruct an expression context where the expression is evaluated on
1665    // the "first column" of some imaginary row.
1666    let mut scx = ecx.qcx.scx.clone();
1667    scx.param_types = RefCell::new(BTreeMap::new());
1668    let qcx = QueryContext::root(&scx, ecx.qcx.lifetime);
1669    let relation_type = SqlRelationType {
1670        column_types: vec![SqlColumnType {
1671            nullable: true,
1672            scalar_type: from.clone(),
1673        }],
1674        keys: vec![vec![0]],
1675    };
1676    let ecx = ExprContext {
1677        qcx: &qcx,
1678        name: "plan_hypothetical_cast",
1679        scope: &Scope::empty(),
1680        relation_type: &relation_type,
1681        allow_aggregates: false,
1682        allow_subqueries: true,
1683        allow_parameters: true,
1684        allow_windows: false,
1685    };
1686
1687    let col_expr = HirScalarExpr::column(0);
1688
1689    // Determine the `ScalarExpr` required to cast our column to the target
1690    // component type.
1691    //
1692    // NOTE: the templates plan element casts through `plan_hypothetical_cast`,
1693    // which is always strict, whatever the enclosing cast's failure mode. Under
1694    // `TRY_CAST` the enclosing function is what gets wrapped, so a bad element
1695    // makes the whole value NULL rather than producing a value with NULL holes
1696    // in it.
1697    plan_cast_with_failure_mode(&ecx, ccx, mode, col_expr, to)
1698        .ok()?
1699        // TODO(jkosh44) Support casts that have correlated implementations.
1700        .lower_uncorrelated(ecx.catalog().system_vars())
1701        .ok()
1702}
1703
1704/// Plans a cast between [`SqlScalarType`]s, specifying which types of casts are
1705/// permitted using [`CastContext`].
1706///
1707/// # Errors
1708///
1709/// If a cast between the `ScalarExpr`'s base type and the specified type is:
1710/// - Not possible, e.g. `Bytes` to `Interval`
1711/// - Not permitted, e.g. implicitly casting from `Float64` to `Float32`.
1712/// - Not implemented yet
1713pub fn plan_cast(
1714    ecx: &ExprContext,
1715    ccx: CastContext,
1716    expr: HirScalarExpr,
1717    to: &SqlScalarType,
1718) -> Result<HirScalarExpr, PlanError> {
1719    plan_cast_with_failure_mode(ecx, ccx, CastFailureMode::Error, expr, to)
1720}
1721
1722/// Like [`plan_cast`], but with the given failure mode. Under
1723/// [`CastFailureMode::NullFallback`] every stage of the cast, including the
1724/// intermediate string cast that string-like types route through, is wrapped so
1725/// that a failure anywhere yields NULL.
1726pub fn plan_cast_with_failure_mode(
1727    ecx: &ExprContext,
1728    ccx: CastContext,
1729    mode: CastFailureMode,
1730    expr: HirScalarExpr,
1731    to: &SqlScalarType,
1732) -> Result<HirScalarExpr, PlanError> {
1733    let from = ecx.scalar_type(&expr);
1734
1735    // Close over `ccx`, `mode`, `from`, and `to` to simplify error messages in
1736    // the face of intermediate expressions.
1737    let cast_inner = |from, to, expr| {
1738        get_cast(ecx, ccx, mode, from, to)
1739            .map(|cast| cast(expr))
1740            .map_err(|e| e.into_plan_error(ecx.name.into()))
1741    };
1742
1743    // String-like types get special handling to match PostgreSQL.
1744    // See: https://github.com/postgres/postgres/blob/6b04abdfc/
1745    //   src/backend/parser/parse_coerce.c#L3205-L3223
1746    let from_category = TypeCategory::from_type(&from);
1747    let to_category = TypeCategory::from_type(to);
1748    if from_category == TypeCategory::String && to_category != TypeCategory::String {
1749        // Converting from stringlike to something non-stringlike. Handle as if
1750        // `from` were a `SqlScalarType::String`.
1751        //
1752        // `"char"` is string-like but stores a byte (`Datum::UInt8`), so the
1753        // string-source cast functions cannot consume it directly. Use its
1754        // registered cast when one exists (`int4`, by byte value) and
1755        // otherwise render it to text first.
1756        let expr = match from {
1757            SqlScalarType::PgLegacyChar => match get_cast(ecx, ccx, mode, &from, to) {
1758                Ok(cast) => return Ok(cast(expr)),
1759                Err(_) => cast_inner(&from, &SqlScalarType::String, expr)?,
1760            },
1761            _ => expr,
1762        };
1763        cast_inner(&SqlScalarType::String, to, expr)
1764    } else if from_category != TypeCategory::String && to_category == TypeCategory::String {
1765        // Converting from non-stringlike to something stringlike. Convert to a
1766        // `SqlScalarType::String` and then to the desired type.
1767        let expr = cast_inner(&from, &SqlScalarType::String, expr)?;
1768        cast_inner(&SqlScalarType::String, to, expr)
1769    } else {
1770        // Standard cast.
1771        cast_inner(&from, to, expr)
1772    }
1773}
1774
1775/// Reports whether it is possible to perform a cast from the specified types.
1776pub fn can_cast(
1777    ecx: &ExprContext,
1778    ccx: CastContext,
1779    cast_from: &SqlScalarType,
1780    cast_to: &SqlScalarType,
1781) -> bool {
1782    get_cast(ecx, ccx, CastFailureMode::Error, cast_from, cast_to).is_ok()
1783}
1784
1785#[cfg(test)]
1786mod tests {
1787    use mz_expr::func::{CastInt64ToInt32, CastStringToInt64, Eq as EqFunc, IsNull};
1788    use mz_repr::SqlScalarType;
1789
1790    use super::extract_unary_stages_for_try_cast;
1791    use crate::plan::hir::HirScalarExpr;
1792
1793    #[mz_ore::test]
1794    fn cast_stages_of_chain_are_innermost_first() {
1795        let stages =
1796            extract_unary_stages_for_try_cast(&SqlScalarType::String, |e: HirScalarExpr| {
1797                e.call_unary(CastStringToInt64.into())
1798                    .call_unary(CastInt64ToInt32.into())
1799            });
1800        assert_eq!(
1801            stages,
1802            Some(vec![CastStringToInt64.into(), CastInt64ToInt32.into()])
1803        );
1804    }
1805
1806    #[mz_ore::test]
1807    fn cast_stages_of_identity_is_empty() {
1808        assert_eq!(
1809            extract_unary_stages_for_try_cast(&SqlScalarType::Int32, |e: HirScalarExpr| e),
1810            Some(vec![])
1811        );
1812    }
1813
1814    #[mz_ore::test]
1815    fn cast_stages_rejects_non_unary_shapes() {
1816        let compares = |e: HirScalarExpr| {
1817            let zero = HirScalarExpr::literal_null(SqlScalarType::Int32);
1818            e.call_binary(zero, EqFunc)
1819        };
1820        assert_eq!(
1821            extract_unary_stages_for_try_cast(&SqlScalarType::Int32, compares),
1822            None
1823        );
1824        let ignores_input = |_: HirScalarExpr| HirScalarExpr::literal_null(SqlScalarType::Int32);
1825        assert_eq!(
1826            extract_unary_stages_for_try_cast(&SqlScalarType::Int32, ignores_input),
1827            None
1828        );
1829    }
1830
1831    /// A stage after the first that does not propagate NULLs would turn the
1832    /// NULL of an earlier failure into a value, so wrapping must refuse it.
1833    #[mz_ore::test]
1834    fn cast_stages_rejects_non_null_propagating_later_stage() {
1835        let chain = |e: HirScalarExpr| {
1836            e.call_unary(CastInt64ToInt32.into())
1837                .call_unary(IsNull.into())
1838        };
1839        let outcome = mz_ore::panic::catch_unwind(|| {
1840            extract_unary_stages_for_try_cast(&SqlScalarType::Int64, chain)
1841        });
1842        if mz_ore::assert::soft_assertions_enabled() {
1843            assert!(outcome.is_err(), "soft assertion should have fired");
1844        } else {
1845            assert_eq!(outcome.expect("no panic"), None);
1846        }
1847    }
1848}