v26-0005-pg_plan_advice-Fix-a-bug-when-a-subquery-is-prun.patch
application/octet-stream
Filename: v26-0005-pg_plan_advice-Fix-a-bug-when-a-subquery-is-prun.patch
Type: application/octet-stream
Part: 1
Message:
Re: pg_plan_advice
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: format-patch
Series: patch v26-0005
Subject: pg_plan_advice: Fix a bug when a subquery is pruned away entirely.
| File | + | − |
|---|---|---|
| contrib/pg_plan_advice/expected/semijoin.out | 17 | 0 |
| contrib/pg_plan_advice/pgpa_planner.c | 16 | 12 |
| contrib/pg_plan_advice/sql/semijoin.sql | 9 | 0 |
From 9350af26a88bc82b38eaf63fbea4ceaf80fd8690 Mon Sep 17 00:00:00 2001
From: Robert Haas <rhaas@postgresql.org>
Date: Tue, 7 Apr 2026 16:56:33 -0400
Subject: [PATCH v26 5/5] pg_plan_advice: Fix a bug when a subquery is pruned
away entirely.
If a subquery is proven empty, and if that subquery contained a
semijoin, and if making one side or the other of that semijoin
unique and performing an inner join was a possible strategy, then
the previous code would fail with ERROR: no rtoffset for plan %s
when attempting to generate advice. Fix that.
Reported-by: Alexander Lakhin <exclusion@gmail.com>
---
contrib/pg_plan_advice/expected/semijoin.out | 17 ++++++++++++
contrib/pg_plan_advice/pgpa_planner.c | 28 +++++++++++---------
contrib/pg_plan_advice/sql/semijoin.sql | 9 +++++++
3 files changed, 42 insertions(+), 12 deletions(-)
diff --git a/contrib/pg_plan_advice/expected/semijoin.out b/contrib/pg_plan_advice/expected/semijoin.out
index 5551c028a1f..680de215117 100644
--- a/contrib/pg_plan_advice/expected/semijoin.out
+++ b/contrib/pg_plan_advice/expected/semijoin.out
@@ -375,3 +375,20 @@ SELECT * FROM generate_series(1,1000) g, sj_narrow s WHERE g = s.val1;
(13 rows)
COMMIT;
+-- Test the case where the subquery containing a semijoin is removed from
+-- the query entirely; this test is just to make sure that advice generation
+-- does not fail.
+EXPLAIN (COSTS OFF, PLAN_ADVICE)
+SELECT * FROM
+ (SELECT * FROM sj_narrow WHERE id IN (SELECT val1 FROM sj_wide)
+ LIMIT 1) x,
+ LATERAL (SELECT 1 WHERE false) y;
+ QUERY PLAN
+--------------------------
+ Result
+ Replaces: Scan on x
+ One-Time Filter: false
+ Generated Plan Advice:
+ NO_GATHER(x)
+(5 rows)
+
diff --git a/contrib/pg_plan_advice/pgpa_planner.c b/contrib/pg_plan_advice/pgpa_planner.c
index 86b37e79b57..72ef3230abc 100644
--- a/contrib/pg_plan_advice/pgpa_planner.c
+++ b/contrib/pg_plan_advice/pgpa_planner.c
@@ -2065,6 +2065,9 @@ pgpa_compute_rt_identifier(pgpa_planner_info *proot, PlannerInfo *root,
/*
* Compute the range table offset for each pgpa_planner_info for which it
* is possible to meaningfully do so.
+ *
+ * For pgpa_planner_info objects for which no RT offset can be computed,
+ * clear sj_unique_rels, which is meaningless in such cases.
*/
static void
pgpa_compute_rt_offsets(pgpa_planner_state *pps, PlannedStmt *pstmt)
@@ -2096,23 +2099,24 @@ pgpa_compute_rt_offsets(pgpa_planner_state *pps, PlannedStmt *pstmt)
* there's no fixed rtoffset that we can apply to the RTIs
* used during planning to locate the corresponding relations.
*/
- if (rtinfo->dummy)
+ if (!rtinfo->dummy)
{
- /*
- * It will not be possible to make any effective use of
- * the sj_unique_rels list in this case, and it also won't
- * be important to do so. So just throw the list away to
- * avoid confusing pgpa_plan_walker.
- */
- proot->sj_unique_rels = NIL;
- break;
+ Assert(!proot->has_rtoffset);
+ proot->has_rtoffset = true;
+ proot->rtoffset = rtinfo->rtoffset;
}
- Assert(!proot->has_rtoffset);
- proot->has_rtoffset = true;
- proot->rtoffset = rtinfo->rtoffset;
break;
}
}
+
+ /*
+ * If we didn't end up setting has_rtoffset, then it will not be
+ * possible to make any effective use of sj_unique_rels, and it also
+ * won't be important to do so. So just throw the list away to avoid
+ * confusing pgpa_plan_walker.
+ */
+ if (!proot->has_rtoffset)
+ proot->sj_unique_rels = NIL;
}
}
diff --git a/contrib/pg_plan_advice/sql/semijoin.sql b/contrib/pg_plan_advice/sql/semijoin.sql
index 5a4ae52d1d9..873f0d3766c 100644
--- a/contrib/pg_plan_advice/sql/semijoin.sql
+++ b/contrib/pg_plan_advice/sql/semijoin.sql
@@ -116,3 +116,12 @@ SET LOCAL pg_plan_advice.advice = 'semijoin_unique(g)';
EXPLAIN (COSTS OFF, PLAN_ADVICE)
SELECT * FROM generate_series(1,1000) g, sj_narrow s WHERE g = s.val1;
COMMIT;
+
+-- Test the case where the subquery containing a semijoin is removed from
+-- the query entirely; this test is just to make sure that advice generation
+-- does not fail.
+EXPLAIN (COSTS OFF, PLAN_ADVICE)
+SELECT * FROM
+ (SELECT * FROM sj_narrow WHERE id IN (SELECT val1 FROM sj_wide)
+ LIMIT 1) x,
+ LATERAL (SELECT 1 WHERE false) y;
--
2.51.0