LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_scan.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 95.6 % 114 109
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 3 3
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 74.6 % 71 53

             Branch data     Line data    Source code
       1                 :             : /*-------------------------------------------------------------------------
       2                 :             :  *
       3                 :             :  * pgpa_scan.c
       4                 :             :  *        analysis of scans in Plan trees
       5                 :             :  *
       6                 :             :  * Copyright (c) 2016-2025, PostgreSQL Global Development Group
       7                 :             :  *
       8                 :             :  *        contrib/pg_plan_advice/pgpa_scan.c
       9                 :             :  *
      10                 :             :  *-------------------------------------------------------------------------
      11                 :             :  */
      12                 :             : #include "postgres.h"
      13                 :             : 
      14                 :             : #include "pgpa_scan.h"
      15                 :             : #include "pgpa_walker.h"
      16                 :             : 
      17                 :             : #include "nodes/parsenodes.h"
      18                 :             : #include "parser/parsetree.h"
      19                 :             : 
      20                 :             : static pgpa_scan *pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
      21                 :             :                                                                  pgpa_scan_strategy strategy,
      22                 :             :                                                                  Bitmapset *relids,
      23                 :             :                                                                  bool needs_no_gather);
      24                 :             : 
      25                 :             : 
      26                 :             : static RTEKind unique_nonjoin_rtekind(Bitmapset *relids, List *rtable);
      27                 :             : 
      28                 :             : /*
      29                 :             :  * Build a pgpa_scan object for a Plan node and update the plan walker
      30                 :             :  * context as appopriate.  If this is an Append or MergeAppend scan, also
      31                 :             :  * build pgpa_scan for any scans that were consolidated into this one by
      32                 :             :  * Append/MergeAppend pull-up.
      33                 :             :  *
      34                 :             :  * If there is at least one ElidedNode for this plan node, pass the uppermost
      35                 :             :  * one as elided_node, else pass NULL.
      36                 :             :  *
      37                 :             :  * Set the 'beneath_any_gather' node if we are underneath a Gather or
      38                 :             :  * Gather Merge node.
      39                 :             :  *
      40                 :             :  * Set the 'within_join_problem' flag if we're inside of a join problem and
      41                 :             :  * not otherwise.
      42                 :             :  */
      43                 :             : pgpa_scan *
      44                 :      111941 : pgpa_build_scan(pgpa_plan_walker_context *walker, Plan *plan,
      45                 :             :                                 ElidedNode *elided_node,
      46                 :             :                                 bool beneath_any_gather, bool within_join_problem)
      47                 :             : {
      48                 :      111941 :         pgpa_scan_strategy strategy = PGPA_SCAN_ORDINARY;
      49                 :      111941 :         Bitmapset  *relids = NULL;
      50                 :      111941 :         int                     rti = -1;
      51                 :      111941 :         bool            needs_no_gather = !beneath_any_gather;
      52                 :      111941 :         List       *child_append_relid_sets = NIL;
      53                 :             : 
      54         [ +  + ]:      111941 :         if (elided_node != NULL)
      55                 :             :         {
      56                 :        2645 :                 NodeTag         elided_type = elided_node->elided_type;
      57                 :             : 
      58                 :             :                 /*
      59                 :             :                  * If setrefs processing elided an Append or MergeAppend node that had
      60                 :             :                  * only one surviving child, it might be a partitionwise operation,
      61                 :             :                  * but then this is either a setop over subqueries, or a partitionwise
      62                 :             :                  * operation (which might be a scan or a join in reality, but here we
      63                 :             :                  * don't care about the distinction and consider it simply a scan).
      64                 :             :                  *
      65                 :             :                  * A setop over subqueries, or a trivial SubQueryScan that was elided,
      66                 :             :                  * is an "ordinary" scan i.e. one for which we need to generate advice
      67                 :             :                  * because the planner has not made any meaningful choice.
      68                 :             :                  */
      69                 :        2645 :                 relids = elided_node->relids;
      70   [ +  +  +  + ]:        2645 :                 if ((elided_type == T_Append || elided_type == T_MergeAppend) &&
      71                 :        5290 :                         unique_nonjoin_rtekind(relids,
      72                 :        5290 :                                                                    walker->pstmt->rtable) == RTE_RELATION)
      73                 :         567 :                         strategy = PGPA_SCAN_PARTITIONWISE;
      74                 :             :                 else
      75                 :        2078 :                         strategy = PGPA_SCAN_ORDINARY;
      76                 :             : 
      77                 :             :                 /*
      78                 :             :                  * If this is an elided Append or MergeAppend node, we don't need to
      79                 :             :                  * emit NO_GATHER() because we'll also emit it for the underlying
      80                 :             :                  * scan, which is good enough.
      81                 :             :                  *
      82                 :             :                  * If it's an elided SubqueryScan, the same argument is likely to
      83                 :             :                  * apply, because the subquery probably contains references to tables,
      84                 :             :                  * and if it doesn't, then planner isn't likely to want to do it in
      85                 :             :                  * parallel anyway. But also, we can't implement NO_GATHER() advice
      86                 :             :                  * for a non-relation RTEKind, because get_relation_info() isn't
      87                 :             :                  * called in such cases. That should probably be fixed at some point,
      88                 :             :                  * but until then we shouldn't emit it.
      89                 :             :                  */
      90                 :        2645 :                 needs_no_gather = false;
      91                 :             : 
      92                 :             :                 /* Join RTIs can be present, but advice never refers to them. */
      93                 :        2645 :                 relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
      94                 :        2645 :         }
      95         [ +  + ]:      109296 :         else if ((rti = pgpa_scanrelid(plan)) != 0)
      96                 :             :         {
      97                 :       49282 :                 RangeTblEntry *rte;
      98                 :             : 
      99                 :       49282 :                 relids = bms_make_singleton(rti);
     100                 :             : 
     101   [ +  +  +  +  :       49282 :                 switch (nodeTag(plan))
                   +  + ]
     102                 :             :                 {
     103                 :             :                         case T_SeqScan:
     104                 :       25934 :                                 strategy = PGPA_SCAN_SEQ;
     105                 :       25934 :                                 break;
     106                 :             :                         case T_BitmapHeapScan:
     107                 :        2656 :                                 strategy = PGPA_SCAN_BITMAP_HEAP;
     108                 :        2656 :                                 break;
     109                 :             :                         case T_IndexScan:
     110                 :       11684 :                                 strategy = PGPA_SCAN_INDEX;
     111                 :       11684 :                                 break;
     112                 :             :                         case T_IndexOnlyScan:
     113                 :        1841 :                                 strategy = PGPA_SCAN_INDEX_ONLY;
     114                 :        1841 :                                 break;
     115                 :             :                         case T_TidScan:
     116                 :             :                         case T_TidRangeScan:
     117                 :         421 :                                 strategy = PGPA_SCAN_TID;
     118                 :         421 :                                 break;
     119                 :             :                         default:
     120                 :             : 
     121                 :             :                                 /*
     122                 :             :                                  * This case includes a ForeignScan targeting a single
     123                 :             :                                  * relation; no other strategy is possible in that case, but
     124                 :             :                                  * see below, where things are different in multi-relation
     125                 :             :                                  * cases.
     126                 :             :                                  */
     127                 :        6746 :                                 strategy = PGPA_SCAN_ORDINARY;
     128                 :             : 
     129                 :             :                                 /*
     130                 :             :                                  * We can't handle NO_GATHER() for non-relation RTEs because
     131                 :             :                                  * get_relation_info won't be called.
     132                 :             :                                  */
     133                 :        6746 :                                 rte = rt_fetch(rti, walker->pstmt->rtable);
     134         [ +  + ]:        6746 :                                 if (rte->rtekind != RTE_RELATION)
     135                 :        6701 :                                         needs_no_gather = false;
     136                 :             : 
     137                 :        6746 :                                 break;
     138                 :             :                 }
     139                 :       49282 :         }
     140         [ +  + ]:       60014 :         else if ((relids = pgpa_relids(plan)) != NULL)
     141                 :             :         {
     142   [ +  -  +  + ]:       20333 :                 switch (nodeTag(plan))
     143                 :             :                 {
     144                 :             :                         case T_ForeignScan:
     145                 :             : 
     146                 :             :                                 /*
     147                 :             :                                  * If multiple relations are being targeted by a single
     148                 :             :                                  * foreign scan, then the foreign join has been pushed to the
     149                 :             :                                  * remote side, and we want that to be reflected in the
     150                 :             :                                  * generated advice.
     151                 :             :                                  */
     152                 :           0 :                                 strategy = PGPA_SCAN_FOREIGN;
     153                 :           0 :                                 break;
     154                 :             :                         case T_Append:
     155                 :             : 
     156                 :             :                                 /*
     157                 :             :                                  * Append nodes can represent partitionwise scans of a a
     158                 :             :                                  * relation, but when they implement a set operation, they are
     159                 :             :                                  * just ordinary scans.
     160                 :             :                                  */
     161                 :        2434 :                                 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
     162         [ +  + ]:        2434 :                                         == RTE_RELATION)
     163                 :        1177 :                                         strategy = PGPA_SCAN_PARTITIONWISE;
     164                 :             :                                 else
     165                 :        1257 :                                         strategy = PGPA_SCAN_ORDINARY;
     166                 :             : 
     167                 :             :                                 /* NO_GATHER() should be emitted for underlying rels only. */
     168                 :        2434 :                                 needs_no_gather = false;
     169                 :             : 
     170                 :             :                                 /* Be sure to account for pulled-up scans. */
     171                 :        2434 :                                 child_append_relid_sets =
     172                 :        2434 :                                         ((Append *) plan)->child_append_relid_sets;
     173                 :        2434 :                                 break;
     174                 :             :                         case T_MergeAppend:
     175                 :             :                                 /* Some logic here as for Append, above. */
     176                 :          79 :                                 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
     177         [ +  + ]:          79 :                                         == RTE_RELATION)
     178                 :          46 :                                         strategy = PGPA_SCAN_PARTITIONWISE;
     179                 :             :                                 else
     180                 :          33 :                                         strategy = PGPA_SCAN_ORDINARY;
     181                 :             : 
     182                 :             :                                 /* NO_GATHER() should be emitted for underlying rels only. */
     183                 :          79 :                                 needs_no_gather = false;
     184                 :             : 
     185                 :             :                                 /* Be sure to account for pulled-up scans. */
     186                 :          79 :                                 child_append_relid_sets =
     187                 :          79 :                                         ((MergeAppend *) plan)->child_append_relid_sets;
     188                 :          79 :                                 break;
     189                 :             :                         default:
     190                 :       17820 :                                 strategy = PGPA_SCAN_ORDINARY;
     191                 :             : 
     192                 :             :                                 /*
     193                 :             :                                  * We can't handle NO_GATHER() for single non-relation RTEs
     194                 :             :                                  * because get_relation_info won't be called.
     195                 :             :                                  */
     196         [ +  + ]:       17820 :                                 if (bms_membership(relids) == BMS_SINGLETON)
     197                 :             :                                 {
     198                 :       17787 :                                         RangeTblEntry *rte;
     199                 :             : 
     200                 :       17787 :                                         rte = rt_fetch(bms_singleton_member(relids),
     201                 :             :                                                                    walker->pstmt->rtable);
     202         [ +  + ]:       17787 :                                         if (rte->rtekind != RTE_RELATION)
     203                 :       17667 :                                                 needs_no_gather = false;
     204                 :       17787 :                                 }
     205                 :             : 
     206                 :       17820 :                                 break;
     207                 :             :                 }
     208                 :             : 
     209                 :             : 
     210                 :             :                 /* Join RTIs can be present, but advice never refers to them. */
     211                 :       20333 :                 relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
     212                 :       20333 :         }
     213                 :             : 
     214                 :             :         /*
     215                 :             :          * If this is an Append or MergeAppend node into which subordinate Append
     216                 :             :          * or MergeAppend paths were merged, each of those merged paths is
     217                 :             :          * effectively another scan for which we need to account.
     218                 :             :          */
     219   [ +  +  +  +  :      224248 :         foreach_node(Bitmapset, child_relids, child_append_relid_sets)
             +  +  +  + ]
     220                 :             :         {
     221                 :         366 :                 Bitmapset  *child_nonjoin_relids;
     222                 :             : 
     223                 :         366 :                 child_nonjoin_relids =
     224                 :         732 :                         pgpa_filter_out_join_relids(child_relids,
     225                 :         366 :                                                                                 walker->pstmt->rtable);
     226                 :         732 :                 (void) pgpa_make_scan(walker, plan, strategy,
     227                 :         366 :                                                           child_nonjoin_relids, false);
     228                 :      112307 :         }
     229                 :             : 
     230                 :             :         /*
     231                 :             :          * If this plan node has no associated RTIs, it's not a scan. When the
     232                 :             :          * 'within_join_problem' flag is set, that's unexpected, so throw an
     233                 :             :          * error, else return quietly.
     234                 :             :          */
     235         [ +  + ]:      111941 :         if (relids == NULL)
     236                 :             :         {
     237         [ +  - ]:       39681 :                 if (within_join_problem)
     238   [ #  #  #  # ]:           0 :                         elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
     239                 :       39681 :                 return NULL;
     240                 :             :         }
     241                 :             : 
     242                 :       72260 :         return pgpa_make_scan(walker, plan, strategy, relids, needs_no_gather);
     243                 :      111941 : }
     244                 :             : 
     245                 :             : /*
     246                 :             :  * Create a single pgpa_scan object and update the pgpa_plan_walker_context.
     247                 :             :  */
     248                 :             : static pgpa_scan *
     249                 :       72626 : pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
     250                 :             :                            pgpa_scan_strategy strategy, Bitmapset *relids,
     251                 :             :                            bool needs_no_gather)
     252                 :             : {
     253                 :       72626 :         pgpa_scan  *scan;
     254                 :             : 
     255                 :             :         /* Create the scan object. */
     256                 :       72626 :         scan = palloc(sizeof(pgpa_scan));
     257                 :       72626 :         scan->plan = plan;
     258                 :       72626 :         scan->strategy = strategy;
     259                 :       72626 :         scan->relids = relids;
     260                 :             : 
     261                 :             :         /* Add it to the appropriate list. */
     262                 :      145252 :         walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
     263                 :       72626 :                                                                                         scan);
     264                 :             : 
     265                 :             :         /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
     266         [ +  + ]:       72626 :         if (needs_no_gather)
     267                 :       84478 :                 walker->no_gather_scans = bms_add_members(walker->no_gather_scans,
     268                 :       42239 :                                                                                                   scan->relids);
     269                 :             : 
     270                 :      145252 :         return scan;
     271                 :       72626 : }
     272                 :             : 
     273                 :             : /*
     274                 :             :  * Determine the unique rtekind of a set of relids.
     275                 :             :  */
     276                 :             : static RTEKind
     277                 :        3085 : unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
     278                 :             : {
     279                 :        3085 :         int                     rti = -1;
     280                 :        3085 :         bool            first = true;
     281                 :        3085 :         RTEKind         rtekind;
     282                 :             : 
     283         [ +  - ]:        3085 :         Assert(relids != NULL);
     284                 :             : 
     285         [ +  + ]:        7657 :         while ((rti = bms_next_member(relids, rti)) >= 0)
     286                 :             :         {
     287                 :        4572 :                 RangeTblEntry *rte = rt_fetch(rti, rtable);
     288                 :             : 
     289         [ +  + ]:        4572 :                 if (rte->rtekind == RTE_JOIN)
     290                 :          89 :                         continue;
     291                 :             : 
     292         [ +  + ]:        4483 :                 if (first)
     293                 :             :                 {
     294                 :        3085 :                         rtekind = rte->rtekind;
     295                 :        3085 :                         first = false;
     296                 :        3085 :                 }
     297         [ +  - ]:        1398 :                 else if (rtekind != rte->rtekind)
     298   [ #  #  #  # ]:           0 :                         elog(ERROR, "rtekind mismatch: %d vs. %d",
     299                 :             :                                  rtekind, rte->rtekind);
     300      [ -  +  + ]:        4572 :         }
     301                 :             : 
     302         [ +  - ]:        3085 :         if (first)
     303   [ #  #  #  # ]:           0 :                 elog(ERROR, "no non-RTE_JOIN RTEs found");
     304                 :             : 
     305                 :        6170 :         return rtekind;
     306                 :        3085 : }
        

Generated by: LCOV version 2.3.2-1