Skip to main content

rupg_analyze/
coerce.rs

1//! The casts of `parse_coerce.c`: which casts exist in each context, the expression of a cast, the common type of a list of expressions, and the cast to `boolean` of a condition.
2
3use rupg_common::{Error, Result, SqlState};
4use rupg_pgcatalog::builtin;
5use rupg_types::{Value, oid};
6
7use crate::Analyzer;
8use crate::expr::{CastForm, Expr, ExprKind, Func, FuncForm};
9use crate::types;
10
11/// `CoercionContext`: where a cast happens. A cast is allowed in its own context and in each later one.
12#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
13pub enum Context {
14    /// An expression, such as the argument of a function.
15    Implicit,
16    /// The value of `INSERT` and `UPDATE`.
17    Assignment,
18    /// `COERCION_PLPGSQL`: the value of an assignment of PL/pgSQL. When no other cast exists, the output function of the source and the input function of the target make the cast.
19    Plpgsql,
20    /// `CAST(x AS type)` and `x::type`.
21    Explicit,
22}
23
24/// `CoercionPathType`: how a cast happens.
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub enum Path {
27    None,
28    /// The function with this OID.
29    Func(u32),
30    /// No change of the binary form.
31    Relabel,
32    /// Each element of an array, with the cast of the element type.
33    ArrayCoerce,
34    /// The output function of the source, then the input function of the target.
35    ViaIo,
36}
37
38/// `find_coercion_pathway`.
39pub fn find_path(target: u32, source: u32, context: Context) -> Path {
40    let (target, source) = (types::base(target), types::base(source));
41    if source == target {
42        return Path::Relabel;
43    }
44    if source == oid::INTERNAL || target == oid::INTERNAL {
45        return Path::None;
46    }
47    if let Some(cast) = builtin::cast(source, target) {
48        let allowed = match cast.context {
49            b'i' => Context::Implicit,
50            b'a' => Context::Assignment,
51            _ => Context::Explicit,
52        };
53        if context < allowed {
54            return Path::None;
55        }
56        return match cast.method {
57            b'f' => Path::Func(cast.func),
58            b'i' => Path::ViaIo,
59            _ => Path::Relabel,
60        };
61    }
62    if target != oid::OIDVECTOR && target != oid::INT2VECTOR {
63        let (target_element, source_element) = (types::element(target), types::element(source));
64        if target_element != 0
65            && source_element != 0
66            && find_path(target_element, source_element, context) != Path::None
67        {
68            return Path::ArrayCoerce;
69        }
70    }
71    // Any type has an I/O cast to a string type in assignment, and a string type has an explicit I/O cast to any type. In PL/pgSQL, any type has an I/O cast to any type.
72    if (context >= Context::Assignment && types::category(target) == types::STRING)
73        || (context >= Context::Explicit && types::category(source) == types::STRING)
74        || context == Context::Plpgsql
75    {
76        Path::ViaIo
77    } else {
78        Path::None
79    }
80}
81
82/// `coerce_to_target_type` with `COERCION_EXPLICIT` and `COERCE_EXPLICIT_CAST` for an expression whose type is known, to a type that is not a domain, as the cast `$1::text` in the body of a SQL function that `inline_function` puts in a query. `None` when the type has no such cast.
83pub fn explicit_cast(expr: Expr, target: u32) -> Option<Expr> {
84    if expr.ty == target {
85        return Some(expr);
86    }
87    let location = expr.location;
88    match find_path(target, expr.ty, Context::Explicit) {
89        Path::None | Path::ArrayCoerce => None,
90        Path::Relabel => Some(Expr {
91            kind: ExprKind::Relabel(Box::new(expr), CastForm::Explicit),
92            ty: target,
93            typmod: -1,
94            location,
95        }),
96        Path::ViaIo => Some(Expr {
97            kind: ExprKind::CoerceViaIo(Box::new(expr), CastForm::Explicit),
98            ty: target,
99            typmod: -1,
100            location,
101        }),
102        Path::Func(func) => Some(build_cast(
103            expr,
104            func,
105            target,
106            -1,
107            Context::Explicit,
108            FuncForm::ExplicitCast,
109            location,
110        )),
111    }
112}
113
114/// `can_coerce_type`: true if each input type has a cast to its target type in the context.
115pub fn can_coerce(inputs: &[u32], targets: &[u32], context: Context) -> bool {
116    let mut generic = false;
117    for (&input, &target) in inputs.iter().zip(targets) {
118        if input == target {
119            continue;
120        }
121        if input == oid::INTERNAL || target == oid::INTERNAL {
122            return false;
123        }
124        if target == oid::ANY {
125            continue;
126        }
127        if types::is_polymorphic(target) {
128            generic = true;
129            continue;
130        }
131        if input == oid::UNKNOWN {
132            continue;
133        }
134        // A composite type is a `record`, and an array of a composite type is a `record[]`.
135        if (target == oid::RECORD && types::is_complex(input))
136            || (target == oid::RECORD_ARRAY && types::is_complex(types::element(input)))
137        {
138            continue;
139        }
140        // `coerce_record_to_complex` casts a `record` to a composite type field by field.
141        if input == oid::RECORD && types::is_complex(types::base(target)) {
142            continue;
143        }
144        if find_path(target, input, context) != Path::None {
145            continue;
146        }
147        return false;
148    }
149    !generic || crate::poly::consistent(inputs, targets)
150}
151
152/// `select_common_type_from_oids`: the type that all the types can take. A list of only `unknown` gives `text`. When two types are in different categories, the error gives the two types and the index of the second.
153pub(crate) fn common_of(tys: &[u32]) -> std::result::Result<u32, (u32, u32, usize)> {
154    let Some(&first) = tys.first() else { return Ok(oid::TEXT) };
155    if first != oid::UNKNOWN && tys.iter().all(|&t| t == first) {
156        return Ok(first);
157    }
158    let mut ptype = types::base(first);
159    let (mut pcategory, mut ppreferred) = types::category_preferred(ptype);
160    for (i, &next) in tys.iter().enumerate().skip(1) {
161        let ntype = types::base(next);
162        if ntype == oid::UNKNOWN || ntype == ptype {
163            continue;
164        }
165        let (ncategory, npreferred) = types::category_preferred(ntype);
166        if ptype == oid::UNKNOWN {
167            (ptype, pcategory, ppreferred) = (ntype, ncategory, npreferred);
168        } else if ncategory != pcategory {
169            return Err((ptype, ntype, i));
170        } else if !ppreferred
171            && can_coerce(&[ptype], &[ntype], Context::Implicit)
172            && !can_coerce(&[ntype], &[ptype], Context::Implicit)
173        {
174            (ptype, pcategory, ppreferred) = (ntype, ncategory, npreferred);
175        }
176    }
177    Ok(if ptype == oid::UNKNOWN { oid::TEXT } else { ptype })
178}
179
180/// `find_typmod_coercion_function`: the function that applies a typmod to a value of the type, and whether it applies to each element of an array.
181fn typmod_function(ty: u32) -> Option<(u32, bool)> {
182    let (ty, array) = match types::element(ty) {
183        0 => (ty, false),
184        element => (element, true),
185    };
186    builtin::cast(ty, ty).filter(|cast| cast.func != 0).map(|cast| (cast.func, array))
187}
188
189impl Analyzer<'_> {
190    /// `coerce_to_target_type`: the expression cast to the type and the typmod, or `None` if the cast does not exist in the context.
191    pub(crate) fn coerce_to_target(
192        &mut self,
193        expr: Expr,
194        target: u32,
195        typmod: i32,
196        context: Context,
197        location: Option<usize>,
198    ) -> Result<Option<Expr>> {
199        if !can_coerce(&[expr.ty], &[target], context) {
200            return Ok(None);
201        }
202        let input = (expr.ty, expr.typmod);
203        let expr = self.coerce(expr, target, typmod, context, location)?;
204        // A length cast hides the cast below it, which the analyzer made in the same step, as `hide_coercion_node` does. With no length cast, the cast stays as it is.
205        let length = typmod >= 0 && (expr.typmod != typmod || expr.ty != target);
206        let hide =
207            length && (expr.ty, expr.typmod) != input && !matches!(expr.kind, ExprKind::Const(_));
208        let mut expr = self.coerce_typmod(expr, target, typmod, context, location);
209        if hide
210            && let ExprKind::Func(Func { args, .. }) = &mut expr.kind
211            && let Some(arg) = args.first_mut()
212        {
213            hide_cast(arg);
214        }
215        Ok(Some(expr))
216    }
217
218    /// `coerce_type`: the expression cast to the type. The caller has checked that the cast exists.
219    pub(crate) fn coerce(
220        &mut self,
221        expr: Expr,
222        target: u32,
223        typmod: i32,
224        context: Context,
225        location: Option<usize>,
226    ) -> Result<Expr> {
227        let input = expr.ty;
228        if target == input {
229            return Ok(expr);
230        }
231        if matches!(
232            target,
233            oid::ANY
234                | oid::ANYELEMENT
235                | oid::ANYNONARRAY
236                | oid::ANYCOMPATIBLE
237                | oid::ANYCOMPATIBLENONARRAY
238        ) {
239            return Ok(expr);
240        }
241        if types::is_polymorphic(target) && input != oid::UNKNOWN {
242            return Ok(expr);
243        }
244        // `coerce_type` keeps the type of a composite value for `record` and for `record[]`, with no `RelabelType`.
245        if (target == oid::RECORD && types::is_complex(input))
246            || (target == oid::RECORD_ARRAY && types::is_complex(types::element(input)))
247        {
248            return Ok(expr);
249        }
250        if input == oid::RECORD && types::is_complex(types::base(target)) {
251            return self.coerce_record_to_complex(expr, target, context, location);
252        }
253        if input == oid::UNKNOWN {
254            if let ExprKind::Const(value) = &expr.kind {
255                // The input function takes the typmod only for `interval`. The caller applies any other typmod with a length cast.
256                let (base, base_typmod) = types::base_typmod(target, typmod);
257                let input_typmod = if base == oid::INTERVAL { base_typmod } else { -1 };
258                let value = match value {
259                    Value::Text(text) => {
260                        let text = text.clone();
261                        // The place of the literal replaces any place that the input function gives, as `pcb_error_callback` does.
262                        self.env.input_at(base, &text, input_typmod, expr.location).map_err(
263                            |e| match expr.location {
264                                Some(at) => e.with_position(Some(at)),
265                                None => e,
266                            },
267                        )?
268                    }
269                    _ => Value::Null,
270                };
271                let constant = Expr::constant(value, base, input_typmod, expr.location);
272                return Ok(coerce_to_domain(constant, target, context, location, false));
273            }
274            if let ExprKind::Param(n) = expr.kind
275                && let Some(ty) = self.param_type(n, target, expr.location)?
276            {
277                return Ok(Expr {
278                    kind: ExprKind::Param(n),
279                    ty,
280                    typmod: -1,
281                    location: expr.location,
282                });
283            }
284        }
285        let (form, cast) = if context == Context::Explicit {
286            (FuncForm::ExplicitCast, CastForm::Explicit)
287        } else {
288            (FuncForm::ImplicitCast, CastForm::Implicit)
289        };
290        // A cast to a domain casts to the base type, then checks the constraints of the domain.
291        let (base, base_typmod) = types::base_typmod(target, typmod);
292        match find_path(target, input, context) {
293            Path::None => Err(Error::internal(format!(
294                "failed to find conversion function from {} to {}",
295                types::name(input),
296                types::name(target)
297            ))),
298            Path::Relabel if base != target => {
299                Ok(coerce_to_domain(expr, target, context, location, false))
300            }
301            Path::Relabel => Ok(Expr {
302                kind: ExprKind::Relabel(Box::new(expr), cast),
303                ty: target,
304                typmod: -1,
305                location,
306            }),
307            Path::ViaIo => {
308                let expr = Expr {
309                    kind: ExprKind::CoerceViaIo(Box::new(expr), cast),
310                    ty: base,
311                    typmod: -1,
312                    location,
313                };
314                Ok(coerce_to_domain(expr, target, context, location, true))
315            }
316            Path::Func(func) => {
317                let expr = build_cast(expr, func, base, base_typmod, context, form, location);
318                Ok(coerce_to_domain(expr, target, context, location, true))
319            }
320            // ArrayCoerceExpr with a relabel of each element: the array keeps its values.
321            Path::ArrayCoerce
322                if find_path(types::element(target), types::element(input), context)
323                    == Path::Relabel =>
324            {
325                Ok(Expr {
326                    kind: ExprKind::Relabel(Box::new(expr), cast),
327                    ty: target,
328                    typmod: -1,
329                    location,
330                })
331            }
332            Path::ArrayCoerce => {
333                let test = Expr {
334                    kind: ExprKind::CaseTest,
335                    ty: types::element(input),
336                    typmod: expr.typmod,
337                    location: None,
338                };
339                let element = self
340                    .coerce_to_target(test, types::element(target), typmod, context, location)?
341                    .ok_or_else(|| {
342                        Error::internal("failed to coerce array element type as expected")
343                    })?;
344                let typmod = element.typmod;
345                Ok(Expr {
346                    kind: ExprKind::ArrayCoerce {
347                        arg: Box::new(expr),
348                        element: Box::new(element),
349                        form: cast,
350                    },
351                    ty: target,
352                    typmod,
353                    location,
354                })
355            }
356        }
357    }
358
359    /// `coerce_record_to_complex`: a `ROW` expression with each field cast to the type of the column of the composite type. The cast to a domain over a composite type then checks the domain.
360    fn coerce_record_to_complex(
361        &mut self,
362        expr: Expr,
363        target: u32,
364        context: Context,
365        location: Option<usize>,
366    ) -> Result<Expr> {
367        let at = location.or(expr.location);
368        let base = types::base(target);
369        let cannot = |detail: Option<String>| {
370            let error = Error::new(
371                SqlState::CANNOT_COERCE,
372                format!("cannot cast type record to {}", types::name(target)),
373            );
374            match detail {
375                Some(detail) => error.with_detail(detail),
376                None => error,
377            }
378            .at_opt(at)
379        };
380        let ExprKind::Row(args, _) = expr.kind else { return Err(cannot(None)) };
381        let columns = self.row_columns(base).ok_or_else(|| cannot(None))?;
382        if args.len() < columns.len() {
383            return Err(cannot(Some("Input has too few columns.".into())));
384        }
385        if args.len() > columns.len() {
386            return Err(cannot(Some("Input has too many columns.".into())));
387        }
388        let mut fields = Vec::with_capacity(args.len());
389        for (i, (arg, column)) in args.into_iter().zip(&columns).enumerate() {
390            let (from, arg_at) = (arg.ty, arg.location);
391            let field = self.coerce_to_target(arg, column.ty, column.typmod, context, None)?;
392            let Some(field) = field else {
393                return Err(Error::new(
394                    SqlState::CANNOT_COERCE,
395                    format!("cannot cast type {} to {}", types::name(from), types::name(column.ty)),
396                )
397                .with_detail(format!(
398                    "Cannot cast type record to {} in column {}.",
399                    types::name(target),
400                    i + 1
401                ))
402                .at_opt(location.or(arg_at)));
403            };
404            fields.push(field);
405        }
406        let form =
407            if context == Context::Explicit { CastForm::Explicit } else { CastForm::Implicit };
408        let row = Expr { kind: ExprKind::Row(fields, form), ty: base, typmod: -1, location };
409        Ok(if base == target {
410            row
411        } else {
412            coerce_to_domain(row, target, context, location, false)
413        })
414    }
415
416    /// `coerce_type_typmod`: the length cast of a type that has a typmod, or a relabel that shows the typmod. The cast shows as explicit in the explicit context.
417    pub(crate) fn coerce_typmod(
418        &mut self,
419        expr: Expr,
420        target: u32,
421        typmod: i32,
422        context: Context,
423        location: Option<usize>,
424    ) -> Expr {
425        let explicit = context == Context::Explicit;
426        typmod_cast(expr, target, typmod, context, explicit, location)
427    }
428}
429
430/// `coerce_type_typmod` with the form of the cast: explicit when `explicit` is true, else implicit.
431fn typmod_cast(
432    expr: Expr,
433    target: u32,
434    typmod: i32,
435    context: Context,
436    explicit: bool,
437    location: Option<usize>,
438) -> Expr {
439    if typmod < 0 || expr.typmod == typmod && expr.ty == target {
440        return expr;
441    }
442    let (form, cast) = if explicit {
443        (FuncForm::ExplicitCast, CastForm::Explicit)
444    } else {
445        (FuncForm::ImplicitCast, CastForm::Implicit)
446    };
447    match typmod_function(target) {
448        Some((func, false)) => build_cast(expr, func, target, typmod, context, form, location),
449        // An ArrayCoerceExpr applies the function to each element.
450        Some((func, true)) => {
451            let ty = types::element(target);
452            let test = Expr { kind: ExprKind::CaseTest, ty, typmod: expr.typmod, location: None };
453            let element = build_cast(test, func, ty, typmod, context, form, location);
454            Expr {
455                kind: ExprKind::ArrayCoerce {
456                    arg: Box::new(expr),
457                    element: Box::new(element),
458                    form: cast,
459                },
460                ty: target,
461                typmod,
462                location,
463            }
464        }
465        None => {
466            let mut expr = expr;
467            if let ExprKind::Const(_) = expr.kind {
468                expr.typmod = typmod;
469                expr
470            } else {
471                Expr { kind: ExprKind::Relabel(Box::new(expr), cast), ty: target, typmod, location }
472            }
473        }
474    }
475}
476
477impl Analyzer<'_> {
478    /// `select_common_type`: the type that all the expressions can take, for `CASE`, `COALESCE` and the others that `context` names.
479    pub(crate) fn common_type(&self, exprs: &[Expr], context: &str) -> Result<u32> {
480        let tys: Vec<u32> = exprs.iter().map(|e| e.ty).collect();
481        common_of(&tys).map_err(|(p, n, i)| {
482            Error::new(
483                SqlState::DATATYPE_MISMATCH,
484                format!(
485                    "{context} types {} and {} cannot be matched",
486                    types::name(p),
487                    types::name(n)
488                ),
489            )
490            .at_opt(exprs[i].place())
491        })
492    }
493
494    /// `coerce_to_common_type`.
495    pub(crate) fn coerce_to_common(
496        &mut self,
497        expr: Expr,
498        target: u32,
499        context: &str,
500    ) -> Result<Expr> {
501        if expr.ty == target {
502            return Ok(expr);
503        }
504        if !can_coerce(&[expr.ty], &[target], Context::Implicit) {
505            return Err(Error::new(
506                SqlState::CANNOT_COERCE,
507                format!(
508                    "{context} could not convert type {} to {}",
509                    types::name(expr.ty),
510                    types::name(target)
511                ),
512            )
513            .at_opt(expr.place()));
514        }
515        self.coerce(expr, target, -1, Context::Implicit, None)
516    }
517
518    /// `coerce_to_boolean`: the condition of `construct`, such as `WHERE` or `AND`, cast to `boolean`.
519    pub(crate) fn coerce_to_boolean(&mut self, expr: Expr, construct: &str) -> Result<Expr> {
520        self.coerce_to_specific(expr, oid::BOOL, construct)
521    }
522
523    /// `coerce_to_specific_type`: the expression as a value of the type, with an assignment cast, for a clause such as `LIMIT`. The value cannot be a set.
524    pub(crate) fn coerce_to_specific(
525        &mut self,
526        expr: Expr,
527        target: u32,
528        construct: &str,
529    ) -> Result<Expr> {
530        let ty = expr.ty;
531        let place = expr.place();
532        let coerced = if ty == target {
533            Some(expr)
534        } else {
535            self.coerce_to_target(expr, target, -1, Context::Assignment, None)?
536        };
537        match coerced {
538            Some(expr) if expr.first_set_call().is_some() => Err(Error::new(
539                SqlState::DATATYPE_MISMATCH,
540                format!("argument of {construct} must not return a set"),
541            )
542            .at_opt(expr.place())),
543            Some(expr) => Ok(expr),
544            None => Err(Error::new(
545                SqlState::DATATYPE_MISMATCH,
546                format!(
547                    "argument of {construct} must be type {}, not type {}",
548                    types::name(target),
549                    types::name(ty)
550                ),
551            )
552            .at_opt(place)),
553        }
554    }
555}
556
557/// `coerce_to_domain`: the expression, which has the base type of the domain `target`, with the typmod of the domain and a `CoerceToDomain` node above it. When `hide` is true, the cast below shows as implicit, as `hide_coercion_node` does. For a type that is not a domain, the expression does not change.
558fn coerce_to_domain(
559    mut expr: Expr,
560    target: u32,
561    context: Context,
562    location: Option<usize>,
563    hide: bool,
564) -> Expr {
565    let (base, typmod) = types::base_typmod(target, -1);
566    if base == target {
567        return expr;
568    }
569    if hide {
570        hide_cast(&mut expr);
571    }
572    let expr = typmod_cast(expr, base, typmod, context, false, location);
573    let form = if context == Context::Explicit { CastForm::Explicit } else { CastForm::Implicit };
574    Expr { kind: ExprKind::CoerceToDomain(Box::new(expr), form), ty: target, typmod: -1, location }
575}
576
577/// `hide_coercion_node`: a cast that the analyzer made shows as implicit.
578pub(crate) fn hide_cast(expr: &mut Expr) {
579    match &mut expr.kind {
580        ExprKind::Func(Func { form, .. })
581            if matches!(form, FuncForm::ExplicitCast | FuncForm::ImplicitCast) =>
582        {
583            *form = FuncForm::ImplicitCast;
584        }
585        ExprKind::Relabel(_, form)
586        | ExprKind::CoerceViaIo(_, form)
587        | ExprKind::ArrayCoerce { form, .. }
588        | ExprKind::Row(_, form)
589        | ExprKind::CoerceToDomain(_, form) => *form = CastForm::Implicit,
590        _ => {}
591    }
592}
593
594/// `build_coercion_expression` for a cast function. A function of two arguments takes the typmod, and one of three also takes true for an explicit cast.
595fn build_cast(
596    expr: Expr,
597    func: u32,
598    target: u32,
599    typmod: i32,
600    context: Context,
601    form: FuncForm,
602    location: Option<usize>,
603) -> Expr {
604    let nargs = builtin::proc_by_oid(func).map_or(1, |p| p.nargs);
605    let mut args = vec![expr];
606    if nargs >= 2 {
607        args.push(Expr::constant(Value::Int4(typmod), oid::INT4, -1, None));
608    }
609    if nargs >= 3 {
610        args.push(Expr::constant(Value::Bool(context == Context::Explicit), oid::BOOL, -1, None));
611    }
612    // A length cast shows the typmod that it applies, as `exprTypmod` reads it from the second argument.
613    let typmod = if nargs >= 2 && typmod_function(target).is_some_and(|(f, _)| f == func) {
614        typmod
615    } else {
616        -1
617    };
618    Expr {
619        kind: ExprKind::Func(Func { oid: func, args, form, variadic: false, retset: false }),
620        ty: target,
621        typmod,
622        location,
623    }
624}
625
626/// A place on an error when there is one.
627pub(crate) trait AtOpt {
628    fn at_opt(self, location: Option<usize>) -> Self;
629}
630
631impl AtOpt for Error {
632    fn at_opt(self, location: Option<usize>) -> Error {
633        match location {
634            Some(at) => self.at(at),
635            None => self,
636        }
637    }
638}
639
640#[cfg(test)]
641mod tests {
642    use super::*;
643
644    #[test]
645    fn paths() {
646        assert_eq!(find_path(oid::INT8, oid::INT4, Context::Implicit), Path::Func(481));
647        assert_eq!(find_path(oid::INT4, oid::INT8, Context::Implicit), Path::None);
648        assert_eq!(find_path(oid::INT4, oid::INT8, Context::Assignment), Path::Func(480));
649        assert_eq!(find_path(oid::TEXT, oid::VARCHAR, Context::Implicit), Path::Relabel);
650        assert_eq!(find_path(oid::TEXT, oid::INT4, Context::Implicit), Path::None);
651        assert_eq!(find_path(oid::TEXT, oid::INT4, Context::Assignment), Path::ViaIo);
652        assert_eq!(find_path(oid::INT4, oid::TEXT, Context::Explicit), Path::ViaIo);
653        assert_eq!(
654            find_path(oid::INT8_ARRAY, oid::INT4_ARRAY, Context::Implicit),
655            Path::ArrayCoerce
656        );
657        assert_eq!(find_path(oid::OID_ARRAY, oid::OIDVECTOR, Context::Implicit), Path::ArrayCoerce);
658        assert_eq!(find_path(oid::OIDVECTOR, oid::OID_ARRAY, Context::Explicit), Path::None);
659        assert!(can_coerce(&[oid::UNKNOWN, oid::INT2], &[oid::INT4, oid::INT4], Context::Implicit));
660        assert!(!can_coerce(&[oid::TEXT], &[oid::INT4], Context::Implicit));
661        assert_eq!(typmod_function(oid::VARCHAR).map(|(_, a)| a), Some(false));
662        assert_eq!(typmod_function(oid::INT4), None);
663    }
664}