Re: CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions

From: jian he <jian(dot)universality(at)gmail(dot)com>
To: Amul Sul <sulamul(at)gmail(dot)com>
Cc: Kirill Reshke <reshkekirill(at)gmail(dot)com>, Corey Huinker <corey(dot)huinker(at)gmail(dot)com>, Vik Fearing <vik(at)postgresfriends(dot)org>, Isaac Morland <isaac(dot)morland(at)gmail(dot)com>, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions
Date: 2026-01-14 06:37:26
Message-ID: CACJufxHGxB7KPAStUHZcWhFEDXsnU06qOugh60X4hHG6J-xoUA@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

On Tue, Jan 13, 2026 at 1:50 PM Amul Sul <sulamul(at)gmail(dot)com> wrote:
>
> > [...]
> > However, we can add a field to node TypeCast for the raw default expression.
> > transformTypeSafeCast seems not needed, so I consolidated
> > the parsing analysis into transformTypeCast.
> >
>
> Yeah, but I am a bit unsure about the naming (i.e., raw_default) and
> the comment stating this is an "untransformed DEFAULT expression".
> What is the status of the arg member of the TypeCast structure -- is
> it transformed or untransformed? If that is also untransformed, we can
> simply drop "untransformed" word from the comment and rename
> raw_default to something more descriptive, such as defaultExpr.
> Currently, raw_default doesn't clearly indicate that it represents an
> expression, IMO.
>

ColumnDef->raw_default comments:
/* default value (untransformed parse tree) */

FunctionParameter->defexpr comments:
/* raw default expr, or NULL if not given */

so i changed it to "defexpr".

> Also, a few comments still refer to old transformTypeSafeCast(), which
> needs to be corrected.
>
ok.

> > ExprState *
> > ExecInitExprExtended(Expr *node, Node *escontext, PlanState *parent)
> >
>
> Yes, that looks like what I was thinking, but with the Node *escontext
> at the last position.
>
ok.

