SQL/JSON: Fix error-handling of some JsonBehavior expressions

To ensure that the errors of executing a JsonBehavior expression that
is coerced in the parser are caught instead of being thrown directly,
pass ErrorSaveContext to ExecInitExprRec() when initializing it.
Also, add a EEOP_JSONEXPR_COERCION_FINISH step to handle the errors
that are caught that way.

Discussion: https://postgr.es/m/CACJufxEo4sUjKCYtda0_qt9tazqqKPmF1cqhW9KBOUeJFqQd2g@mail.gmail.com
Backpatch-through: 17
This commit is contained in:
Amit Langote 2024-07-26 15:59:27 +09:00
parent c7301c3b6f
commit 63e6c5f4a2
4 changed files with 65 additions and 4 deletions

View File

@ -4400,6 +4400,8 @@ ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state,
if (jsexpr->on_error &&
jsexpr->on_error->btype != JSON_BEHAVIOR_ERROR)
{
ErrorSaveContext *saved_escontext;
jsestate->jump_error = state->steps_len;
/* JUMP to end if false, that is, skip the ON ERROR expression. */
@ -4410,15 +4412,36 @@ ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state,
scratch->d.jump.jumpdone = -1; /* set below */
ExprEvalPushStep(state, scratch);
/* Steps to evaluate the ON ERROR expression */
/*
* Steps to evaluate the ON ERROR expression; handle errors softly to
* rethrow them in COERCION_FINISH step that will be added later.
*/
saved_escontext = state->escontext;
state->escontext = escontext;
ExecInitExprRec((Expr *) jsexpr->on_error->expr,
state, resv, resnull);
state->escontext = saved_escontext;
/* Step to coerce the ON ERROR expression if needed */
if (jsexpr->on_error->coerce)
ExecInitJsonCoercion(state, jsexpr->returning, escontext,
jsexpr->omit_quotes, resv, resnull);
/*
* Add a COERCION_FINISH step to check for errors that may occur when
* coercing and rethrow them.
*/
if (jsexpr->on_error->coerce ||
IsA(jsexpr->on_error->expr, CoerceViaIO) ||
IsA(jsexpr->on_error->expr, CoerceToDomain))
{
scratch->opcode = EEOP_JSONEXPR_COERCION_FINISH;
scratch->resvalue = resv;
scratch->resnull = resnull;
scratch->d.jsonexpr.jsestate = jsestate;
ExprEvalPushStep(state, scratch);
}
/* JUMP to end to skip the ON EMPTY steps added below. */
jumps_to_end = lappend_int(jumps_to_end, state->steps_len);
scratch->opcode = EEOP_JUMP;
@ -4433,6 +4456,8 @@ ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state,
if (jsexpr->on_empty != NULL &&
jsexpr->on_empty->btype != JSON_BEHAVIOR_ERROR)
{
ErrorSaveContext *saved_escontext;
jsestate->jump_empty = state->steps_len;
/* JUMP to end if false, that is, skip the ON EMPTY expression. */
@ -4443,14 +4468,36 @@ ExecInitJsonExpr(JsonExpr *jsexpr, ExprState *state,
scratch->d.jump.jumpdone = -1; /* set below */
ExprEvalPushStep(state, scratch);
/* Steps to evaluate the ON EMPTY expression */
/*
* Steps to evaluate the ON EMPTY expression; handle errors softly to
* rethrow them in COERCION_FINISH step that will be added later.
*/
saved_escontext = state->escontext;
state->escontext = escontext;
ExecInitExprRec((Expr *) jsexpr->on_empty->expr,
state, resv, resnull);
state->escontext = saved_escontext;
/* Step to coerce the ON EMPTY expression if needed */
if (jsexpr->on_empty->coerce)
ExecInitJsonCoercion(state, jsexpr->returning, escontext,
jsexpr->omit_quotes, resv, resnull);
/*
* Add a COERCION_FINISH step to check for errors that may occur when
* coercing and rethrow them.
*/
if (jsexpr->on_empty->coerce ||
IsA(jsexpr->on_empty->expr, CoerceViaIO) ||
IsA(jsexpr->on_empty->expr, CoerceToDomain))
{
scratch->opcode = EEOP_JSONEXPR_COERCION_FINISH;
scratch->resvalue = resv;
scratch->resnull = resnull;
scratch->d.jsonexpr.jsestate = jsestate;
ExprEvalPushStep(state, scratch);
}
}
foreach(lc, jumps_to_end)

View File

@ -4558,6 +4558,12 @@ ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op)
*op->resvalue = (Datum) 0;
*op->resnull = true;
jsestate->error.value = BoolGetDatum(true);
/*
* Reset for next use such as for catching errors when coercing a
* JsonBehavior expression.
*/
jsestate->escontext.error_occurred = false;
}
}

View File

@ -227,7 +227,11 @@ SELECT * FROM JSON_TABLE(jsonb '{"d1": "H"}', '$'
SELECT * FROM JSON_TABLE(jsonb '{"d1": "H"}', '$'
COLUMNS (js1 jsonb_test_domain PATH '$.a2' DEFAULT 'foo'::jsonb_test_domain ON EMPTY));
ERROR: value for domain jsonb_test_domain violates check constraint "jsonb_test_domain_check"
js1
-----
(1 row)
SELECT * FROM JSON_TABLE(jsonb '{"d1": "H"}', '$'
COLUMNS (js1 jsonb_test_domain PATH '$.a2' DEFAULT 'foo1'::jsonb_test_domain ON EMPTY));
js1

View File

@ -1232,7 +1232,11 @@ DROP TABLE test_jsonb_mutability;
DROP FUNCTION ret_setint;
CREATE DOMAIN queryfuncs_test_domain AS text CHECK (value <> 'foo');
SELECT JSON_VALUE(jsonb '{"d1": "H"}', '$.a2' RETURNING queryfuncs_test_domain DEFAULT 'foo'::queryfuncs_test_domain ON EMPTY);
ERROR: value for domain queryfuncs_test_domain violates check constraint "queryfuncs_test_domain_check"
json_value
------------
(1 row)
SELECT JSON_VALUE(jsonb '{"d1": "H"}', '$.a2' RETURNING queryfuncs_test_domain DEFAULT 'foo1'::queryfuncs_test_domain ON EMPTY);
json_value
------------