LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_scan.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 93.8 % 97 91
Test Date: 2026-04-28 07:16:28 Functions: 100.0 % 3 3
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * pgpa_scan.c
       4              :  *    analysis of scans in Plan trees
       5              :  *
       6              :  * Copyright (c) 2016-2026, 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              : 
      24              : 
      25              : static RTEKind unique_nonjoin_rtekind(Bitmapset *relids, List *rtable);
      26              : 
      27              : /*
      28              :  * Build a pgpa_scan object for a Plan node and update the plan walker
      29              :  * context as appropriate.  If this is an Append or MergeAppend scan, also
      30              :  * build pgpa_scan for any scans that were consolidated into this one by
      31              :  * Append/MergeAppend pull-up.
      32              :  *
      33              :  * If there is at least one ElidedNode for this plan node, pass the uppermost
      34              :  * one as elided_node, else pass NULL.
      35              :  *
      36              :  * Set the 'beneath_any_gather' node if we are underneath a Gather or
      37              :  * Gather Merge node (except for a single-copy Gather node, for which
      38              :  * GATHER or GATHER_MERGE advice should not be emitted).
      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       234816 : 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       234816 :     pgpa_scan_strategy strategy = PGPA_SCAN_ORDINARY;
      49       234816 :     Bitmapset  *relids = NULL;
      50       234816 :     int         rti = -1;
      51       234816 :     List       *child_append_relid_sets = NIL;
      52       234816 :     NodeTag     nodetype = nodeTag(plan);
      53              : 
      54       234816 :     if (elided_node != NULL)
      55              :     {
      56         5630 :         nodetype = elided_node->elided_type;
      57         5630 :         relids = elided_node->relids;
      58              : 
      59              :         /*
      60              :          * If setrefs processing elided an Append or MergeAppend node that had
      61              :          * only one surviving child, it could be either a partitionwise
      62              :          * operation or a setop over subqueries, depending on the rtekind.
      63              :          *
      64              :          * A setop over subqueries, or a trivial SubqueryScan that was elided,
      65              :          * is an "ordinary" scan i.e. one for which we do not need to generate
      66              :          * advice because the planner has not made any meaningful choice.
      67              :          *
      68              :          * Note that the PGPA_SCAN_PARTITIONWISE case also includes
      69              :          * partitionwise joins; this module considers those to be a form of
      70              :          * scan, since they lack internal structure that we can decompose.
      71              :          *
      72              :          * Note also that it's possible for relids to be NULL here, if the
      73              :          * elided Append node is part of a partitionwise aggregate. In that
      74              :          * case, it doesn't matter what strategy we choose, but we do need to
      75              :          * avoid calling unique_nonjoin_rtekind(), which would fail an
      76              :          * assertion.
      77              :          */
      78         5630 :         if ((nodetype == T_Append || nodetype == T_MergeAppend) &&
      79         1208 :             relids != NULL &&
      80         1208 :             unique_nonjoin_rtekind(relids,
      81         1208 :                                    walker->pstmt->rtable) == RTE_RELATION)
      82         1196 :             strategy = PGPA_SCAN_PARTITIONWISE;
      83              :         else
      84         4434 :             strategy = PGPA_SCAN_ORDINARY;
      85              : 
      86              :         /* Join RTIs can be present, but advice never refers to them. */
      87         5630 :         relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
      88              :     }
      89       229186 :     else if ((rti = pgpa_scanrelid(plan)) != 0)
      90              :     {
      91       101797 :         relids = bms_make_singleton(rti);
      92              : 
      93       101797 :         switch (nodeTag(plan))
      94              :         {
      95        53954 :             case T_SeqScan:
      96        53954 :                 strategy = PGPA_SCAN_SEQ;
      97        53954 :                 break;
      98         5325 :             case T_BitmapHeapScan:
      99         5325 :                 strategy = PGPA_SCAN_BITMAP_HEAP;
     100         5325 :                 break;
     101        23629 :             case T_IndexScan:
     102        23629 :                 strategy = PGPA_SCAN_INDEX;
     103        23629 :                 break;
     104         3843 :             case T_IndexOnlyScan:
     105         3843 :                 strategy = PGPA_SCAN_INDEX_ONLY;
     106         3843 :                 break;
     107          840 :             case T_TidScan:
     108              :             case T_TidRangeScan:
     109          840 :                 strategy = PGPA_SCAN_TID;
     110          840 :                 break;
     111        14206 :             default:
     112              : 
     113              :                 /*
     114              :                  * This case includes a ForeignScan targeting a single
     115              :                  * relation; no other strategy is possible in that case, but
     116              :                  * see below, where things are different in multi-relation
     117              :                  * cases.
     118              :                  */
     119        14206 :                 strategy = PGPA_SCAN_ORDINARY;
     120        14206 :                 break;
     121              :         }
     122              :     }
     123       127389 :     else if (pgpa_is_scan_level_materialize(plan))
     124              :     {
     125              :         /*
     126              :          * Non-repeatable tablesample methods can be wrapped in a Materialize
     127              :          * node that must be treated as part of the scan itself. See
     128              :          * set_tablesample_rel_pathlist().
     129              :          */
     130            2 :         rti = pgpa_scanrelid(plan->lefttree);
     131            2 :         relids = bms_make_singleton(rti);
     132            2 :         strategy = PGPA_SCAN_ORDINARY;
     133              :     }
     134       127387 :     else if ((relids = pgpa_relids(plan)) != NULL)
     135              :     {
     136        42842 :         switch (nodeTag(plan))
     137              :         {
     138            0 :             case T_ForeignScan:
     139              : 
     140              :                 /*
     141              :                  * If multiple relations are being targeted by a single
     142              :                  * foreign scan, then the foreign join has been pushed to the
     143              :                  * remote side, and we want that to be reflected in the
     144              :                  * generated advice.
     145              :                  */
     146            0 :                 strategy = PGPA_SCAN_FOREIGN;
     147            0 :                 break;
     148         5085 :             case T_Append:
     149              : 
     150              :                 /*
     151              :                  * Append nodes can represent partitionwise scans of a
     152              :                  * relation, but when they implement a set operation, they are
     153              :                  * just ordinary scans.
     154              :                  */
     155         5085 :                 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
     156              :                     == RTE_RELATION)
     157         2441 :                     strategy = PGPA_SCAN_PARTITIONWISE;
     158              :                 else
     159         2644 :                     strategy = PGPA_SCAN_ORDINARY;
     160              : 
     161              :                 /* Be sure to account for pulled-up scans. */
     162         5085 :                 child_append_relid_sets =
     163              :                     ((Append *) plan)->child_append_relid_sets;
     164         5085 :                 break;
     165          162 :             case T_MergeAppend:
     166              :                 /* Same logic here as for Append, above. */
     167          162 :                 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
     168              :                     == RTE_RELATION)
     169           96 :                     strategy = PGPA_SCAN_PARTITIONWISE;
     170              :                 else
     171           66 :                     strategy = PGPA_SCAN_ORDINARY;
     172              : 
     173              :                 /* Be sure to account for pulled-up scans. */
     174          162 :                 child_append_relid_sets =
     175              :                     ((MergeAppend *) plan)->child_append_relid_sets;
     176          162 :                 break;
     177        37595 :             default:
     178        37595 :                 strategy = PGPA_SCAN_ORDINARY;
     179        37595 :                 break;
     180              :         }
     181              : 
     182              : 
     183              :         /* Join RTIs can be present, but advice never refers to them. */
     184        42842 :         relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
     185              :     }
     186              : 
     187              :     /*
     188              :      * If this is an Append or MergeAppend node into which subordinate Append
     189              :      * or MergeAppend paths were merged, each of those merged paths is
     190              :      * effectively another scan for which we need to account.
     191              :      */
     192       470372 :     foreach_node(Bitmapset, child_relids, child_append_relid_sets)
     193              :     {
     194              :         Bitmapset  *child_nonjoin_relids;
     195              : 
     196              :         child_nonjoin_relids =
     197          740 :             pgpa_filter_out_join_relids(child_relids,
     198          740 :                                         walker->pstmt->rtable);
     199          740 :         (void) pgpa_make_scan(walker, plan, strategy,
     200              :                               child_nonjoin_relids);
     201              :     }
     202              : 
     203              :     /*
     204              :      * If this plan node has no associated RTIs, it's not a scan. When the
     205              :      * 'within_join_problem' flag is set, that's unexpected, so throw an
     206              :      * error, else return quietly.
     207              :      */
     208       234816 :     if (relids == NULL)
     209              :     {
     210        84545 :         if (within_join_problem)
     211            0 :             elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
     212        84545 :         return NULL;
     213              :     }
     214              : 
     215              :     /*
     216              :      * Add the appropriate set of RTIs to walker->no_gather_scans.
     217              :      *
     218              :      * Add nothing if we're beneath a Gather or Gather Merge node, since
     219              :      * NO_GATHER advice is clearly inappropriate in that situation.
     220              :      *
     221              :      * Add nothing if this is an Append or MergeAppend node, whether or not
     222              :      * elided. We'll emit NO_GATHER() for the underlying scan, which is good
     223              :      * enough.
     224              :      */
     225       150271 :     if (!beneath_any_gather && nodetype != T_Append &&
     226              :         nodetype != T_MergeAppend)
     227       142397 :         walker->no_gather_scans =
     228       142397 :             bms_add_members(walker->no_gather_scans, relids);
     229              : 
     230              :     /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
     231       150271 :     return pgpa_make_scan(walker, plan, strategy, relids);
     232              : }
     233              : 
     234              : /*
     235              :  * Create a single pgpa_scan object and update the pgpa_plan_walker_context.
     236              :  */
     237              : static pgpa_scan *
     238       151011 : pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
     239              :                pgpa_scan_strategy strategy, Bitmapset *relids)
     240              : {
     241              :     pgpa_scan  *scan;
     242              : 
     243              :     /* Create the scan object. */
     244       151011 :     scan = palloc(sizeof(pgpa_scan));
     245       151011 :     scan->plan = plan;
     246       151011 :     scan->strategy = strategy;
     247       151011 :     scan->relids = relids;
     248              : 
     249              :     /* Add it to the appropriate list. */
     250       151011 :     walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
     251              :                                             scan);
     252              : 
     253       151011 :     return scan;
     254              : }
     255              : 
     256              : /*
     257              :  * Determine the unique rtekind of a set of relids.
     258              :  */
     259              : static RTEKind
     260         6455 : unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
     261              : {
     262         6455 :     int         rti = -1;
     263         6455 :     bool        first = true;
     264         6455 :     RTEKind     rtekind = RTE_RELATION; /* silence compiler warning */
     265              : 
     266              :     Assert(relids != NULL);
     267              : 
     268        15919 :     while ((rti = bms_next_member(relids, rti)) >= 0)
     269              :     {
     270         9464 :         RangeTblEntry *rte = rt_fetch(rti, rtable);
     271              : 
     272         9464 :         if (rte->rtekind == RTE_JOIN)
     273          182 :             continue;
     274              : 
     275         9282 :         if (first)
     276              :         {
     277         6455 :             rtekind = rte->rtekind;
     278         6455 :             first = false;
     279              :         }
     280         2827 :         else if (rtekind != rte->rtekind)
     281            0 :             elog(ERROR, "rtekind mismatch: %d vs. %d",
     282              :                  rtekind, rte->rtekind);
     283              :     }
     284              : 
     285         6455 :     if (first)
     286            0 :         elog(ERROR, "no non-RTE_JOIN RTEs found");
     287              : 
     288         6455 :     return rtekind;
     289              : }
        

Generated by: LCOV version 2.0-1