Files
Robert Haas e0b37905ac pg_plan_advice: DO_NOT_SCAN is a simple tag, not a generic one.
Generic tags allow sublists, e.g. MERGE_JOIN((x y)), but simple
tags do not, e.g. SEQ_SCAN(x) is valid but SEQ_SCAN((x)) is not.
DO_NOT_SCAN was intended to be simple tag, but was accidentally
implemented as a generic one. This could result in assertion
failures. Repair.

Reported-by: Nikita Kalinin <[email protected]>
Analyzed-by: Tender Wang <[email protected]>
Analyzed-by: Ayush Tiwari <[email protected]>
Discussion: http://postgr.es/m/[email protected]
2026-05-29 13:51:09 -04:00

304 lines
6.5 KiB
Plaintext

%{
/*
* Parser for plan advice
*
* Copyright (c) 2000-2026, PostgreSQL Global Development Group
*
* contrib/pg_plan_advice/pgpa_parser.y
*/
#include "postgres.h"
#include <float.h>
#include <math.h>
#include "fmgr.h"
#include "nodes/miscnodes.h"
#include "utils/builtins.h"
#include "utils/float.h"
#include "pgpa_ast.h"
#include "pgpa_parser.h"
/*
* Bison doesn't allocate anything that needs to live across parser calls,
* so we can easily have it use palloc instead of malloc. This prevents
* memory leaks if we error out during parsing.
*/
#define YYMALLOC palloc
#define YYFREE pfree
%}
/* BISON Declarations */
%parse-param {List **result}
%parse-param {char **parse_error_msg_p}
%parse-param {yyscan_t yyscanner}
%lex-param {List **result}
%lex-param {char **parse_error_msg_p}
%lex-param {yyscan_t yyscanner}
%pure-parser
%expect 0
%name-prefix="pgpa_yy"
%union
{
char *str;
int integer;
List *list;
pgpa_advice_item *item;
pgpa_advice_target *target;
pgpa_index_target *itarget;
}
%token <str> TOK_IDENT TOK_TAG_JOIN_ORDER TOK_TAG_INDEX
%token <str> TOK_TAG_SIMPLE TOK_TAG_GENERIC
%token <integer> TOK_INTEGER
%type <integer> opt_ri_occurrence
%type <item> advice_item
%type <list> advice_item_list generic_target_list
%type <list> index_target_list join_order_target_list
%type <list> opt_partition simple_target_list
%type <str> identifier opt_plan_name
%type <target> generic_sublist join_order_sublist
%type <target> relation_identifier
%type <itarget> index_name
%start parse_toplevel
/* Grammar follows */
%%
parse_toplevel: advice_item_list
{
(void) yynerrs; /* suppress compiler warning */
*result = $1;
}
;
advice_item_list: advice_item_list advice_item
{ $$ = lappend($1, $2); }
|
{ $$ = NIL; }
;
advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
{
$$ = palloc0_object(pgpa_advice_item);
$$->tag = PGPA_TAG_JOIN_ORDER;
$$->targets = $3;
if ($3 == NIL)
pgpa_yyerror(result, parse_error_msg_p, yyscanner,
"JOIN_ORDER must have at least one target");
}
| TOK_TAG_INDEX '(' index_target_list ')'
{
$$ = palloc0_object(pgpa_advice_item);
if (strcmp($1, "index_only_scan") == 0)
$$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
else if (strcmp($1, "index_scan") == 0)
$$->tag = PGPA_TAG_INDEX_SCAN;
else
elog(ERROR, "tag parsing failed: %s", $1);
$$->targets = $3;
}
| TOK_TAG_SIMPLE '(' simple_target_list ')'
{
$$ = palloc0_object(pgpa_advice_item);
if (strcmp($1, "bitmap_heap_scan") == 0)
$$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
else if (strcmp($1, "do_not_scan") == 0)
$$->tag = PGPA_TAG_DO_NOT_SCAN;
else if (strcmp($1, "no_gather") == 0)
$$->tag = PGPA_TAG_NO_GATHER;
else if (strcmp($1, "seq_scan") == 0)
$$->tag = PGPA_TAG_SEQ_SCAN;
else if (strcmp($1, "tid_scan") == 0)
$$->tag = PGPA_TAG_TID_SCAN;
else
elog(ERROR, "tag parsing failed: %s", $1);
$$->targets = $3;
}
| TOK_TAG_GENERIC '(' generic_target_list ')'
{
bool fail;
$$ = palloc0_object(pgpa_advice_item);
$$->tag = pgpa_parse_advice_tag($1, &fail);
if (fail)
{
pgpa_yyerror(result, parse_error_msg_p, yyscanner,
"unrecognized advice tag");
}
if ($$->tag == PGPA_TAG_FOREIGN_JOIN)
{
foreach_ptr(pgpa_advice_target, target, $3)
{
if (target->ttype == PGPA_TARGET_IDENTIFIER ||
list_length(target->children) == 1)
pgpa_yyerror(result, parse_error_msg_p, yyscanner,
"FOREIGN_JOIN targets must contain more than one relation identifier");
}
}
$$->targets = $3;
}
;
relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
{
$$ = palloc0_object(pgpa_advice_target);
$$->ttype = PGPA_TARGET_IDENTIFIER;
$$->rid.alias_name = $1;
$$->rid.occurrence = $2;
if (list_length($3) == 2)
{
$$->rid.partnsp = linitial($3);
$$->rid.partrel = lsecond($3);
}
else if ($3 != NIL)
$$->rid.partrel = linitial($3);
$$->rid.plan_name = $4;
}
;
index_name: identifier
{
$$ = palloc0_object(pgpa_index_target);
$$->indname = $1;
}
| identifier '.' identifier
{
$$ = palloc0_object(pgpa_index_target);
$$->indnamespace = $1;
$$->indname = $3;
}
;
opt_ri_occurrence:
'#' TOK_INTEGER
{
if ($2 <= 0)
pgpa_yyerror(result, parse_error_msg_p, yyscanner,
"only positive occurrence numbers are permitted");
$$ = $2;
}
|
{
/* The default occurrence number is 1. */
$$ = 1;
}
;
identifier: TOK_IDENT
| TOK_TAG_JOIN_ORDER
| TOK_TAG_INDEX
| TOK_TAG_SIMPLE
| TOK_TAG_GENERIC
;
/*
* When generating advice, we always schema-qualify the partition name, but
* when parsing advice, we accept a specification that lacks one.
*/
opt_partition:
'/' identifier '.' identifier
{ $$ = list_make2($2, $4); }
| '/' identifier
{ $$ = list_make1($2); }
|
{ $$ = NIL; }
;
opt_plan_name:
'@' identifier
{ $$ = $2; }
|
{ $$ = NULL; }
;
generic_target_list: generic_target_list relation_identifier
{ $$ = lappend($1, $2); }
| generic_target_list generic_sublist
{ $$ = lappend($1, $2); }
|
{ $$ = NIL; }
;
generic_sublist: '(' simple_target_list ')'
{
$$ = palloc0_object(pgpa_advice_target);
$$->ttype = PGPA_TARGET_ORDERED_LIST;
$$->children = $2;
}
;
index_target_list:
index_target_list relation_identifier index_name
{
$2->itarget = $3;
$$ = lappend($1, $2);
}
|
{ $$ = NIL; }
;
join_order_target_list: join_order_target_list relation_identifier
{ $$ = lappend($1, $2); }
| join_order_target_list join_order_sublist
{ $$ = lappend($1, $2); }
|
{ $$ = NIL; }
;
join_order_sublist:
'(' join_order_target_list ')'
{
$$ = palloc0_object(pgpa_advice_target);
$$->ttype = PGPA_TARGET_ORDERED_LIST;
$$->children = $2;
}
| '{' simple_target_list '}'
{
$$ = palloc0_object(pgpa_advice_target);
$$->ttype = PGPA_TARGET_UNORDERED_LIST;
$$->children = $2;
}
;
simple_target_list: simple_target_list relation_identifier
{ $$ = lappend($1, $2); }
|
{ $$ = NIL; }
;
%%
/*
* Parse an advice_string and return the resulting list of pgpa_advice_item
* objects. If a parse error occurs, instead return NULL.
*
* If the return value is NULL, *error_p will be set to the error message;
* otherwise, *error_p will be set to NULL.
*/
List *
pgpa_parse(const char *advice_string, char **error_p)
{
yyscan_t scanner;
List *result;
char *error = NULL;
pgpa_scanner_init(advice_string, &scanner);
pgpa_yyparse(&result, &error, scanner);
pgpa_scanner_finish(scanner);
if (error != NULL)
{
*error_p = error;
return NULL;
}
*error_p = NULL;
return result;
}