> + /*
> + * The collation of DEFAULT expression must match the collation of the
> + * target type.
> + */
> + defColl = exprCollation(defexpr);
> +
> + if (OidIsValid(targetTypecoll) && OidIsValid(defColl) &&
> + targetTypecoll != defColl)
> + ereport(ERROR,
> + errcode(ERRCODE_DATATYPE_MISMATCH),
> + errmsg("the collation of CAST DEFAULT expression
> conflicts with target type collation"),
>
> What if one collation is an invalid OID and the other is valid?
> Shouldn't we throw an error in that case?

That is unlikely to happen. Since a collatable type can not be cast to
non-collatable type.
To be bullet-proof, use ``if (targetTypecoll != defColl)`` should be fine.

> --
>
> + SafeTypeCastState *stcstate;
> +
> + stcstate = palloc0(sizeof(SafeTypeCastState));
> + stcstate->stcexpr = stcexpr;
> + stcstate->escontext.type = T_ErrorSaveContext;
> + state->escontext = &stcstate->escontext;
>
> Can't we reuse the state->escontext pointer in the SafeTypeCastState
> instead of allocating a separate ErrorSaveContext?
> --
>

When entering ExecInitSafeTypeCastExpr, we cannot be
sure about ExprState->escontext, it can be NULL.
Therefore, reusing the state->escontext pointer is not possible, i think.

> + ErrorSaveContext *saved_escontext;
> +
> + saved_escontext = state->escontext;
> +
> + state->escontext = NULL;
> +
> + ExecInitExprRec((Expr *) stcstate->stcexpr->default_expr,
> + state, resv, resnull);
> +
> + state->escontext = saved_escontext;
>
> Here should be some comment explanation as to why you are setting
> state->escontext = NULL, and why that is needed for the default
> expression initialization but not for the case_expr evaluation.
> --

``
select cast(a as int default b on conversion error) from (values ('a',
'b')) t(a,b);
``

The above example should fail during evaluation of the DEFAULT
expression. Therefore,
we cannot compile `stcstate->stcexpr->defexpr` error safely.

As a result, setting ``state->escontext = NULL;`` is necessary.
Since we do not want to override its previous value, saving and restoring it via
`saved_escontext` is required.

I've added comments to explain it.

summary:
1. when entering ExecInitSafeTypeCastExpr, we can not use
ExprState->escontext directly
2. we evaluation the cast expression error safe, ExprState->escontext
should pass
a valid ErrorSaveContext
3. we do not evaluation the default expression error safe, ExprState->escontext
should be NULL

>
> +
> + fmgr_info(functionId, &flinfo);
> +
> + return InputFunctionCallSafe(&flinfo, str, typioparam, typmod,
> + escontext, result);
>
> Remove the empty line between these two for consistency with other
> function's formatting.
> --
ok.

> + ? errhint("Safe type cast for user-defined types are
> not yet supported")
> + : errhint("Explicit cast is defined but definition is
> not error safe"),
>
> Hints should end with a period.
> --
ok.

> + /*
> + * Here, we cannot deparsing cast_expr directly, since
> + * transformTypeSafeCast may have folded it into a simple
> + * constant or NULL. Instead, we use source_expr and
> + * default_expr to reconstruct the CAST DEFAULT clause.
> + */
>
> I am wondering how other places handle expression deparsing; do they
> hold the original expression along with the transformed expression?
> Also, why are we only folding constants from the source_expr but not
> from the default expression?
>

SELECT CAST(1111 AS "char" DEFAULT 1 ON CONVERSION ERROR);
SELECT CAST(1111 AS "char" DEFAULT (cast (1111 AS "char")) ON CONVERSION ERROR);

We want the first case to succeed and the second case to fail. To ensure the
first case does not fail, we need avoid running eval_const_expressions on
SafeTypeCastExpr->cast_expr. The second case will still fail when
eval_const_expressions_mutator performs constant folding on
SafeTypeCastExpr->default_expr.

We cannot perform constant folding on SafeTypeCastExpr->cast_expr, but still
want to detect some simple cases—specifically, whether the source expression can
be coerced to the target type, so part of the constant-folding logic is in the
transformTypeCast->evaluate_expr. evaluate_expr is error-safe, this is what we
want. As a consequence, ruleutils.c can only use source_expr.

We already have a similar approach for handling deparsing in get_rule_expr.
```
case T_JsonValueExpr:
{
JsonValueExpr *jve = (JsonValueExpr *) node;
get_rule_expr((Node *) jve->raw_expr, context, false);
get_json_format(jve->format, context->buf);
}
break;
```

rebased, many variable names have been renamed, comments adjusted.

--
jian
https://www.enterprisedb.com/

Attachment Content-Type Size
v18-0022-CAST-expr-AS-newtype-DEFAULT-expr-ON-CONVERSION-ERROR.patch text/x-patch 118.5 KB
v18-0021-error-safe-for-casting-geometry-data-type.patch text/x-patch 11.9 KB
v18-0019-introduce-float8-safe-function.patch text/x-patch 3.8 KB
v18-0023-error-safe-for-user-defined-CREATE-CAST.patch text/x-patch 71.7 KB
v18-0020-refactor-point_dt.patch text/x-patch 8.6 KB
v18-0018-refactor-float_overflow_error-float_underflow_error-float_zero_d.patch text/x-patch 14.2 KB
v18-0017-error-safe-for-casting-jsonb-to-other-types-per-pg_cast.patch text/x-patch 6.3 KB
v18-0016-error-safe-for-casting-timestamp-to-other-types-per-pg_cast.patch text/x-patch 3.1 KB
v18-0014-error-safe-for-casting-interval-to-other-types-per-pg_cast.patch text/x-patch 2.1 KB
v18-0015-error-safe-for-casting-timestamptz-to-other-types-per-pg_cast.patch text/x-patch 3.6 KB
v18-0013-error-safe-for-casting-date-to-other-types-per-pg_cast.patch text/x-patch 2.2 KB
v18-0011-error-safe-for-casting-float4-to-other-types-per-pg_cast.patch text/x-patch 3.1 KB
v18-0012-error-safe-for-casting-float8-to-other-types-per-pg_cast.patch text/x-patch 3.6 KB
v18-0010-error-safe-for-casting-numeric-to-other-types-per-pg_cast.patch text/x-patch 5.2 KB
v18-0009-error-safe-for-casting-bigint-to-other-types-per-pg_cast.patch text/x-patch 3.9 KB
v18-0008-error-safe-for-casting-integer-to-other-types-per-pg_cast.patch text/x-patch 2.9 KB
v18-0007-error-safe-for-casting-macaddr8-to-other-types-per-pg_cast.patch text/x-patch 1.5 KB
v18-0005-error-safe-for-casting-character-varying-to-other-types-per-pg_c.patch text/x-patch 2.1 KB
v18-0006-error-safe-for-casting-inet-to-other-types-per-pg_cast.patch text/x-patch 1.8 KB
v18-0003-error-safe-for-casting-character-to-other-types-per-pg_cast.patch text/x-patch 4.6 KB
v18-0004-error-safe-for-casting-text-to-other-types-per-pg_cast.patch text/x-patch 9.9 KB
v18-0002-error-safe-for-casting-bit-varbit-to-other-types-per-pg_cast.patch text/x-patch 2.6 KB
v18-0001-error-safe-for-casting-bytea-to-other-types-per-pg_cast.patch text/x-patch 2.1 KB

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Andreas Karlsson 2026-01-14 06:43:35 Re: Parallelizing startup with many databases
Previous Message Andreas Karlsson 2026-01-14 06:31:40 Re: Remove header lock BufferGetLSNAtomic() on architectures with 64 bit atomic operations