| 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.
| 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 |