From 18a315123ca5b32627906c74a77c22355dbee9f1 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Thu, 10 Sep 2026 10:12:46 -0400
Subject: [PATCH v5 3/4] pg_plan_advice: Disallow empty sublists within
 JOIN_ORDER()

Commit 8c9c2e5c09aeda713640ccb26bfca0fdccc310d5 fixed a similar
issue for FOREIGN_JOIN(). Handle this case similar to the way that
commit handled that case.

Backpatch-through: 19
Discussion: https://postgr.es/m/CA+TgmoYmXy-jiP5qDhqNEiYFEBzQsArO6O2d9E8szNZqi1bePQ@mail.gmail.com
---
 contrib/pg_plan_advice/expected/syntax.out | 7 +++++++
 contrib/pg_plan_advice/pgpa_parser.y       | 6 ++++++
 contrib/pg_plan_advice/sql/syntax.sql      | 4 ++++
 3 files changed, 17 insertions(+)

diff --git a/contrib/pg_plan_advice/expected/syntax.out b/contrib/pg_plan_advice/expected/syntax.out
index d53d9598634..6062f245307 100644
--- a/contrib/pg_plan_advice/expected/syntax.out
+++ b/contrib/pg_plan_advice/expected/syntax.out
@@ -212,6 +212,13 @@ DETAIL:  Could not parse advice: FOREIGN_JOIN targets must contain more than one
 SET pg_plan_advice.advice = 'FOREIGN_JOIN(())';
 ERROR:  invalid value for parameter "pg_plan_advice.advice": "FOREIGN_JOIN(())"
 DETAIL:  Could not parse advice: FOREIGN_JOIN targets must contain more than one relation identifier at or near ")"
+-- Join order requires at least one relation identifier.
+SET pg_plan_advice.advice = 'JOIN_ORDER(())';
+ERROR:  invalid value for parameter "pg_plan_advice.advice": "JOIN_ORDER(())"
+DETAIL:  Could not parse advice: JOIN_ORDER targets must contain at least one relation identifier at or near ")"
+SET pg_plan_advice.advice = 'JOIN_ORDER(a ({}))';
+ERROR:  invalid value for parameter "pg_plan_advice.advice": "JOIN_ORDER(a ({}))"
+DETAIL:  Could not parse advice: JOIN_ORDER targets must contain at least one relation identifier at or near "}"
 -- Tag keywords used as alias names work fine, because the 'identifier'
 -- nonterminal accepts all token types.
 SET pg_plan_advice.advice = 'SEQ_SCAN(hash_join)';
diff --git a/contrib/pg_plan_advice/pgpa_parser.y b/contrib/pg_plan_advice/pgpa_parser.y
index 295f16ad064..8ffa1f1dd40 100644
--- a/contrib/pg_plan_advice/pgpa_parser.y
+++ b/contrib/pg_plan_advice/pgpa_parser.y
@@ -257,12 +257,18 @@ join_order_sublist:
 			$$ = palloc0_object(pgpa_advice_target);
 			$$->ttype = PGPA_TARGET_ORDERED_LIST;
 			$$->children = $2;
+			if ($2 == NIL)
+				pgpa_yyerror(result, parse_error_msg_p, yyscanner,
+							 "JOIN_ORDER targets must contain at least one relation identifier");
 		}
 	| '{' simple_target_list '}'
 		{
 			$$ = palloc0_object(pgpa_advice_target);
 			$$->ttype = PGPA_TARGET_UNORDERED_LIST;
 			$$->children = $2;
+			if ($2 == NIL)
+				pgpa_yyerror(result, parse_error_msg_p, yyscanner,
+							 "JOIN_ORDER targets must contain at least one relation identifier");
 		}
 	;
 
diff --git a/contrib/pg_plan_advice/sql/syntax.sql b/contrib/pg_plan_advice/sql/syntax.sql
index de11e8c6fc2..c762b8b19b2 100644
--- a/contrib/pg_plan_advice/sql/syntax.sql
+++ b/contrib/pg_plan_advice/sql/syntax.sql
@@ -75,6 +75,10 @@ SET pg_plan_advice.advice = 'FOREIGN_JOIN(a)';
 SET pg_plan_advice.advice = 'FOREIGN_JOIN((a))';
 SET pg_plan_advice.advice = 'FOREIGN_JOIN(())';
 
+-- Join order requires at least one relation identifier.
+SET pg_plan_advice.advice = 'JOIN_ORDER(())';
+SET pg_plan_advice.advice = 'JOIN_ORDER(a ({}))';
+
 -- Tag keywords used as alias names work fine, because the 'identifier'
 -- nonterminal accepts all token types.
 SET pg_plan_advice.advice = 'SEQ_SCAN(hash_join)';
-- 
2.51.0

