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-03-16 00:15:06 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          236 :             *result = $1;
      75              :         }
      76              :     ;
      77              : 
      78              : advice_item_list: advice_item_list advice_item
      79          250 :         { $$ = lappend($1, $2); }
      80              :     |
      81          243 :         { $$ = NIL; }
      82              :     ;
      83              : 
      84              : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
      85              :         {
      86           40 :             $$ = palloc0_object(pgpa_advice_item);
      87           40 :             $$->tag = PGPA_TAG_JOIN_ORDER;
      88           40 :             $$->targets = $3;
      89           40 :             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           38 :             $$ = palloc0_object(pgpa_advice_item);
      96           38 :             if (strcmp($1, "index_only_scan") == 0)
      97           12 :                 $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
      98           26 :             else if (strcmp($1, "index_scan") == 0)
      99           26 :                 $$->tag = PGPA_TAG_INDEX_SCAN;
     100              :             else
     101            0 :                 elog(ERROR, "tag parsing failed: %s", $1);
     102           38 :             $$->targets = $3;
     103              :         }
     104              :     | TOK_TAG_SIMPLE '(' simple_target_list ')'
     105              :         {
     106           70 :             $$ = palloc0_object(pgpa_advice_item);
     107           70 :             if (strcmp($1, "bitmap_heap_scan") == 0)
     108            6 :                 $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
     109           64 :             else if (strcmp($1, "no_gather") == 0)
     110           12 :                 $$->tag = PGPA_TAG_NO_GATHER;
     111           52 :             else if (strcmp($1, "seq_scan") == 0)
     112           45 :                 $$->tag = PGPA_TAG_SEQ_SCAN;
     113            7 :             else if (strcmp($1, "tid_scan") == 0)
     114            7 :                 $$->tag = PGPA_TAG_TID_SCAN;
     115              :             else
     116            0 :                 elog(ERROR, "tag parsing failed: %s", $1);
     117           70 :             $$->targets = $3;
     118              :         }
     119              :     | TOK_TAG_GENERIC '(' generic_target_list ')'
     120              :         {
     121              :             bool    fail;
     122              : 
     123          102 :             $$ = palloc0_object(pgpa_advice_item);
     124          102 :             $$->tag = pgpa_parse_advice_tag($1, &fail);
     125          102 :             if (fail)
     126              :             {
     127            0 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     128              :                              "unrecognized advice tag");
     129              :             }
     130              : 
     131          102 :             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          102 :             $$->targets = $3;
     143              :         }
     144              :     ;
     145              : 
     146              : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
     147              :         {
     148          338 :             $$ = palloc0_object(pgpa_advice_target);
     149          338 :             $$->ttype = PGPA_TARGET_IDENTIFIER;
     150          338 :             $$->rid.alias_name = $1;
     151          338 :             $$->rid.occurrence = $2;
     152          338 :             if (list_length($3) == 2)
     153              :             {
     154           12 :                 $$->rid.partnsp = linitial($3);
     155           12 :                 $$->rid.partrel = lsecond($3);
     156              :             }
     157          326 :             else if ($3 != NIL)
     158           14 :                 $$->rid.partrel = linitial($3);
     159          338 :             $$->rid.plan_name = $4;
     160              :         }
     161              :     ;
     162              : 
     163              : index_name: identifier
     164              :         {
     165           34 :             $$ = palloc0_object(pgpa_index_target);
     166           34 :             $$->indname = $1;
     167              :         }
     168              :     | identifier '.' identifier
     169              :         {
     170            8 :             $$ = palloc0_object(pgpa_index_target);
     171            8 :             $$->indnamespace = $1;
     172            8 :             $$->indname = $3;
     173              :         }
     174              :     ;
     175              : 
     176              : opt_ri_occurrence:
     177              :     '#' TOK_INTEGER
     178              :         {
     179            8 :             if ($2 <= 0)
     180            0 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     181              :                              "only positive occurrence numbers are permitted");
     182            8 :             $$ = $2;
     183              :         }
     184              :     |
     185              :         {
     186              :             /* The default occurrence number is 1. */
     187          330 :             $$ = 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              :     '/' TOK_IDENT '.' TOK_IDENT
     204           12 :         { $$ = list_make2($2, $4); }
     205              :     | '/' TOK_IDENT
     206           14 :         { $$ = list_make1($2); }
     207              :     |
     208          312 :         { $$ = NIL; }
     209              :     ;
     210              : 
     211              : opt_plan_name:
     212              :     '@' TOK_IDENT
     213           10 :         { $$ = $2; }
     214              :     |
     215          328 :         { $$ = NULL; }
     216              :     ;
     217              : 
     218              : generic_target_list: generic_target_list relation_identifier
     219           91 :         { $$ = lappend($1, $2); }
     220              :     | generic_target_list generic_sublist
     221           23 :         { $$ = lappend($1, $2); }
     222              :     |
     223          103 :         { $$ = NIL; }
     224              :     ;
     225              : 
     226              : generic_sublist: '(' simple_target_list ')'
     227              :         {
     228           23 :             $$ = palloc0_object(pgpa_advice_target);
     229           23 :             $$->ttype = PGPA_TARGET_ORDERED_LIST;
     230           23 :             $$->children = $2;
     231              :         }
     232              :     ;
     233              : 
     234              : index_target_list:
     235              :       index_target_list relation_identifier index_name
     236              :         {
     237           42 :             $2->itarget = $3;
     238           42 :             $$ = lappend($1, $2);
     239              :         }
     240              :     |
     241           38 :         { $$ = NIL; }
     242              :     ;
     243              : 
     244              : join_order_target_list: join_order_target_list relation_identifier
     245           85 :         { $$ = lappend($1, $2); }
     246              :     | join_order_target_list join_order_sublist
     247            6 :         { $$ = lappend($1, $2); }
     248              :     |
     249           44 :         { $$ = NIL; }
     250              :     ;
     251              : 
     252              : join_order_sublist:
     253              :     '(' join_order_target_list ')'
     254              :         {
     255            4 :             $$ = palloc0_object(pgpa_advice_target);
     256            4 :             $$->ttype = PGPA_TARGET_ORDERED_LIST;
     257            4 :             $$->children = $2;
     258              :         }
     259              :     | '{' simple_target_list '}'
     260              :         {
     261            2 :             $$ = palloc0_object(pgpa_advice_target);
     262            2 :             $$->ttype = PGPA_TARGET_UNORDERED_LIST;
     263            2 :             $$->children = $2;
     264              :         }
     265              :     ;
     266              : 
     267              : simple_target_list: simple_target_list relation_identifier
     268          120 :         { $$ = lappend($1, $2); }
     269              :     |
     270          101 :         { $$ = 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          243 : pgpa_parse(const char *advice_string, char **error_p)
     284              : {
     285              :     yyscan_t    scanner;
     286              :     List       *result;
     287          243 :     char       *error = NULL;
     288              : 
     289          243 :     pgpa_scanner_init(advice_string, &scanner);
     290          243 :     pgpa_yyparse(&result, &error, scanner);
     291          243 :     pgpa_scanner_finish(scanner);
     292              : 
     293          243 :     if (error != NULL)
     294              :     {
     295           17 :         *error_p = error;
     296           17 :         return NULL;
     297              :     }
     298              : 
     299          226 :     *error_p = NULL;
     300          226 :     return result;
     301              : }
        

Generated by: LCOV version 2.0-1