LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_scan.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 78.3 % 92 72
Test Date: 2026-03-16 00:15:06 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          384 : 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          384 :     pgpa_scan_strategy strategy = PGPA_SCAN_ORDINARY;
      49          384 :     Bitmapset  *relids = NULL;
      50          384 :     int         rti = -1;
      51          384 :     List       *child_append_relid_sets = NIL;
      52          384 :     NodeTag     nodetype = nodeTag(plan);
      53              : 
      54          384 :     if (elided_node != NULL)
      55              :     {
      56            8 :         nodetype = elided_node->elided_type;
      57            8 :         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            8 :         if ((nodetype == T_Append || nodetype == T_MergeAppend) &&
      73            0 :             unique_nonjoin_rtekind(relids,
      74            0 :                                    walker->pstmt->rtable) == RTE_RELATION)
      75            0 :             strategy = PGPA_SCAN_PARTITIONWISE;
      76              :         else
      77            8 :             strategy = PGPA_SCAN_ORDINARY;
      78              : 
      79              :         /* Join RTIs can be present, but advice never refers to them. */
      80            8 :         relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
      81              :     }
      82          376 :     else if ((rti = pgpa_scanrelid(plan)) != 0)
      83              :     {
      84          258 :         relids = bms_make_singleton(rti);
      85              : 
      86          258 :         switch (nodeTag(plan))
      87              :         {
      88          160 :             case T_SeqScan:
      89          160 :                 strategy = PGPA_SCAN_SEQ;
      90          160 :                 break;
      91            3 :             case T_BitmapHeapScan:
      92            3 :                 strategy = PGPA_SCAN_BITMAP_HEAP;
      93            3 :                 break;
      94           65 :             case T_IndexScan:
      95           65 :                 strategy = PGPA_SCAN_INDEX;
      96           65 :                 break;
      97            7 :             case T_IndexOnlyScan:
      98            7 :                 strategy = PGPA_SCAN_INDEX_ONLY;
      99            7 :                 break;
     100            4 :             case T_TidScan:
     101              :             case T_TidRangeScan:
     102            4 :                 strategy = PGPA_SCAN_TID;
     103            4 :                 break;
     104           19 :             default:
     105              : 
     106              :                 /*
     107              :                  * This case includes a ForeignScan targeting a single
     108              :                  * relation; no other strategy is possible in that case, but
     109              :                  * see below, where things are different in multi-relation
     110              :                  * cases.
     111              :                  */
     112           19 :                 strategy = PGPA_SCAN_ORDINARY;
     113           19 :                 break;
     114              :         }
     115              :     }
     116          118 :     else if ((relids = pgpa_relids(plan)) != NULL)
     117              :     {
     118           22 :         switch (nodeTag(plan))
     119              :         {
     120            0 :             case T_ForeignScan:
     121              : 
     122              :                 /*
     123              :                  * If multiple relations are being targeted by a single
     124              :                  * foreign scan, then the foreign join has been pushed to the
     125              :                  * remote side, and we want that to be reflected in the
     126              :                  * generated advice.
     127              :                  */
     128            0 :                 strategy = PGPA_SCAN_FOREIGN;
     129            0 :                 break;
     130           11 :             case T_Append:
     131              : 
     132              :                 /*
     133              :                  * Append nodes can represent partitionwise scans of a
     134              :                  * relation, but when they implement a set operation, they are
     135              :                  * just ordinary scans.
     136              :                  */
     137           11 :                 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
     138              :                     == RTE_RELATION)
     139           11 :                     strategy = PGPA_SCAN_PARTITIONWISE;
     140              :                 else
     141            0 :                     strategy = PGPA_SCAN_ORDINARY;
     142              : 
     143              :                 /* Be sure to account for pulled-up scans. */
     144           11 :                 child_append_relid_sets =
     145              :                     ((Append *) plan)->child_append_relid_sets;
     146           11 :                 break;
     147            0 :             case T_MergeAppend:
     148              :                 /* Same logic here as for Append, above. */
     149            0 :                 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
     150              :                     == RTE_RELATION)
     151            0 :                     strategy = PGPA_SCAN_PARTITIONWISE;
     152              :                 else
     153            0 :                     strategy = PGPA_SCAN_ORDINARY;
     154              : 
     155              :                 /* Be sure to account for pulled-up scans. */
     156            0 :                 child_append_relid_sets =
     157              :                     ((MergeAppend *) plan)->child_append_relid_sets;
     158            0 :                 break;
     159           11 :             default:
     160           11 :                 strategy = PGPA_SCAN_ORDINARY;
     161           11 :                 break;
     162              :         }
     163              : 
     164              : 
     165              :         /* Join RTIs can be present, but advice never refers to them. */
     166           22 :         relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
     167              :     }
     168              : 
     169              :     /*
     170              :      * If this is an Append or MergeAppend node into which subordinate Append
     171              :      * or MergeAppend paths were merged, each of those merged paths is
     172              :      * effectively another scan for which we need to account.
     173              :      */
     174          768 :     foreach_node(Bitmapset, child_relids, child_append_relid_sets)
     175              :     {
     176              :         Bitmapset  *child_nonjoin_relids;
     177              : 
     178              :         child_nonjoin_relids =
     179            0 :             pgpa_filter_out_join_relids(child_relids,
     180            0 :                                         walker->pstmt->rtable);
     181            0 :         (void) pgpa_make_scan(walker, plan, strategy,
     182              :                               child_nonjoin_relids);
     183              :     }
     184              : 
     185              :     /*
     186              :      * If this plan node has no associated RTIs, it's not a scan. When the
     187              :      * 'within_join_problem' flag is set, that's unexpected, so throw an
     188              :      * error, else return quietly.
     189              :      */
     190          384 :     if (relids == NULL)
     191              :     {
     192           96 :         if (within_join_problem)
     193            0 :             elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
     194           96 :         return NULL;
     195              :     }
     196              : 
     197              :     /*
     198              :      * Add the appropriate set of RTIs to walker->no_gather_scans.
     199              :      *
     200              :      * Add nothing if we're beneath a Gather or Gather Merge node, since
     201              :      * NO_GATHER advice is clearly inappropriate in that situation.
     202              :      *
     203              :      * Add nothing if this is an Append or MergeAppend node, whether or not
     204              :      * elided. We'll emit NO_GATHER() for the underlying scan, which is good
     205              :      * enough.
     206              :      */
     207          288 :     if (!beneath_any_gather && nodetype != T_Append &&
     208              :         nodetype != T_MergeAppend)
     209          256 :         walker->no_gather_scans =
     210          256 :             bms_add_members(walker->no_gather_scans, relids);
     211              : 
     212              :     /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
     213          288 :     return pgpa_make_scan(walker, plan, strategy, relids);
     214              : }
     215              : 
     216              : /*
     217              :  * Create a single pgpa_scan object and update the pgpa_plan_walker_context.
     218              :  */
     219              : static pgpa_scan *
     220          288 : pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
     221              :                pgpa_scan_strategy strategy, Bitmapset *relids)
     222              : {
     223              :     pgpa_scan  *scan;
     224              : 
     225              :     /* Create the scan object. */
     226          288 :     scan = palloc(sizeof(pgpa_scan));
     227          288 :     scan->plan = plan;
     228          288 :     scan->strategy = strategy;
     229          288 :     scan->relids = relids;
     230              : 
     231              :     /* Add it to the appropriate list. */
     232          288 :     walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
     233              :                                             scan);
     234              : 
     235          288 :     return scan;
     236              : }
     237              : 
     238              : /*
     239              :  * Determine the unique rtekind of a set of relids.
     240              :  */
     241              : static RTEKind
     242           11 : unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
     243              : {
     244           11 :     int         rti = -1;
     245           11 :     bool        first = true;
     246           11 :     RTEKind     rtekind = RTE_RELATION; /* silence compiler warning */
     247              : 
     248              :     Assert(relids != NULL);
     249              : 
     250           31 :     while ((rti = bms_next_member(relids, rti)) >= 0)
     251              :     {
     252           20 :         RangeTblEntry *rte = rt_fetch(rti, rtable);
     253              : 
     254           20 :         if (rte->rtekind == RTE_JOIN)
     255            0 :             continue;
     256              : 
     257           20 :         if (first)
     258              :         {
     259           11 :             rtekind = rte->rtekind;
     260           11 :             first = false;
     261              :         }
     262            9 :         else if (rtekind != rte->rtekind)
     263            0 :             elog(ERROR, "rtekind mismatch: %d vs. %d",
     264              :                  rtekind, rte->rtekind);
     265              :     }
     266              : 
     267           11 :     if (first)
     268            0 :         elog(ERROR, "no non-RTE_JOIN RTEs found");
     269              : 
     270           11 :     return rtekind;
     271              : }
        

Generated by: LCOV version 2.0-1