mirror of
https://github.com/hanzoai/postgres.git
synced 2026-10-30 18:33:07 +00:00
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]
304 lines
6.5 KiB
Plaintext
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;
|
|
}
|