Add pg_plan_advice contrib module.
Provide a facility that (1) can be used to stabilize certain plan choices
so that the planner cannot reverse course without authorization and
(2) can be used by knowledgeable users to insist on plan choices contrary
to what the planner believes best. In both cases, terrible outcomes are
possible: users should think twice and perhaps three times before
constraining the planner's ability to do as it thinks best; nevertheless,
there are problems that are much more easily solved with these facilities
than without them.
This patch takes the approach of analyzing a finished plan to produce
textual output, which we call "plan advice", that describes key
decisions made during plan; if that plan advice is provided during
future planning cycles, it will force those key decisions to be made in
the same way. Not all planner decisions can be controlled using advice;
for example, decisions about how to perform aggregation are currently
out of scope, as is choice of sort order. Plan advice can also be edited
by the user, or even written from scratch in simple cases, making it
possible to generate outcomes that the planner would not have produced.
Partial advice can be provided to control some planner outcomes but not
others.
Currently, plan advice is focused only on specific outcomes, such as
the choice to use a sequential scan for a particular relation, and not
on estimates that might contribute to those outcomes, such as a
possibly-incorrect selectivity estimate. While it would be useful to
users to be able to provide plan advice that affects selectivity
estimates or other aspects of costing, that is out of scope for this
commit.
Reviewed-by: Lukas Fittl <lukas@fittl.com>
Reviewed-by: Jakub Wartak <jakub.wartak@enterprisedb.com>
Reviewed-by: Greg Burd <greg@burd.me>
Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com>
Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
Reviewed-by: Dian Fay <di@nmfay.com>
Reviewed-by: Ajay Pal <ajay.pal.k@gmail.com>
Reviewed-by: John Naylor <johncnaylorls@gmail.com>
Reviewed-by: Alexandra Wang <alexandra.wang.oss@gmail.com>
Discussion: http://postgr.es/m/CA+TgmoZ-Jh1T6QyWoCODMVQdhTUPYkaZjWztzP1En4=ZHoKPzw@mail.gmail.com
2026-03-12 12:59:52 -04:00
|
|
|
LOAD 'pg_plan_advice';
|
|
|
|
|
SET max_parallel_workers_per_gather = 0;
|
|
|
|
|
SET seq_page_cost = 0.1;
|
|
|
|
|
SET random_page_cost = 0.1;
|
|
|
|
|
SET cpu_tuple_cost = 0;
|
|
|
|
|
SET cpu_index_tuple_cost = 0;
|
|
|
|
|
|
|
|
|
|
CREATE TABLE scan_table (a int primary key, b text)
|
|
|
|
|
WITH (autovacuum_enabled = false);
|
|
|
|
|
INSERT INTO scan_table
|
|
|
|
|
SELECT g, 'some text ' || g FROM generate_series(1, 100000) g;
|
|
|
|
|
CREATE INDEX scan_table_b ON scan_table USING brin (b);
|
|
|
|
|
VACUUM ANALYZE scan_table;
|
|
|
|
|
|
|
|
|
|
-- Sequential scan
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table;
|
|
|
|
|
|
|
|
|
|
-- Index scan
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
|
|
|
|
|
-- Index-only scan
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT a FROM scan_table WHERE a = 1;
|
|
|
|
|
|
|
|
|
|
-- Bitmap heap scan
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table
|
|
|
|
|
WHERE b > 'some text 8';
|
|
|
|
|
|
|
|
|
|
-- TID scan
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE ctid = '(0,1)';
|
|
|
|
|
|
|
|
|
|
-- TID range scan
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table
|
|
|
|
|
WHERE ctid > '(1,1)' AND ctid < '(2,1)';
|
|
|
|
|
|
|
|
|
|
-- Try forcing each of our test queries to use the scan type they
|
|
|
|
|
-- wanted to use anyway. This should succeed.
|
|
|
|
|
BEGIN;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(scan_table)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_ONLY_SCAN(scan_table scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT a FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'BITMAP_HEAP_SCAN(scan_table)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table
|
|
|
|
|
WHERE b > 'some text 8';
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'TID_SCAN(scan_table)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE ctid = '(0,1)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table
|
|
|
|
|
WHERE ctid > '(1,1)' AND ctid < '(2,1)';
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- Try to force a full scan of the table to use some other scan type. All
|
|
|
|
|
-- of these will fail. An index scan or bitmap heap scan could potentially
|
|
|
|
|
-- generate the correct answer, but the planner does not even consider these
|
|
|
|
|
-- possibilities due to the lack of a WHERE clause.
|
|
|
|
|
BEGIN;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_ONLY_SCAN(scan_table scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'BITMAP_HEAP_SCAN(scan_table)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'TID_SCAN(scan_table)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table;
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- Try again to force index use. This should now succeed for the INDEX_SCAN
|
|
|
|
|
-- and BITMAP_HEAP_SCAN, but the INDEX_ONLY_SCAN can't be forced because the
|
|
|
|
|
-- query fetches columns not included in the index.
|
|
|
|
|
BEGIN;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a > 0;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_ONLY_SCAN(scan_table scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a > 0;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'BITMAP_HEAP_SCAN(scan_table)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a > 0;
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- We can force a primary key lookup to use a sequential scan, but we
|
|
|
|
|
-- can't force it to use an index-only scan (due to the column list)
|
pg_plan_advice: Invent DO_NOT_SCAN(relation_identifier).
The premise of src/test/modules/test_plan_advice is that if we plan
a query once, generate plan advice, and then replan it using that
same advice, all of that advice should apply cleanly, since the
settings and everything else are the same. Unfortunately, that's
not the case: the test suite is the main regression tests, and
concurrent activity can change the statistics on tables involved
in the query, especially system catalogs. That's OK as long as it
only affects costing, but in a few cases, it affects which relations
appear in the final plan at all.
In the buildfarm failures observed to date, this happens because
we consider alternative subplans for the same portion of the query;
in theory, MinMaxAggPath is vulnerable to a similar hazard. In both
cases, the planner clones an entire subquery, and the clone has a
different plan name, and therefore different range table identifiers,
than the original. If a cost change results in flipping between one
of these plans and the other, the test_plan_advice tests will fail,
because the range table identifiers to which advice was applied won't
even be present in the output of the second planning cycle.
To fix, invent a new DO_NOT_SCAN advice tag. When generating advice,
emit it for relations that should not appear in the final plan at
all, because some alternative version of that relation was used
instead. When DO_NOT_SCAN is supplied, disable all scan methods for
that relation.
To make this work, we reuse a bunch of the machinery that previously
existed for the purpose of ensuring that we build the same set of
relation identifiers during planning as we do from the final
PlannedStmt. In the process, this commit slightly weakens the
cross-check mechanism: before this commit, it would fire whenever
the pg_plan_advice module was loaded, even if pg_plan_advice wasn't
actually doing anything; now, it will only engage when we have some
other reason to create a pgpa_planner_state. The old way was complex
and didn't add much useful test coverage, so this seems like an
acceptable sacrifice.
Discussion: http://postgr.es/m/CA+TgmoYuWmN-00Ec5pY7zAcpSFQUQLbgAdVWGR9kOR-HM-fHrA@mail.gmail.com
Reviewed-by: Lukas Fittl <lukas@fittl.com>
2026-03-26 17:09:57 -04:00
|
|
|
-- or a TID scan (due to the absence of a TID qual). If we apply DO_NOT_SCAN
|
|
|
|
|
-- here, we should get a valid plan anyway, but with the scan disabled.
|
Add pg_plan_advice contrib module.
Provide a facility that (1) can be used to stabilize certain plan choices
so that the planner cannot reverse course without authorization and
(2) can be used by knowledgeable users to insist on plan choices contrary
to what the planner believes best. In both cases, terrible outcomes are
possible: users should think twice and perhaps three times before
constraining the planner's ability to do as it thinks best; nevertheless,
there are problems that are much more easily solved with these facilities
than without them.
This patch takes the approach of analyzing a finished plan to produce
textual output, which we call "plan advice", that describes key
decisions made during plan; if that plan advice is provided during
future planning cycles, it will force those key decisions to be made in
the same way. Not all planner decisions can be controlled using advice;
for example, decisions about how to perform aggregation are currently
out of scope, as is choice of sort order. Plan advice can also be edited
by the user, or even written from scratch in simple cases, making it
possible to generate outcomes that the planner would not have produced.
Partial advice can be provided to control some planner outcomes but not
others.
Currently, plan advice is focused only on specific outcomes, such as
the choice to use a sequential scan for a particular relation, and not
on estimates that might contribute to those outcomes, such as a
possibly-incorrect selectivity estimate. While it would be useful to
users to be able to provide plan advice that affects selectivity
estimates or other aspects of costing, that is out of scope for this
commit.
Reviewed-by: Lukas Fittl <lukas@fittl.com>
Reviewed-by: Jakub Wartak <jakub.wartak@enterprisedb.com>
Reviewed-by: Greg Burd <greg@burd.me>
Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com>
Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
Reviewed-by: Dian Fay <di@nmfay.com>
Reviewed-by: Ajay Pal <ajay.pal.k@gmail.com>
Reviewed-by: John Naylor <johncnaylorls@gmail.com>
Reviewed-by: Alexandra Wang <alexandra.wang.oss@gmail.com>
Discussion: http://postgr.es/m/CA+TgmoZ-Jh1T6QyWoCODMVQdhTUPYkaZjWztzP1En4=ZHoKPzw@mail.gmail.com
2026-03-12 12:59:52 -04:00
|
|
|
BEGIN;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(scan_table)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_ONLY_SCAN(scan_table scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'TID_SCAN(scan_table)';
|
pg_plan_advice: Invent DO_NOT_SCAN(relation_identifier).
The premise of src/test/modules/test_plan_advice is that if we plan
a query once, generate plan advice, and then replan it using that
same advice, all of that advice should apply cleanly, since the
settings and everything else are the same. Unfortunately, that's
not the case: the test suite is the main regression tests, and
concurrent activity can change the statistics on tables involved
in the query, especially system catalogs. That's OK as long as it
only affects costing, but in a few cases, it affects which relations
appear in the final plan at all.
In the buildfarm failures observed to date, this happens because
we consider alternative subplans for the same portion of the query;
in theory, MinMaxAggPath is vulnerable to a similar hazard. In both
cases, the planner clones an entire subquery, and the clone has a
different plan name, and therefore different range table identifiers,
than the original. If a cost change results in flipping between one
of these plans and the other, the test_plan_advice tests will fail,
because the range table identifiers to which advice was applied won't
even be present in the output of the second planning cycle.
To fix, invent a new DO_NOT_SCAN advice tag. When generating advice,
emit it for relations that should not appear in the final plan at
all, because some alternative version of that relation was used
instead. When DO_NOT_SCAN is supplied, disable all scan methods for
that relation.
To make this work, we reuse a bunch of the machinery that previously
existed for the purpose of ensuring that we build the same set of
relation identifiers during planning as we do from the final
PlannedStmt. In the process, this commit slightly weakens the
cross-check mechanism: before this commit, it would fire whenever
the pg_plan_advice module was loaded, even if pg_plan_advice wasn't
actually doing anything; now, it will only engage when we have some
other reason to create a pgpa_planner_state. The old way was complex
and didn't add much useful test coverage, so this seems like an
acceptable sacrifice.
Discussion: http://postgr.es/m/CA+TgmoYuWmN-00Ec5pY7zAcpSFQUQLbgAdVWGR9kOR-HM-fHrA@mail.gmail.com
Reviewed-by: Lukas Fittl <lukas@fittl.com>
2026-03-26 17:09:57 -04:00
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'DO_NOT_SCAN(scan_table)';
|
Add pg_plan_advice contrib module.
Provide a facility that (1) can be used to stabilize certain plan choices
so that the planner cannot reverse course without authorization and
(2) can be used by knowledgeable users to insist on plan choices contrary
to what the planner believes best. In both cases, terrible outcomes are
possible: users should think twice and perhaps three times before
constraining the planner's ability to do as it thinks best; nevertheless,
there are problems that are much more easily solved with these facilities
than without them.
This patch takes the approach of analyzing a finished plan to produce
textual output, which we call "plan advice", that describes key
decisions made during plan; if that plan advice is provided during
future planning cycles, it will force those key decisions to be made in
the same way. Not all planner decisions can be controlled using advice;
for example, decisions about how to perform aggregation are currently
out of scope, as is choice of sort order. Plan advice can also be edited
by the user, or even written from scratch in simple cases, making it
possible to generate outcomes that the planner would not have produced.
Partial advice can be provided to control some planner outcomes but not
others.
Currently, plan advice is focused only on specific outcomes, such as
the choice to use a sequential scan for a particular relation, and not
on estimates that might contribute to those outcomes, such as a
possibly-incorrect selectivity estimate. While it would be useful to
users to be able to provide plan advice that affects selectivity
estimates or other aspects of costing, that is out of scope for this
commit.
Reviewed-by: Lukas Fittl <lukas@fittl.com>
Reviewed-by: Jakub Wartak <jakub.wartak@enterprisedb.com>
Reviewed-by: Greg Burd <greg@burd.me>
Reviewed-by: Jacob Champion <jacob.champion@enterprisedb.com>
Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
Reviewed-by: Dian Fay <di@nmfay.com>
Reviewed-by: Ajay Pal <ajay.pal.k@gmail.com>
Reviewed-by: John Naylor <johncnaylorls@gmail.com>
Reviewed-by: Alexandra Wang <alexandra.wang.oss@gmail.com>
Discussion: http://postgr.es/m/CA+TgmoZ-Jh1T6QyWoCODMVQdhTUPYkaZjWztzP1En4=ZHoKPzw@mail.gmail.com
2026-03-12 12:59:52 -04:00
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- We can forcibly downgrade an index-only scan to an index scan, but we can't
|
|
|
|
|
-- force the use of an index that the planner thinks is inapplicable.
|
|
|
|
|
BEGIN;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table public.scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table scan_table_b)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- We can force the use of a sequential scan in place of a bitmap heap scan,
|
|
|
|
|
-- but a plain index scan on a BRIN index is not possible.
|
|
|
|
|
BEGIN;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(scan_table)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table
|
|
|
|
|
WHERE b > 'some text 8';
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table scan_table_b)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- We can force the use of a sequential scan rather than a TID scan or
|
|
|
|
|
-- TID range scan.
|
|
|
|
|
BEGIN;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(scan_table)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE ctid = '(0,1)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table
|
|
|
|
|
WHERE ctid > '(1,1)' AND ctid < '(2,1)';
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- Test more complex scenarios with index scans.
|
|
|
|
|
BEGIN;
|
|
|
|
|
-- Should still work if we mention the schema.
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table public.scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
-- But not if we mention the wrong schema.
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table cilbup.scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
-- It's OK to repeat the same advice.
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table scan_table_pkey scan_table scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
-- But it doesn't work if the index target is even notionally different.
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table scan_table_pkey scan_table public.scan_table_pkey)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT * FROM scan_table WHERE a = 1;
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- Test assorted incorrect advice.
|
|
|
|
|
BEGIN;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(nothing)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT a FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(nothing whatsoever)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT a FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_SCAN(scan_table bogus)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT a FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_ONLY_SCAN(nothing whatsoever)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT a FROM scan_table WHERE a = 1;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'INDEX_ONLY_SCAN(scan_table bogus)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE) SELECT a FROM scan_table WHERE a = 1;
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- Test our ability to refer to multiple instances of the same alias.
|
|
|
|
|
BEGIN;
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (generate_series(1,10) g LEFT JOIN scan_table s ON g = s.a) x
|
|
|
|
|
LEFT JOIN scan_table s ON g = s.a;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(s)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (generate_series(1,10) g LEFT JOIN scan_table s ON g = s.a) x
|
|
|
|
|
LEFT JOIN scan_table s ON g = s.a;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(s#2)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (generate_series(1,10) g LEFT JOIN scan_table s ON g = s.a) x
|
|
|
|
|
LEFT JOIN scan_table s ON g = s.a;
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(s) SEQ_SCAN(s#2)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (generate_series(1,10) g LEFT JOIN scan_table s ON g = s.a) x
|
|
|
|
|
LEFT JOIN scan_table s ON g = s.a;
|
|
|
|
|
COMMIT;
|
|
|
|
|
|
|
|
|
|
-- Test our ability to refer to scans within a subquery.
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (SELECT * FROM scan_table s WHERE a = 1 OFFSET 0) x;
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (SELECT * FROM scan_table s WHERE a = 1 OFFSET 0);
|
|
|
|
|
BEGIN;
|
|
|
|
|
-- Should not match.
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(s)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (SELECT * FROM scan_table s WHERE a = 1 OFFSET 0) x;
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (SELECT * FROM scan_table s WHERE a = 1 OFFSET 0);
|
|
|
|
|
-- Should match first query only.
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(s@x)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (SELECT * FROM scan_table s WHERE a = 1 OFFSET 0) x;
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (SELECT * FROM scan_table s WHERE a = 1 OFFSET 0);
|
|
|
|
|
-- Should match second query only.
|
|
|
|
|
SET LOCAL pg_plan_advice.advice = 'SEQ_SCAN(s@unnamed_subquery)';
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (SELECT * FROM scan_table s WHERE a = 1 OFFSET 0) x;
|
|
|
|
|
EXPLAIN (COSTS OFF, PLAN_ADVICE)
|
|
|
|
|
SELECT * FROM (SELECT * FROM scan_table s WHERE a = 1 OFFSET 0);
|
|
|
|
|
COMMIT;
|