LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_scan.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 97.6 % 123 120
Test Date: 2026-11-06 16:15:55 Functions: 100.0 % 4 4
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 86.4 % 88 76

             Branch data     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 'needs_no_gather' flag if NO_GATHER advice should be generated for
      37                 :             :  * this scan.
      38                 :             :  *
      39                 :             :  * Set the 'within_join_problem' flag if we're inside of a join problem and
      40                 :             :  * not otherwise.
      41                 :             :  */
      42                 :             : pgpa_scan *
      43                 :      234466 : pgpa_build_scan(pgpa_plan_walker_context *walker, Plan *plan,
      44                 :             :                 ElidedNode *elided_node,
      45                 :             :                 bool needs_no_gather, bool within_join_problem)
      46                 :             : {
      47                 :      234466 :     pgpa_scan_strategy strategy = PGPA_SCAN_ORDINARY;
      48                 :      234466 :     Bitmapset  *relids = NULL;
      49                 :      234466 :     int         rti = -1;
      50                 :      234466 :     List       *child_append_relid_sets = NIL;
      51                 :      234466 :     NodeTag     nodetype = nodeTag(plan);
      52                 :             : 
      53         [ +  + ]:      234466 :     if (elided_node != NULL)
      54                 :             :     {
      55                 :        5609 :         nodetype = elided_node->elided_type;
      56                 :        5609 :         relids = elided_node->relids;
      57                 :             : 
      58                 :             :         /*
      59                 :             :          * If setrefs processing elided an Append or MergeAppend node that had
      60                 :             :          * only one surviving child, it could be either a partitionwise
      61                 :             :          * operation or a setop over subqueries, depending on the rtekind.
      62                 :             :          *
      63                 :             :          * A setop over subqueries, or a trivial SubqueryScan that was elided,
      64                 :             :          * is an "ordinary" scan i.e. one for which we do not need to generate
      65                 :             :          * advice because the planner has not made any meaningful choice.
      66                 :             :          *
      67                 :             :          * Note that the PGPA_SCAN_PARTITIONWISE case also includes
      68                 :             :          * partitionwise joins; this module considers those to be a form of
      69                 :             :          * scan, since they lack internal structure that we can decompose.
      70                 :             :          *
      71                 :             :          * Note also that it's possible for relids to be NULL here, if the
      72                 :             :          * elided Append node is part of a partitionwise aggregate. In that
      73                 :             :          * case, it doesn't matter what strategy we choose, but we do need to
      74                 :             :          * avoid calling unique_nonjoin_rtekind(), which would fail an
      75                 :             :          * assertion.
      76                 :             :          */
      77   [ +  +  -  +  :        5609 :         if ((nodetype == T_Append || nodetype == T_MergeAppend) &&
                   +  - ]
      78         [ +  + ]:        1166 :             relids != NULL &&
      79                 :        1166 :             unique_nonjoin_rtekind(relids,
      80                 :        1166 :                                    walker->pstmt->rtable) == RTE_RELATION)
      81                 :        1148 :             strategy = PGPA_SCAN_PARTITIONWISE;
      82                 :             :         else
      83                 :        4461 :             strategy = PGPA_SCAN_ORDINARY;
      84                 :             : 
      85                 :             :         /* Be sure to account for pulled-up scans, as for a live Append. */
      86                 :        5609 :         child_append_relid_sets = elided_node->child_append_relid_sets;
      87                 :             : 
      88                 :             :         /* Join RTIs can be present, but advice never refers to them. */
      89                 :        5609 :         relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
      90                 :             :     }
      91         [ +  + ]:      228857 :     else if ((rti = pgpa_scanrelid(plan)) != 0)
      92                 :             :     {
      93                 :      102149 :         relids = bms_make_singleton(rti);
      94                 :             : 
      95   [ +  +  +  +  :      102149 :         switch (nodeTag(plan))
                   +  + ]
      96                 :             :         {
      97                 :       52369 :             case T_SeqScan:
      98                 :       52369 :                 strategy = PGPA_SCAN_SEQ;
      99                 :       52369 :                 break;
     100                 :        4737 :             case T_BitmapHeapScan:
     101                 :        4737 :                 strategy = PGPA_SCAN_BITMAP_HEAP;
     102                 :        4737 :                 break;
     103                 :       26076 :             case T_IndexScan:
     104                 :       26076 :                 strategy = PGPA_SCAN_INDEX;
     105                 :       26076 :                 break;
     106                 :        3747 :             case T_IndexOnlyScan:
     107                 :        3747 :                 strategy = PGPA_SCAN_INDEX_ONLY;
     108                 :        3747 :                 break;
     109                 :         836 :             case T_TidScan:
     110                 :             :             case T_TidRangeScan:
     111                 :         836 :                 strategy = PGPA_SCAN_TID;
     112                 :         836 :                 break;
     113                 :       14384 :             default:
     114                 :             : 
     115                 :             :                 /*
     116                 :             :                  * This case includes a ForeignScan targeting a single
     117                 :             :                  * relation; no other strategy is possible in that case, but
     118                 :             :                  * see below, where things are different in multi-relation
     119                 :             :                  * cases.
     120                 :             :                  */
     121                 :       14384 :                 strategy = PGPA_SCAN_ORDINARY;
     122                 :       14384 :                 break;
     123                 :             :         }
     124                 :             :     }
     125         [ +  + ]:      126708 :     else if (pgpa_is_scan_level_materialize(plan))
     126                 :             :     {
     127                 :             :         /*
     128                 :             :          * Non-repeatable tablesample methods can be wrapped in a Materialize
     129                 :             :          * node that must be treated as part of the scan itself. See
     130                 :             :          * set_tablesample_rel_pathlist().
     131                 :             :          */
     132                 :           2 :         rti = pgpa_scanrelid(plan->lefttree);
     133                 :           2 :         relids = bms_make_singleton(rti);
     134                 :           2 :         strategy = PGPA_SCAN_ORDINARY;
     135                 :             :     }
     136         [ +  + ]:      126706 :     else if ((relids = pgpa_relids(plan)) != NULL)
     137                 :             :     {
     138   [ +  +  +  + ]:       42933 :         switch (nodeTag(plan))
     139                 :             :         {
     140                 :           2 :             case T_ForeignScan:
     141                 :             : 
     142                 :             :                 /*
     143                 :             :                  * If multiple relations are being targeted by a single
     144                 :             :                  * foreign scan, then the foreign join has been pushed to the
     145                 :             :                  * remote side, and we want that to be reflected in the
     146                 :             :                  * generated advice. We can't emit FOREIGN_JOIN() advice for a
     147                 :             :                  * single relation, so treat that case as an ordinary scan.
     148                 :             :                  */
     149         [ +  + ]:           2 :                 if (bms_membership(relids) == BMS_MULTIPLE)
     150                 :           1 :                     strategy = PGPA_SCAN_FOREIGN;
     151                 :             :                 else
     152                 :           1 :                     strategy = PGPA_SCAN_ORDINARY;
     153                 :           2 :                 break;
     154                 :        4989 :             case T_Append:
     155                 :             : 
     156                 :             :                 /*
     157                 :             :                  * Append nodes can represent partitionwise scans of a
     158                 :             :                  * relation, but when they implement a set operation, they are
     159                 :             :                  * just ordinary scans.
     160                 :             :                  */
     161         [ +  + ]:        4989 :                 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
     162                 :             :                     == RTE_RELATION)
     163                 :        2343 :                     strategy = PGPA_SCAN_PARTITIONWISE;
     164                 :             :                 else
     165                 :        2646 :                     strategy = PGPA_SCAN_ORDINARY;
     166                 :             : 
     167                 :             :                 /* Be sure to account for pulled-up scans. */
     168                 :        4989 :                 child_append_relid_sets =
     169                 :             :                     ((Append *) plan)->child_append_relid_sets;
     170                 :        4989 :                 break;
     171                 :         158 :             case T_MergeAppend:
     172                 :             :                 /* Same logic here as for Append, above. */
     173         [ +  + ]:         158 :                 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
     174                 :             :                     == RTE_RELATION)
     175                 :         100 :                     strategy = PGPA_SCAN_PARTITIONWISE;
     176                 :             :                 else
     177                 :          58 :                     strategy = PGPA_SCAN_ORDINARY;
     178                 :             : 
     179                 :             :                 /* Be sure to account for pulled-up scans. */
     180                 :         158 :                 child_append_relid_sets =
     181                 :             :                     ((MergeAppend *) plan)->child_append_relid_sets;
     182                 :         158 :                 break;
     183                 :       37784 :             default:
     184                 :       37784 :                 strategy = PGPA_SCAN_ORDINARY;
     185                 :       37784 :                 break;
     186                 :             :         }
     187                 :             : 
     188                 :             : 
     189                 :             :         /* Join RTIs can be present, but advice never refers to them. */
     190                 :       42933 :         relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
     191                 :             :     }
     192                 :             : 
     193                 :             :     /*
     194                 :             :      * If this is an Append or MergeAppend node into which subordinate Append
     195                 :             :      * or MergeAppend paths were merged, each of those merged paths is
     196                 :             :      * effectively another scan for which we need to account.
     197                 :             :      */
     198   [ +  +  +  +  :      469867 :     foreach_node(Bitmapset, child_relids, child_append_relid_sets)
                   +  + ]
     199                 :             :     {
     200                 :             :         Bitmapset  *child_nonjoin_relids;
     201                 :             : 
     202                 :             :         child_nonjoin_relids =
     203                 :         935 :             pgpa_filter_out_join_relids(child_relids,
     204                 :         935 :                                         walker->pstmt->rtable);
     205         [ +  + ]:         935 :         if (unique_nonjoin_rtekind(child_nonjoin_relids, walker->pstmt->rtable)
     206                 :             :             == RTE_RELATION)
     207                 :         913 :             (void) pgpa_make_scan(walker, plan, PGPA_SCAN_PARTITIONWISE,
     208                 :             :                                   child_nonjoin_relids);
     209                 :             :         else
     210                 :          22 :             (void) pgpa_make_scan(walker, plan, PGPA_SCAN_ORDINARY,
     211                 :             :                                   child_nonjoin_relids);
     212                 :             :     }
     213                 :             : 
     214                 :             :     /*
     215                 :             :      * If this plan node has no associated RTIs, it's not a scan. When the
     216                 :             :      * 'within_join_problem' flag is set, that's unexpected, so throw an
     217                 :             :      * error, else return quietly.
     218                 :             :      */
     219         [ +  + ]:      234466 :     if (relids == NULL)
     220                 :             :     {
     221         [ -  + ]:       83773 :         if (within_join_problem)
     222         [ #  # ]:           0 :             elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
     223                 :       83773 :         return NULL;
     224                 :             :     }
     225                 :             : 
     226                 :             :     /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
     227         [ +  + ]:      150693 :     if (needs_no_gather)
     228                 :      143736 :         walker->no_gather_scans =
     229                 :      143736 :             bms_add_members(walker->no_gather_scans, relids);
     230                 :             : 
     231                 :             :     /* Now create and return the final pgpa_scan object. */
     232                 :      150693 :     return pgpa_make_scan(walker, plan, strategy, relids);
     233                 :             : }
     234                 :             : 
     235                 :             : /*
     236                 :             :  * Create a single pgpa_scan object and update the pgpa_plan_walker_context.
     237                 :             :  */
     238                 :             : static pgpa_scan *
     239                 :      151628 : pgpa_make_scan(pgpa_plan_walker_context *walker, Plan *plan,
     240                 :             :                pgpa_scan_strategy strategy, Bitmapset *relids)
     241                 :             : {
     242                 :             :     pgpa_scan  *scan;
     243                 :             : 
     244                 :             :     /* Create the scan object. */
     245                 :      151628 :     scan = palloc_object(pgpa_scan);
     246                 :      151628 :     scan->plan = plan;
     247                 :      151628 :     scan->strategy = strategy;
     248                 :      151628 :     scan->relids = relids;
     249                 :             : 
     250                 :             :     /* Add it to the appropriate list. */
     251                 :      151628 :     walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
     252                 :             :                                             scan);
     253                 :             : 
     254                 :      151628 :     return scan;
     255                 :             : }
     256                 :             : 
     257                 :             : /*
     258                 :             :  * Does this Plan node or associated ElidedNode look like the topmost node
     259                 :             :  * generated while planning an unflattened set operation?
     260                 :             :  *
     261                 :             :  * The return value of this function is a little bit inexact, because for
     262                 :             :  * example if a set operation produces a HashAggregate on top of an Append,
     263                 :             :  * this function will only identify the latter. Since the HashAggregate
     264                 :             :  * node is not RTI-bearing, the difference doesn't currently matter.
     265                 :             :  *
     266                 :             :  * Pass the plan node as 'plan', and if we're considering an elided node
     267                 :             :  * associated with that plan node, also pass it as 'elided_node'.
     268                 :             :  */
     269                 :             : bool
     270                 :      252689 : pgpa_is_setop_parent(Plan *plan, ElidedNode *elided_node, List *rtable)
     271                 :             : {
     272                 :             :     Bitmapset  *relids;
     273                 :      252689 :     int         rti = -1;
     274                 :             : 
     275                 :             :     /*
     276                 :             :      * If we're considering a SetOp or RecursiveUnion, then this is definitely
     277                 :             :      * a setop parent. If we're considering an Append, a MergeAppend, or an
     278                 :             :      * upper Result node, this might be a setop parent. If it's anything else,
     279                 :             :      * this is not a setop parent.
     280                 :             :      */
     281         [ +  + ]:      252689 :     if (elided_node != NULL)
     282                 :             :     {
     283         [ +  + ]:        3297 :         if (elided_node->elided_type == T_Append ||
     284         [ -  + ]:        2131 :             elided_node->elided_type == T_MergeAppend)
     285                 :        1166 :             relids = elided_node->relids;
     286                 :             :         else
     287                 :        2131 :             return false;
     288                 :             :     }
     289                 :             :     else
     290                 :             :     {
     291   [ +  +  +  + ]:      249392 :         if (IsA(plan, SetOp) || IsA(plan, RecursiveUnion))
     292                 :         378 :             return true;
     293   [ +  +  +  + ]:      249014 :         else if (IsA(plan, Append) || IsA(plan, MergeAppend) ||
     294         [ +  + ]:      243707 :                  (IsA(plan, Result) &&
     295         [ +  + ]:       41553 :                   (((Result *) plan)->result_type == RESULT_TYPE_UPPER)))
     296                 :        5328 :             relids = pgpa_relids(plan);
     297                 :             :         else
     298                 :      243686 :             return false;
     299                 :             :     }
     300                 :             : 
     301                 :             :     /*
     302                 :             :      * As of this writing, we reach here with relids == NULL only for a
     303                 :             :      * partitionwise aggregate, which is not a setop parent.
     304                 :             :      */
     305         [ +  + ]:        6494 :     if (relids == NULL)
     306                 :         203 :         return false;
     307                 :             : 
     308                 :             :     /*
     309                 :             :      * Conceptually, this Append or MergeAppend, or the Result node that
     310                 :             :      * replaced it, could be a partitionwise scan, a partitionwise join, a
     311                 :             :      * flattened UNION ALL, or an unflattened set operation. We want to return
     312                 :             :      * true only in that last case; for our purposes, if a UNION ALL is
     313                 :             :      * flattened, it no longer counts as a set operation.
     314                 :             :      *
     315                 :             :      * An unflattened set operation has no RTI of its own, so a plan node
     316                 :             :      * implementing one carries the RTIs of its children, which must always be
     317                 :             :      * subqueries with rte->inh == false. A flattened set operation does have
     318                 :             :      * an RTI, and that one has rte->inh == true.
     319                 :             :      */
     320         [ +  + ]:        9972 :     while ((rti = bms_next_member(relids, rti)) >= 0)
     321                 :             :     {
     322                 :        8674 :         RangeTblEntry *rte = rt_fetch(rti, rtable);
     323                 :             : 
     324   [ +  +  +  + ]:        8674 :         if (rte->rtekind != RTE_SUBQUERY || rte->inh)
     325                 :        4993 :             return false;
     326                 :             :     }
     327                 :             : 
     328                 :        1298 :     return true;
     329                 :             : }
     330                 :             : 
     331                 :             : /*
     332                 :             :  * Determine the unique rtekind of a set of relids.
     333                 :             :  */
     334                 :             : static RTEKind
     335                 :        7248 : unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
     336                 :             : {
     337                 :        7248 :     int         rti = -1;
     338                 :        7248 :     bool        first = true;
     339                 :        7248 :     RTEKind     rtekind = RTE_RELATION; /* silence compiler warning */
     340                 :             : 
     341                 :             :     Assert(relids != NULL);
     342                 :             : 
     343         [ +  + ]:       17640 :     while ((rti = bms_next_member(relids, rti)) >= 0)
     344                 :             :     {
     345                 :       10392 :         RangeTblEntry *rte = rt_fetch(rti, rtable);
     346                 :             : 
     347         [ +  + ]:       10392 :         if (rte->rtekind == RTE_JOIN)
     348                 :         214 :             continue;
     349                 :             : 
     350         [ +  + ]:       10178 :         if (first)
     351                 :             :         {
     352                 :        7248 :             rtekind = rte->rtekind;
     353                 :        7248 :             first = false;
     354                 :             :         }
     355         [ -  + ]:        2930 :         else if (rtekind != rte->rtekind)
     356         [ #  # ]:           0 :             elog(ERROR, "rtekind mismatch: %d vs. %d",
     357                 :             :                  rtekind, rte->rtekind);
     358                 :             :     }
     359                 :             : 
     360         [ -  + ]:        7248 :     if (first)
     361         [ #  # ]:           0 :         elog(ERROR, "no non-RTE_JOIN RTEs found");
     362                 :             : 
     363                 :        7248 :     return rtekind;
     364                 :             : }
        

Generated by: LCOV version 2.0-1