LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_parser.y (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 95.7 % 92 88
Test Date: 2026-06-30 20:16:43 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        43832 :             *result = $1;
      75              :         }
      76              :     ;
      77              : 
      78              : advice_item_list: advice_item_list advice_item
      79        94741 :         { $$ = lappend($1, $2); }
      80              :     |
      81        43840 :         { $$ = NIL; }
      82              :     ;
      83              : 
      84              : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
      85              :         {
      86        10931 :             $$ = palloc0_object(pgpa_advice_item);
      87        10931 :             $$->tag = PGPA_TAG_JOIN_ORDER;
      88        10931 :             $$->targets = $3;
      89        10931 :             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         8714 :             $$ = palloc0_object(pgpa_advice_item);
      96         8714 :             if (strcmp($1, "index_only_scan") == 0)
      97         1715 :                 $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
      98         6999 :             else if (strcmp($1, "index_scan") == 0)
      99         6999 :                 $$->tag = PGPA_TAG_INDEX_SCAN;
     100              :             else
     101            0 :                 elog(ERROR, "tag parsing failed: %s", $1);
     102         8714 :             $$->targets = $3;
     103              :         }
     104              :     | TOK_TAG_SIMPLE '(' simple_target_list ')'
     105              :         {
     106        62717 :             $$ = palloc0_object(pgpa_advice_item);
     107        62717 :             if (strcmp($1, "bitmap_heap_scan") == 0)
     108         2333 :                 $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
     109        60384 :             else if (strcmp($1, "do_not_scan") == 0)
     110          180 :                 $$->tag = PGPA_TAG_DO_NOT_SCAN;
     111        60204 :             else if (strcmp($1, "no_gather") == 0)
     112        43414 :                 $$->tag = PGPA_TAG_NO_GATHER;
     113        16790 :             else if (strcmp($1, "seq_scan") == 0)
     114        16385 :                 $$->tag = PGPA_TAG_SEQ_SCAN;
     115          405 :             else if (strcmp($1, "tid_scan") == 0)
     116          405 :                 $$->tag = PGPA_TAG_TID_SCAN;
     117              :             else
     118            0 :                 elog(ERROR, "tag parsing failed: %s", $1);
     119        62717 :             $$->targets = $3;
     120              :         }
     121              :     | TOK_TAG_GENERIC '(' generic_target_list ')'
     122              :         {
     123              :             bool    fail;
     124              : 
     125        12379 :             $$ = palloc0_object(pgpa_advice_item);
     126        12379 :             $$->tag = pgpa_parse_advice_tag($1, &fail);
     127        12379 :             if (fail)
     128              :             {
     129            0 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     130              :                              "unrecognized advice tag");
     131              :             }
     132              : 
     133        12379 :             if ($$->tag == PGPA_TAG_FOREIGN_JOIN)
     134              :             {
     135           12 :                 foreach_ptr(pgpa_advice_target, target, $3)
     136              :                 {
     137            7 :                     if (target->ttype == PGPA_TARGET_IDENTIFIER ||
     138            3 :                         list_length(target->children) == 1)
     139            2 :                             pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     140              :                                          "FOREIGN_JOIN targets must contain more than one relation identifier");
     141              :                 }
     142              :             }
     143              : 
     144        12379 :             $$->targets = $3;
     145              :         }
     146              :     ;
     147              : 
     148              : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
     149              :         {
     150       158239 :             $$ = palloc0_object(pgpa_advice_target);
     151       158239 :             $$->ttype = PGPA_TARGET_IDENTIFIER;
     152       158239 :             $$->rid.alias_name = $1;
     153       158239 :             $$->rid.occurrence = $2;
     154       158239 :             if (list_length($3) == 2)
     155              :             {
     156        14211 :                 $$->rid.partnsp = linitial($3);
     157        14211 :                 $$->rid.partrel = lsecond($3);
     158              :             }
     159       144028 :             else if ($3 != NIL)
     160           14 :                 $$->rid.partrel = linitial($3);
     161       158239 :             $$->rid.plan_name = $4;
     162              :         }
     163              :     ;
     164              : 
     165              : index_name: identifier
     166              :         {
     167           35 :             $$ = palloc0_object(pgpa_index_target);
     168           35 :             $$->indname = $1;
     169              :         }
     170              :     | identifier '.' identifier
     171              :         {
     172        13463 :             $$ = palloc0_object(pgpa_index_target);
     173        13463 :             $$->indnamespace = $1;
     174        13463 :             $$->indname = $3;
     175              :         }
     176              :     ;
     177              : 
     178              : opt_ri_occurrence:
     179              :     '#' TOK_INTEGER
     180              :         {
     181         3391 :             if ($2 <= 0)
     182            0 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     183              :                              "only positive occurrence numbers are permitted");
     184         3391 :             $$ = $2;
     185              :         }
     186              :     |
     187              :         {
     188              :             /* The default occurrence number is 1. */
     189       154848 :             $$ = 1;
     190              :         }
     191              :     ;
     192              : 
     193              : identifier: TOK_IDENT
     194              :     | TOK_TAG_JOIN_ORDER
     195              :     | TOK_TAG_INDEX
     196              :     | TOK_TAG_SIMPLE
     197              :     | TOK_TAG_GENERIC
     198              :     ;
     199              : 
     200              : /*
     201              :  * When generating advice, we always schema-qualify the partition name, but
     202              :  * when parsing advice, we accept a specification that lacks one.
     203              :  */
     204              : opt_partition:
     205              :     '/' identifier '.' identifier
     206        14211 :         { $$ = list_make2($2, $4); }
     207              :     | '/' identifier
     208           14 :         { $$ = list_make1($2); }
     209              :     |
     210       144014 :         { $$ = NIL; }
     211              :     ;
     212              : 
     213              : opt_plan_name:
     214              :     '@' identifier
     215        36097 :         { $$ = $2; }
     216              :     |
     217       122142 :         { $$ = NULL; }
     218              :     ;
     219              : 
     220              : generic_target_list: generic_target_list relation_identifier
     221        16800 :         { $$ = lappend($1, $2); }
     222              :     | generic_target_list generic_sublist
     223          850 :         { $$ = lappend($1, $2); }
     224              :     |
     225        12380 :         { $$ = NIL; }
     226              :     ;
     227              : 
     228              : generic_sublist: '(' simple_target_list ')'
     229              :         {
     230          850 :             $$ = palloc0_object(pgpa_advice_target);
     231          850 :             $$->ttype = PGPA_TARGET_ORDERED_LIST;
     232          850 :             $$->children = $2;
     233              :         }
     234              :     ;
     235              : 
     236              : index_target_list:
     237              :       index_target_list relation_identifier index_name
     238              :         {
     239        13498 :             $2->itarget = $3;
     240        13498 :             $$ = lappend($1, $2);
     241              :         }
     242              :     |
     243         8714 :         { $$ = NIL; }
     244              :     ;
     245              : 
     246              : join_order_target_list: join_order_target_list relation_identifier
     247        25405 :         { $$ = lappend($1, $2); }
     248              :     | join_order_target_list join_order_sublist
     249          538 :         { $$ = lappend($1, $2); }
     250              :     |
     251        11454 :         { $$ = NIL; }
     252              :     ;
     253              : 
     254              : join_order_sublist:
     255              :     '(' join_order_target_list ')'
     256              :         {
     257          523 :             $$ = palloc0_object(pgpa_advice_target);
     258          523 :             $$->ttype = PGPA_TARGET_ORDERED_LIST;
     259          523 :             $$->children = $2;
     260              :         }
     261              :     | '{' simple_target_list '}'
     262              :         {
     263           15 :             $$ = palloc0_object(pgpa_advice_target);
     264           15 :             $$->ttype = PGPA_TARGET_UNORDERED_LIST;
     265           15 :             $$->children = $2;
     266              :         }
     267              :     ;
     268              : 
     269              : simple_target_list: simple_target_list relation_identifier
     270       102536 :         { $$ = lappend($1, $2); }
     271              :     |
     272        63589 :         { $$ = NIL; }
     273              :     ;
     274              : 
     275              : %%
     276              : 
     277              : /*
     278              :  * Parse an advice_string and return the resulting list of pgpa_advice_item
     279              :  * objects. If a parse error occurs, instead return NULL.
     280              :  *
     281              :  * If the return value is NULL, *error_p will be set to the error message;
     282              :  * otherwise, *error_p will be set to NULL.
     283              :  */
     284              : List *
     285        43840 : pgpa_parse(const char *advice_string, char **error_p)
     286              : {
     287              :     yyscan_t    scanner;
     288              :     List       *result;
     289        43840 :     char       *error = NULL;
     290              : 
     291        43840 :     pgpa_scanner_init(advice_string, &scanner);
     292        43840 :     pgpa_yyparse(&result, &error, scanner);
     293        43840 :     pgpa_scanner_finish(scanner);
     294              : 
     295        43840 :     if (error != NULL)
     296              :     {
     297           18 :         *error_p = error;
     298           18 :         return NULL;
     299              :     }
     300              : 
     301        43822 :     *error_p = NULL;
     302        43822 :     return result;
     303              : }
        

Generated by: LCOV version 2.0-1