From cfa814833ef89b0401e9cc52594b2c0324e0b5ed Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Thu, 9 Jul 2026 21:42:43 +0300
Subject: [PATCH v1 3/3] Make JSON_TABLE generated path names avoid collisions

generateJsonTablePathName() produced names of the form
"json_table_path_N" in the same namespace as user-supplied path and
column names, without checking whether the name was already in use.
When an unnamed NESTED path's generated name happened to match a
user-supplied path name that a PLAN clause referenced, two sibling paths
matched the same plan entry and one of them, together with its columns,
was silently dropped from the output.

Bump the counter until the generated name is unused, so a generated name
can no longer coincide with a user-supplied one.  The still-uncovered
path is then correctly reported as not found in the plan.

Reported-by: Thom Brown <thom@linux.com>
Discussion: https://postgr.es/m/CAA-aLv7aZGSExnbjJRw8eKkoXbu34TdoKLLA2gPye3aHjO5OSA@mail.gmail.com
---
 src/backend/parser/parse_jsontable.c            | 17 +++++++++++++----
 src/test/regress/expected/sqljson_jsontable.out | 15 +++++++++++++++
 src/test/regress/sql/sqljson_jsontable.sql      | 12 ++++++++++++
 3 files changed, 40 insertions(+), 4 deletions(-)

diff --git a/src/backend/parser/parse_jsontable.c b/src/backend/parser/parse_jsontable.c
index 674ab37c45a..978b2a66413 100644
--- a/src/backend/parser/parse_jsontable.c
+++ b/src/backend/parser/parse_jsontable.c
@@ -249,12 +249,21 @@ static char *
 generateJsonTablePathName(JsonTableParseContext *cxt)
 {
 	char		namebuf[32];
-	char	   *name = namebuf;
+	char	   *name;
 
-	snprintf(namebuf, sizeof(namebuf), "json_table_path_%d",
-			 cxt->pathNameId++);
+	/*
+	 * Bump the counter until we produce a name that is not already used as a
+	 * path or column name.  Otherwise a generated name could coincide with a
+	 * user-supplied one, which would confuse PLAN clause matching and could
+	 * silently drop a column.
+	 */
+	do
+	{
+		snprintf(namebuf, sizeof(namebuf), "json_table_path_%d",
+				 cxt->pathNameId++);
+	} while (LookupPathOrColumnName(cxt, namebuf));
 
-	name = pstrdup(name);
+	name = pstrdup(namebuf);
 	cxt->pathNames = lappend(cxt->pathNames, name);
 
 	return name;
diff --git a/src/test/regress/expected/sqljson_jsontable.out b/src/test/regress/expected/sqljson_jsontable.out
index 7f2327c55f5..5bdb3c0318e 100644
--- a/src/test/regress/expected/sqljson_jsontable.out
+++ b/src/test/regress/expected/sqljson_jsontable.out
@@ -1066,6 +1066,21 @@ ERROR:  invalid JSON_TABLE plan clause
 LINE 12:        PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21)...
                       ^
 DETAIL:  Expected INNER or OUTER.
+-- An unnamed NESTED path whose generated name would clash with a
+-- user-supplied path name must not silently drop columns: the generated
+-- name is made unique, so the still-uncovered path is reported instead.
+SELECT * FROM JSON_TABLE(
+       jsonb '[{"x":[1],"y":[2]}]', '$[*]' AS p0
+       COLUMNS (
+               NESTED PATH '$.x[*]' AS json_table_path_0 COLUMNS (x int PATH '$'),
+               NESTED PATH '$.y[*]' COLUMNS (y int PATH '$')
+       )
+       PLAN (p0 OUTER json_table_path_0)
+) jt;
+ERROR:  invalid JSON_TABLE specification
+LINE 5:                NESTED PATH '$.y[*]' COLUMNS (y int PATH '$')
+                       ^
+DETAIL:  PLAN clause for nested path json_table_path_1 was not found.
 -- JSON_TABLE: plan execution
 CREATE TEMP TABLE jsonb_table_test (js jsonb);
 INSERT INTO jsonb_table_test
diff --git a/src/test/regress/sql/sqljson_jsontable.sql b/src/test/regress/sql/sqljson_jsontable.sql
index e3f5ee1cab2..44fd68d5422 100644
--- a/src/test/regress/sql/sqljson_jsontable.sql
+++ b/src/test/regress/sql/sqljson_jsontable.sql
@@ -587,6 +587,18 @@ SELECT * FROM JSON_TABLE(
        PLAN ((p1 INNER (p12 CROSS p11)) CROSS (p2 INNER p21))
 ) jt;
 
+-- An unnamed NESTED path whose generated name would clash with a
+-- user-supplied path name must not silently drop columns: the generated
+-- name is made unique, so the still-uncovered path is reported instead.
+SELECT * FROM JSON_TABLE(
+       jsonb '[{"x":[1],"y":[2]}]', '$[*]' AS p0
+       COLUMNS (
+               NESTED PATH '$.x[*]' AS json_table_path_0 COLUMNS (x int PATH '$'),
+               NESTED PATH '$.y[*]' COLUMNS (y int PATH '$')
+       )
+       PLAN (p0 OUTER json_table_path_0)
+) jt;
+
 -- JSON_TABLE: plan execution
 
 CREATE TEMP TABLE jsonb_table_test (js jsonb);
-- 
2.50.1 (Apple Git-155)

