From 18a852b7bddd3355fb9f41f9ef467beef3160866 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Thu, 9 Jul 2026 21:39:31 +0300 Subject: [PATCH v2 2/4] Fix JSON_TABLE PLAN deparse to keep parentheses around nested joins get_json_table_plan() parenthesized the child of a parent/child (OUTER/INNER) plan only when that child was a sibling (UNION/CROSS) join, not when it was itself a parent/child join. A plan such as PLAN (p0 OUTER (p1 INNER p11)) was therefore deparsed as PLAN (p0 OUTER p1 INNER p11), which does not parse back to the same plan tree -- a dump/restore hazard. Parenthesize the child whenever it is not a bare path name, matching the logic already used for the operands of sibling joins. Reported-by: Thom Brown Discussion: https://postgr.es/m/CAA-aLv7aZGSExnbjJRw8eKkoXbu34TdoKLLA2gPye3aHjO5OSA@mail.gmail.com --- src/backend/utils/adt/ruleutils.c | 3 +- .../regress/expected/sqljson_jsontable.out | 29 +++++++++++++++++++ src/test/regress/sql/sqljson_jsontable.sql | 15 ++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index b83c8b9d7b5..0ea38c18ca4 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -12768,7 +12768,8 @@ get_json_table_plan(TableFunc *tf, JsonTablePlan *plan, deparse_context *context appendStringInfoString(context->buf, s->outerJoin ? " OUTER " : " INNER "); get_json_table_plan(tf, s->child, context, - IsA(s->child, JsonTableSiblingJoin)); + IsA(s->child, JsonTableSiblingJoin) || + castNode(JsonTablePathScan, s->child)->child); } } else if (IsA(plan, JsonTableSiblingJoin)) diff --git a/src/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out index e714e36baba..7f2327c55f5 100644 --- a/src/test/regress/expected/sqljson_jsontable.out +++ b/src/test/regress/expected/sqljson_jsontable.out @@ -504,6 +504,35 @@ DROP VIEW jsonb_table_view4; DROP VIEW jsonb_table_view5; DROP VIEW jsonb_table_view6; DROP DOMAIN jsonb_test_domain; +-- Parentheses around a nested parent/child plan must survive a dump, so the +-- plan parses back to the same tree. +CREATE VIEW jsonb_table_view_plan AS +SELECT * FROM JSON_TABLE( + jsonb 'null', '$' AS p0 + COLUMNS ( + NESTED PATH '$.a[*]' AS p1 COLUMNS ( + NESTED PATH '$.b[*]' AS p11 COLUMNS (b int PATH '$') + ) + ) + PLAN (p0 OUTER (p1 INNER p11)) +); +\sv jsonb_table_view_plan +CREATE OR REPLACE VIEW public.jsonb_table_view_plan AS + SELECT b + FROM JSON_TABLE( + 'null'::jsonb, '$' AS p0 + COLUMNS ( + NESTED PATH '$."a"[*]' AS p1 + COLUMNS ( + NESTED PATH '$."b"[*]' AS p11 + COLUMNS ( + b integer PATH '$' + ) + ) + ) + PLAN (p0 OUTER (p1 INNER p11)) + ) +DROP VIEW jsonb_table_view_plan; -- JSON_TABLE: only one FOR ORDINALITY columns allowed SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, a int PATH '$.a' ERROR ON EMPTY)) jt; ERROR: only one FOR ORDINALITY column is allowed diff --git a/src/test/regress/sql/sqljson_jsontable.sql b/src/test/regress/sql/sqljson_jsontable.sql index 22f9c5ae92f..e3f5ee1cab2 100644 --- a/src/test/regress/sql/sqljson_jsontable.sql +++ b/src/test/regress/sql/sqljson_jsontable.sql @@ -237,6 +237,21 @@ DROP VIEW jsonb_table_view5; DROP VIEW jsonb_table_view6; DROP DOMAIN jsonb_test_domain; +-- Parentheses around a nested parent/child plan must survive a dump, so the +-- plan parses back to the same tree. +CREATE VIEW jsonb_table_view_plan AS +SELECT * FROM JSON_TABLE( + jsonb 'null', '$' AS p0 + COLUMNS ( + NESTED PATH '$.a[*]' AS p1 COLUMNS ( + NESTED PATH '$.b[*]' AS p11 COLUMNS (b int PATH '$') + ) + ) + PLAN (p0 OUTER (p1 INNER p11)) +); +\sv jsonb_table_view_plan +DROP VIEW jsonb_table_view_plan; + -- JSON_TABLE: only one FOR ORDINALITY columns allowed SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (id FOR ORDINALITY, id2 FOR ORDINALITY, a int PATH '$.a' ERROR ON EMPTY)) jt; SELECT * FROM JSON_TABLE(jsonb '1', '$' COLUMNS (id FOR ORDINALITY, a int PATH '$' ERROR ON EMPTY)) jt; -- 2.50.1 (Apple Git-155)