| From: | jian he <jian(dot)universality(at)gmail(dot)com> |
|---|---|
| To: | Amit Langote <amitlangote09(at)gmail(dot)com> |
| Cc: | PostgreSQL-development <pgsql-hackers(at)postgresql(dot)org> |
| Subject: | Re: SQL/JSON DEFAULT ON ERROR/ON EMPTY evaluation fail should rethrow error unconditionally |
| Date: | 2026-10-28 05:37:36 |
| Message-ID: | CACJufxFzoHfLM2Ynj9vUB0L2fctgh9Ds5c2dDM5P7N4Rvbta+w@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
On Tue, Oct 27, 2026 at 9:22 PM Amit Langote <amitlangote09(at)gmail(dot)com> wrote:
>
> I’ve looked at the patch and it seems to be fixing the following two bugs:
>
> 1. Lack of a COERCION_FINISH step after ON ERROR/EMPTY expression
> evaluation in some cases meant the soft error saved during that
> evaluation was never rethrown and the query silently returned NULL.
>
Hi. in ExecInitJsonExpr, first we have:
escontext = jsexpr->on_error->btype != JSON_BEHAVIOR_ERROR ?
&jsestate->escontext : NULL;
then
if (jsexpr->on_error->btype != JSON_BEHAVIOR_ERROR &&
(!(IsA(jsexpr->on_error->expr, Const) &&
((Const *) jsexpr->on_error->expr)->constisnull) ||
returning_domain))
{
}
Within the IF branch above, escontext is never NULL, so the `if
(escontext)` check can be removed.
The comment there needs updating accordingly.
For ON EMPTY clause
if (jsexpr->on_empty != NULL &&
jsexpr->on_empty->btype != JSON_BEHAVIOR_ERROR &&
(!(IsA(jsexpr->on_empty->expr, Const) &&
((Const *) jsexpr->on_empty->expr)->constisnull) ||
returning_domain))
{
}
escontext can be NULL. So, the comment
/*
* Steps to evaluate the ON EMPTY expression; handle errors softly to
* rethrow them in COERCION_FINISH step that will be added later.
*/
is not fully accurate. We can change it to:
/*
* Steps to evaluate the ON EMPTY expression. Unless ON ERROR is
* ERROR, errors are handled softly, to be rethrown by the
* COERCION_FINISH step added below; otherwise they are thrown
* directly.
*/
> 2. Once a soft error has occurred, the dummy NULL it leaves behind
> should not be processed any further, but in a few places
> (CoerceToDomain, populate_domain(), populate_record(),
> ExecEvalJsonCoercion() called on the DEFAULT result) it currently is.
>
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -5188,6 +5188,21 @@ ExecEvalJsonCoercion(ExprState *state, ExprEvalStep *op,
{
ErrorSaveContext *escontext = op->d.jsonexpr_coercion.escontext;
+ /*
+ * An ON ERROR or ON EMPTY DEFAULT expression is evaluated with escontext
+ * in effect, so if it failed, the error has been saved in escontext and
+ * the value we were given to coerce is a dummy NULL. Return it as is;
+ * the COERCION_FINISH step that follows will throw the saved error, and
+ * running the coercion on the NULL might instead throw a hard error or
+ * overwrite that saved error.
+ */
+ if (SOFT_ERROR_OCCURRED(escontext))
+ {
+ *op->resnull = true;
+ *op->resvalue = (Datum) 0;
+ return;
+ }
+
The first sentence of the comment is not entirely accurate.
The value in op->resvalue is not guaranteed to be a dummy NULL.
For example, ExecInitCoerceToDomain, writes the result of domain CHECK
constraint check into
scratch->d.domaincheck.checkvalue rather than op->resvalue, so after a soft
CHECK violation op->resvalue still holds the original value.
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Roman Khapov | 2026-10-28 05:50:32 | Re: kqueue backend for pgbench |
| Previous Message | shveta malik | 2026-10-28 05:20:25 | Re: Proposal: Conflict log history table for Logical Replication |