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>
Discussion: http://postgr.es/m/CA+TgmobOOmmXSJz3e+cjTY-bA1+W0dqVDqzxUBEvGtW62whYGg@mail.gmail.com
This commit is contained in:
Robert Haas 2026-04-13 10:34:09 -04:00
parent 1faf9dfa47
commit 0f93ebb311
3 changed files with 42 additions and 12 deletions

View file

@ -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)

View file

@ -2066,6 +2066,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)
@ -2097,23 +2100,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;
}
}

View file

@ -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;