LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_parser.y (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 96.8 % 93 90
Test Date: 2026-09-26 04:15:43 Functions: 100.0 % 1 1
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 81.8 % 44 36

             Branch data     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                 :       43401 :             *result = $1;
      75                 :             :         }
      76                 :             :     ;
      77                 :             : 
      78                 :             : advice_item_list: advice_item_list advice_item
      79                 :       95061 :         { $$ = lappend($1, $2); }
      80                 :             :     |
      81                 :       43411 :         { $$ = NIL; }
      82                 :             :     ;
      83                 :             : 
      84                 :             : advice_item: TOK_TAG_JOIN_ORDER '(' join_order_target_list ')'
      85                 :             :         {
      86                 :       10989 :             $$ = palloc0_object(pgpa_advice_item);
      87                 :       10989 :             $$->tag = PGPA_TAG_JOIN_ORDER;
      88                 :       10989 :             $$->targets = $3;
      89         [ +  + ]:       10989 :             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                 :        9119 :             $$ = palloc0_object(pgpa_advice_item);
      96         [ +  + ]:        9119 :             if (strcmp($1, "index_only_scan") == 0)
      97                 :        1603 :                 $$->tag = PGPA_TAG_INDEX_ONLY_SCAN;
      98         [ +  - ]:        7516 :             else if (strcmp($1, "index_scan") == 0)
      99                 :        7516 :                 $$->tag = PGPA_TAG_INDEX_SCAN;
     100                 :             :             else
     101         [ #  # ]:           0 :                 elog(ERROR, "tag parsing failed: %s", $1);
     102                 :        9119 :             $$->targets = $3;
     103                 :             :         }
     104                 :             :     | TOK_TAG_SIMPLE '(' simple_target_list ')'
     105                 :             :         {
     106                 :       62535 :             $$ = palloc0_object(pgpa_advice_item);
     107         [ +  + ]:       62535 :             if (strcmp($1, "bitmap_heap_scan") == 0)
     108                 :        2503 :                 $$->tag = PGPA_TAG_BITMAP_HEAP_SCAN;
     109         [ +  + ]:       60032 :             else if (strcmp($1, "do_not_scan") == 0)
     110                 :         233 :                 $$->tag = PGPA_TAG_DO_NOT_SCAN;
     111         [ +  + ]:       59799 :             else if (strcmp($1, "no_gather") == 0)
     112                 :       42968 :                 $$->tag = PGPA_TAG_NO_GATHER;
     113         [ +  + ]:       16831 :             else if (strcmp($1, "seq_scan") == 0)
     114                 :       16430 :                 $$->tag = PGPA_TAG_SEQ_SCAN;
     115         [ +  - ]:         401 :             else if (strcmp($1, "tid_scan") == 0)
     116                 :         401 :                 $$->tag = PGPA_TAG_TID_SCAN;
     117                 :             :             else
     118         [ #  # ]:           0 :                 elog(ERROR, "tag parsing failed: %s", $1);
     119                 :       62535 :             $$->targets = $3;
     120                 :             :         }
     121                 :             :     | TOK_TAG_GENERIC '(' generic_target_list ')'
     122                 :             :         {
     123                 :             :             bool    fail;
     124                 :             : 
     125                 :       12418 :             $$ = palloc0_object(pgpa_advice_item);
     126                 :       12418 :             $$->tag = pgpa_parse_advice_tag($1, &fail);
     127         [ -  + ]:       12418 :             if (fail)
     128                 :             :             {
     129                 :           0 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     130                 :             :                              "unrecognized advice tag");
     131                 :             :             }
     132                 :             : 
     133         [ +  + ]:       12418 :             if ($$->tag == PGPA_TAG_FOREIGN_JOIN)
     134                 :             :             {
     135   [ +  -  +  +  :          15 :                 foreach_ptr(pgpa_advice_target, target, $3)
                   +  + ]
     136                 :             :                 {
     137   [ +  +  +  + ]:           9 :                     if (target->ttype == PGPA_TARGET_IDENTIFIER ||
     138                 :           4 :                         list_length(target->children) < 2)
     139                 :           3 :                             pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     140                 :             :                                          "FOREIGN_JOIN targets must contain more than one relation identifier");
     141                 :             :                 }
     142                 :             :             }
     143                 :             : 
     144                 :       12418 :             $$->targets = $3;
     145                 :             :         }
     146                 :             :     ;
     147                 :             : 
     148                 :             : relation_identifier: identifier opt_ri_occurrence opt_partition opt_plan_name
     149                 :             :         {
     150                 :      159440 :             $$ = palloc0_object(pgpa_advice_target);
     151                 :      159440 :             $$->ttype = PGPA_TARGET_IDENTIFIER;
     152                 :      159440 :             $$->rid.alias_name = $1;
     153                 :      159440 :             $$->rid.occurrence = $2;
     154         [ +  + ]:      159440 :             if ($3 != NIL)
     155                 :             :             {
     156                 :             :                 Assert(list_length($3) == 2);
     157                 :       13861 :                 $$->rid.partnsp = linitial($3);
     158                 :       13861 :                 $$->rid.partrel = lsecond($3);
     159                 :             :             }
     160                 :      159440 :             $$->rid.plan_name = $4;
     161                 :             :         }
     162                 :             :     ;
     163                 :             : 
     164                 :             : index_name: identifier
     165                 :             :         {
     166                 :          35 :             $$ = palloc0_object(pgpa_index_target);
     167                 :          35 :             $$->indname = $1;
     168                 :             :         }
     169                 :             :     | identifier '.' identifier
     170                 :             :         {
     171                 :       14620 :             $$ = palloc0_object(pgpa_index_target);
     172                 :       14620 :             $$->indnamespace = $1;
     173                 :       14620 :             $$->indname = $3;
     174                 :             :         }
     175                 :             :     ;
     176                 :             : 
     177                 :             : opt_ri_occurrence:
     178                 :             :     '#' TOK_INTEGER
     179                 :             :         {
     180         [ +  + ]:        3096 :             if ($2 <= 0)
     181                 :           1 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     182                 :             :                              "only positive occurrence numbers are permitted");
     183                 :        3096 :             $$ = $2;
     184                 :             :         }
     185                 :             :     |
     186                 :             :         {
     187                 :             :             /* The default occurrence number is 1. */
     188                 :      156345 :             $$ = 1;
     189                 :             :         }
     190                 :             :     ;
     191                 :             : 
     192                 :             : identifier: TOK_IDENT
     193                 :             :     | TOK_TAG_JOIN_ORDER
     194                 :             :     | TOK_TAG_INDEX
     195                 :             :     | TOK_TAG_SIMPLE
     196                 :             :     | TOK_TAG_GENERIC
     197                 :             :     ;
     198                 :             : 
     199                 :             : /*
     200                 :             :  * The partition name must always be schema-qualified. Otherwise, a relation
     201                 :             :  * identifier could refer to more than one partition of the same partitioned
     202                 :             :  * table, and relation identifiers are required to be unique.
     203                 :             :  */
     204                 :             : opt_partition:
     205                 :             :     '/' identifier '.' identifier
     206                 :       13861 :         { $$ = list_make2($2, $4); }
     207                 :             :     |
     208                 :      145579 :         { $$ = NIL; }
     209                 :             :     ;
     210                 :             : 
     211                 :             : opt_plan_name:
     212                 :             :     '@' identifier
     213                 :       36803 :         { $$ = $2; }
     214                 :             :     |
     215                 :      122637 :         { $$ = NULL; }
     216                 :             :     ;
     217                 :             : 
     218                 :             : generic_target_list: generic_target_list relation_identifier
     219                 :       17204 :         { $$ = lappend($1, $2); }
     220                 :             :     | generic_target_list generic_sublist
     221                 :         792 :         { $$ = lappend($1, $2); }
     222                 :             :     |
     223                 :       12419 :         { $$ = NIL; }
     224                 :             :     ;
     225                 :             : 
     226                 :             : generic_sublist: '(' simple_target_list ')'
     227                 :             :         {
     228                 :         792 :             $$ = palloc0_object(pgpa_advice_target);
     229                 :         792 :             $$->ttype = PGPA_TARGET_ORDERED_LIST;
     230                 :         792 :             $$->children = $2;
     231                 :             :         }
     232                 :             :     ;
     233                 :             : 
     234                 :             : index_target_list:
     235                 :             :       index_target_list relation_identifier index_name
     236                 :             :         {
     237                 :       14655 :             $2->itarget = $3;
     238                 :       14655 :             $$ = lappend($1, $2);
     239                 :             :         }
     240                 :             :     |
     241                 :        9119 :         { $$ = NIL; }
     242                 :             :     ;
     243                 :             : 
     244                 :             : join_order_target_list: join_order_target_list relation_identifier
     245                 :       25920 :         { $$ = lappend($1, $2); }
     246                 :             :     | join_order_target_list join_order_sublist
     247                 :         496 :         { $$ = lappend($1, $2); }
     248                 :             :     |
     249                 :       11459 :         { $$ = NIL; }
     250                 :             :     ;
     251                 :             : 
     252                 :             : join_order_sublist:
     253                 :             :     '(' join_order_target_list ')'
     254                 :             :         {
     255                 :         470 :             $$ = palloc0_object(pgpa_advice_target);
     256                 :         470 :             $$->ttype = PGPA_TARGET_ORDERED_LIST;
     257                 :         470 :             $$->children = $2;
     258         [ +  + ]:         470 :             if ($2 == NIL)
     259                 :           1 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     260                 :             :                              "JOIN_ORDER targets must contain at least one relation identifier");
     261                 :             :         }
     262                 :             :     | '{' simple_target_list '}'
     263                 :             :         {
     264                 :          26 :             $$ = palloc0_object(pgpa_advice_target);
     265                 :          26 :             $$->ttype = PGPA_TARGET_UNORDERED_LIST;
     266                 :          26 :             $$->children = $2;
     267         [ +  + ]:          26 :             if ($2 == NIL)
     268                 :           1 :                 pgpa_yyerror(result, parse_error_msg_p, yyscanner,
     269                 :             :                              "JOIN_ORDER targets must contain at least one relation identifier");
     270                 :             :         }
     271                 :             :     ;
     272                 :             : 
     273                 :             : simple_target_list: simple_target_list relation_identifier
     274                 :      101661 :         { $$ = lappend($1, $2); }
     275                 :             :     |
     276                 :       63362 :         { $$ = NIL; }
     277                 :             :     ;
     278                 :             : 
     279                 :             : %%
     280                 :             : 
     281                 :             : /*
     282                 :             :  * Parse an advice_string and return the resulting list of pgpa_advice_item
     283                 :             :  * objects. If a parse error occurs, instead return NULL.
     284                 :             :  *
     285                 :             :  * If the return value is NULL, *error_p will be set to the error message;
     286                 :             :  * otherwise, *error_p will be set to NULL.
     287                 :             :  */
     288                 :             : List *
     289                 :       43411 : pgpa_parse(const char *advice_string, char **error_p)
     290                 :             : {
     291                 :             :     yyscan_t    scanner;
     292                 :             :     List       *result;
     293                 :       43411 :     char       *error = NULL;
     294                 :             : 
     295                 :       43411 :     pgpa_scanner_init(advice_string, &scanner);
     296                 :       43411 :     pgpa_yyparse(&result, &error, scanner);
     297                 :       43411 :     pgpa_scanner_finish(scanner);
     298                 :             : 
     299         [ +  + ]:       43411 :     if (error != NULL)
     300                 :             :     {
     301                 :          24 :         *error_p = error;
     302                 :          24 :         return NULL;
     303                 :             :     }
     304                 :             : 
     305                 :       43387 :     *error_p = NULL;
     306                 :       43387 :     return result;
     307                 :             : }
        

Generated by: LCOV version 2.0-1