LCOV - differential code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_scan.c (source / functions) Coverage Total Hit UBC GNC CBC DCB
Current: c38de3ae6098ccf1904109f2e550eb4777f66024 vs e352ac8c5c90ed6ce792f044c22145578542a330 Lines: 97.6 % 124 121 3 1 120 1
Current Date: 2026-11-05 14:14:01 +0100 Functions: 100.0 % 4 4 1 3
Baseline: lcov-20261105-baseline Branches: 85.6 % 90 77 13 77
Baseline Date: 2026-11-05 14:14:01 +0100 Line coverage date bins:
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
(1,7] days: 100.0 % 22 22 22
(7,30] days: 100.0 % 4 4 4
(30,360] days: 96.9 % 98 95 3 1 94
Function coverage date bins:
(1,7] days: 100.0 % 1 1 1
(30,360] days: 100.0 % 3 3 1 2
Branch coverage date bins:
(1,7] days: 96.4 % 28 27 1 27
(7,30] days: 100.0 % 2 2 2
(30,360] days: 80.0 % 60 48 12 48

 Age         Owner                    Branch data    TLA  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 *
  238 rhaas@postgresql.org       43                 :CBC      234046 : 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                 :         234046 :     pgpa_scan_strategy strategy = PGPA_SCAN_ORDINARY;
                                 48                 :         234046 :     Bitmapset  *relids = NULL;
                                 49                 :         234046 :     int         rti = -1;
                                 50                 :         234046 :     List       *child_append_relid_sets = NIL;
                                 51                 :         234046 :     NodeTag     nodetype = nodeTag(plan);
                                 52                 :                : 
                                 53         [ +  + ]:         234046 :     if (elided_node != NULL)
                                 54                 :                :     {
                                 55                 :           5603 :         nodetype = elided_node->elided_type;
                                 56                 :           5603 :         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   [ +  +  -  +  :           5603 :         if ((nodetype == T_Append || nodetype == T_MergeAppend) &&
                                              +  - ]
  220                            78         [ +  + ]:           1166 :             relids != NULL &&
  238                            79                 :           1166 :             unique_nonjoin_rtekind(relids,
                                 80                 :           1166 :                                    walker->pstmt->rtable) == RTE_RELATION)
                                 81                 :           1148 :             strategy = PGPA_SCAN_PARTITIONWISE;
                                 82                 :                :         else
                                 83                 :           4455 :             strategy = PGPA_SCAN_ORDINARY;
                                 84                 :                : 
                                 85                 :                :         /* Be sure to account for pulled-up scans, as for a live Append. */
   28                            86                 :           5603 :         child_append_relid_sets = elided_node->child_append_relid_sets;
                                 87                 :                : 
                                 88                 :                :         /* Join RTIs can be present, but advice never refers to them. */
  238                            89                 :           5603 :         relids = pgpa_filter_out_join_relids(relids, walker->pstmt->rtable);
                                 90                 :                :     }
                                 91         [ +  + ]:         228443 :     else if ((rti = pgpa_scanrelid(plan)) != 0)
                                 92                 :                :     {
                                 93                 :         102061 :         relids = bms_make_singleton(rti);
                                 94                 :                : 
                                 95   [ +  +  +  +  :         102061 :         switch (nodeTag(plan))
                                              +  + ]
                                 96                 :                :         {
                                 97                 :          52723 :             case T_SeqScan:
                                 98                 :          52723 :                 strategy = PGPA_SCAN_SEQ;
                                 99                 :          52723 :                 break;
                                100                 :           4563 :             case T_BitmapHeapScan:
                                101                 :           4563 :                 strategy = PGPA_SCAN_BITMAP_HEAP;
                                102                 :           4563 :                 break;
                                103                 :          26214 :             case T_IndexScan:
                                104                 :          26214 :                 strategy = PGPA_SCAN_INDEX;
                                105                 :          26214 :                 break;
                                106                 :           3379 :             case T_IndexOnlyScan:
                                107                 :           3379 :                 strategy = PGPA_SCAN_INDEX_ONLY;
                                108                 :           3379 :                 break;
                                109                 :            836 :             case T_TidScan:
                                110                 :                :             case T_TidRangeScan:
                                111                 :            836 :                 strategy = PGPA_SCAN_TID;
                                112                 :            836 :                 break;
                                113                 :          14346 :             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                 :          14346 :                 strategy = PGPA_SCAN_ORDINARY;
                                122                 :          14346 :                 break;
                                123                 :                :         }
                                124                 :                :     }
  206                           125         [ +  + ]:         126382 :     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                 :                :     }
  238                           136         [ +  + ]:         126380 :     else if ((relids = pgpa_relids(plan)) != NULL)
                                137                 :                :     {
                                138   [ +  +  +  + ]:          43057 :         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                 :                :                  */
  126                           149         [ +  + ]:              2 :                 if (bms_membership(relids) == BMS_MULTIPLE)
                                150                 :              1 :                     strategy = PGPA_SCAN_FOREIGN;
                                151                 :                :                 else
                                152                 :              1 :                     strategy = PGPA_SCAN_ORDINARY;
  238                           153                 :              2 :                 break;
                                154                 :           4977 :             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         [ +  + ]:           4977 :                 if (unique_nonjoin_rtekind(relids, walker->pstmt->rtable)
                                162                 :                :                     == RTE_RELATION)
                                163                 :           2343 :                     strategy = PGPA_SCAN_PARTITIONWISE;
                                164                 :                :                 else
                                165                 :           2634 :                     strategy = PGPA_SCAN_ORDINARY;
                                166                 :                : 
                                167                 :                :                 /* Be sure to account for pulled-up scans. */
                                168                 :           4977 :                 child_append_relid_sets =
                                169                 :                :                     ((Append *) plan)->child_append_relid_sets;
                                170                 :           4977 :                 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                 :          37920 :             default:
                                184                 :          37920 :                 strategy = PGPA_SCAN_ORDINARY;
                                185                 :          37920 :                 break;
                                186                 :                :         }
                                187                 :                : 
                                188                 :                : 
                                189                 :                :         /* Join RTIs can be present, but advice never refers to them. */
                                190                 :          43057 :         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   [ +  +  +  +  :         469027 :     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);
   28                           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                 :                :      */
  238                           219         [ +  + ]:         234046 :     if (relids == NULL)
                                220                 :                :     {
                                221         [ -  + ]:          83323 :         if (within_join_problem)
  238 rhaas@postgresql.org      222         [ #  # ]:UBC           0 :             elog(ERROR, "plan node has no RTIs: %d", (int) nodeTag(plan));
  238 rhaas@postgresql.org      223                 :CBC       83323 :         return NULL;
                                224                 :                :     }
                                225                 :                : 
                                226                 :                :     /* Caller tells us whether NO_GATHER() advice for this scan is needed. */
    7                           227         [ +  + ]:         150723 :     if (needs_no_gather)
  238                           228                 :         143766 :         walker->no_gather_scans =
                                229                 :         143766 :             bms_add_members(walker->no_gather_scans, relids);
                                230                 :                : 
                                231                 :                :     /* Now create and return the final pgpa_scan object. */
                                232                 :         150723 :     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                 :         151658 : 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. */
   80 michael@paquier.xyz       245                 :GNC      151658 :     scan = palloc_object(pgpa_scan);
  238 rhaas@postgresql.org      246                 :CBC      151658 :     scan->plan = plan;
                                247                 :         151658 :     scan->strategy = strategy;
                                248                 :         151658 :     scan->relids = relids;
                                249                 :                : 
                                250                 :                :     /* Add it to the appropriate list. */
                                251                 :         151658 :     walker->scans[scan->strategy] = lappend(walker->scans[scan->strategy],
                                252                 :                :                                             scan);
                                253                 :                : 
                                254                 :         151658 :     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
    7                           270                 :         251469 : pgpa_is_setop_parent(Plan *plan, ElidedNode *elided_node, List *rtable)
                                271                 :                : {
                                272                 :                :     Bitmapset  *relids;
                                273                 :         251469 :     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         [ +  + ]:         251469 :     if (elided_node != NULL)
                                282                 :                :     {
                                283         [ +  + ]:           3291 :         if (elided_node->elided_type == T_Append ||
                                284         [ -  + ]:           2125 :             elided_node->elided_type == T_MergeAppend)
                                285                 :           1166 :             relids = elided_node->relids;
                                286                 :                :         else
                                287                 :           2125 :             return false;
                                288                 :                :     }
                                289                 :                :     else
                                290                 :                :     {
                                291   [ +  +  +  + ]:         248178 :         if (IsA(plan, SetOp) || IsA(plan, RecursiveUnion))
                                292                 :            378 :             return true;
                                293   [ +  +  +  + ]:         247800 :         else if (IsA(plan, Append) || IsA(plan, MergeAppend) ||
                                294         [ +  + ]:         242505 :                  (IsA(plan, Result) &&
                                295         [ +  + ]:          41689 :                   (((Result *) plan)->result_type == RESULT_TYPE_UPPER)))
                                296                 :           5316 :             relids = pgpa_relids(plan);
                                297                 :                :         else
                                298                 :         242484 :             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         [ +  + ]:           6482 :     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         [ +  + ]:           9960 :     while ((rti = bms_next_member(relids, rti)) >= 0)
                                321                 :                :     {
                                322                 :           8662 :         RangeTblEntry *rte = rt_fetch(rti, rtable);
                                323                 :                : 
                                324   [ +  +  +  + ]:           8662 :         if (rte->rtekind != RTE_SUBQUERY || rte->inh)
                                325                 :           4981 :             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
  238                           335                 :           7236 : unique_nonjoin_rtekind(Bitmapset *relids, List *rtable)
                                336                 :                : {
                                337                 :           7236 :     int         rti = -1;
                                338                 :           7236 :     bool        first = true;
  237 nathan@postgresql.or      339                 :           7236 :     RTEKind     rtekind = RTE_RELATION; /* silence compiler warning */
                                340                 :                : 
  238 rhaas@postgresql.org      341         [ -  + ]:           7236 :     Assert(relids != NULL);
                                342                 :                : 
                                343         [ +  + ]:          17616 :     while ((rti = bms_next_member(relids, rti)) >= 0)
                                344                 :                :     {
                                345                 :          10380 :         RangeTblEntry *rte = rt_fetch(rti, rtable);
                                346                 :                : 
                                347         [ +  + ]:          10380 :         if (rte->rtekind == RTE_JOIN)
                                348                 :            214 :             continue;
                                349                 :                : 
                                350         [ +  + ]:          10166 :         if (first)
                                351                 :                :         {
                                352                 :           7236 :             rtekind = rte->rtekind;
                                353                 :           7236 :             first = false;
                                354                 :                :         }
                                355         [ -  + ]:           2930 :         else if (rtekind != rte->rtekind)
  238 rhaas@postgresql.org      356         [ #  # ]:UBC           0 :             elog(ERROR, "rtekind mismatch: %d vs. %d",
                                357                 :                :                  rtekind, rte->rtekind);
                                358                 :                :     }
                                359                 :                : 
  238 rhaas@postgresql.org      360         [ -  + ]:CBC        7236 :     if (first)
  238 rhaas@postgresql.org      361         [ #  # ]:UBC           0 :         elog(ERROR, "no non-RTE_JOIN RTEs found");
                                362                 :                : 
  238 rhaas@postgresql.org      363                 :CBC        7236 :     return rtekind;
                                364                 :                : }
        

Generated by: LCOV version 2.0-1