]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
SQL/JSON: Fix error-handling of some JsonBehavior expressions
authorAmit Langote <amitlan@postgresql.org>
Fri, 26 Jul 2024 06:59:27 +0000 (15:59 +0900)
committerAmit Langote <amitlan@postgresql.org>
Fri, 26 Jul 2024 07:00:06 +0000 (16:00 +0900)
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

src/backend/executor/execExpr.c
src/backend/executor/execExprInterp.c
src/test/regress/expected/sqljson_jsontable.out
src/test/regress/expected/sqljson_queryfuncs.out

index ccd4863778429c744b65c124e0b8da50c26937fc..b10359e3d6be119ebf52b61c2edc7f2fc709fa9a 100644 (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)
index d8735286c4d40b30d436ad773e981048f5bc380f..4c9b2a8c17828e7e914eaec1e42eb92f164ebbc9 100644 (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;
        }
 }
 
index 5fd43be36778394b4346a19f588b114f2bc9c050..19817b4be8cbb5348c62114abf3a128040d4af67 100644 (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  
index 074aedb2dd1ac3393490b8a5a856da4f838a48f0..ec8caee91c7c3466f0e6d883326a639d9f92a852 100644 (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 
 ------------