LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_parser.y (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 95.6 % 90 86
Test Date: 2026-04-06 21:16:29 Functions: 100.0 % 1 1
Legend: Lines:     hit not hit

            Line data    Source code
       1              : %{
       2              : /*
       3              :  * Parser for plan advice
       4              :  *
       5              :  * Copyright (c) 2000-2026, PostgreSQL Global Development Group
       6              :  *
       7              :  * contrib/pg_plan_advice/pgpa_parser.y
       8              :  */
       9              : 
      10              : #include "postgres.h"
      11              : 
      12              : #include <float.h>
      13              : #include <math.h>
      14              : 
      15              : #include "fmgr.h"
      16              : #include "nodes/miscnodes.h"
      17              : #include "utils/builtins.h"
      18              : #include "utils/float.h"
      19              : 
      20              : #include "pgpa_ast.h"
      21              : #include "pgpa_parser.h"
      22              : 
      23              : /*
      24              :  * Bison doesn't allocate anything that needs to live across parser calls,
      25              :  * so we can easily have it use palloc instead of malloc.  This prevents
      26              :  * memory leaks if we error out during parsing.
      27              :  */
      28              : #define YYMALLOC palloc
      29              : #define YYFREE   pfree
      30              : %}
      31              : 
      32              : /* BISON Declarations */
      33              : %parse-param {List **result}
      34              : %parse-param {char **parse_error_msg_p}
      35              : %parse-param {yyscan_t yyscanner}
      36              : %lex-param {List **result}
      37              : %lex-param {char **parse_error_msg_p}
      38              : %lex-param {yyscan_t yyscanner}
      39              : %pure-parser
      40              : %expect 0
      41              : %name-prefix="pgpa_yy"
      42              : 
      43              : %union
      44              : {
      45              :     char       *str;
      46              :     int         integer;
      47              :     List       *list;
      48              :     pgpa_advice_item *item;
      49              :     pgpa_advice_target *target;
      50              :     pgpa_index_target *itarget;
      51              : }
      52              : %token <str> TOK_IDENT TOK_TAG_JOIN_ORDER TOK_TAG_INDEX
      53              : %token <str> TOK_TAG_SIMPLE TOK_TAG_GENERIC
      54              : %token <integer> TOK_INTEGER
      55              : 
      56              : %type <integer> opt_ri_occurrence
      57              : %type <item> advice_item
      58              : %type <list> advice_item_list generic_target_list
      59              : %type <list> index_target_list join_order_target_list
      60              : %type <list> opt_partition simple_target_list
      61              : %type <str> identifier opt_plan_name
      62              : %type <target> generic_sublist join_order_sublist
      63              : %type <target> relation_identifier
      64              : %type <itarget> index_name
      65              : 
      66              : %start parse_toplevel
      67              : 
      68              : /* Grammar follows */
      69              : %%
      70              : 
      71              : parse_toplevel: advice_item_list
      72              :         {
      73              :             (void) yynerrs;             /* suppress compiler warning */
      74        43645 :             *result = $1;
      75              :         }
      76              :     ;
      77              : 
      78              : advice_item_list: advice_item_list advice_item
      79        95341 :         { $$ = lappend($1, $2); }
      80              :     |
      81        43652 :         { $$ = NIL; }
      82              :     ;
      83              : 
      84              : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
      85              :         {
      86        11158 :             $$ = palloc0_object(pgpa_advice_item);
      87        11158 :             $$->tag = PGPA_TAG_JOIN_ORDER;
      88        11158 :             $$->targets = $3;
      89        11158 :             if ($3 == NIL)
      90            1 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
      91              :                              "JOIN_ORDER must have at least one target");
      92              :         }
      93              :     | TOK_TAG_INDEX '(' index_target_list ')'
      94              :         {
      95         8693 :             $$ = palloc0_object(pgpa_advice_item);
      96         8693 :             if (strcmp($1, "index_only_scan") == 0)
      97         1677 :                 $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
      98         7016 :             else if (strcmp($1, "index_scan") == 0)
      99         7016 :                 $$->tag = PGPA_TAG_INDEX_SCAN;
     100              :             else
     101            0 :                 elog(ERROR, "tag parsing failed: %s", $1);
     102         8693 :             $$->targets = $3;
     103              :         }
     104              :     | TOK_TAG_SIMPLE '(' simple_target_list ')'
     105              :         {
     106        62712 :             $$ = palloc0_object(pgpa_advice_item);
     107        62712 :             if (strcmp($1, "bitmap_heap_scan") == 0)
     108         2557 :                 $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
     109        60155 :             else if (strcmp($1, "no_gather") == 0)
     110        43236 :                 $$->tag = PGPA_TAG_NO_GATHER;
     111        16919 :             else if (strcmp($1, "seq_scan") == 0)
     112        16516 :                 $$->tag = PGPA_TAG_SEQ_SCAN;
     113          403 :             else if (strcmp($1, "tid_scan") == 0)
     114          403 :                 $$->tag = PGPA_TAG_TID_SCAN;
     115              :             else
     116            0 :                 elog(ERROR, "tag parsing failed: %s", $1);
     117        62712 :             $$->targets = $3;
     118              :         }
     119              :     | TOK_TAG_GENERIC '(' generic_target_list ')'
     120              :         {
     121              :             bool    fail;
     122              : 
     123        12778 :             $$ = palloc0_object(pgpa_advice_item);
     124        12778 :             $$->tag = pgpa_parse_advice_tag($1, &fail);
     125        12778 :             if (fail)
     126              :             {
     127            0 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     128              :                              "unrecognized advice tag");
     129              :             }
     130              : 
     131        12778 :             if ($$->tag == PGPA_TAG_FOREIGN_JOIN)
     132              :             {
     133           12 :                 foreach_ptr(pgpa_advice_target, target, $3)
     134              :                 {
     135            7 :                     if (target->ttype == PGPA_TARGET_IDENTIFIER ||
     136            3 :                         list_length(target->children) == 1)
     137            2 :                             pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     138              :                                          "FOREIGN_JOIN targets must contain more than one relation identifier");
     139              :                 }
     140              :             }
     141              : 
     142        12778 :             $$->targets = $3;
     143              :         }
     144              :     ;
     145              : 
     146              : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
     147              :         {
     148       160168 :             $$ = palloc0_object(pgpa_advice_target);
     149       160168 :             $$->ttype = PGPA_TARGET_IDENTIFIER;
     150       160168 :             $$->rid.alias_name = $1;
     151       160168 :             $$->rid.occurrence = $2;
     152       160168 :             if (list_length($3) == 2)
     153              :             {
     154        14157 :                 $$->rid.partnsp = linitial($3);
     155        14157 :                 $$->rid.partrel = lsecond($3);
     156              :             }
     157       146011 :             else if ($3 != NIL)
     158           14 :                 $$->rid.partrel = linitial($3);
     159       160168 :             $$->rid.plan_name = $4;
     160              :         }
     161              :     ;
     162              : 
     163              : index_name: identifier
     164              :         {
     165           35 :             $$ = palloc0_object(pgpa_index_target);
     166           35 :             $$->indname = $1;
     167              :         }
     168              :     | identifier '.' identifier
     169              :         {
     170        13720 :             $$ = palloc0_object(pgpa_index_target);
     171        13720 :             $$->indnamespace = $1;
     172        13720 :             $$->indname = $3;
     173              :         }
     174              :     ;
     175              : 
     176              : opt_ri_occurrence:
     177              :     '#' TOK_INTEGER
     178              :         {
     179         3373 :             if ($2 <= 0)
     180            0 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     181              :                              "only positive occurrence numbers are permitted");
     182         3373 :             $$ = $2;
     183              :         }
     184              :     |
     185              :         {
     186              :             /* The default occurrence number is 1. */
     187       156795 :             $$ = 1;
     188              :         }
     189              :     ;
     190              : 
     191              : identifier: TOK_IDENT
     192              :     | TOK_TAG_JOIN_ORDER
     193              :     | TOK_TAG_INDEX
     194              :     | TOK_TAG_SIMPLE
     195              :     | TOK_TAG_GENERIC
     196              :     ;
     197              : 
     198              : /*
     199              :  * When generating advice, we always schema-qualify the partition name, but
     200              :  * when parsing advice, we accept a specification that lacks one.
     201              :  */
     202              : opt_partition:
     203              :     '/' identifier '.' identifier
     204        14157 :         { $$ = list_make2($2, $4); }
     205              :     | '/' identifier
     206           14 :         { $$ = list_make1($2); }
     207              :     |
     208       145997 :         { $$ = NIL; }
     209              :     ;
     210              : 
     211              : opt_plan_name:
     212              :     '@' identifier
     213        36539 :         { $$ = $2; }
     214              :     |
     215       123629 :         { $$ = NULL; }
     216              :     ;
     217              : 
     218              : generic_target_list: generic_target_list relation_identifier
     219        17534 :         { $$ = lappend($1, $2); }
     220              :     | generic_target_list generic_sublist
     221          854 :         { $$ = lappend($1, $2); }
     222              :     |
     223        12779 :         { $$ = NIL; }
     224              :     ;
     225              : 
     226              : generic_sublist: '(' simple_target_list ')'
     227              :         {
     228          854 :             $$ = palloc0_object(pgpa_advice_target);
     229          854 :             $$->ttype = PGPA_TARGET_ORDERED_LIST;
     230          854 :             $$->children = $2;
     231              :         }
     232              :     ;
     233              : 
     234              : index_target_list:
     235              :       index_target_list relation_identifier index_name
     236              :         {
     237        13755 :             $2->itarget = $3;
     238        13755 :             $$ = lappend($1, $2);
     239              :         }
     240              :     |
     241         8693 :         { $$ = NIL; }
     242              :     ;
     243              : 
     244              : join_order_target_list: join_order_target_list relation_identifier
     245        25971 :         { $$ = lappend($1, $2); }
     246              :     | join_order_target_list join_order_sublist
     247          542 :         { $$ = lappend($1, $2); }
     248              :     |
     249        11685 :         { $$ = NIL; }
     250              :     ;
     251              : 
     252              : join_order_sublist:
     253              :     '(' join_order_target_list ')'
     254              :         {
     255          527 :             $$ = palloc0_object(pgpa_advice_target);
     256          527 :             $$->ttype = PGPA_TARGET_ORDERED_LIST;
     257          527 :             $$->children = $2;
     258              :         }
     259              :     | '{' simple_target_list '}'
     260              :         {
     261           15 :             $$ = palloc0_object(pgpa_advice_target);
     262           15 :             $$->ttype = PGPA_TARGET_UNORDERED_LIST;
     263           15 :             $$->children = $2;
     264              :         }
     265              :     ;
     266              : 
     267              : simple_target_list: simple_target_list relation_identifier
     268       102908 :         { $$ = lappend($1, $2); }
     269              :     |
     270        63587 :         { $$ = NIL; }
     271              :     ;
     272              : 
     273              : %%
     274              : 
     275              : /*
     276              :  * Parse an advice_string and return the resulting list of pgpa_advice_item
     277              :  * objects. If a parse error occurs, instead return NULL.
     278              :  *
     279              :  * If the return value is NULL, *error_p will be set to the error message;
     280              :  * otherwise, *error_p will be set to NULL.
     281              :  */
     282              : List *
     283        43652 : pgpa_parse(const char *advice_string, char **error_p)
     284              : {
     285              :     yyscan_t    scanner;
     286              :     List       *result;
     287        43652 :     char       *error = NULL;
     288              : 
     289        43652 :     pgpa_scanner_init(advice_string, &scanner);
     290        43652 :     pgpa_yyparse(&result, &error, scanner);
     291        43652 :     pgpa_scanner_finish(scanner);
     292              : 
     293        43652 :     if (error != NULL)
     294              :     {
     295           17 :         *error_p = error;
     296           17 :         return NULL;
     297              :     }
     298              : 
     299        43635 :     *error_p = NULL;
     300        43635 :     return result;
     301              : }
        

Generated by: LCOV version 2.0-1