LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_ast.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 94.0 % 184 173
Test Date: 2026-03-16 00:15:06 Functions: 100.0 % 8 8
Legend: Lines:     hit not hit

            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 "utils/array.h"
      19              : #include "utils/builtins.h"
      20              : 
      21              : static bool pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
      22              :                                           pgpa_advice_target *target,
      23              :                                           bool *rids_used);
      24              : 
      25              : /*
      26              :  * Get a C string that corresponds to the specified advice tag.
      27              :  */
      28              : char *
      29          133 : pgpa_cstring_advice_tag(pgpa_advice_tag_type advice_tag)
      30              : {
      31          133 :     switch (advice_tag)
      32              :     {
      33            3 :         case PGPA_TAG_BITMAP_HEAP_SCAN:
      34            3 :             return "BITMAP_HEAP_SCAN";
      35            1 :         case PGPA_TAG_FOREIGN_JOIN:
      36            1 :             return "FOREIGN_JOIN";
      37            8 :         case PGPA_TAG_GATHER:
      38            8 :             return "GATHER";
      39            6 :         case PGPA_TAG_GATHER_MERGE:
      40            6 :             return "GATHER_MERGE";
      41            6 :         case PGPA_TAG_HASH_JOIN:
      42            6 :             return "HASH_JOIN";
      43            6 :         case PGPA_TAG_INDEX_ONLY_SCAN:
      44            6 :             return "INDEX_ONLY_SCAN";
      45           15 :         case PGPA_TAG_INDEX_SCAN:
      46           15 :             return "INDEX_SCAN";
      47           19 :         case PGPA_TAG_JOIN_ORDER:
      48           19 :             return "JOIN_ORDER";
      49            2 :         case PGPA_TAG_MERGE_JOIN_MATERIALIZE:
      50            2 :             return "MERGE_JOIN_MATERIALIZE";
      51            2 :         case PGPA_TAG_MERGE_JOIN_PLAIN:
      52            2 :             return "MERGE_JOIN_PLAIN";
      53            3 :         case PGPA_TAG_NESTED_LOOP_MATERIALIZE:
      54            3 :             return "NESTED_LOOP_MATERIALIZE";
      55            2 :         case PGPA_TAG_NESTED_LOOP_MEMOIZE:
      56            2 :             return "NESTED_LOOP_MEMOIZE";
      57            5 :         case PGPA_TAG_NESTED_LOOP_PLAIN:
      58            5 :             return "NESTED_LOOP_PLAIN";
      59            7 :         case PGPA_TAG_NO_GATHER:
      60            7 :             return "NO_GATHER";
      61            8 :         case PGPA_TAG_PARTITIONWISE:
      62            8 :             return "PARTITIONWISE";
      63            6 :         case PGPA_TAG_SEMIJOIN_NON_UNIQUE:
      64            6 :             return "SEMIJOIN_NON_UNIQUE";
      65            7 :         case PGPA_TAG_SEMIJOIN_UNIQUE:
      66            7 :             return "SEMIJOIN_UNIQUE";
      67           23 :         case PGPA_TAG_SEQ_SCAN:
      68           23 :             return "SEQ_SCAN";
      69            4 :         case PGPA_TAG_TID_SCAN:
      70            4 :             return "TID_SCAN";
      71              :     }
      72              : 
      73            0 :     pg_unreachable();
      74              :     return NULL;
      75              : }
      76              : 
      77              : /*
      78              :  * Convert an advice tag, formatted as a string that has already been
      79              :  * downcased as appropriate, to a pgpa_advice_tag_type.
      80              :  *
      81              :  * If we succeed, set *fail = false and return the result; if we fail,
      82              :  * set *fail = true and return an arbitrary value.
      83              :  */
      84              : pgpa_advice_tag_type
      85          777 : pgpa_parse_advice_tag(const char *tag, bool *fail)
      86              : {
      87          777 :     *fail = false;
      88              : 
      89          777 :     switch (tag[0])
      90              :     {
      91           10 :         case 'b':
      92           10 :             if (strcmp(tag, "bitmap_heap_scan") == 0)
      93            6 :                 return PGPA_TAG_BITMAP_HEAP_SCAN;
      94            4 :             break;
      95           76 :         case 'f':
      96           76 :             if (strcmp(tag, "foreign_join") == 0)
      97            8 :                 return PGPA_TAG_FOREIGN_JOIN;
      98           68 :             break;
      99           55 :         case 'g':
     100           55 :             if (strcmp(tag, "gather") == 0)
     101           29 :                 return PGPA_TAG_GATHER;
     102           26 :             if (strcmp(tag, "gather_merge") == 0)
     103           20 :                 return PGPA_TAG_GATHER_MERGE;
     104            6 :             break;
     105           24 :         case 'h':
     106           24 :             if (strcmp(tag, "hash_join") == 0)
     107           24 :                 return PGPA_TAG_HASH_JOIN;
     108            0 :             break;
     109           38 :         case 'i':
     110           38 :             if (strcmp(tag, "index_scan") == 0)
     111           26 :                 return PGPA_TAG_INDEX_SCAN;
     112           12 :             if (strcmp(tag, "index_only_scan") == 0)
     113           12 :                 return PGPA_TAG_INDEX_ONLY_SCAN;
     114            0 :             break;
     115           40 :         case 'j':
     116           40 :             if (strcmp(tag, "join_order") == 0)
     117           40 :                 return PGPA_TAG_JOIN_ORDER;
     118            0 :             break;
     119           16 :         case 'm':
     120           16 :             if (strcmp(tag, "merge_join_materialize") == 0)
     121            8 :                 return PGPA_TAG_MERGE_JOIN_MATERIALIZE;
     122            8 :             if (strcmp(tag, "merge_join_plain") == 0)
     123            8 :                 return PGPA_TAG_MERGE_JOIN_PLAIN;
     124            0 :             break;
     125           58 :         case 'n':
     126           58 :             if (strcmp(tag, "nested_loop_materialize") == 0)
     127           12 :                 return PGPA_TAG_NESTED_LOOP_MATERIALIZE;
     128           46 :             if (strcmp(tag, "nested_loop_memoize") == 0)
     129            8 :                 return PGPA_TAG_NESTED_LOOP_MEMOIZE;
     130           38 :             if (strcmp(tag, "nested_loop_plain") == 0)
     131           20 :                 return PGPA_TAG_NESTED_LOOP_PLAIN;
     132           18 :             if (strcmp(tag, "no_gather") == 0)
     133           12 :                 return PGPA_TAG_NO_GATHER;
     134            6 :             break;
     135           78 :         case 'p':
     136           78 :             if (strcmp(tag, "partitionwise") == 0)
     137           16 :                 return PGPA_TAG_PARTITIONWISE;
     138           62 :             break;
     139          231 :         case 's':
     140          231 :             if (strcmp(tag, "semijoin_non_unique") == 0)
     141           24 :                 return PGPA_TAG_SEMIJOIN_NON_UNIQUE;
     142          207 :             if (strcmp(tag, "semijoin_unique") == 0)
     143           28 :                 return PGPA_TAG_SEMIJOIN_UNIQUE;
     144          179 :             if (strcmp(tag, "seq_scan") == 0)
     145           51 :                 return PGPA_TAG_SEQ_SCAN;
     146          128 :             break;
     147            7 :         case 't':
     148            7 :             if (strcmp(tag, "tid_scan") == 0)
     149            7 :                 return PGPA_TAG_TID_SCAN;
     150            0 :             break;
     151              :     }
     152              : 
     153              :     /* didn't work out */
     154          418 :     *fail = true;
     155              : 
     156              :     /* return an arbitrary value to unwind the call stack */
     157          418 :     return PGPA_TAG_SEQ_SCAN;
     158              : }
     159              : 
     160              : /*
     161              :  * Format a pgpa_advice_target as a string and append result to a StringInfo.
     162              :  */
     163              : void
     164          202 : pgpa_format_advice_target(StringInfo str, pgpa_advice_target *target)
     165              : {
     166          202 :     if (target->ttype != PGPA_TARGET_IDENTIFIER)
     167              :     {
     168           33 :         bool        first = true;
     169              :         char       *delims;
     170              : 
     171           33 :         if (target->ttype == PGPA_TARGET_UNORDERED_LIST)
     172            1 :             delims = "{}";
     173              :         else
     174           32 :             delims = "()";
     175              : 
     176           33 :         appendStringInfoChar(str, delims[0]);
     177          135 :         foreach_ptr(pgpa_advice_target, child_target, target->children)
     178              :         {
     179           69 :             if (first)
     180           33 :                 first = false;
     181              :             else
     182           36 :                 appendStringInfoChar(str, ' ');
     183           69 :             pgpa_format_advice_target(str, child_target);
     184              :         }
     185           33 :         appendStringInfoChar(str, delims[1]);
     186              :     }
     187              :     else
     188              :     {
     189              :         const char *rt_identifier;
     190              : 
     191          169 :         rt_identifier = pgpa_identifier_string(&target->rid);
     192          169 :         appendStringInfoString(str, rt_identifier);
     193              :     }
     194          202 : }
     195              : 
     196              : /*
     197              :  * Format a pgpa_index_target as a string and append result to a StringInfo.
     198              :  */
     199              : void
     200           21 : pgpa_format_index_target(StringInfo str, pgpa_index_target *itarget)
     201              : {
     202           21 :     if (itarget->indnamespace != NULL)
     203            4 :         appendStringInfo(str, "%s.",
     204            4 :                          quote_identifier(itarget->indnamespace));
     205           21 :     appendStringInfoString(str, quote_identifier(itarget->indname));
     206           21 : }
     207              : 
     208              : /*
     209              :  * Determine whether two pgpa_index_target objects are exactly identical.
     210              :  */
     211              : bool
     212            2 : pgpa_index_targets_equal(pgpa_index_target *i1, pgpa_index_target *i2)
     213              : {
     214              :     /* indnamespace can be NULL, and two NULL values are equal */
     215            2 :     if ((i1->indnamespace != NULL || i2->indnamespace != NULL) &&
     216            1 :         (i1->indnamespace == NULL || i2->indnamespace == NULL ||
     217            0 :          strcmp(i1->indnamespace, i2->indnamespace) != 0))
     218            1 :         return false;
     219            1 :     if (strcmp(i1->indname, i2->indname) != 0)
     220            0 :         return false;
     221              : 
     222            1 :     return true;
     223              : }
     224              : 
     225              : /*
     226              :  * Check whether an identifier matches an any part of an advice target.
     227              :  */
     228              : bool
     229         1629 : pgpa_identifier_matches_target(pgpa_identifier *rid, pgpa_advice_target *target)
     230              : {
     231              :     /* For non-identifiers, check all descendants. */
     232         1629 :     if (target->ttype != PGPA_TARGET_IDENTIFIER)
     233              :     {
     234          297 :         foreach_ptr(pgpa_advice_target, child_target, target->children)
     235              :         {
     236          297 :             if (pgpa_identifier_matches_target(rid, child_target))
     237          175 :                 return true;
     238              :         }
     239            0 :         return false;
     240              :     }
     241              : 
     242              :     /* Straightforward comparisons of alias name and occurrence number. */
     243         1454 :     if (strcmp(rid->alias_name, target->rid.alias_name) != 0)
     244          733 :         return false;
     245          721 :     if (rid->occurrence != target->rid.occurrence)
     246            4 :         return false;
     247              : 
     248              :     /*
     249              :      * If a relation identifier mentions a partition name, it should also
     250              :      * specify a partition schema. But the target may leave the schema NULL to
     251              :      * match anything.
     252              :      */
     253              :     Assert(rid->partnsp != NULL || rid->partrel == NULL);
     254          717 :     if (rid->partnsp != NULL && target->rid.partnsp != NULL &&
     255           20 :         strcmp(rid->partnsp, target->rid.partnsp) != 0)
     256            0 :         return false;
     257              : 
     258              :     /*
     259              :      * These fields can be NULL on either side, but NULL only matches another
     260              :      * NULL.
     261              :      */
     262          717 :     if (!strings_equal_or_both_null(rid->partrel, target->rid.partrel))
     263           11 :         return false;
     264          706 :     if (!strings_equal_or_both_null(rid->plan_name, target->rid.plan_name))
     265            0 :         return false;
     266              : 
     267          706 :     return true;
     268              : }
     269              : 
     270              : /*
     271              :  * Match identifiers to advice targets and return an enum value indicating
     272              :  * the relationship between the set of keys and the set of targets.
     273              :  *
     274              :  * See the comments for pgpa_itm_type.
     275              :  */
     276              : pgpa_itm_type
     277          561 : pgpa_identifiers_match_target(int nrids, pgpa_identifier *rids,
     278              :                               pgpa_advice_target *target)
     279              : {
     280          561 :     bool        all_rids_used = true;
     281          561 :     bool        any_rids_used = false;
     282              :     bool        all_targets_used;
     283          561 :     bool       *rids_used = palloc0_array(bool, nrids);
     284              : 
     285              :     all_targets_used =
     286          561 :         pgpa_identifiers_cover_target(nrids, rids, target, rids_used);
     287              : 
     288         1338 :     for (int i = 0; i < nrids; ++i)
     289              :     {
     290          777 :         if (rids_used[i])
     291          435 :             any_rids_used = true;
     292              :         else
     293          342 :             all_rids_used = false;
     294              :     }
     295              : 
     296          561 :     if (all_rids_used)
     297              :     {
     298          245 :         if (all_targets_used)
     299          197 :             return PGPA_ITM_EQUAL;
     300              :         else
     301           48 :             return PGPA_ITM_KEYS_ARE_SUBSET;
     302              :     }
     303              :     else
     304              :     {
     305          316 :         if (all_targets_used)
     306           94 :             return PGPA_ITM_TARGETS_ARE_SUBSET;
     307          222 :         else if (any_rids_used)
     308           38 :             return PGPA_ITM_INTERSECTING;
     309              :         else
     310          184 :             return PGPA_ITM_DISJOINT;
     311              :     }
     312              : }
     313              : 
     314              : /*
     315              :  * Returns true if every target or sub-target is matched by at least one
     316              :  * identifier, and otherwise false.
     317              :  *
     318              :  * Also sets rids_used[i] = true for each idenifier that matches at least one
     319              :  * target.
     320              :  */
     321              : static bool
     322         1035 : pgpa_identifiers_cover_target(int nrids, pgpa_identifier *rids,
     323              :                               pgpa_advice_target *target, bool *rids_used)
     324              : {
     325         1035 :     bool        result = false;
     326              : 
     327         1035 :     if (target->ttype != PGPA_TARGET_IDENTIFIER)
     328              :     {
     329          315 :         result = true;
     330              : 
     331         1104 :         foreach_ptr(pgpa_advice_target, child_target, target->children)
     332              :         {
     333          474 :             if (!pgpa_identifiers_cover_target(nrids, rids, child_target,
     334              :                                                rids_used))
     335          215 :                 result = false;
     336              :         }
     337              :     }
     338              :     else
     339              :     {
     340         1777 :         for (int i = 0; i < nrids; ++i)
     341              :         {
     342         1057 :             if (pgpa_identifier_matches_target(&rids[i], target))
     343              :             {
     344          435 :                 rids_used[i] = true;
     345          435 :                 result = true;
     346              :             }
     347              :         }
     348              :     }
     349              : 
     350         1035 :     return result;
     351              : }
        

Generated by: LCOV version 2.0-1