pg_plan_advice: FOREIGN_JOIN advice generated for a single-relation foreign scan is not round-trip safe

From: Mahendra Singh Thalor <mahi6run(at)gmail(dot)com>
To: PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org>
Cc: Robert Haas <robertmhaas(at)gmail(dot)com>
Subject: pg_plan_advice: FOREIGN_JOIN advice generated for a single-relation foreign scan is not round-trip safe
Date: 2026-06-30 14:36:40
Message-ID: CAKYtNAofuAJBz6++SeikpCb=Y=MO1QgEuZNJ+KZOP2johF1r4Q@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

While testing pg_plan_advice together with postgres_fdw, I noticed that the
advice generated for a single-relation foreign scan is not round-trip safe,
which contradicts the "round-trip safe" guarantee documented in
contrib/pg_plan_advice/README.

When postgres_fdw pushes an aggregate down over a single foreign table, the
resulting ForeignScan has scanrelid == 0 but fs_relids names exactly one
relation. pg_plan_advice generates FOREIGN_JOIN(<rel>) advice for it.
However,
the advice parser requires a FOREIGN_JOIN target to name more than one
relation, so feeding the generated advice back in fails to parse.

*Reproducer* (uses a loopback postgres_fdw server pointing back at the same
cluster; adjust host/port/dbname/user to match your instance):

LOAD 'pg_plan_advice';
CREATE EXTENSION IF NOT EXISTS postgres_fdw;

CREATE SERVER loopback FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host 'localhost', port '5432', dbname 'postgres');
CREATE USER MAPPING FOR CURRENT_USER SERVER loopback;

CREATE TABLE base_tab (a int);
CREATE FOREIGN TABLE ftab (a int)
SERVER loopback OPTIONS (table_name 'base_tab');

-- aggregate pushed down: a single-relation ForeignScan (scanrelid 0)
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT count(*) FROM ftab;

-- now feed the generated advice back in
SET pg_plan_advice.advice = 'FOREIGN_JOIN(ftab)';
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT count(*) FROM ftab;

*On unpatched master this produces:*

Foreign Scan
Relations: Aggregate on (ftab)
Generated Plan Advice:
FOREIGN_JOIN(ftab) <- single-relation FOREIGN_JOIN
NO_GATHER(ftab)

ERROR: invalid value for parameter "pg_plan_advice.advice":
"FOREIGN_JOIN(ftab)"
DETAIL: Could not parse advice: FOREIGN_JOIN targets must contain more
than
one relation identifier at or near ")"

*Analysis*:

In pgpa_build_scan() (contrib/pg_plan_advice/pgpa_scan.c), a base-relation
foreign scan has scanrelid != 0 and is correctly classified as
PGPA_SCAN_ORDINARY. But an upper-rel foreign scan (such as an aggregate
pushdown) has scanrelid == 0, so it falls into the pgpa_relids() branch,
where
the T_ForeignScan case unconditionally chose PGPA_SCAN_FOREIGN -- even when
fs_relids contains a single relation. The generator then emits FOREIGN_JOIN,
which the parser (pgpa_parser.y) rejects for a single relation.

*Fix*:

The attached patch chooses PGPA_SCAN_FOREIGN only when more than one
relation
is involved (bms_membership(relids) == BMS_MULTIPLE), and otherwise treats
the
foreign scan as an ordinary scan, matching what we already do for
base-relation foreign scans:

--- a/contrib/pg_plan_advice/pgpa_scan.c
+++ b/contrib/pg_plan_advice/pgpa_scan.c
@@ -142,8 +142,19 @@ pgpa_build_scan(...)
* foreign scan, then the foreign join has been pushed
to the
* remote side, and we want that to be reflected in the
* generated advice.
+ *
+ * A foreign scan can also reach this branch while
targeting a
+ * single relation -- for example, when an aggregate
is pushed
+ * down over one relation, so that scanrelid is 0 but
fs_relids
+ * contains exactly one member. There is no foreign
join in
+ * that case, so emitting FOREIGN_JOIN advice would be
wrong
+ * (and not round-trip safe, since a FOREIGN_JOIN
target must
+ * name more than one relation). Treat it as an
ordinary scan.
*/
- strategy = PGPA_SCAN_FOREIGN;
+ if (bms_membership(relids) == BMS_MULTIPLE)
+ strategy = PGPA_SCAN_FOREIGN;
+ else
+ strategy = PGPA_SCAN_ORDINARY;
break;

The patch also adds a TAP test (contrib/pg_plan_advice/t/001_foreign_scan.pl
)
using a loopback postgres_fdw server, since exercising this requires both
pg_plan_advice and postgres_fdw. The test verifies that:

(1) no FOREIGN_JOIN advice is generated for a single-relation foreign
scan,
(2) the generated advice is round-trip safe, and
(3) a genuine, pushed-down multi-relation foreign join still gets
FOREIGN_JOIN advice.

The TAP test fails on unpatched master and passes with the fix; the existing
pg_plan_advice regression suite continues to pass.

Patch attached. Please review this patch and let me know feedback.

--
Thanks and Regards
Mahendra Singh Thalor
EnterpriseDB: http://www.enterprisedb.com

Attachment Content-Type Size
v1-pg_plan_advice-do-not-generate-FOREIGN_JOIN-advice-for-single-relation-foreign-scan.patch text/x-patch 6.9 KB

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Robert Haas 2026-06-30 14:52:07 Re: pg_plan_advice: FOREIGN_JOIN advice generated for a single-relation foreign scan is not round-trip safe
Previous Message Tom Lane 2026-06-30 14:03:27 Re: occasional ECPG failures on dikkop (FreeBSD)