mirror of https://github.com/postgres/postgres
SQL/JSON: Improve error-handling of JsonBehavior expressions
Instead of returning a NULL when the JsonBehavior expression value could not be coerced to the RETURNING type, throw the error message informing the user that it is the JsonBehavior expression that caused the error with the actual coercion error message shown in its DETAIL line. Discussion: https://postgr.es/m/CACJufxEo4sUjKCYtda0_qt9tazqqKPmF1cqhW9KBOUeJFqQd2g@mail.gmail.com Backpatch-through: 17
This commit is contained in:
parent
63e6c5f4a2
commit
231b7d670b
|
@ -4284,13 +4284,12 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op,
|
|||
memset(&jsestate->error, 0, sizeof(NullableDatum));
|
||||
memset(&jsestate->empty, 0, sizeof(NullableDatum));
|
||||
|
||||
/*
|
||||
* Also reset ErrorSaveContext contents for the next row. Since we don't
|
||||
* set details_wanted, we don't need to also reset error_data, which would
|
||||
* be NULL anyway.
|
||||
*/
|
||||
Assert(!jsestate->escontext.details_wanted &&
|
||||
jsestate->escontext.error_data == NULL);
|
||||
/* Also reset ErrorSaveContext contents for the next row. */
|
||||
if (jsestate->escontext.details_wanted)
|
||||
{
|
||||
jsestate->escontext.error_data = NULL;
|
||||
jsestate->escontext.details_wanted = false;
|
||||
}
|
||||
jsestate->escontext.error_occurred = false;
|
||||
|
||||
switch (jsexpr->op)
|
||||
|
@ -4400,6 +4399,14 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op,
|
|||
error = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* When setting up the ErrorSaveContext (if needed) for capturing the
|
||||
* errors that occur when coercing the JsonBehavior expression, set
|
||||
* details_wanted to be able to show the actual error message as the
|
||||
* DETAIL of the error message that tells that it is the JsonBehavior
|
||||
* expression that caused the error; see ExecEvalJsonCoercionFinish().
|
||||
*/
|
||||
|
||||
/* Handle ON EMPTY. */
|
||||
if (empty)
|
||||
{
|
||||
|
@ -4410,6 +4417,9 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op,
|
|||
if (jsexpr->on_empty->btype != JSON_BEHAVIOR_ERROR)
|
||||
{
|
||||
jsestate->empty.value = BoolGetDatum(true);
|
||||
/* Set up to catch coercion errors of the ON EMPTY value. */
|
||||
jsestate->escontext.error_occurred = false;
|
||||
jsestate->escontext.details_wanted = true;
|
||||
Assert(jsestate->jump_empty >= 0);
|
||||
return jsestate->jump_empty;
|
||||
}
|
||||
|
@ -4417,6 +4427,9 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op,
|
|||
else if (jsexpr->on_error->btype != JSON_BEHAVIOR_ERROR)
|
||||
{
|
||||
jsestate->error.value = BoolGetDatum(true);
|
||||
/* Set up to catch coercion errors of the ON ERROR value. */
|
||||
jsestate->escontext.error_occurred = false;
|
||||
jsestate->escontext.details_wanted = true;
|
||||
Assert(!throw_error && jsestate->jump_error >= 0);
|
||||
return jsestate->jump_error;
|
||||
}
|
||||
|
@ -4442,6 +4455,9 @@ ExecEvalJsonExprPath(ExprState *state, ExprEvalStep *op,
|
|||
*op->resvalue = (Datum) 0;
|
||||
*op->resnull = true;
|
||||
jsestate->error.value = BoolGetDatum(true);
|
||||
/* Set up to catch coercion errors of the ON ERROR value. */
|
||||
jsestate->escontext.error_occurred = false;
|
||||
jsestate->escontext.details_wanted = true;
|
||||
return jsestate->jump_error;
|
||||
}
|
||||
|
||||
|
@ -4544,9 +4560,33 @@ ExecEvalJsonCoercion(ExprState *state, ExprEvalStep *op,
|
|||
(Node *) escontext);
|
||||
}
|
||||
|
||||
static char *
|
||||
GetJsonBehaviorValueString(JsonBehavior *behavior)
|
||||
{
|
||||
/*
|
||||
* The order of array elements must correspond to the order of
|
||||
* JsonBehaviorType members.
|
||||
*/
|
||||
const char *behavior_names[] =
|
||||
{
|
||||
"NULL",
|
||||
"ERROR",
|
||||
"EMPTY",
|
||||
"TRUE",
|
||||
"FALSE",
|
||||
"UNKNOWN",
|
||||
"EMPTY ARRAY",
|
||||
"EMPTY OBJECT",
|
||||
"DEFAULT"
|
||||
};
|
||||
|
||||
return pstrdup(behavior_names[behavior->btype]);
|
||||
}
|
||||
|
||||
/*
|
||||
* Checks if an error occurred in ExecEvalJsonCoercion(). If so, this sets
|
||||
* JsonExprState.error to trigger the ON ERROR handling steps.
|
||||
* JsonExprState.error to trigger the ON ERROR handling steps, unless the
|
||||
* error is thrown when coercing a JsonBehavior value.
|
||||
*/
|
||||
void
|
||||
ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op)
|
||||
|
@ -4555,8 +4595,28 @@ ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op)
|
|||
|
||||
if (SOFT_ERROR_OCCURRED(&jsestate->escontext))
|
||||
{
|
||||
/*
|
||||
* jsestate->error or jsetate->empty being set means that the error
|
||||
* occurred when coercing the JsonBehavior value. Throw the error in
|
||||
* that case with the actual coercion error message shown in the
|
||||
* DETAIL part.
|
||||
*/
|
||||
if (DatumGetBool(jsestate->error.value))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("could not coerce ON ERROR expression (%s) to the RETURNING type",
|
||||
GetJsonBehaviorValueString(jsestate->jsexpr->on_error)),
|
||||
errdetail("%s", jsestate->escontext.error_data->message)));
|
||||
else if (DatumGetBool(jsestate->empty.value))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_DATATYPE_MISMATCH),
|
||||
errmsg("could not coerce ON EMPTY expression (%s) to the RETURNING type",
|
||||
GetJsonBehaviorValueString(jsestate->jsexpr->on_empty)),
|
||||
errdetail("%s", jsestate->escontext.error_data->message)));
|
||||
|
||||
*op->resvalue = (Datum) 0;
|
||||
*op->resnull = true;
|
||||
|
||||
jsestate->error.value = BoolGetDatum(true);
|
||||
|
||||
/*
|
||||
|
@ -4564,6 +4624,8 @@ ExecEvalJsonCoercionFinish(ExprState *state, ExprEvalStep *op)
|
|||
* JsonBehavior expression.
|
||||
*/
|
||||
jsestate->escontext.error_occurred = false;
|
||||
jsestate->escontext.error_occurred = false;
|
||||
jsestate->escontext.details_wanted = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -227,11 +227,8 @@ 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));
|
||||
js1
|
||||
-----
|
||||
|
||||
(1 row)
|
||||
|
||||
ERROR: could not coerce ON EMPTY expression (DEFAULT) to the RETURNING type
|
||||
DETAIL: value for domain jsonb_test_domain violates check constraint "jsonb_test_domain_check"
|
||||
SELECT * FROM JSON_TABLE(jsonb '{"d1": "H"}', '$'
|
||||
COLUMNS (js1 jsonb_test_domain PATH '$.a2' DEFAULT 'foo1'::jsonb_test_domain ON EMPTY));
|
||||
js1
|
||||
|
|
|
@ -313,11 +313,8 @@ SELECT JSON_VALUE(jsonb '"2017-02-20"', '$' RETURNING date) + 9;
|
|||
-- Test NULL checks execution in domain types
|
||||
CREATE DOMAIN sqljsonb_int_not_null AS int NOT NULL;
|
||||
SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null);
|
||||
json_value
|
||||
------------
|
||||
|
||||
(1 row)
|
||||
|
||||
ERROR: could not coerce ON ERROR expression (NULL) to the RETURNING type
|
||||
DETAIL: domain sqljsonb_int_not_null does not allow null values
|
||||
SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null ERROR ON ERROR);
|
||||
ERROR: domain sqljsonb_int_not_null does not allow null values
|
||||
SELECT JSON_VALUE(jsonb 'null', '$' RETURNING sqljsonb_int_not_null DEFAULT 2 ON EMPTY ERROR ON ERROR);
|
||||
|
@ -1035,11 +1032,8 @@ SELECT JSON_QUERY(jsonb '{"a": 1}', '$.a' RETURNING sqljsonb_int_not_null);
|
|||
(1 row)
|
||||
|
||||
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null);
|
||||
json_query
|
||||
------------
|
||||
|
||||
(1 row)
|
||||
|
||||
ERROR: could not coerce ON EMPTY expression (NULL) to the RETURNING type
|
||||
DETAIL: domain sqljsonb_int_not_null does not allow null values
|
||||
SELECT JSON_QUERY(jsonb '{"a": 1}', '$.b' RETURNING sqljsonb_int_not_null ERROR ON EMPTY ERROR ON ERROR);
|
||||
ERROR: no SQL/JSON item found for specified path
|
||||
-- Test timestamptz passing and output
|
||||
|
@ -1232,11 +1226,8 @@ 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);
|
||||
json_value
|
||||
------------
|
||||
|
||||
(1 row)
|
||||
|
||||
ERROR: could not coerce ON EMPTY expression (DEFAULT) to the RETURNING type
|
||||
DETAIL: value for domain queryfuncs_test_domain violates check constraint "queryfuncs_test_domain_check"
|
||||
SELECT JSON_VALUE(jsonb '{"d1": "H"}', '$.a2' RETURNING queryfuncs_test_domain DEFAULT 'foo1'::queryfuncs_test_domain ON EMPTY);
|
||||
json_value
|
||||
------------
|
||||
|
|
Loading…
Reference in New Issue