LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_ast.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 97.4 % 193 188
Test Date: 2026-09-26 09:15:51 Functions: 100.0 % 8 8
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 90.5 % 148 134

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * pgpa_ast.c
       4                 :             :  *    additional supporting code related to plan advice parsing
       5                 :             :  *
       6                 :             :  * Copyright (c) 2016-2026, PostgreSQL Global Development Group
       7                 :             :  *
       8                 :             :  *    contrib/pg_plan_advice/pgpa_ast.c
       9                 :             :  *
      10                 :             :  *-------------------------------------------------------------------------
      11                 :             :  */
      12                 :             : 
      13                 :             : #include "postgres.h"
      14                 :             : 
      15                 :             : #include "pgpa_ast.h"
      16                 :             : 
      17                 :             : #include "funcapi.h"
      18                 :             : #include "miscadmin.h"
      19                 :             : #include "utils/array.h"
      20                 :             : #include "utils/builtins.h"
      21                 :             : 
      22                 :             : static bool pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
      23                 :             :                                           pgpa_advice_target *target,
      24                 :             :                                           bool *rids_used);
      25                 :             : 
      26                 :             : /*
      27                 :             :  * Get a C string that corresponds to the specified advice tag.
      28                 :             :  */
      29                 :             : char *
      30                 :      143466 : pgpa_cstring_advice_tag(pgpa_advice_tag_type advice_tag)
      31                 :             : {
      32   [ +  +  +  +  :      143466 :     switch (advice_tag)
          +  +  +  +  +  
          +  +  +  +  +  
          +  +  +  +  +  
                   +  - ]
      33                 :             :     {
      34                 :        2543 :         case PGPA_TAG_BITMAP_HEAP_SCAN:
      35                 :        2543 :             return "BITMAP_HEAP_SCAN";
      36                 :         434 :         case PGPA_TAG_DO_NOT_SCAN:
      37                 :         434 :             return "DO_NOT_SCAN";
      38                 :           1 :         case PGPA_TAG_FOREIGN_JOIN:
      39                 :           1 :             return "FOREIGN_JOIN";
      40                 :         172 :         case PGPA_TAG_GATHER:
      41                 :         172 :             return "GATHER";
      42                 :          58 :         case PGPA_TAG_GATHER_MERGE:
      43                 :          58 :             return "GATHER_MERGE";
      44                 :        4399 :         case PGPA_TAG_HASH_JOIN:
      45                 :        4399 :             return "HASH_JOIN";
      46                 :        1708 :         case PGPA_TAG_INDEX_ONLY_SCAN:
      47                 :        1708 :             return "INDEX_ONLY_SCAN";
      48                 :       12665 :         case PGPA_TAG_INDEX_SCAN:
      49                 :       12665 :             return "INDEX_SCAN";
      50                 :       10959 :         case PGPA_TAG_JOIN_ORDER:
      51                 :       10959 :             return "JOIN_ORDER";
      52                 :          29 :         case PGPA_TAG_MERGE_JOIN_MATERIALIZE:
      53                 :          29 :             return "MERGE_JOIN_MATERIALIZE";
      54                 :         513 :         case PGPA_TAG_MERGE_JOIN_PLAIN:
      55                 :         513 :             return "MERGE_JOIN_PLAIN";
      56                 :         513 :         case PGPA_TAG_NESTED_LOOP_MATERIALIZE:
      57                 :         513 :             return "NESTED_LOOP_MATERIALIZE";
      58                 :         232 :         case PGPA_TAG_NESTED_LOOP_MEMOIZE:
      59                 :         232 :             return "NESTED_LOOP_MEMOIZE";
      60                 :        9222 :         case PGPA_TAG_NESTED_LOOP_PLAIN:
      61                 :        9222 :             return "NESTED_LOOP_PLAIN";
      62                 :       70792 :         case PGPA_TAG_NO_GATHER:
      63                 :       70792 :             return "NO_GATHER";
      64                 :        2092 :         case PGPA_TAG_PARTITIONWISE:
      65                 :        2092 :             return "PARTITIONWISE";
      66                 :         623 :         case PGPA_TAG_SEMIJOIN_NON_UNIQUE:
      67                 :         623 :             return "SEMIJOIN_NON_UNIQUE";
      68                 :          82 :         case PGPA_TAG_SEMIJOIN_UNIQUE:
      69                 :          82 :             return "SEMIJOIN_UNIQUE";
      70                 :       26011 :         case PGPA_TAG_SEQ_SCAN:
      71                 :       26011 :             return "SEQ_SCAN";
      72                 :         418 :         case PGPA_TAG_TID_SCAN:
      73                 :         418 :             return "TID_SCAN";
      74                 :             :     }
      75                 :             : 
      76                 :           0 :     pg_unreachable();
      77                 :             :     return NULL;
      78                 :             : }
      79                 :             : 
      80                 :             : /*
      81                 :             :  * Convert an advice tag, formatted as a string that has already been
      82                 :             :  * downcased as appropriate, to a pgpa_advice_tag_type.
      83                 :             :  *
      84                 :             :  * If we succeed, set *fail = false and return the result; if we fail,
      85                 :             :  * set *fail = true and return an arbitrary value.
      86                 :             :  */
      87                 :             : pgpa_advice_tag_type
      88                 :      340146 : pgpa_parse_advice_tag(const char *tag, bool *fail)
      89                 :             : {
      90                 :      340146 :     *fail = false;
      91                 :             : 
      92   [ +  +  +  +  :      340146 :     switch (tag[0])
          +  +  +  +  +  
             +  +  +  + ]
      93                 :             :     {
      94                 :        9203 :         case 'b':
      95         [ +  + ]:        9203 :             if (strcmp(tag, "bitmap_heap_scan") == 0)
      96                 :        2442 :                 return PGPA_TAG_BITMAP_HEAP_SCAN;
      97                 :        6761 :             break;
      98                 :        2827 :         case 'd':
      99         [ +  + ]:        2827 :             if (strcmp(tag, "do_not_scan") == 0)
     100                 :         234 :                 return PGPA_TAG_DO_NOT_SCAN;
     101                 :        2593 :             break;
     102                 :        4947 :         case 'f':
     103         [ +  + ]:        4947 :             if (strcmp(tag, "foreign_join") == 0)
     104                 :          10 :                 return PGPA_TAG_FOREIGN_JOIN;
     105                 :        4937 :             break;
     106                 :        2270 :         case 'g':
     107         [ +  + ]:        2270 :             if (strcmp(tag, "gather") == 0)
     108                 :         339 :                 return PGPA_TAG_GATHER;
     109         [ +  + ]:        1931 :             if (strcmp(tag, "gather_merge") == 0)
     110                 :         112 :                 return PGPA_TAG_GATHER_MERGE;
     111                 :        1819 :             break;
     112                 :        6886 :         case 'h':
     113         [ +  + ]:        6886 :             if (strcmp(tag, "hash_join") == 0)
     114                 :        6237 :                 return PGPA_TAG_HASH_JOIN;
     115                 :         649 :             break;
     116                 :       17098 :         case 'i':
     117         [ +  + ]:       17098 :             if (strcmp(tag, "index_scan") == 0)
     118                 :        7528 :                 return PGPA_TAG_INDEX_SCAN;
     119         [ +  + ]:        9570 :             if (strcmp(tag, "index_only_scan") == 0)
     120                 :        1496 :                 return PGPA_TAG_INDEX_ONLY_SCAN;
     121                 :        8074 :             break;
     122                 :       11721 :         case 'j':
     123         [ +  + ]:       11721 :             if (strcmp(tag, "join_order") == 0)
     124                 :       10991 :                 return PGPA_TAG_JOIN_ORDER;
     125                 :         730 :             break;
     126                 :        4389 :         case 'm':
     127         [ +  + ]:        4389 :             if (strcmp(tag, "merge_join_materialize") == 0)
     128                 :          62 :                 return PGPA_TAG_MERGE_JOIN_MATERIALIZE;
     129         [ +  + ]:        4327 :             if (strcmp(tag, "merge_join_plain") == 0)
     130                 :         934 :                 return PGPA_TAG_MERGE_JOIN_PLAIN;
     131                 :        3393 :             break;
     132                 :       61531 :         case 'n':
     133         [ +  + ]:       61531 :             if (strcmp(tag, "nested_loop_materialize") == 0)
     134                 :         934 :                 return PGPA_TAG_NESTED_LOOP_MATERIALIZE;
     135         [ +  + ]:       60597 :             if (strcmp(tag, "nested_loop_memoize") == 0)
     136                 :         436 :                 return PGPA_TAG_NESTED_LOOP_MEMOIZE;
     137         [ +  + ]:       60161 :             if (strcmp(tag, "nested_loop_plain") == 0)
     138                 :       11236 :                 return PGPA_TAG_NESTED_LOOP_PLAIN;
     139         [ +  + ]:       48925 :             if (strcmp(tag, "no_gather") == 0)
     140                 :       43090 :                 return PGPA_TAG_NO_GATHER;
     141                 :        5835 :             break;
     142                 :       87013 :         case 'p':
     143         [ +  + ]:       87013 :             if (strcmp(tag, "partitionwise") == 0)
     144                 :        3204 :                 return PGPA_TAG_PARTITIONWISE;
     145                 :       83809 :             break;
     146                 :       41483 :         case 's':
     147         [ +  + ]:       41483 :             if (strcmp(tag, "semijoin_non_unique") == 0)
     148                 :        1176 :                 return PGPA_TAG_SEMIJOIN_NON_UNIQUE;
     149         [ +  + ]:       40307 :             if (strcmp(tag, "semijoin_unique") == 0)
     150                 :         156 :                 return PGPA_TAG_SEMIJOIN_UNIQUE;
     151         [ +  + ]:       40151 :             if (strcmp(tag, "seq_scan") == 0)
     152                 :       16510 :                 return PGPA_TAG_SEQ_SCAN;
     153                 :       23641 :             break;
     154                 :       24202 :         case 't':
     155         [ +  + ]:       24202 :             if (strcmp(tag, "tid_scan") == 0)
     156                 :         401 :                 return PGPA_TAG_TID_SCAN;
     157                 :       23801 :             break;
     158                 :             :     }
     159                 :             : 
     160                 :             :     /* didn't work out */
     161                 :      232618 :     *fail = true;
     162                 :             : 
     163                 :             :     /* return an arbitrary value to unwind the call stack */
     164                 :      232618 :     return PGPA_TAG_SEQ_SCAN;
     165                 :             : }
     166                 :             : 
     167                 :             : /*
     168                 :             :  * Format a pgpa_advice_target as a string and append result to a StringInfo.
     169                 :             :  */
     170                 :             : void
     171                 :      171544 : pgpa_format_advice_target(StringInfo str, pgpa_advice_target *target)
     172                 :             : {
     173                 :      171544 :     check_stack_depth();
     174                 :             : 
     175         [ +  + ]:      171544 :     if (target->ttype != PGPA_TARGET_IDENTIFIER)
     176                 :             :     {
     177                 :       12198 :         bool        first = true;
     178                 :             :         char       *delims;
     179                 :             : 
     180         [ +  + ]:       12198 :         if (target->ttype == PGPA_TARGET_UNORDERED_LIST)
     181                 :          19 :             delims = "{}";
     182                 :             :         else
     183                 :       12179 :             delims = "()";
     184                 :             : 
     185                 :       12198 :         appendStringInfoChar(str, delims[0]);
     186   [ +  -  +  +  :       52474 :         foreach_ptr(pgpa_advice_target, child_target, target->children)
                   +  + ]
     187                 :             :         {
     188         [ +  + ]:       28078 :             if (first)
     189                 :       12198 :                 first = false;
     190                 :             :             else
     191                 :       15880 :                 appendStringInfoChar(str, ' ');
     192                 :       28078 :             pgpa_format_advice_target(str, child_target);
     193                 :             :         }
     194                 :       12198 :         appendStringInfoChar(str, delims[1]);
     195                 :             :     }
     196                 :             :     else
     197                 :             :     {
     198                 :             :         const char *rt_identifier;
     199                 :             : 
     200                 :      159346 :         rt_identifier = pgpa_identifier_string(&target->rid);
     201                 :      159346 :         appendStringInfoString(str, rt_identifier);
     202                 :             :     }
     203                 :      171544 : }
     204                 :             : 
     205                 :             : /*
     206                 :             :  * Format a pgpa_index_target as a string and append result to a StringInfo.
     207                 :             :  */
     208                 :             : void
     209                 :       14373 : pgpa_format_index_target(StringInfo str, pgpa_index_target *itarget)
     210                 :             : {
     211         [ +  + ]:       14373 :     if (itarget->indnamespace != NULL)
     212                 :       14355 :         appendStringInfo(str, "%s.",
     213                 :       14355 :                          quote_identifier(itarget->indnamespace));
     214                 :       14373 :     appendStringInfoString(str, quote_identifier(itarget->indname));
     215                 :       14373 : }
     216                 :             : 
     217                 :             : /*
     218                 :             :  * Determine whether two pgpa_index_target objects are exactly identical.
     219                 :             :  */
     220                 :             : bool
     221                 :           2 : pgpa_index_targets_equal(pgpa_index_target *i1, pgpa_index_target *i2)
     222                 :             : {
     223                 :             :     /* indnamespace can be NULL, and two NULL values are equal */
     224   [ +  -  +  + ]:           2 :     if ((i1->indnamespace != NULL || i2->indnamespace != NULL) &&
     225   [ -  +  -  - ]:           1 :         (i1->indnamespace == NULL || i2->indnamespace == NULL ||
     226         [ #  # ]:           0 :          strcmp(i1->indnamespace, i2->indnamespace) != 0))
     227                 :           1 :         return false;
     228         [ -  + ]:           1 :     if (strcmp(i1->indname, i2->indname) != 0)
     229                 :           0 :         return false;
     230                 :             : 
     231                 :           1 :     return true;
     232                 :             : }
     233                 :             : 
     234                 :             : /*
     235                 :             :  * Check whether an identifier matches an any part of an advice target.
     236                 :             :  */
     237                 :             : bool
     238                 :     1304205 : pgpa_identifier_matches_target(pgpa_identifier *rid, pgpa_advice_target *target)
     239                 :             : {
     240                 :     1304205 :     check_stack_depth();
     241                 :             : 
     242                 :             :     /* For non-identifiers, check all descendants. */
     243         [ +  + ]:     1304205 :     if (target->ttype != PGPA_TARGET_IDENTIFIER)
     244                 :             :     {
     245   [ +  -  +  +  :      152903 :         foreach_ptr(pgpa_advice_target, child_target, target->children)
                   +  + ]
     246                 :             :         {
     247         [ +  + ]:      147481 :             if (pgpa_identifier_matches_target(rid, child_target))
     248                 :       71413 :                 return true;
     249                 :             :         }
     250                 :        2711 :         return false;
     251                 :             :     }
     252                 :             : 
     253                 :             :     /* Straightforward comparisons of alias name and occurrence number. */
     254         [ +  + ]:     1230081 :     if (strcmp(rid->alias_name, target->rid.alias_name) != 0)
     255                 :      666904 :         return false;
     256         [ +  + ]:      563177 :     if (rid->occurrence != target->rid.occurrence)
     257                 :       16784 :         return false;
     258                 :             : 
     259                 :             :     /*
     260                 :             :      * A relation identifier should either include both of partition name and
     261                 :             :      * partition schema, or neither one.
     262                 :             :      */
     263                 :             :     Assert((rid->partnsp == NULL) == (rid->partrel == NULL));
     264                 :             : 
     265                 :             :     /*
     266                 :             :      * These fields can be NULL on either side, but NULL only matches another
     267                 :             :      * NULL.
     268                 :             :      */
     269         [ +  + ]:      546393 :     if (!strings_equal_or_both_null(rid->partnsp, target->rid.partnsp))
     270                 :          11 :         return false;
     271         [ -  + ]:      546382 :     if (!strings_equal_or_both_null(rid->partrel, target->rid.partrel))
     272                 :           0 :         return false;
     273         [ -  + ]:      546382 :     if (!strings_equal_or_both_null(rid->plan_name, target->rid.plan_name))
     274                 :           0 :         return false;
     275                 :             : 
     276                 :      546382 :     return true;
     277                 :             : }
     278                 :             : 
     279                 :             : /*
     280                 :             :  * Match identifiers to advice targets and return an enum value indicating
     281                 :             :  * the relationship between the set of keys and the set of targets.
     282                 :             :  *
     283                 :             :  * See the comments for pgpa_itm_type.
     284                 :             :  */
     285                 :             : pgpa_itm_type
     286                 :      338778 : pgpa_identifiers_match_target(int nrids, pgpa_identifier *rids,
     287                 :             :                               pgpa_advice_target *target)
     288                 :             : {
     289                 :      338778 :     bool        all_rids_used = true;
     290                 :      338778 :     bool        any_rids_used = false;
     291                 :             :     bool        all_targets_used;
     292                 :      338778 :     bool       *rids_used = palloc0_array(bool, nrids);
     293                 :             : 
     294                 :             :     /*
     295                 :             :      * This function is called from within various loops within
     296                 :             :      * pgpa_planner.c; to avoid needing a separate CHECK_FOR_INTERRUPTS() in
     297                 :             :      * each one, we check here instead.
     298                 :             :      */
     299         [ -  + ]:      338778 :     CHECK_FOR_INTERRUPTS();
     300                 :             : 
     301                 :             :     all_targets_used =
     302                 :      338778 :         pgpa_identifiers_cover_target(nrids, rids, target, rids_used);
     303                 :             : 
     304         [ +  + ]:      930438 :     for (int i = 0; i < nrids; ++i)
     305                 :             :     {
     306         [ +  + ]:      591660 :         if (rids_used[i])
     307                 :      268964 :             any_rids_used = true;
     308                 :             :         else
     309                 :      322696 :             all_rids_used = false;
     310                 :             :     }
     311                 :             : 
     312         [ +  + ]:      338778 :     if (all_rids_used)
     313                 :             :     {
     314         [ +  + ]:      133301 :         if (all_targets_used)
     315                 :      121157 :             return PGPA_ITM_EQUAL;
     316                 :             :         else
     317                 :       12144 :             return PGPA_ITM_KEYS_ARE_SUBSET;
     318                 :             :     }
     319                 :             :     else
     320                 :             :     {
     321         [ +  + ]:      205477 :         if (all_targets_used)
     322                 :       80251 :             return PGPA_ITM_TARGETS_ARE_SUBSET;
     323         [ +  + ]:      125226 :         else if (any_rids_used)
     324                 :       12007 :             return PGPA_ITM_INTERSECTING;
     325                 :             :         else
     326                 :      113219 :             return PGPA_ITM_DISJOINT;
     327                 :             :     }
     328                 :             : }
     329                 :             : 
     330                 :             : /*
     331                 :             :  * Returns true if every target or sub-target is matched by at least one
     332                 :             :  * identifier, and otherwise false.
     333                 :             :  *
     334                 :             :  * Also sets rids_used[i] = true for each identifier that matches at least one
     335                 :             :  * target.
     336                 :             :  */
     337                 :             : static bool
     338                 :      564133 : pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
     339                 :             :                               pgpa_advice_target *target, bool *rids_used)
     340                 :             : {
     341                 :      564133 :     bool        result = false;
     342                 :             : 
     343                 :      564133 :     check_stack_depth();
     344                 :             : 
     345         [ +  + ]:      564133 :     if (target->ttype != PGPA_TARGET_IDENTIFIER)
     346                 :             :     {
     347                 :      134268 :         result = true;
     348                 :             : 
     349   [ +  -  +  +  :      493891 :         foreach_ptr(pgpa_advice_target, child_target, target->children)
                   +  + ]
     350                 :             :         {
     351         [ +  + ]:      225355 :             if (!pgpa_identifiers_cover_target(nrids, rids, child_target,
     352                 :             :                                                rids_used))
     353                 :       99513 :                 result = false;
     354                 :             :         }
     355                 :             :     }
     356                 :             :     else
     357                 :             :     {
     358         [ +  + ]:     1301345 :         for (int i = 0; i < nrids; ++i)
     359                 :             :         {
     360         [ +  + ]:      871480 :             if (pgpa_identifier_matches_target(&rids[i], target))
     361                 :             :             {
     362                 :      268964 :                 rids_used[i] = true;
     363                 :      268964 :                 result = true;
     364                 :             :             }
     365                 :             :         }
     366                 :             :     }
     367                 :             : 
     368                 :      564133 :     return result;
     369                 :             : }
        

Generated by: LCOV version 2.0-1