1
0
mirror of https://github.com/postgres/postgres.git synced 2026-10-23 13:29:09 +03:00
Files
postgres/contrib/pg_plan_advice/sql/syntax.sql
Robert Haas 63920eb099 pg_plan_advice: Disallow partition name without partition schema.
Up until now, pg_plan_advice has had a feature that allows an advice
target to mention a partition name but omit the partition schema;
that is, something like SEQ_SCAN(foo/bar) forces a sequential scan on
every child of table foo whose partition name is bar, regardless of
the schema in which bar appears. However, that feature turns out to
have a nasty design flaw: while advice enforcement handles this case
just fine, the advice feedback code doesn't know about it and will
mark such advice as "matched, failed" even when everything worked
perfectly. Unfortunately, there seems to be no simple code fix for
this problem.

Since the release of PostgreSQL 19 is imminent, take the conservative
course and revert this feature of pg_plan_advice. In other words,
require the partition schema whenever the partition name is present.
You must now write e.g. SEQ_SCAN(foo/public.bar) rather than just
SEQ_SCAN(foo/bar). This doesn't affect any cases where automatically
generated advice is supplied, since generated advice has always
included the partition schema anyway. Manually written advice will
have to conform to the new, stricter rule. For a later release, we
can consider whether to again relax this restriction in some way,
but now is not the time to design new things.

Reported-by: Noah Misch <noah@leadboat.com>
Backpatch-through: 19
Discussion: https://postgr.es/m/CA+TgmoYmXy-jiP5qDhqNEiYFEBzQsArO6O2d9E8szNZqi1bePQ@mail.gmail.com
2026-09-16 21:03:58 -04:00

101 lines
3.8 KiB
SQL

LOAD 'pg_plan_advice';
-- An empty string is allowed. Empty target lists are allowed for most advice
-- tags, but not for JOIN_ORDER. "Supplied Plan Advice" should be omitted in
-- text format when there is no actual advice, but not in non-text format.
SET pg_plan_advice.advice = '';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'SEQ_SCAN()';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'NESTED_LOOP_PLAIN()';
EXPLAIN (COSTS OFF, FORMAT JSON) SELECT 1;
SET pg_plan_advice.advice = 'JOIN_ORDER()';
-- Test assorted variations in capitalization, whitespace, and which parts of
-- the relation identifier are included. These should all work.
SET pg_plan_advice.advice = 'SEQ_SCAN(x)';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'seq_scan(x@y)';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'SEQ_scan(x#2)';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'SEQ_SCAN(x#1_0)';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = ' SEQ_SCAN ( x / y . z ) ';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'SEQ_SCAN("x"#2/"y"."z"@"t")';
EXPLAIN (COSTS OFF) SELECT 1;
-- Syntax errors.
SET pg_plan_advice.advice = 'SEQUENTIAL_SCAN(x)';
SET pg_plan_advice.advice = 'SEQ_SCAN';
SET pg_plan_advice.advice = 'SEQ_SCAN(';
SET pg_plan_advice.advice = 'SEQ_SCAN("';
SET pg_plan_advice.advice = 'SEQ_SCAN("")';
SET pg_plan_advice.advice = 'SEQ_SCAN("a"';
SET pg_plan_advice.advice = 'SEQ_SCAN(#';
SET pg_plan_advice.advice = 'SEQ_SCAN(x#1_0_)';
SET pg_plan_advice.advice = '()';
SET pg_plan_advice.advice = '123';
-- Out of range values.
SET pg_plan_advice.advice = 'SEQ_SCAN(x#99999999999_99)';
-- Partition schema is missing.
SET pg_plan_advice.advice = 'SEQ_SCAN(x/y)';
-- Tags like SEQ_SCAN and NO_GATHER don't allow sublists at all; other tags,
-- except for JOIN_ORDER, allow at most one level of sublist. Hence, these
-- examples should error out.
SET pg_plan_advice.advice = 'SEQ_SCAN((x))';
SET pg_plan_advice.advice = 'DO_NOT_SCAN((x))';
SET pg_plan_advice.advice = 'GATHER(((x)))';
-- Legal comments.
SET pg_plan_advice.advice = '/**/';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'HASH_JOIN(_)/***/';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = '/* comment */ HASH_JOIN(/*x*/y)';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = '/* comment */ HASH_JOIN(y//*x*/z.zz)';
EXPLAIN (COSTS OFF) SELECT 1;
-- Unterminated comments.
SET pg_plan_advice.advice = '/*';
SET pg_plan_advice.advice = 'JOIN_ORDER("fOO") /* oops';
-- Nested comments are not supported, so the first of these is legal and
-- the second is not.
SET pg_plan_advice.advice = '/*/*/';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = '/*/* stuff */*/';
-- Foreign join requires multiple relation identifiers.
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)';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'SEQ_SCAN(seq_scan)';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'SEQ_SCAN(gather)';
EXPLAIN (COSTS OFF) SELECT 1;
SET pg_plan_advice.advice = 'SEQ_SCAN(join_order)';
EXPLAIN (COSTS OFF) SELECT 1;
-- Tag keywords used as partition names or plan names should also work,
-- since pgpa_identifier_string() can generate these from real partition
-- and subquery names.
SET pg_plan_advice.advice = 'SEQ_SCAN(t/public.hash_join)';
SET pg_plan_advice.advice = 'SEQ_SCAN(t/hash_join.foo)';
SET pg_plan_advice.advice = 'SEQ_SCAN(t@hash_join)';
SET pg_plan_advice.advice = 'SEQ_SCAN(t@seq_scan)';