From 081ffeddc12b2ae0fb3bb11907022326346e3805 Mon Sep 17 00:00:00 2001 From: Amit Langote Date: Fri, 21 Jun 2024 20:55:31 +0900 Subject: [PATCH v1] SQL/JSON: Disallow incompatible values in ON ERROR/EMPTY Currently, the grammar allows specifying any of the all supported values in the ON ERROR and ON EMPTY clause for each SQL/JSON function that supports those clauses. But the semantics of each of those function allows only a subset of the values for any given function. So, check during parse analysis that the provided value is valid for the given function and throw a syntax error if not. Reported-by: Jian He Discussion: https://postgr.es/m/CACJufxFgWGqpESSYzyJ6tSurr3vFYBSNEmCfkGyB_dMdptFnZQ%40mail.gmail.com --- src/backend/parser/parse_expr.c | 72 +++++++++++++++++-- .../regress/expected/sqljson_jsontable.out | 6 ++ .../regress/expected/sqljson_queryfuncs.out | 16 +++++ src/test/regress/sql/sqljson_jsontable.sql | 3 + src/test/regress/sql/sqljson_queryfuncs.sql | 5 ++ 5 files changed, 96 insertions(+), 6 deletions(-) diff --git a/src/backend/parser/parse_expr.c b/src/backend/parser/parse_expr.c index 00cd7358eb..3d79d17125 100644 --- a/src/backend/parser/parse_expr.c +++ b/src/backend/parser/parse_expr.c @@ -4300,14 +4300,74 @@ transformJsonFuncExpr(ParseState *pstate, JsonFuncExpr *func) } /* OMIT QUOTES is meaningless when strings are wrapped. */ - if (func->op == JSON_QUERY_OP && - func->quotes == JS_QUOTES_OMIT && - (func->wrapper == JSW_CONDITIONAL || - func->wrapper == JSW_UNCONDITIONAL)) + if (func->op == JSON_QUERY_OP) + { + if (func->quotes == JS_QUOTES_OMIT && + (func->wrapper == JSW_CONDITIONAL || + func->wrapper == JSW_UNCONDITIONAL)) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("SQL/JSON QUOTES behavior must not be specified when WITH WRAPPER is used"), + parser_errposition(pstate, func->location)); + if (func->on_empty != NULL && + func->on_empty->btype != JSON_BEHAVIOR_ERROR && + func->on_empty->btype != JSON_BEHAVIOR_NULL && + func->on_empty->btype != JSON_BEHAVIOR_EMPTY && + func->on_empty->btype != JSON_BEHAVIOR_EMPTY_ARRAY && + func->on_empty->btype != JSON_BEHAVIOR_EMPTY_OBJECT && + func->on_empty->btype != JSON_BEHAVIOR_DEFAULT) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid ON EMPTY behavior"), + errdetail("Only ERROR, NULL, EMPTY [ ARRAY | OBJECT }, or DEFAULT is allowed in ON EMPTY for JSON_QUERY()."), + parser_errposition(pstate, func->on_empty->location)); + if (func->on_error != NULL && + func->on_error->btype != JSON_BEHAVIOR_ERROR && + func->on_error->btype != JSON_BEHAVIOR_NULL && + func->on_error->btype != JSON_BEHAVIOR_EMPTY && + func->on_error->btype != JSON_BEHAVIOR_EMPTY_ARRAY && + func->on_error->btype != JSON_BEHAVIOR_EMPTY_OBJECT && + func->on_error->btype != JSON_BEHAVIOR_DEFAULT) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid ON ERROR behavior"), + errdetail("Only ERROR, NULL, EMPTY [ ARRAY | OBJECT }, or DEFAULT is allowed in ON ERROR for JSON_QUERY()."), + parser_errposition(pstate, func->on_error->location)); + } + + /* Check that ON ERROR/EMPTY behavior values are valid for the function. */ + if (func->op == JSON_EXISTS_OP && + func->on_error != NULL && + func->on_error->btype != JSON_BEHAVIOR_ERROR && + func->on_error->btype != JSON_BEHAVIOR_TRUE && + func->on_error->btype != JSON_BEHAVIOR_FALSE && + func->on_error->btype != JSON_BEHAVIOR_UNKNOWN) ereport(ERROR, errcode(ERRCODE_SYNTAX_ERROR), - errmsg("SQL/JSON QUOTES behavior must not be specified when WITH WRAPPER is used"), - parser_errposition(pstate, func->location)); + errmsg("invalid ON ERROR behavior"), + errdetail("Only ERROR, TRUE, FALSE, or UNKNOWN is allowed in ON ERROR for JSON_EXISTS()."), + parser_errposition(pstate, func->on_error->location)); + if (func->op == JSON_VALUE_OP) + { + if (func->on_empty != NULL && + func->on_empty->btype != JSON_BEHAVIOR_ERROR && + func->on_empty->btype != JSON_BEHAVIOR_NULL && + func->on_empty->btype != JSON_BEHAVIOR_DEFAULT) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid ON ERROR behavior"), + errdetail("Only ERROR, NULL, or DEFAULT is allowed in ON EMPTY for JSON_VALUE()."), + parser_errposition(pstate, func->on_empty->location)); + if (func->on_error != NULL && + func->on_error->btype != JSON_BEHAVIOR_ERROR && + func->on_error->btype != JSON_BEHAVIOR_NULL && + func->on_error->btype != JSON_BEHAVIOR_DEFAULT) + ereport(ERROR, + errcode(ERRCODE_SYNTAX_ERROR), + errmsg("invalid ON EMPTY behavior"), + errdetail("Only ERROR, NULL, or DEFAULT is allowed in ON ERROR for JSON_VALUE()."), + parser_errposition(pstate, func->on_error->location)); + } jsexpr = makeNode(JsonExpr); jsexpr->location = func->location; diff --git a/src/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out index cee90cead1..eace29efbf 100644 --- a/src/test/regress/expected/sqljson_jsontable.out +++ b/src/test/regress/expected/sqljson_jsontable.out @@ -1067,3 +1067,9 @@ CREATE OR REPLACE VIEW public.jsonb_table_view7 AS ) sub DROP VIEW jsonb_table_view7; DROP TABLE s; +-- Test ON ERROR / EMPTY value validity for the function +SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int) NULL ON ERROR); -- fail +ERROR: invalid ON ERROR behavior +LINE 1: ... * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int) NULL ON ER... + ^ +DETAIL: Only EMPTY or ERROR is allowed in the top-level ON ERROR clause. diff --git a/src/test/regress/expected/sqljson_queryfuncs.out b/src/test/regress/expected/sqljson_queryfuncs.out index 9cb250a27a..0c4b62b0bb 100644 --- a/src/test/regress/expected/sqljson_queryfuncs.out +++ b/src/test/regress/expected/sqljson_queryfuncs.out @@ -1353,3 +1353,19 @@ SELECT JSON_QUERY(jsonb 'null', '$xyz' PASSING 1 AS xyz); 1 (1 row) +-- Test ON ERROR / EMPTY value validity for the function; all fail. +SELECT JSON_EXISTS(jsonb '1', '$' DEFAULT 1 ON ERROR); +ERROR: invalid ON ERROR behavior +LINE 1: SELECT JSON_EXISTS(jsonb '1', '$' DEFAULT 1 ON ERROR); + ^ +DETAIL: Only ERROR, TRUE, FALSE, or UNKNOWN is allowed in ON ERROR for JSON_EXISTS(). +SELECT JSON_VALUE(jsonb '1', '$' EMPTY ON ERROR); +ERROR: invalid ON EMPTY behavior +LINE 1: SELECT JSON_VALUE(jsonb '1', '$' EMPTY ON ERROR); + ^ +DETAIL: Only ERROR, NULL, or DEFAULT is allowed in ON ERROR for JSON_VALUE(). +SELECT JSON_QUERY(jsonb '1', '$' TRUE ON ERROR); +ERROR: invalid ON ERROR behavior +LINE 1: SELECT JSON_QUERY(jsonb '1', '$' TRUE ON ERROR); + ^ +DETAIL: Only ERROR, NULL, EMPTY [ ARRAY | OBJECT }, or DEFAULT is allowed in ON ERROR for JSON_QUERY(). diff --git a/src/test/regress/sql/sqljson_jsontable.sql b/src/test/regress/sql/sqljson_jsontable.sql index a1f924146e..1f81464c5f 100644 --- a/src/test/regress/sql/sqljson_jsontable.sql +++ b/src/test/regress/sql/sqljson_jsontable.sql @@ -518,3 +518,6 @@ SELECT sub.* FROM s, \sv jsonb_table_view7 DROP VIEW jsonb_table_view7; DROP TABLE s; + +-- Test ON ERROR / EMPTY value validity for the function +SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int) NULL ON ERROR); -- fail diff --git a/src/test/regress/sql/sqljson_queryfuncs.sql b/src/test/regress/sql/sqljson_queryfuncs.sql index dc6380141b..4586fdb8a4 100644 --- a/src/test/regress/sql/sqljson_queryfuncs.sql +++ b/src/test/regress/sql/sqljson_queryfuncs.sql @@ -459,3 +459,8 @@ SELECT json_value('"aaa"', path RETURNING json) FROM jsonpaths; SELECT JSON_QUERY(jsonb 'null', '$xyz' PASSING 1 AS xy); SELECT JSON_QUERY(jsonb 'null', '$xy' PASSING 1 AS xyz); SELECT JSON_QUERY(jsonb 'null', '$xyz' PASSING 1 AS xyz); + +-- Test ON ERROR / EMPTY value validity for the function; all fail. +SELECT JSON_EXISTS(jsonb '1', '$' DEFAULT 1 ON ERROR); +SELECT JSON_VALUE(jsonb '1', '$' EMPTY ON ERROR); +SELECT JSON_QUERY(jsonb '1', '$' TRUE ON ERROR); -- 2.43.0