diff --git a/doc/src/sgml/func/func-json.sgml b/doc/src/sgml/func/func-json.sgml
index 0763ec81ed8..f8ff4355a31 100644
--- a/doc/src/sgml/func/func-json.sgml
+++ b/doc/src/sgml/func/func-json.sgml
@@ -4021,10 +4021,18 @@ where json_table_column is:
handle errors when evaluating the top-level
path_expression. Use ERROR
if you want the errors to be thrown and EMPTY 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
- ON ERROR clause is specified against a given column.
+ return an empty table, that is, a table containing 0 rows.
+
+
+ This clause also provides the default error behavior for the individual
+ columns: a regular or formatted column that does not specify its own
+ ON ERROR clause uses ERROR ON ERROR
+ when ERROR is specified here, and
+ NULL ON ERROR otherwise (including when this clause is
+ omitted, in which case EMPTY is the default). A column
+ that specifies its own ON ERROR clause is unaffected.
+ EXISTS columns are an exception: they always default to
+ FALSE ON ERROR regardless of this clause.
diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
index d86c6946ee9..0c6c0a6dbf9 100644
--- a/src/backend/parser/parse_jsontable.c
+++ b/src/backend/parser/parse_jsontable.c
@@ -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 , 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;
diff --git a/src/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out
index ae64dbed303..4129a731faa 100644
--- a/src/test/regress/expected/sqljson_jsontable.out
+++ b/src/test/regress/expected/sqljson_jsontable.out
@@ -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 , 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;
diff --git a/src/test/regress/sql/sqljson_jsontable.sql b/src/test/regress/sql/sqljson_jsontable.sql
index 2a33aaec57f..b93b43899dd 100644
--- a/src/test/regress/sql/sqljson_jsontable.sql
+++ b/src/test/regress/sql/sqljson_jsontable.sql
@@ -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 , 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;