mirror of
https://github.com/postgres/postgres.git
synced 2026-10-23 13:29:09 +03:00
JSON_TABLE: propagate table-level ON ERROR to columns per SQL standard
Per ISO/IEC 9075-2:2023, 7.11 <JSON table>, Syntax Rules 1)e)iv) and
1)f)xi), a regular or formatted JSON_TABLE column that does not specify
its own ON ERROR clause takes its default error behavior from the
table-level ON ERROR clause: with ERROR ON ERROR on the table, the
column behaves as ERROR ON ERROR; otherwise the column defaults to
NULL ON ERROR.
PostgreSQL instead always defaulted such columns to NULL ON ERROR, so
SELECT * FROM JSON_TABLE(jsonb '"err"', '$'
COLUMNS (a int PATH '$') ERROR ON ERROR) jt;
returned a NULL row where the standard requires an error. The
documentation stated the divergence as if it were a rule, saying that
the table-level clause "does not affect the errors that occur when
evaluating columns".
Implement the standard behavior in the JSON_TABLE syntactic
transformation: when a column lacks its own ON ERROR clause and the
table-level behavior is ERROR ON ERROR, synthesize an implicit ERROR ON
ERROR for the column before it is transformed into a JsonExpr. A column
with its own ON ERROR clause is unaffected. EXISTS columns are not
covered by those syntax rules, so they keep their FALSE ON ERROR
default. A JSON_TABLE stored in a view is now deparsed with the
previously implicit ERROR ON ERROR shown explicitly on the affected
columns, which is a semantically equivalent, round-trip-stable form.
86ab7f4c72 introduced this cascade as part of the PLAN clause and
af6fad879f reverted it, on the grounds that it should be a deliberate
and separately documented change rather than a side effect of an
unrelated feature. This is that change.
Do not back-patch. A query that specifies ERROR ON ERROR at the table
level and relies on its columns still yielding NULL now raises an error
instead of returning rows. Nothing is silently wrong in the released
branches: the table-level clause is ignored for columns consistently,
and that is what the documentation has promised since PostgreSQL 17, so
users could reasonably have depended on it.
Discussion: https://postgr.es/m/CAPpHfdtDXseGhrL14a2asOkwGnFTVrDk8SQc3iWZgWEEhXXMGw%40mail.gmail.com
Reviewed-by: Nikita Malakhov <hukutoc@gmail.com>
This commit is contained in:
@@ -4021,10 +4021,18 @@ where <replaceable class="parameter">json_table_column</replaceable> is:
|
||||
handle errors when evaluating the top-level
|
||||
<replaceable>path_expression</replaceable>. Use <literal>ERROR</literal>
|
||||
if you want the errors to be thrown and <literal>EMPTY</literal> to
|
||||
return an empty table, that is, a table containing 0 rows. Note that
|
||||
this clause does not affect the errors that occur when evaluating
|
||||
columns, for which the behavior depends on whether the
|
||||
<literal>ON ERROR</literal> clause is specified against a given column.
|
||||
return an empty table, that is, a table containing 0 rows.
|
||||
</para>
|
||||
<para>
|
||||
This clause also provides the default error behavior for the individual
|
||||
columns: a regular or formatted column that does not specify its own
|
||||
<literal>ON ERROR</literal> clause uses <literal>ERROR ON ERROR</literal>
|
||||
when <literal>ERROR</literal> is specified here, and
|
||||
<literal>NULL ON ERROR</literal> otherwise (including when this clause is
|
||||
omitted, in which case <literal>EMPTY</literal> is the default). A column
|
||||
that specifies its own <literal>ON ERROR</literal> clause is unaffected.
|
||||
<literal>EXISTS</literal> columns are an exception: they always default to
|
||||
<literal>FALSE ON ERROR</literal> regardless of this clause.
|
||||
</para>
|
||||
</listitem>
|
||||
</varlistentry>
|
||||
|
||||
@@ -49,7 +49,8 @@ static JsonTablePlan *transformJsonTableNestedColumns(JsonTableParseContext *cxt
|
||||
List *columns);
|
||||
static JsonFuncExpr *transformJsonTableColumn(JsonTableColumn *jtc,
|
||||
Node *contextItemExpr,
|
||||
List *passingArgs);
|
||||
List *passingArgs,
|
||||
bool errorOnError);
|
||||
static bool isCompositeType(Oid typid);
|
||||
static JsonTablePlan *makeJsonTablePathScan(JsonTableParseContext *cxt,
|
||||
JsonTablePathSpec *pathspec,
|
||||
@@ -68,7 +69,8 @@ static JsonTablePlan *makeJsonTableSiblingJoin(bool cross,
|
||||
JsonTablePlan *lplan,
|
||||
JsonTablePlan *rplan);
|
||||
static void
|
||||
appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs);
|
||||
appendJsonTableColumns(JsonTableParseContext *cxt, List *columns,
|
||||
List *passingArgs, bool errorOnError);
|
||||
|
||||
/*
|
||||
* transformJsonTable -
|
||||
@@ -344,7 +346,7 @@ transformJsonTableColumns(JsonTableParseContext *cxt,
|
||||
validateJsonTableChildPlan(cxt, childPlanSpec, columns);
|
||||
}
|
||||
|
||||
appendJsonTableColumns(cxt, columns, passingArgs);
|
||||
appendJsonTableColumns(cxt, columns, passingArgs, errorOnError);
|
||||
|
||||
/* End of column range. */
|
||||
if (list_length(tf->colvalexprs) == colMin)
|
||||
@@ -375,7 +377,8 @@ transformJsonTableColumns(JsonTableParseContext *cxt,
|
||||
|
||||
/* Append transformed non-nested JSON_TABLE columns to the TableFunc node */
|
||||
static void
|
||||
appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingArgs)
|
||||
appendJsonTableColumns(JsonTableParseContext *cxt, List *columns,
|
||||
List *passingArgs, bool errorOnError)
|
||||
{
|
||||
ListCell *col;
|
||||
ParseState *pstate = cxt->pstate;
|
||||
@@ -439,7 +442,7 @@ appendJsonTableColumns(JsonTableParseContext *cxt, List *columns, List *passingA
|
||||
param->typeMod = -1;
|
||||
|
||||
jfe = transformJsonTableColumn(rawc, (Node *) param,
|
||||
passingArgs);
|
||||
passingArgs, errorOnError);
|
||||
|
||||
colexpr = transformExpr(pstate, (Node *) jfe,
|
||||
EXPR_KIND_FROM_FUNCTION);
|
||||
@@ -494,7 +497,7 @@ isCompositeType(Oid typid)
|
||||
*/
|
||||
static JsonFuncExpr *
|
||||
transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
|
||||
List *passingArgs)
|
||||
List *passingArgs, bool errorOnError)
|
||||
{
|
||||
Node *pathspec;
|
||||
JsonFuncExpr *jfexpr = makeNode(JsonFuncExpr);
|
||||
@@ -536,6 +539,20 @@ transformJsonTableColumn(JsonTableColumn *jtc, Node *contextItemExpr,
|
||||
jfexpr->output->returning->format = jtc->format;
|
||||
jfexpr->on_empty = jtc->on_empty;
|
||||
jfexpr->on_error = jtc->on_error;
|
||||
|
||||
/*
|
||||
* Per the SQL/JSON standard, a regular or formatted column that does not
|
||||
* specify its own ON ERROR clause inherits ERROR ON ERROR from a
|
||||
* table-level ERROR ON ERROR clause; otherwise it defaults to NULL ON
|
||||
* ERROR (applied downstream in transformJsonExprCommon()). See ISO/IEC
|
||||
* 9075-2:2023, 7.11 <JSON table>, Syntax Rules 1)e)iv) (regular columns)
|
||||
* and 1)f)xi) (formatted columns). EXISTS columns are not covered by the
|
||||
* standard, so they keep their own default (FALSE ON ERROR).
|
||||
*/
|
||||
if (jfexpr->on_error == NULL && errorOnError &&
|
||||
jfexpr->op != JSON_EXISTS_OP)
|
||||
jfexpr->on_error = makeJsonBehavior(JSON_BEHAVIOR_ERROR, NULL, -1);
|
||||
|
||||
jfexpr->quotes = jtc->quotes;
|
||||
jfexpr->wrapper = jtc->wrapper;
|
||||
jfexpr->location = jtc->location;
|
||||
|
||||
@@ -562,12 +562,13 @@ FROM
|
||||
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
|
||||
ON true;
|
||||
ERROR: invalid input syntax for type integer: "err"
|
||||
-- TABLE-level ERROR ON ERROR is not propagated to columns
|
||||
-- TABLE-level ERROR ON ERROR is propagated to a column without its own
|
||||
-- ON ERROR clause (per SQL standard), so "err" raises instead of yielding NULL.
|
||||
SELECT *
|
||||
FROM
|
||||
(VALUES ('1'), ('"err"')) vals(js)
|
||||
LEFT OUTER JOIN
|
||||
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
|
||||
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt
|
||||
ON true;
|
||||
ERROR: invalid input syntax for type integer: "err"
|
||||
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
|
||||
@@ -576,15 +577,28 @@ SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR O
|
||||
ERROR: jsonpath member accessor can only be applied to an object
|
||||
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
|
||||
ERROR: no SQL/JSON item found for specified path of column "a"
|
||||
-- Table-level ERROR ON ERROR is not propagated to a column lacking its own
|
||||
-- ON ERROR clause: the column keeps the default NULL ON ERROR behavior, so a
|
||||
-- conversion failure yields NULL rather than raising an error.
|
||||
-- Table-level ERROR ON ERROR is propagated to a column lacking its own ON ERROR
|
||||
-- clause, so a conversion failure raises an error rather than yielding NULL
|
||||
-- (ISO/IEC 9075-2:2023, 7.11 <JSON table>, SR 1)e)iv)).
|
||||
SELECT * FROM JSON_TABLE(jsonb '"err"', '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt;
|
||||
ERROR: invalid input syntax for type integer: "err"
|
||||
-- ... but an explicit column-level ON ERROR still wins over the table-level one.
|
||||
SELECT * FROM JSON_TABLE(jsonb '"err"', '$' COLUMNS (a int PATH '$' NULL ON ERROR) ERROR ON ERROR) jt;
|
||||
a
|
||||
---
|
||||
|
||||
(1 row)
|
||||
|
||||
-- Propagation likewise applies to formatted columns (SR 1)f)xi)), but not to
|
||||
-- EXISTS columns, which are not part of the standard and keep FALSE ON ERROR.
|
||||
SELECT * FROM JSON_TABLE(jsonb '{"a":1}', '$' COLUMNS (a int[] PATH '$.a') ERROR ON ERROR) jt;
|
||||
ERROR: expected JSON array
|
||||
SELECT * FROM JSON_TABLE(jsonb '{}', 'strict $' COLUMNS (a int EXISTS PATH 'strict $.x') ERROR ON ERROR) jt;
|
||||
a
|
||||
---
|
||||
0
|
||||
(1 row)
|
||||
|
||||
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
|
||||
a
|
||||
---
|
||||
@@ -1884,7 +1898,7 @@ CREATE OR REPLACE VIEW public.json_table_view9 AS
|
||||
FROM JSON_TABLE(
|
||||
'"a"'::text, '$' AS json_table_path_0
|
||||
COLUMNS (
|
||||
a text PATH '$'
|
||||
a text PATH '$' ERROR ON ERROR
|
||||
) ERROR ON ERROR
|
||||
)
|
||||
DROP VIEW json_table_view8, json_table_view9;
|
||||
@@ -1922,7 +1936,7 @@ CREATE OR REPLACE VIEW public.json_table_view_on_empty AS
|
||||
FROM JSON_TABLE(
|
||||
'{}'::jsonb, '$' AS p0
|
||||
COLUMNS (
|
||||
a integer PATH '$."nosuch"' ERROR ON EMPTY
|
||||
a integer PATH '$."nosuch"' ERROR ON EMPTY ERROR ON ERROR
|
||||
) ERROR ON ERROR
|
||||
)
|
||||
DROP VIEW json_table_view_on_empty;
|
||||
|
||||
@@ -269,22 +269,29 @@ FROM
|
||||
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
|
||||
ON true;
|
||||
|
||||
-- TABLE-level ERROR ON ERROR is not propagated to columns
|
||||
-- TABLE-level ERROR ON ERROR is propagated to a column without its own
|
||||
-- ON ERROR clause (per SQL standard), so "err" raises instead of yielding NULL.
|
||||
SELECT *
|
||||
FROM
|
||||
(VALUES ('1'), ('"err"')) vals(js)
|
||||
LEFT OUTER JOIN
|
||||
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$' ERROR ON ERROR)) jt
|
||||
JSON_TABLE(vals.js::jsonb, '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt
|
||||
ON true;
|
||||
|
||||
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH '$.a' ERROR ON EMPTY)) jt;
|
||||
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'strict $.a' ERROR ON ERROR) ERROR ON ERROR) jt;
|
||||
SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (a int PATH 'lax $.a' ERROR ON EMPTY) ERROR ON ERROR) jt;
|
||||
|
||||
-- Table-level ERROR ON ERROR is not propagated to a column lacking its own
|
||||
-- ON ERROR clause: the column keeps the default NULL ON ERROR behavior, so a
|
||||
-- conversion failure yields NULL rather than raising an error.
|
||||
-- Table-level ERROR ON ERROR is propagated to a column lacking its own ON ERROR
|
||||
-- clause, so a conversion failure raises an error rather than yielding NULL
|
||||
-- (ISO/IEC 9075-2:2023, 7.11 <JSON table>, SR 1)e)iv)).
|
||||
SELECT * FROM JSON_TABLE(jsonb '"err"', '$' COLUMNS (a int PATH '$') ERROR ON ERROR) jt;
|
||||
-- ... but an explicit column-level ON ERROR still wins over the table-level one.
|
||||
SELECT * FROM JSON_TABLE(jsonb '"err"', '$' COLUMNS (a int PATH '$' NULL ON ERROR) ERROR ON ERROR) jt;
|
||||
-- Propagation likewise applies to formatted columns (SR 1)f)xi)), but not to
|
||||
-- EXISTS columns, which are not part of the standard and keep FALSE ON ERROR.
|
||||
SELECT * FROM JSON_TABLE(jsonb '{"a":1}', '$' COLUMNS (a int[] PATH '$.a') ERROR ON ERROR) jt;
|
||||
SELECT * FROM JSON_TABLE(jsonb '{}', 'strict $' COLUMNS (a int EXISTS PATH 'strict $.x') ERROR ON ERROR) jt;
|
||||
|
||||
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH '$' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
|
||||
SELECT * FROM JSON_TABLE(jsonb '"a"', '$' COLUMNS (a int PATH 'strict $.a' DEFAULT 1 ON EMPTY DEFAULT 2 ON ERROR)) jt;
|
||||
|
||||
Reference in New Issue
Block a user