LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_walker.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 95.2 % 375 357
Test Date: 2026-04-06 21:16:29 Functions: 100.0 % 21 21
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * pgpa_walker.c
       4              :  *    Main entrypoints for analyzing a plan to generate an advice string
       5              :  *
       6              :  * Copyright (c) 2016-2026, PostgreSQL Global Development Group
       7              :  *
       8              :  *    contrib/pg_plan_advice/pgpa_walker.c
       9              :  *
      10              :  *-------------------------------------------------------------------------
      11              :  */
      12              : #include "postgres.h"
      13              : 
      14              : #include "pgpa_join.h"
      15              : #include "pgpa_planner.h"
      16              : #include "pgpa_scan.h"
      17              : #include "pgpa_walker.h"
      18              : 
      19              : #include "nodes/plannodes.h"
      20              : #include "parser/parsetree.h"
      21              : #include "utils/lsyscache.h"
      22              : 
      23              : static void pgpa_walk_recursively(pgpa_plan_walker_context *walker, Plan *plan,
      24              :                                   bool within_join_problem,
      25              :                                   pgpa_join_unroller *join_unroller,
      26              :                                   List *active_query_features,
      27              :                                   bool beneath_any_gather);
      28              : static Bitmapset *pgpa_process_unrolled_join(pgpa_plan_walker_context *walker,
      29              :                                              pgpa_unrolled_join *ujoin);
      30              : 
      31              : static pgpa_query_feature *pgpa_add_feature(pgpa_plan_walker_context *walker,
      32              :                                             pgpa_qf_type type,
      33              :                                             Plan *plan);
      34              : 
      35              : static void pgpa_qf_add_rti(List *active_query_features, Index rti);
      36              : static void pgpa_qf_add_rtis(List *active_query_features, Bitmapset *relids);
      37              : static void pgpa_qf_add_plan_rtis(List *active_query_features, Plan *plan,
      38              :                                   List *rtable);
      39              : 
      40              : static bool pgpa_walker_join_order_matches(pgpa_unrolled_join *ujoin,
      41              :                                            Index rtable_length,
      42              :                                            pgpa_identifier *rt_identifiers,
      43              :                                            pgpa_advice_target *target,
      44              :                                            bool toplevel);
      45              : static bool pgpa_walker_join_order_matches_member(pgpa_join_member *member,
      46              :                                                   Index rtable_length,
      47              :                                                   pgpa_identifier *rt_identifiers,
      48              :                                                   pgpa_advice_target *target);
      49              : static pgpa_scan *pgpa_walker_find_scan(pgpa_plan_walker_context *walker,
      50              :                                         pgpa_scan_strategy strategy,
      51              :                                         Bitmapset *relids);
      52              : static bool pgpa_walker_index_target_matches_plan(pgpa_index_target *itarget,
      53              :                                                   Plan *plan);
      54              : static bool pgpa_walker_contains_feature(pgpa_plan_walker_context *walker,
      55              :                                          pgpa_qf_type type,
      56              :                                          Bitmapset *relids);
      57              : static bool pgpa_walker_contains_join(pgpa_plan_walker_context *walker,
      58              :                                       pgpa_join_strategy strategy,
      59              :                                       Bitmapset *relids);
      60              : static bool pgpa_walker_contains_no_gather(pgpa_plan_walker_context *walker,
      61              :                                            Bitmapset *relids);
      62              : static void pgpa_classify_alternative_subplans(pgpa_plan_walker_context *walker,
      63              :                                                List *proots,
      64              :                                                List **chosen_proots,
      65              :                                                List **discarded_proots);
      66              : 
      67              : /*
      68              :  * Top-level entrypoint for the plan tree walk.
      69              :  *
      70              :  * Populates walker based on a traversal of the Plan trees in pstmt.
      71              :  *
      72              :  * proots is the list of pgpa_planner_info objects that were generated
      73              :  * during planning.
      74              :  */
      75              : void
      76        86992 : pgpa_plan_walker(pgpa_plan_walker_context *walker, PlannedStmt *pstmt,
      77              :                  List *proots)
      78              : {
      79              :     ListCell   *lc;
      80        86992 :     List       *sj_unique_rtis = NULL;
      81        86992 :     List       *sj_nonunique_qfs = NULL;
      82              :     List       *chosen_proots;
      83              :     List       *discarded_proots;
      84              : 
      85              :     /* Initialization. */
      86        86992 :     memset(walker, 0, sizeof(pgpa_plan_walker_context));
      87        86992 :     walker->pstmt = pstmt;
      88              : 
      89              :     /* Walk the main plan tree. */
      90        86992 :     pgpa_walk_recursively(walker, pstmt->planTree, false, NULL, NIL, false);
      91              : 
      92              :     /* Main plan tree walk won't reach subplans, so walk those. */
      93        96092 :     foreach(lc, pstmt->subplans)
      94              :     {
      95         9100 :         Plan       *plan = lfirst(lc);
      96              : 
      97         9100 :         if (plan != NULL)
      98         8714 :             pgpa_walk_recursively(walker, plan, false, NULL, NIL, false);
      99              :     }
     100              : 
     101              :     /* Adjust RTIs from sj_unique_rels for the flattened range table. */
     102       278040 :     foreach_ptr(pgpa_planner_info, proot, proots)
     103              :     {
     104              :         /* If there are no sj_unique_rels for this proot, we can skip it. */
     105       104056 :         if (proot->sj_unique_rels == NIL)
     106       102774 :             continue;
     107              : 
     108              :         /* If this is a subplan, find the range table offset. */
     109         1282 :         if (!proot->has_rtoffset)
     110            0 :             elog(ERROR, "no rtoffset for plan %s", proot->plan_name);
     111              : 
     112              :         /* Offset each relid set by the proot's rtoffset. */
     113         4006 :         foreach_node(Bitmapset, relids, proot->sj_unique_rels)
     114              :         {
     115         1442 :             int         rtindex = -1;
     116         1442 :             Bitmapset  *flat_relids = NULL;
     117              : 
     118         2968 :             while ((rtindex = bms_next_member(relids, rtindex)) >= 0)
     119         1526 :                 flat_relids = bms_add_member(flat_relids,
     120         1526 :                                              rtindex + proot->rtoffset);
     121              : 
     122         1442 :             sj_unique_rtis = lappend(sj_unique_rtis, flat_relids);
     123              :         }
     124              :     }
     125              : 
     126              :     /*
     127              :      * Remove any non-unique semijoin query features for which making the rel
     128              :      * unique wasn't considered.
     129              :      */
     130       175279 :     foreach_ptr(pgpa_query_feature, qf,
     131              :                 walker->query_features[PGPAQF_SEMIJOIN_NON_UNIQUE])
     132              :     {
     133         1295 :         if (list_member(sj_unique_rtis, qf->relids))
     134         1241 :             sj_nonunique_qfs = lappend(sj_nonunique_qfs, qf);
     135              :     }
     136        86992 :     walker->query_features[PGPAQF_SEMIJOIN_NON_UNIQUE] = sj_nonunique_qfs;
     137              : 
     138              :     /*
     139              :      * If we find any cases where analysis of the Plan tree shows that the
     140              :      * semijoin was made unique but this possibility was never observed to be
     141              :      * considered during planning, then we have a bug somewhere.
     142              :      */
     143       174137 :     foreach_ptr(pgpa_query_feature, qf,
     144              :                 walker->query_features[PGPAQF_SEMIJOIN_UNIQUE])
     145              :     {
     146          153 :         if (!list_member(sj_unique_rtis, qf->relids))
     147              :         {
     148              :             StringInfoData buf;
     149              : 
     150            0 :             initStringInfo(&buf);
     151            0 :             outBitmapset(&buf, qf->relids);
     152            0 :             elog(ERROR,
     153              :                  "unique semijoin found for relids %s but not observed during planning",
     154              :                  buf.data);
     155              :         }
     156              :     }
     157              : 
     158              :     /*
     159              :      * It's possible for a Gather or Gather Merge query feature to find no
     160              :      * RTIs when partitionwise aggregation is in use. We shouldn't emit
     161              :      * something like GATHER_MERGE(()), so instead emit nothing. This means
     162              :      * that we won't advise either GATHER or GATHER_MERGE or NO_GATHER in such
     163              :      * cases, which might be something we want to improve in the future.
     164              :      *
     165              :      * (Should the Partial Aggregates in such a case be created in an
     166              :      * UPPERREL_GROUP_AGG with a non-empty relid set? Right now that doesn't
     167              :      * happen, but it seems like it would make life easier for us if it did.)
     168              :      */
     169       434960 :     for (int t = 0; t < NUM_PGPA_QF_TYPES; ++t)
     170              :     {
     171       347968 :         List       *query_features = NIL;
     172              : 
     173       697807 :         foreach_ptr(pgpa_query_feature, qf, walker->query_features[t])
     174              :         {
     175         1871 :             if (qf->relids != NULL)
     176         1849 :                 query_features = lappend(query_features, qf);
     177              :             else
     178              :                 Assert(t == PGPAQF_GATHER || t == PGPAQF_GATHER_MERGE);
     179              :         }
     180              : 
     181       347968 :         walker->query_features[t] = query_features;
     182              :     }
     183              : 
     184              :     /* Classify alternative subplans. */
     185        86992 :     pgpa_classify_alternative_subplans(walker, proots,
     186              :                                        &chosen_proots, &discarded_proots);
     187              : 
     188              :     /*
     189              :      * Figure out which of the discarded alternatives have a non-discarded
     190              :      * alternative. Those are the ones for which we want to emit DO_NOT_SCAN
     191              :      * advice. (If every alternative was discarded, then there's no point.)
     192              :      */
     193       174684 :     foreach_ptr(pgpa_planner_info, discarded_proot, discarded_proots)
     194              :     {
     195          700 :         bool        some_alternative_chosen = false;
     196              : 
     197         1596 :         foreach_ptr(pgpa_planner_info, chosen_proot, chosen_proots)
     198              :         {
     199          896 :             if (strings_equal_or_both_null(discarded_proot->alternative_plan_name,
     200          896 :                                            chosen_proot->alternative_plan_name))
     201              :             {
     202          700 :                 some_alternative_chosen = true;
     203          700 :                 break;
     204              :             }
     205              :         }
     206              : 
     207          700 :         if (some_alternative_chosen)
     208              :         {
     209         6300 :             for (int rti = 1; rti <= discarded_proot->rid_array_size; rti++)
     210              :             {
     211         5600 :                 pgpa_identifier *rid = &discarded_proot->rid_array[rti - 1];
     212              : 
     213         5600 :                 if (rid->alias_name != NULL)
     214          830 :                     walker->do_not_scan_identifiers =
     215          830 :                         lappend(walker->do_not_scan_identifiers, rid);
     216              :             }
     217              :         }
     218              :     }
     219        86992 : }
     220              : 
     221              : /*
     222              :  * Main workhorse for the plan tree walk.
     223              :  *
     224              :  * If within_join_problem is true, we encountered a join at some higher level
     225              :  * of the tree walk and haven't yet descended out of the portion of the plan
     226              :  * tree that is part of that same join problem. We're no longer in the same
     227              :  * join problem if (1) we cross into a different subquery or (2) we descend
     228              :  * through an Append or MergeAppend node, below which any further joins would
     229              :  * be partitionwise joins planned separately from the outer join problem.
     230              :  *
     231              :  * If join_unroller != NULL, the join unroller code expects us to find a join
     232              :  * that should be unrolled into that object. This implies that we're within a
     233              :  * join problem, but the reverse is not true: when we've traversed all the
     234              :  * joins but are still looking for the scan that is the leaf of the join tree,
     235              :  * join_unroller will be NULL but within_join_problem will be true.
     236              :  *
     237              :  * Each element of active_query_features corresponds to some item of advice
     238              :  * that needs to enumerate all the relations it affects. We add RTIs we find
     239              :  * during tree traversal to each of these query features.
     240              :  *
     241              :  * If beneath_any_gather == true, some higher level of the tree traversal found
     242              :  * a Gather or Gather Merge node.
     243              :  */
     244              : static void
     245       249706 : pgpa_walk_recursively(pgpa_plan_walker_context *walker, Plan *plan,
     246              :                       bool within_join_problem,
     247              :                       pgpa_join_unroller *join_unroller,
     248              :                       List *active_query_features,
     249              :                       bool beneath_any_gather)
     250              : {
     251       249706 :     pgpa_join_unroller *outer_join_unroller = NULL;
     252       249706 :     pgpa_join_unroller *inner_join_unroller = NULL;
     253       249706 :     bool        join_unroller_toplevel = false;
     254              :     ListCell   *lc;
     255       249706 :     List       *extraplans = NIL;
     256       249706 :     List       *elided_nodes = NIL;
     257              : 
     258              :     Assert(within_join_problem || join_unroller == NULL);
     259              : 
     260              :     /*
     261              :      * Check the future_query_features list to see whether this was previously
     262              :      * identified as a plan node that needs to be treated as a query feature.
     263              :      * We must do this before handling elided nodes, because if there's an
     264              :      * elided node associated with a future query feature, the RTIs associated
     265              :      * with the elided node should be the only ones attributed to the query
     266              :      * feature.
     267              :      */
     268       501295 :     foreach_ptr(pgpa_query_feature, qf, walker->future_query_features)
     269              :     {
     270         3331 :         if (qf->plan == plan)
     271              :         {
     272         1448 :             active_query_features = list_copy(active_query_features);
     273         1448 :             active_query_features = lappend(active_query_features, qf);
     274         1448 :             walker->future_query_features =
     275         1448 :                 list_delete_ptr(walker->future_query_features, qf);
     276         1448 :             break;
     277              :         }
     278              :     }
     279              : 
     280              :     /*
     281              :      * Find all elided nodes for this Plan node.
     282              :      */
     283       564350 :     foreach_node(ElidedNode, n, walker->pstmt->elidedNodes)
     284              :     {
     285        64938 :         if (n->plan_node_id == plan->plan_node_id)
     286         5598 :             elided_nodes = lappend(elided_nodes, n);
     287              :     }
     288              : 
     289              :     /* If we found any elided_nodes, handle them. */
     290       249706 :     if (elided_nodes != NIL)
     291              :     {
     292         5564 :         int         num_elided_nodes = list_length(elided_nodes);
     293              :         ElidedNode *last_elided_node;
     294              : 
     295              :         /*
     296              :          * RTIs for the final -- and thus logically uppermost -- elided node
     297              :          * should be collected for query features passed down by the caller.
     298              :          * However, elided nodes act as barriers to query features, which
     299              :          * means that (1) the remaining elided nodes, if any, should be
     300              :          * ignored for purposes of query features and (2) the list of active
     301              :          * query features should be reset to empty so that we do not add RTIs
     302              :          * from the plan node that is logically beneath the elided node to the
     303              :          * query features passed down from the caller.
     304              :          */
     305         5564 :         last_elided_node = list_nth(elided_nodes, num_elided_nodes - 1);
     306         5564 :         pgpa_qf_add_rtis(active_query_features,
     307              :                          pgpa_filter_out_join_relids(last_elided_node->relids,
     308         5564 :                                                      walker->pstmt->rtable));
     309         5564 :         active_query_features = NIL;
     310              : 
     311              :         /*
     312              :          * If we're within a join problem, the join_unroller is responsible
     313              :          * for building the scan for the final elided node, so throw it out.
     314              :          */
     315         5564 :         if (within_join_problem)
     316          510 :             elided_nodes = list_truncate(elided_nodes, num_elided_nodes - 1);
     317              : 
     318              :         /* Build scans for all (or the remaining) elided nodes. */
     319        16216 :         foreach_node(ElidedNode, elided_node, elided_nodes)
     320              :         {
     321         5088 :             (void) pgpa_build_scan(walker, plan, elided_node,
     322              :                                    beneath_any_gather, within_join_problem);
     323              :         }
     324              : 
     325              :         /*
     326              :          * If there were any elided nodes, then everything beneath those nodes
     327              :          * is not part of the same join problem.
     328              :          *
     329              :          * In more detail, if an Append or MergeAppend was elided, then a
     330              :          * partitionwise join was chosen and only a single child survived; if
     331              :          * a SubqueryScan was elided, the subquery was planned without
     332              :          * flattening it into the parent.
     333              :          */
     334         5564 :         within_join_problem = false;
     335         5564 :         join_unroller = NULL;
     336              :     }
     337              : 
     338              :     /*
     339              :      * If this is a Gather or Gather Merge node, directly add it to the list
     340              :      * of currently-active query features. We must do this after handling
     341              :      * elided nodes, since the Gather or Gather Merge node occurs logically
     342              :      * beneath any associated elided nodes.
     343              :      *
     344              :      * Exception: We disregard any single_copy Gather nodes. These are created
     345              :      * by debug_parallel_query, and having them affect the plan advice is
     346              :      * counterproductive, as the result will be to advise the use of a real
     347              :      * Gather node, rather than a single copy one.
     348              :      */
     349       249706 :     if (IsA(plan, Gather) && !((Gather *) plan)->single_copy)
     350              :     {
     351              :         active_query_features =
     352          343 :             lappend(list_copy(active_query_features),
     353          343 :                     pgpa_add_feature(walker, PGPAQF_GATHER, plan));
     354          343 :         beneath_any_gather = true;
     355              :     }
     356       249363 :     else if (IsA(plan, GatherMerge))
     357              :     {
     358              :         active_query_features =
     359          134 :             lappend(list_copy(active_query_features),
     360          134 :                     pgpa_add_feature(walker, PGPAQF_GATHER_MERGE, plan));
     361          134 :         beneath_any_gather = true;
     362              :     }
     363              : 
     364              :     /*
     365              :      * If we're within a join problem, the join unroller is responsible for
     366              :      * building any required scan for this node. If not, we do it here.
     367              :      */
     368       249706 :     if (!within_join_problem)
     369       177274 :         (void) pgpa_build_scan(walker, plan, NULL, beneath_any_gather, false);
     370              : 
     371              :     /*
     372              :      * If this join needs to be unrolled but there's no join unroller already
     373              :      * available, create one.
     374              :      */
     375       249706 :     if (join_unroller == NULL && pgpa_is_join(plan))
     376              :     {
     377        22321 :         join_unroller = pgpa_create_join_unroller();
     378        22321 :         join_unroller_toplevel = true;
     379        22321 :         within_join_problem = true;
     380              :     }
     381              : 
     382              :     /*
     383              :      * If this join is to be unrolled, pgpa_unroll_join() will return the join
     384              :      * unroller object that should be passed down when we recurse into the
     385              :      * outer and inner sides of the plan.
     386              :      */
     387       249706 :     if (join_unroller != NULL)
     388        30538 :         pgpa_unroll_join(walker, plan, beneath_any_gather, join_unroller,
     389              :                          &outer_join_unroller, &inner_join_unroller);
     390              : 
     391              :     /* Add RTIs from the plan node to all active query features. */
     392       249706 :     pgpa_qf_add_plan_rtis(active_query_features, plan, walker->pstmt->rtable);
     393              : 
     394              :     /*
     395              :      * Recurse into the outer and inner subtrees.
     396              :      *
     397              :      * As an exception, if this is a ForeignScan, don't recurse. postgres_fdw
     398              :      * sometimes stores an EPQ recheck plan in plan->lefttree, but that's
     399              :      * going to mention the same set of relations as the ForeignScan itself,
     400              :      * and we have no way to emit advice targeting the EPQ case vs. the
     401              :      * non-EPQ case. Moreover, it's not entirely clear what other FDWs might
     402              :      * do with the left and right subtrees. Maybe some better handling is
     403              :      * needed here, but for now, we just punt.
     404              :      */
     405       249706 :     if (!IsA(plan, ForeignScan))
     406              :     {
     407       249706 :         if (plan->lefttree != NULL)
     408       104792 :             pgpa_walk_recursively(walker, plan->lefttree, within_join_problem,
     409              :                                   outer_join_unroller, active_query_features,
     410              :                                   beneath_any_gather);
     411       249706 :         if (plan->righttree != NULL)
     412        30066 :             pgpa_walk_recursively(walker, plan->righttree, within_join_problem,
     413              :                                   inner_join_unroller, active_query_features,
     414              :                                   beneath_any_gather);
     415              :     }
     416              : 
     417              :     /*
     418              :      * If we created a join unroller up above, then it's also our join to use
     419              :      * it to build the final pgpa_unrolled_join, and to destroy the object.
     420              :      */
     421       249706 :     if (join_unroller_toplevel)
     422              :     {
     423              :         pgpa_unrolled_join *ujoin;
     424              : 
     425        22321 :         ujoin = pgpa_build_unrolled_join(walker, join_unroller);
     426        22321 :         walker->toplevel_unrolled_joins =
     427        22321 :             lappend(walker->toplevel_unrolled_joins, ujoin);
     428        22321 :         pgpa_destroy_join_unroller(join_unroller);
     429        22321 :         (void) pgpa_process_unrolled_join(walker, ujoin);
     430              :     }
     431              : 
     432              :     /*
     433              :      * Some plan types can have additional children. Nodes like Append that
     434              :      * can have any number of children store them in a List; a SubqueryScan
     435              :      * just has a field for a single additional Plan.
     436              :      */
     437       249706 :     switch (nodeTag(plan))
     438              :     {
     439         5231 :         case T_Append:
     440              :             {
     441         5231 :                 Append     *aplan = (Append *) plan;
     442              : 
     443         5231 :                 extraplans = aplan->appendplans;
     444              :             }
     445         5231 :             break;
     446          186 :         case T_MergeAppend:
     447              :             {
     448          186 :                 MergeAppend *maplan = (MergeAppend *) plan;
     449              : 
     450          186 :                 extraplans = maplan->mergeplans;
     451              :             }
     452          186 :             break;
     453           34 :         case T_BitmapAnd:
     454           34 :             extraplans = ((BitmapAnd *) plan)->bitmapplans;
     455           34 :             break;
     456           66 :         case T_BitmapOr:
     457           66 :             extraplans = ((BitmapOr *) plan)->bitmapplans;
     458           66 :             break;
     459         3254 :         case T_SubqueryScan:
     460              : 
     461              :             /*
     462              :              * We don't pass down active_query_features across here, because
     463              :              * those are specific to a subquery level.
     464              :              */
     465         3254 :             pgpa_walk_recursively(walker, ((SubqueryScan *) plan)->subplan,
     466              :                                   0, NULL, NIL, beneath_any_gather);
     467         3254 :             break;
     468            0 :         case T_CustomScan:
     469            0 :             extraplans = ((CustomScan *) plan)->custom_plans;
     470            0 :             break;
     471       240935 :         default:
     472       240935 :             break;
     473              :     }
     474              : 
     475              :     /* If we found a list of extra children, iterate over it. */
     476       265594 :     foreach(lc, extraplans)
     477              :     {
     478        15888 :         Plan       *subplan = lfirst(lc);
     479              : 
     480        15888 :         pgpa_walk_recursively(walker, subplan, false, NULL, NIL,
     481              :                               beneath_any_gather);
     482              :     }
     483       249706 : }
     484              : 
     485              : /*
     486              :  * Perform final processing of a newly-constructed pgpa_unrolled_join. This
     487              :  * only needs to be called for toplevel pgpa_unrolled_join objects, since it
     488              :  * recurses to sub-joins as needed.
     489              :  *
     490              :  * Our goal is to add the set of inner relids to the relevant join_strategies
     491              :  * list, and to do the same for any sub-joins. To that end, the return value
     492              :  * is the set of relids found beneath the join, but it is expected that
     493              :  * the toplevel caller will ignore this.
     494              :  */
     495              : static Bitmapset *
     496        23371 : pgpa_process_unrolled_join(pgpa_plan_walker_context *walker,
     497              :                            pgpa_unrolled_join *ujoin)
     498              : {
     499        23371 :     Bitmapset  *all_relids = bms_copy(ujoin->outer.scan->relids);
     500              : 
     501              :     /* If this fails, we didn't unroll properly. */
     502              :     Assert(ujoin->outer.unrolled_join == NULL);
     503              : 
     504        53053 :     for (int k = 0; k < ujoin->ninner; ++k)
     505              :     {
     506        29682 :         pgpa_join_member *member = &ujoin->inner[k];
     507              :         Bitmapset  *relids;
     508              : 
     509        29682 :         if (member->unrolled_join != NULL)
     510         1050 :             relids = pgpa_process_unrolled_join(walker,
     511              :                                                 member->unrolled_join);
     512              :         else
     513              :         {
     514              :             Assert(member->scan != NULL);
     515        28632 :             relids = member->scan->relids;
     516              :         }
     517        59364 :         walker->join_strategies[ujoin->strategy[k]] =
     518        29682 :             lappend(walker->join_strategies[ujoin->strategy[k]], relids);
     519        29682 :         all_relids = bms_add_members(all_relids, relids);
     520              :     }
     521              : 
     522        23371 :     return all_relids;
     523              : }
     524              : 
     525              : /*
     526              :  * Arrange for the given plan node to be treated as a query feature when the
     527              :  * tree walk reaches it.
     528              :  *
     529              :  * Make sure to only use this for nodes that the tree walk can't have reached
     530              :  * yet!
     531              :  */
     532              : void
     533         1448 : pgpa_add_future_feature(pgpa_plan_walker_context *walker,
     534              :                         pgpa_qf_type type, Plan *plan)
     535              : {
     536         1448 :     pgpa_query_feature *qf = pgpa_add_feature(walker, type, plan);
     537              : 
     538         1448 :     walker->future_query_features =
     539         1448 :         lappend(walker->future_query_features, qf);
     540         1448 : }
     541              : 
     542              : /*
     543              :  * Return the last of any elided nodes associated with this plan node ID.
     544              :  *
     545              :  * The last elided node is the one that would have been uppermost in the plan
     546              :  * tree had it not been removed during setrefs processing.
     547              :  */
     548              : ElidedNode *
     549        72076 : pgpa_last_elided_node(PlannedStmt *pstmt, Plan *plan)
     550              : {
     551        72076 :     ElidedNode *elided_node = NULL;
     552              : 
     553       176750 :     foreach_node(ElidedNode, n, pstmt->elidedNodes)
     554              :     {
     555        32598 :         if (n->plan_node_id == plan->plan_node_id)
     556          512 :             elided_node = n;
     557              :     }
     558              : 
     559        72076 :     return elided_node;
     560              : }
     561              : 
     562              : /*
     563              :  * Certain plan nodes can refer to a set of RTIs. Extract and return the set.
     564              :  */
     565              : Bitmapset *
     566       376857 : pgpa_relids(Plan *plan)
     567              : {
     568       376857 :     if (IsA(plan, Result))
     569        82718 :         return ((Result *) plan)->relids;
     570       294139 :     else if (IsA(plan, ForeignScan))
     571            0 :         return ((ForeignScan *) plan)->fs_relids;
     572       294139 :     else if (IsA(plan, Append))
     573        10462 :         return ((Append *) plan)->apprelids;
     574       283677 :     else if (IsA(plan, MergeAppend))
     575          372 :         return ((MergeAppend *) plan)->apprelids;
     576              : 
     577       283305 :     return NULL;
     578              : }
     579              : 
     580              : /*
     581              :  * Extract the scanned RTI from a plan node.
     582              :  *
     583              :  * Returns 0 if there isn't one.
     584              :  */
     585              : Index
     586       435678 : pgpa_scanrelid(Plan *plan)
     587              : {
     588       435678 :     switch (nodeTag(plan))
     589              :     {
     590       203232 :         case T_SeqScan:
     591              :         case T_SampleScan:
     592              :         case T_BitmapHeapScan:
     593              :         case T_TidScan:
     594              :         case T_TidRangeScan:
     595              :         case T_SubqueryScan:
     596              :         case T_FunctionScan:
     597              :         case T_TableFuncScan:
     598              :         case T_ValuesScan:
     599              :         case T_CteScan:
     600              :         case T_NamedTuplestoreScan:
     601              :         case T_WorkTableScan:
     602              :         case T_ForeignScan:
     603              :         case T_CustomScan:
     604              :         case T_IndexScan:
     605              :         case T_IndexOnlyScan:
     606       203232 :             return ((Scan *) plan)->scanrelid;
     607       232446 :         default:
     608       232446 :             return 0;
     609              :     }
     610              : }
     611              : 
     612              : /*
     613              :  * Construct a new Bitmapset containing non-RTE_JOIN members of 'relids'.
     614              :  */
     615              : Bitmapset *
     616        97490 : pgpa_filter_out_join_relids(Bitmapset *relids, List *rtable)
     617              : {
     618        97490 :     int         rti = -1;
     619        97490 :     Bitmapset  *result = NULL;
     620              : 
     621       201422 :     while ((rti = bms_next_member(relids, rti)) >= 0)
     622              :     {
     623       103932 :         RangeTblEntry *rte = rt_fetch(rti, rtable);
     624              : 
     625       103932 :         if (rte->rtekind != RTE_JOIN)
     626       103452 :             result = bms_add_member(result, rti);
     627              :     }
     628              : 
     629        97490 :     return result;
     630              : }
     631              : 
     632              : /*
     633              :  * Create a pgpa_query_feature and add it to the list of all query features
     634              :  * for this plan.
     635              :  */
     636              : static pgpa_query_feature *
     637         1925 : pgpa_add_feature(pgpa_plan_walker_context *walker,
     638              :                  pgpa_qf_type type, Plan *plan)
     639              : {
     640         1925 :     pgpa_query_feature *qf = palloc0_object(pgpa_query_feature);
     641              : 
     642         1925 :     qf->type = type;
     643         1925 :     qf->plan = plan;
     644              : 
     645         3850 :     walker->query_features[qf->type] =
     646         1925 :         lappend(walker->query_features[qf->type], qf);
     647              : 
     648         1925 :     return qf;
     649              : }
     650              : 
     651              : /*
     652              :  * Add a single RTI to each active query feature.
     653              :  */
     654              : static void
     655       101616 : pgpa_qf_add_rti(List *active_query_features, Index rti)
     656              : {
     657       204201 :     foreach_ptr(pgpa_query_feature, qf, active_query_features)
     658              :     {
     659          969 :         qf->relids = bms_add_member(qf->relids, rti);
     660              :     }
     661       101616 : }
     662              : 
     663              : /*
     664              :  * Add a set of RTIs to each active query feature.
     665              :  */
     666              : static void
     667        48359 : pgpa_qf_add_rtis(List *active_query_features, Bitmapset *relids)
     668              : {
     669        97880 :     foreach_ptr(pgpa_query_feature, qf, active_query_features)
     670              :     {
     671         1162 :         qf->relids = bms_add_members(qf->relids, relids);
     672              :     }
     673        48359 : }
     674              : 
     675              : /*
     676              :  * Add RTIs directly contained in a plan node to each active query feature,
     677              :  * but filter out any join RTIs, since advice doesn't mention those.
     678              :  */
     679              : static void
     680       249706 : pgpa_qf_add_plan_rtis(List *active_query_features, Plan *plan, List *rtable)
     681              : {
     682              :     Bitmapset  *relids;
     683              :     Index       rti;
     684              : 
     685       249706 :     if ((relids = pgpa_relids(plan)) != NULL)
     686              :     {
     687        42795 :         relids = pgpa_filter_out_join_relids(relids, rtable);
     688        42795 :         pgpa_qf_add_rtis(active_query_features, relids);
     689              :     }
     690       206911 :     else if ((rti = pgpa_scanrelid(plan)) != 0)
     691       101616 :         pgpa_qf_add_rti(active_query_features, rti);
     692       249706 : }
     693              : 
     694              : /*
     695              :  * If we generated plan advice using the provided walker object and array
     696              :  * of identifiers, would we generate the specified tag/target combination?
     697              :  *
     698              :  * If yes, the plan conforms to the advice; if no, it does not. Note that
     699              :  * we have no way of knowing whether the planner was forced to emit a plan
     700              :  * that conformed to the advice or just happened to do so.
     701              :  */
     702              : bool
     703       144075 : pgpa_walker_would_advise(pgpa_plan_walker_context *walker,
     704              :                          pgpa_identifier *rt_identifiers,
     705              :                          pgpa_advice_tag_type tag,
     706              :                          pgpa_advice_target *target)
     707              : {
     708       144075 :     Index       rtable_length = list_length(walker->pstmt->rtable);
     709       144075 :     Bitmapset  *relids = NULL;
     710              : 
     711       144075 :     if (tag == PGPA_TAG_JOIN_ORDER)
     712              :     {
     713        15399 :         foreach_ptr(pgpa_unrolled_join, ujoin, walker->toplevel_unrolled_joins)
     714              :         {
     715        15395 :             if (pgpa_walker_join_order_matches(ujoin, rtable_length,
     716              :                                                rt_identifiers, target, true))
     717        11132 :                 return true;
     718              :         }
     719              : 
     720            2 :         return false;
     721              :     }
     722              : 
     723              :     /*
     724              :      * DO_NOT_SCAN advice targets rels that may not be in the flat range table
     725              :      * (e.g. MinMaxAgg losers), so pgpa_compute_rti_from_identifier won't work
     726              :      * here. Instead, check directly against the do_not_scan_identifiers list.
     727              :      */
     728       132941 :     if (tag == PGPA_TAG_DO_NOT_SCAN)
     729              :     {
     730          416 :         if (target->ttype != PGPA_TARGET_IDENTIFIER)
     731            0 :             return false;
     732          730 :         foreach_ptr(pgpa_identifier, rid, walker->do_not_scan_identifiers)
     733              :         {
     734          728 :             if (strcmp(rid->alias_name, target->rid.alias_name) == 0 &&
     735         1432 :                 rid->occurrence == target->rid.occurrence &&
     736          716 :                 strings_equal_or_both_null(rid->partnsp,
     737          644 :                                            target->rid.partnsp) &&
     738          644 :                 strings_equal_or_both_null(rid->partrel,
     739          571 :                                            target->rid.partrel) &&
     740          571 :                 strings_equal_or_both_null(rid->plan_name,
     741              :                                            target->rid.plan_name))
     742          415 :                 return true;
     743              :         }
     744            1 :         return false;
     745              :     }
     746              : 
     747       132525 :     if (target->ttype == PGPA_TARGET_IDENTIFIER)
     748              :     {
     749              :         Index       rti;
     750              : 
     751       131684 :         rti = pgpa_compute_rti_from_identifier(rtable_length, rt_identifiers,
     752              :                                                &target->rid);
     753       131684 :         if (rti == 0)
     754            0 :             return false;
     755       131684 :         relids = bms_make_singleton(rti);
     756              :     }
     757              :     else
     758              :     {
     759              :         Assert(target->ttype == PGPA_TARGET_ORDERED_LIST);
     760         3583 :         foreach_ptr(pgpa_advice_target, child_target, target->children)
     761              :         {
     762              :             Index       rti;
     763              : 
     764              :             Assert(child_target->ttype == PGPA_TARGET_IDENTIFIER);
     765         1901 :             rti = pgpa_compute_rti_from_identifier(rtable_length,
     766              :                                                    rt_identifiers,
     767              :                                                    &child_target->rid);
     768         1901 :             if (rti == 0)
     769            0 :                 return false;
     770         1901 :             relids = bms_add_member(relids, rti);
     771              :         }
     772              :     }
     773              : 
     774       132525 :     switch (tag)
     775              :     {
     776              :         case PGPA_TAG_JOIN_ORDER:
     777              :             /* should have been handled above */
     778              :             pg_unreachable();
     779              :             break;
     780              :         case PGPA_TAG_DO_NOT_SCAN:
     781              :             /* should have been handled above */
     782              :             pg_unreachable();
     783              :             break;
     784         2671 :         case PGPA_TAG_BITMAP_HEAP_SCAN:
     785         2671 :             return pgpa_walker_find_scan(walker,
     786              :                                          PGPA_SCAN_BITMAP_HEAP,
     787         2671 :                                          relids) != NULL;
     788            1 :         case PGPA_TAG_FOREIGN_JOIN:
     789            1 :             return pgpa_walker_find_scan(walker,
     790              :                                          PGPA_SCAN_FOREIGN,
     791            1 :                                          relids) != NULL;
     792         1887 :         case PGPA_TAG_INDEX_ONLY_SCAN:
     793              :             {
     794              :                 pgpa_scan  *scan;
     795              : 
     796         1887 :                 scan = pgpa_walker_find_scan(walker, PGPA_SCAN_INDEX_ONLY,
     797              :                                              relids);
     798         1887 :                 if (scan == NULL)
     799            3 :                     return false;
     800              : 
     801         1884 :                 return pgpa_walker_index_target_matches_plan(target->itarget, scan->plan);
     802              :             }
     803        11845 :         case PGPA_TAG_INDEX_SCAN:
     804              :             {
     805              :                 pgpa_scan  *scan;
     806              : 
     807        11845 :                 scan = pgpa_walker_find_scan(walker, PGPA_SCAN_INDEX,
     808              :                                              relids);
     809        11845 :                 if (scan == NULL)
     810            2 :                     return false;
     811              : 
     812        11843 :                 return pgpa_walker_index_target_matches_plan(target->itarget, scan->plan);
     813              :             }
     814         2174 :         case PGPA_TAG_PARTITIONWISE:
     815         2174 :             return pgpa_walker_find_scan(walker,
     816              :                                          PGPA_SCAN_PARTITIONWISE,
     817         2174 :                                          relids) != NULL;
     818        26805 :         case PGPA_TAG_SEQ_SCAN:
     819        26805 :             return pgpa_walker_find_scan(walker,
     820              :                                          PGPA_SCAN_SEQ,
     821        26805 :                                          relids) != NULL;
     822          420 :         case PGPA_TAG_TID_SCAN:
     823          420 :             return pgpa_walker_find_scan(walker,
     824              :                                          PGPA_SCAN_TID,
     825          420 :                                          relids) != NULL;
     826          172 :         case PGPA_TAG_GATHER:
     827          172 :             return pgpa_walker_contains_feature(walker,
     828              :                                                 PGPAQF_GATHER,
     829              :                                                 relids);
     830           61 :         case PGPA_TAG_GATHER_MERGE:
     831           61 :             return pgpa_walker_contains_feature(walker,
     832              :                                                 PGPAQF_GATHER_MERGE,
     833              :                                                 relids);
     834          624 :         case PGPA_TAG_SEMIJOIN_NON_UNIQUE:
     835          624 :             return pgpa_walker_contains_feature(walker,
     836              :                                                 PGPAQF_SEMIJOIN_NON_UNIQUE,
     837              :                                                 relids);
     838           79 :         case PGPA_TAG_SEMIJOIN_UNIQUE:
     839           79 :             return pgpa_walker_contains_feature(walker,
     840              :                                                 PGPAQF_SEMIJOIN_UNIQUE,
     841              :                                                 relids);
     842         4417 :         case PGPA_TAG_HASH_JOIN:
     843         4417 :             return pgpa_walker_contains_join(walker,
     844              :                                              JSTRAT_HASH_JOIN,
     845              :                                              relids);
     846           27 :         case PGPA_TAG_MERGE_JOIN_MATERIALIZE:
     847           27 :             return pgpa_walker_contains_join(walker,
     848              :                                              JSTRAT_MERGE_JOIN_MATERIALIZE,
     849              :                                              relids);
     850          544 :         case PGPA_TAG_MERGE_JOIN_PLAIN:
     851          544 :             return pgpa_walker_contains_join(walker,
     852              :                                              JSTRAT_MERGE_JOIN_PLAIN,
     853              :                                              relids);
     854          498 :         case PGPA_TAG_NESTED_LOOP_MATERIALIZE:
     855          498 :             return pgpa_walker_contains_join(walker,
     856              :                                              JSTRAT_NESTED_LOOP_MATERIALIZE,
     857              :                                              relids);
     858          210 :         case PGPA_TAG_NESTED_LOOP_MEMOIZE:
     859          210 :             return pgpa_walker_contains_join(walker,
     860              :                                              JSTRAT_NESTED_LOOP_MEMOIZE,
     861              :                                              relids);
     862         9102 :         case PGPA_TAG_NESTED_LOOP_PLAIN:
     863         9102 :             return pgpa_walker_contains_join(walker,
     864              :                                              JSTRAT_NESTED_LOOP_PLAIN,
     865              :                                              relids);
     866        70988 :         case PGPA_TAG_NO_GATHER:
     867        70988 :             return pgpa_walker_contains_no_gather(walker, relids);
     868              :     }
     869              : 
     870              :     /* should not get here */
     871            0 :     return false;
     872              : }
     873              : 
     874              : /*
     875              :  * Does the index target match the Plan?
     876              :  *
     877              :  * Should only be called when we know that itarget mandates an Index Scan or
     878              :  * Index Only Scan and this corresponds to the type of Plan. Here, our job is
     879              :  * just to check whether it's the same index.
     880              :  */
     881              : static bool
     882        13727 : pgpa_walker_index_target_matches_plan(pgpa_index_target *itarget, Plan *plan)
     883              : {
     884        13727 :     Oid         indexoid = InvalidOid;
     885              : 
     886              :     /* Retrieve the index OID from the plan. */
     887        13727 :     if (IsA(plan, IndexScan))
     888        11843 :         indexoid = ((IndexScan *) plan)->indexid;
     889         1884 :     else if (IsA(plan, IndexOnlyScan))
     890         1884 :         indexoid = ((IndexOnlyScan *) plan)->indexid;
     891              :     else
     892            0 :         elog(ERROR, "unrecognized node type: %d", (int) nodeTag(plan));
     893              : 
     894              :     /* Check whether schema name matches, if specified in index target. */
     895        13727 :     if (itarget->indnamespace != NULL)
     896              :     {
     897        13716 :         Oid         nspoid = get_rel_namespace(indexoid);
     898        13716 :         char       *relnamespace = get_namespace_name_or_temp(nspoid);
     899              : 
     900        13716 :         if (strcmp(itarget->indnamespace, relnamespace) != 0)
     901            1 :             return false;
     902              :     }
     903              : 
     904              :     /* Check whether relation name matches. */
     905        13726 :     return (strcmp(itarget->indname, get_rel_name(indexoid)) == 0);
     906              : }
     907              : 
     908              : /*
     909              :  * Does an unrolled join match the join order specified by an advice target?
     910              :  */
     911              : static bool
     912        15919 : pgpa_walker_join_order_matches(pgpa_unrolled_join *ujoin,
     913              :                                Index rtable_length,
     914              :                                pgpa_identifier *rt_identifiers,
     915              :                                pgpa_advice_target *target,
     916              :                                bool toplevel)
     917              : {
     918        15919 :     int         nchildren = list_length(target->children);
     919              : 
     920              :     Assert(target->ttype == PGPA_TARGET_ORDERED_LIST);
     921              : 
     922              :     /* At toplevel, we allow a prefix match. */
     923        15919 :     if (toplevel)
     924              :     {
     925        15395 :         if (nchildren > ujoin->ninner + 1)
     926          111 :             return false;
     927              :     }
     928              :     else
     929              :     {
     930          524 :         if (nchildren != ujoin->ninner + 1)
     931            0 :             return false;
     932              :     }
     933              : 
     934              :     /* Outermost rel must match. */
     935        15808 :     if (!pgpa_walker_join_order_matches_member(&ujoin->outer,
     936              :                                                rtable_length,
     937              :                                                rt_identifiers,
     938        15808 :                                                linitial(target->children)))
     939         4151 :         return false;
     940              : 
     941              :     /* Each inner rel must match. */
     942        26455 :     for (int n = 0; n < nchildren - 1; ++n)
     943              :     {
     944        14799 :         pgpa_advice_target *child_target = list_nth(target->children, n + 1);
     945              : 
     946        14799 :         if (!pgpa_walker_join_order_matches_member(&ujoin->inner[n],
     947              :                                                    rtable_length,
     948              :                                                    rt_identifiers,
     949              :                                                    child_target))
     950            1 :             return false;
     951              :     }
     952              : 
     953        11656 :     return true;
     954              : }
     955              : 
     956              : /*
     957              :  * Does one member of an unrolled join match an advice target?
     958              :  */
     959              : static bool
     960        30607 : pgpa_walker_join_order_matches_member(pgpa_join_member *member,
     961              :                                       Index rtable_length,
     962              :                                       pgpa_identifier *rt_identifiers,
     963              :                                       pgpa_advice_target *target)
     964              : {
     965        30607 :     Bitmapset  *relids = NULL;
     966              : 
     967        30607 :     if (member->unrolled_join != NULL)
     968              :     {
     969          525 :         if (target->ttype != PGPA_TARGET_ORDERED_LIST)
     970            1 :             return false;
     971          524 :         return pgpa_walker_join_order_matches(member->unrolled_join,
     972              :                                               rtable_length,
     973              :                                               rt_identifiers,
     974              :                                               target,
     975              :                                               false);
     976              :     }
     977              : 
     978              :     Assert(member->scan != NULL);
     979        30082 :     switch (target->ttype)
     980              :     {
     981            0 :         case PGPA_TARGET_ORDERED_LIST:
     982              :             /* Could only match an unrolled join */
     983            0 :             return false;
     984              : 
     985           19 :         case PGPA_TARGET_UNORDERED_LIST:
     986              :             {
     987           86 :                 foreach_ptr(pgpa_advice_target, child_target, target->children)
     988              :                 {
     989              :                     Index       rti;
     990              : 
     991           48 :                     rti = pgpa_compute_rti_from_identifier(rtable_length,
     992              :                                                            rt_identifiers,
     993              :                                                            &child_target->rid);
     994           48 :                     if (rti == 0)
     995            0 :                         return false;
     996           48 :                     relids = bms_add_member(relids, rti);
     997              :                 }
     998           19 :                 break;
     999              :             }
    1000              : 
    1001        30063 :         case PGPA_TARGET_IDENTIFIER:
    1002              :             {
    1003              :                 Index       rti;
    1004              : 
    1005        30063 :                 rti = pgpa_compute_rti_from_identifier(rtable_length,
    1006              :                                                        rt_identifiers,
    1007              :                                                        &target->rid);
    1008        30063 :                 if (rti == 0)
    1009            0 :                     return false;
    1010        30063 :                 relids = bms_make_singleton(rti);
    1011        30063 :                 break;
    1012              :             }
    1013              :     }
    1014              : 
    1015        30082 :     return bms_equal(member->scan->relids, relids);
    1016              : }
    1017              : 
    1018              : /*
    1019              :  * Find the scan where the walker says that the given scan strategy should be
    1020              :  * used for the given relid set, if one exists.
    1021              :  *
    1022              :  * Returns the pgpa_scan object, or NULL if none was found.
    1023              :  */
    1024              : static pgpa_scan *
    1025        45803 : pgpa_walker_find_scan(pgpa_plan_walker_context *walker,
    1026              :                       pgpa_scan_strategy strategy,
    1027              :                       Bitmapset *relids)
    1028              : {
    1029        45803 :     List       *scans = walker->scans[strategy];
    1030              : 
    1031        90168 :     foreach_ptr(pgpa_scan, scan, scans)
    1032              :     {
    1033        90144 :         if (bms_equal(scan->relids, relids))
    1034        45791 :             return scan;
    1035              :     }
    1036              : 
    1037           12 :     return NULL;
    1038              : }
    1039              : 
    1040              : /*
    1041              :  * Does this walker say that the given query feature applies to the given
    1042              :  * relid set?
    1043              :  */
    1044              : static bool
    1045          936 : pgpa_walker_contains_feature(pgpa_plan_walker_context *walker,
    1046              :                              pgpa_qf_type type,
    1047              :                              Bitmapset *relids)
    1048              : {
    1049          936 :     List       *query_features = walker->query_features[type];
    1050              : 
    1051         1058 :     foreach_ptr(pgpa_query_feature, qf, query_features)
    1052              :     {
    1053         1046 :         if (bms_equal(qf->relids, relids))
    1054          930 :             return true;
    1055              :     }
    1056              : 
    1057            6 :     return false;
    1058              : }
    1059              : 
    1060              : /*
    1061              :  * Does the walker say that the given join strategy should be used for the
    1062              :  * given relid set?
    1063              :  */
    1064              : static bool
    1065        14798 : pgpa_walker_contains_join(pgpa_plan_walker_context *walker,
    1066              :                           pgpa_join_strategy strategy,
    1067              :                           Bitmapset *relids)
    1068              : {
    1069        14798 :     List       *join_strategies = walker->join_strategies[strategy];
    1070              : 
    1071        24942 :     foreach_ptr(Bitmapset, jsrelids, join_strategies)
    1072              :     {
    1073        24932 :         if (bms_equal(jsrelids, relids))
    1074        14793 :             return true;
    1075              :     }
    1076              : 
    1077            5 :     return false;
    1078              : }
    1079              : 
    1080              : /*
    1081              :  * Does the walker say that the given relids should be marked as NO_GATHER?
    1082              :  */
    1083              : static bool
    1084        70988 : pgpa_walker_contains_no_gather(pgpa_plan_walker_context *walker,
    1085              :                                Bitmapset *relids)
    1086              : {
    1087        70988 :     return bms_is_subset(relids, walker->no_gather_scans);
    1088              : }
    1089              : 
    1090              : /*
    1091              :  * Classify alternative subplans as chosen or discarded.
    1092              :  */
    1093              : static void
    1094        86992 : pgpa_classify_alternative_subplans(pgpa_plan_walker_context *walker,
    1095              :                                    List *proots,
    1096              :                                    List **chosen_proots,
    1097              :                                    List **discarded_proots)
    1098              : {
    1099        86992 :     Bitmapset  *all_scan_rtis = NULL;
    1100              : 
    1101              :     /* Initialize both output lists to empty. */
    1102        86992 :     *chosen_proots = NIL;
    1103        86992 :     *discarded_proots = NIL;
    1104              : 
    1105              :     /* Collect all scan RTIs. */
    1106       782928 :     for (int s = 0; s < NUM_PGPA_SCAN_STRATEGY; s++)
    1107      1542619 :         foreach_ptr(pgpa_scan, scan, walker->scans[s])
    1108       150747 :             all_scan_rtis = bms_add_members(all_scan_rtis, scan->relids);
    1109              : 
    1110              :     /* Now classify each subplan. */
    1111       278040 :     foreach_ptr(pgpa_planner_info, proot, proots)
    1112              :     {
    1113       104056 :         bool        chosen = false;
    1114              : 
    1115              :         /*
    1116              :          * We're only interested in classifying subplans for which there are
    1117              :          * alternatives.
    1118              :          */
    1119       104056 :         if (!proot->is_alternative_plan)
    1120       102730 :             continue;
    1121              : 
    1122              :         /*
    1123              :          * A subplan has been chosen if any of its scan RTIs appear in the
    1124              :          * final plan. This cannot be the case if it has no RT offset.
    1125              :          */
    1126         1326 :         if (proot->has_rtoffset)
    1127              :         {
    1128         5072 :             for (int rti = 1; rti <= proot->rid_array_size; rti++)
    1129              :             {
    1130         5778 :                 if (proot->rid_array[rti - 1].alias_name != NULL &&
    1131         1200 :                     bms_is_member(proot->rtoffset + rti, all_scan_rtis))
    1132              :                 {
    1133          626 :                     chosen = true;
    1134          626 :                     break;
    1135              :                 }
    1136              :             }
    1137              :         }
    1138              : 
    1139              :         /* Add it to the correct list. */
    1140         1326 :         if (chosen)
    1141          626 :             *chosen_proots = lappend(*chosen_proots, proot);
    1142              :         else
    1143          700 :             *discarded_proots = lappend(*discarded_proots, proot);
    1144              :     }
    1145        86992 : }
        

Generated by: LCOV version 2.0-1