LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_planner.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 90.3 % 667 602
Test Date: 2026-04-07 01:16:21 Functions: 91.3 % 23 21
Legend: Lines:     hit not hit

            Line data    Source code
       1              : /*-------------------------------------------------------------------------
       2              :  *
       3              :  * pgpa_planner.c
       4              :  *    Use planner hooks to observe and modify planner behavior
       5              :  *
       6              :  * All interaction with the core planner happens here. Much of it has to
       7              :  * do with enforcing supplied advice, but we also need these hooks to
       8              :  * generate advice strings (though the heavy lifting in that case is
       9              :  * mostly done by pgpa_walker.c).
      10              :  *
      11              :  * Copyright (c) 2016-2026, PostgreSQL Global Development Group
      12              :  *
      13              :  *    contrib/pg_plan_advice/pgpa_planner.c
      14              :  *
      15              :  *-------------------------------------------------------------------------
      16              :  */
      17              : #include "postgres.h"
      18              : 
      19              : #include "pg_plan_advice.h"
      20              : #include "pgpa_identifier.h"
      21              : #include "pgpa_output.h"
      22              : #include "pgpa_planner.h"
      23              : #include "pgpa_trove.h"
      24              : #include "pgpa_walker.h"
      25              : 
      26              : #include "commands/defrem.h"
      27              : #include "common/hashfn_unstable.h"
      28              : #include "nodes/makefuncs.h"
      29              : #include "optimizer/extendplan.h"
      30              : #include "optimizer/pathnode.h"
      31              : #include "optimizer/paths.h"
      32              : #include "optimizer/plancat.h"
      33              : #include "optimizer/planner.h"
      34              : #include "parser/parsetree.h"
      35              : #include "utils/lsyscache.h"
      36              : 
      37              : typedef enum pgpa_jo_outcome
      38              : {
      39              :     PGPA_JO_PERMITTED,          /* permit this join order */
      40              :     PGPA_JO_DENIED,             /* deny this join order */
      41              :     PGPA_JO_INDIFFERENT         /* do neither */
      42              : } pgpa_jo_outcome;
      43              : 
      44              : typedef struct pgpa_planner_state
      45              : {
      46              :     MemoryContext mcxt;
      47              :     bool        generate_advice_feedback;
      48              :     bool        generate_advice_string;
      49              :     pgpa_trove *trove;
      50              :     List       *proots;
      51              :     pgpa_planner_info *last_proot;
      52              : } pgpa_planner_state;
      53              : 
      54              : typedef struct pgpa_join_state
      55              : {
      56              :     /* Most-recently-considered outer rel. */
      57              :     RelOptInfo *outerrel;
      58              : 
      59              :     /* Most-recently-considered inner rel. */
      60              :     RelOptInfo *innerrel;
      61              : 
      62              :     /*
      63              :      * Array of relation identifiers for all members of this joinrel, with
      64              :      * outerrel identifiers before innerrel identifiers.
      65              :      */
      66              :     pgpa_identifier *rids;
      67              : 
      68              :     /* Number of outer rel identifiers. */
      69              :     int         outer_count;
      70              : 
      71              :     /* Number of inner rel identifiers. */
      72              :     int         inner_count;
      73              : 
      74              :     /*
      75              :      * Trove lookup results.
      76              :      *
      77              :      * join_entries and rel_entries are arrays of entries, and join_indexes
      78              :      * and rel_indexes are the integer offsets within those arrays of entries
      79              :      * potentially relevant to us. The "join" fields correspond to a lookup
      80              :      * using PGPA_TROVE_LOOKUP_JOIN and the "rel" fields to a lookup using
      81              :      * PGPA_TROVE_LOOKUP_REL.
      82              :      */
      83              :     pgpa_trove_entry *join_entries;
      84              :     Bitmapset  *join_indexes;
      85              :     pgpa_trove_entry *rel_entries;
      86              :     Bitmapset  *rel_indexes;
      87              : } pgpa_join_state;
      88              : 
      89              : /* Saved hook values */
      90              : static build_simple_rel_hook_type prev_build_simple_rel = NULL;
      91              : static join_path_setup_hook_type prev_join_path_setup = NULL;
      92              : static joinrel_setup_hook_type prev_joinrel_setup = NULL;
      93              : static planner_setup_hook_type prev_planner_setup = NULL;
      94              : static planner_shutdown_hook_type prev_planner_shutdown = NULL;
      95              : 
      96              : /* Other global variables */
      97              : int         pgpa_planner_generate_advice = 0;
      98              : static int  planner_extension_id = -1;
      99              : 
     100              : /* Function prototypes. */
     101              : static void pgpa_planner_setup(PlannerGlobal *glob, Query *parse,
     102              :                                const char *query_string,
     103              :                                int cursorOptions,
     104              :                                double *tuple_fraction,
     105              :                                ExplainState *es);
     106              : static void pgpa_planner_shutdown(PlannerGlobal *glob, Query *parse,
     107              :                                   const char *query_string, PlannedStmt *pstmt);
     108              : static void pgpa_build_simple_rel(PlannerInfo *root,
     109              :                                   RelOptInfo *rel,
     110              :                                   RangeTblEntry *rte);
     111              : static void pgpa_joinrel_setup(PlannerInfo *root,
     112              :                                RelOptInfo *joinrel,
     113              :                                RelOptInfo *outerrel,
     114              :                                RelOptInfo *innerrel,
     115              :                                SpecialJoinInfo *sjinfo,
     116              :                                List *restrictlist);
     117              : static void pgpa_join_path_setup(PlannerInfo *root,
     118              :                                  RelOptInfo *joinrel,
     119              :                                  RelOptInfo *outerrel,
     120              :                                  RelOptInfo *innerrel,
     121              :                                  JoinType jointype,
     122              :                                  JoinPathExtraData *extra);
     123              : static pgpa_join_state *pgpa_get_join_state(PlannerInfo *root,
     124              :                                             RelOptInfo *joinrel,
     125              :                                             RelOptInfo *outerrel,
     126              :                                             RelOptInfo *innerrel);
     127              : static void pgpa_planner_apply_joinrel_advice(uint64 *pgs_mask_p,
     128              :                                               char *plan_name,
     129              :                                               pgpa_join_state *pjs);
     130              : static void pgpa_planner_apply_join_path_advice(JoinType jointype,
     131              :                                                 uint64 *pgs_mask_p,
     132              :                                                 char *plan_name,
     133              :                                                 pgpa_join_state *pjs);
     134              : static void pgpa_planner_apply_scan_advice(RelOptInfo *rel,
     135              :                                            pgpa_trove_entry *scan_entries,
     136              :                                            Bitmapset *scan_indexes,
     137              :                                            pgpa_trove_entry *rel_entries,
     138              :                                            Bitmapset *rel_indexes);
     139              : static uint64 pgpa_join_strategy_mask_from_advice_tag(pgpa_advice_tag_type tag);
     140              : static pgpa_jo_outcome pgpa_join_order_permits_join(int outer_count,
     141              :                                                     int inner_count,
     142              :                                                     pgpa_identifier *rids,
     143              :                                                     pgpa_trove_entry *entry);
     144              : static bool pgpa_join_method_permits_join(int outer_count, int inner_count,
     145              :                                           pgpa_identifier *rids,
     146              :                                           pgpa_trove_entry *entry,
     147              :                                           bool *restrict_method);
     148              : static bool pgpa_opaque_join_permits_join(int outer_count, int inner_count,
     149              :                                           pgpa_identifier *rids,
     150              :                                           pgpa_trove_entry *entry,
     151              :                                           bool *restrict_method);
     152              : static bool pgpa_semijoin_permits_join(int outer_count, int inner_count,
     153              :                                        pgpa_identifier *rids,
     154              :                                        pgpa_trove_entry *entry,
     155              :                                        bool outer_is_nullable,
     156              :                                        bool *restrict_method);
     157              : 
     158              : static List *pgpa_planner_append_feedback(List *list, pgpa_trove *trove,
     159              :                                           pgpa_trove_lookup_type type,
     160              :                                           pgpa_identifier *rt_identifiers,
     161              :                                           pgpa_plan_walker_context *walker);
     162              : static void pgpa_planner_feedback_warning(List *feedback);
     163              : 
     164              : static pgpa_planner_info *pgpa_planner_get_proot(pgpa_planner_state *pps,
     165              :                                                  PlannerInfo *root);
     166              : 
     167              : static inline void pgpa_compute_rt_identifier(pgpa_planner_info *proot,
     168              :                                               PlannerInfo *root,
     169              :                                               RelOptInfo *rel);
     170              : static void pgpa_compute_rt_offsets(pgpa_planner_state *pps,
     171              :                                     PlannedStmt *pstmt);
     172              : static void pgpa_validate_rt_identifiers(pgpa_planner_state *pps,
     173              :                                          PlannedStmt *pstmt);
     174              : 
     175              : static char *pgpa_bms_to_cstring(Bitmapset *bms);
     176              : static const char *pgpa_jointype_to_cstring(JoinType jointype);
     177              : 
     178              : /*
     179              :  * Install planner-related hooks.
     180              :  */
     181              : void
     182           14 : pgpa_planner_install_hooks(void)
     183              : {
     184           14 :     planner_extension_id = GetPlannerExtensionId("pg_plan_advice");
     185           14 :     prev_planner_setup = planner_setup_hook;
     186           14 :     planner_setup_hook = pgpa_planner_setup;
     187           14 :     prev_planner_shutdown = planner_shutdown_hook;
     188           14 :     planner_shutdown_hook = pgpa_planner_shutdown;
     189           14 :     prev_build_simple_rel = build_simple_rel_hook;
     190           14 :     build_simple_rel_hook = pgpa_build_simple_rel;
     191           14 :     prev_joinrel_setup = joinrel_setup_hook;
     192           14 :     joinrel_setup_hook = pgpa_joinrel_setup;
     193           14 :     prev_join_path_setup = join_path_setup_hook;
     194           14 :     join_path_setup_hook = pgpa_join_path_setup;
     195           14 : }
     196              : 
     197              : /*
     198              :  * Carry out whatever setup work we need to do before planning.
     199              :  */
     200              : static void
     201        88436 : pgpa_planner_setup(PlannerGlobal *glob, Query *parse, const char *query_string,
     202              :                    int cursorOptions, double *tuple_fraction,
     203              :                    ExplainState *es)
     204              : {
     205        88436 :     pgpa_trove *trove = NULL;
     206              :     pgpa_planner_state *pps;
     207              :     char       *supplied_advice;
     208        88436 :     bool        generate_advice_feedback = false;
     209        88436 :     bool        generate_advice_string = false;
     210        88436 :     bool        needs_pps = false;
     211              : 
     212              :     /*
     213              :      * Decide whether we need to generate an advice string. We must do this if
     214              :      * the user has told us to do it categorically, or if another loadable
     215              :      * module has requested it, or if the user has requested it using the
     216              :      * EXPLAIN (PLAN_ADVICE) option.
     217              :      */
     218        44280 :     generate_advice_string = (pg_plan_advice_always_store_advice_details ||
     219       132716 :                               pgpa_planner_generate_advice ||
     220        44280 :                               pg_plan_advice_should_explain(es));
     221        88436 :     if (generate_advice_string)
     222        44276 :         needs_pps = true;
     223              : 
     224              :     /*
     225              :      * If any advice was provided, build a trove of advice for use during
     226              :      * planning.
     227              :      */
     228        88436 :     supplied_advice = pg_plan_advice_get_supplied_query_advice(glob, parse,
     229              :                                                                query_string,
     230              :                                                                cursorOptions,
     231              :                                                                es);
     232        87668 :     if (supplied_advice != NULL && supplied_advice[0] != '\0')
     233              :     {
     234              :         List       *advice_items;
     235              :         char       *error;
     236              : 
     237              :         /*
     238              :          * If the supplied advice string comes from pg_plan_advice.advice,
     239              :          * parsing shouldn't fail here, because we must have previously parsed
     240              :          * successfully in pg_plan_advice_advice_check_hook. However, it might
     241              :          * also come from a hook registered via pg_plan_advice_add_advisor,
     242              :          * and we can't be sure whether that's valid. (Plus, having an error
     243              :          * check here seems like a good idea anyway, just for safety.)
     244              :          */
     245        43439 :         advice_items = pgpa_parse(supplied_advice, &error);
     246        43439 :         if (error)
     247            0 :             ereport(WARNING,
     248              :                     errmsg("could not parse supplied advice: %s", error));
     249              : 
     250              :         /*
     251              :          * It's possible that the advice string was non-empty but contained no
     252              :          * actual advice, e.g. it was all whitespace.
     253              :          */
     254        43439 :         if (advice_items != NIL)
     255              :         {
     256        43437 :             trove = pgpa_build_trove(advice_items);
     257        43437 :             needs_pps = true;
     258              : 
     259              :             /*
     260              :              * If we know that we're running under EXPLAIN, or if the user has
     261              :              * told us to always do the work, generate advice feedback.
     262              :              */
     263        43437 :             if (es != NULL || pg_plan_advice_feedback_warnings ||
     264              :                 pg_plan_advice_always_store_advice_details)
     265        43436 :                 generate_advice_feedback = true;
     266              :         }
     267              :     }
     268              : 
     269              :     /*
     270              :      * We only create and initialize a private state object if it's needed for
     271              :      * some purpose. That could be (1) recording that we will need to generate
     272              :      * an advice string or (2) storing a trove of supplied advice.
     273              :      *
     274              :      * Currently, the active memory context should be one that will last for
     275              :      * the entire duration of query planning, but if GEQO is in use, it's
     276              :      * possible that some of our callbacks may be invoked later with
     277              :      * CurrentMemoryContext set to some shorter-lived context. So, record the
     278              :      * context that should be used for allocations that need to live as long
     279              :      * as the pgpa_planner_state itself.
     280              :      */
     281        87668 :     if (needs_pps)
     282              :     {
     283        87610 :         pps = palloc0_object(pgpa_planner_state);
     284        87610 :         pps->mcxt = CurrentMemoryContext;
     285        87610 :         pps->generate_advice_feedback = generate_advice_feedback;
     286        87610 :         pps->generate_advice_string = generate_advice_string;
     287        87610 :         pps->trove = trove;
     288        87610 :         SetPlannerGlobalExtensionState(glob, planner_extension_id, pps);
     289              :     }
     290              : 
     291              :     /* Pass call to previous hook. */
     292        87668 :     if (prev_planner_setup)
     293            0 :         (*prev_planner_setup) (glob, parse, query_string, cursorOptions,
     294              :                                tuple_fraction, es);
     295        87668 : }
     296              : 
     297              : /*
     298              :  * Carry out whatever work we want to do after planning is complete.
     299              :  */
     300              : static void
     301        86897 : pgpa_planner_shutdown(PlannerGlobal *glob, Query *parse,
     302              :                       const char *query_string, PlannedStmt *pstmt)
     303              : {
     304              :     pgpa_planner_state *pps;
     305        86897 :     pgpa_trove *trove = NULL;
     306        86897 :     pgpa_plan_walker_context walker = {0};  /* placate compiler */
     307        86897 :     bool        generate_advice_feedback = false;
     308        86897 :     bool        generate_advice_string = false;
     309        86897 :     List       *pgpa_items = NIL;
     310        86897 :     pgpa_identifier *rt_identifiers = NULL;
     311              : 
     312              :     /* Fetch our private state, set up by pgpa_planner_setup(). */
     313        86897 :     pps = GetPlannerGlobalExtensionState(glob, planner_extension_id);
     314        86897 :     if (pps != NULL)
     315              :     {
     316              :         /* Set up some local variables. */
     317        86839 :         trove = pps->trove;
     318        86839 :         generate_advice_feedback = pps->generate_advice_feedback;
     319        86839 :         generate_advice_string = pps->generate_advice_string;
     320              : 
     321              :         /* Compute range table offsets. */
     322        86839 :         pgpa_compute_rt_offsets(pps, pstmt);
     323              : 
     324              :         /* Cross-check range table identifiers. */
     325        86839 :         pgpa_validate_rt_identifiers(pps, pstmt);
     326              :     }
     327              : 
     328              :     /*
     329              :      * If we're trying to generate an advice string or if we're trying to
     330              :      * provide advice feedback, then we will need to create range table
     331              :      * identifiers.
     332              :      */
     333        86897 :     if (generate_advice_string || generate_advice_feedback)
     334              :     {
     335        86838 :         pgpa_plan_walker(&walker, pstmt, pps->proots);
     336        86838 :         rt_identifiers = pgpa_create_identifiers_for_planned_stmt(pstmt);
     337              :     }
     338              : 
     339              :     /* Generate the advice string, if we need to do so. */
     340        86897 :     if (generate_advice_string)
     341              :     {
     342              :         char       *advice_string;
     343              :         StringInfoData buf;
     344              : 
     345              :         /* Generate a textual advice string. */
     346        43505 :         initStringInfo(&buf);
     347        43505 :         pgpa_output_advice(&buf, &walker, rt_identifiers);
     348        43505 :         advice_string = buf.data;
     349              : 
     350              :         /* Save the advice string in the final plan. */
     351        43505 :         pgpa_items = lappend(pgpa_items,
     352        43505 :                              makeDefElem("advice_string",
     353        43505 :                                          (Node *) makeString(advice_string),
     354              :                                          -1));
     355              :     }
     356              : 
     357              :     /*
     358              :      * If we're trying to provide advice feedback, then we will need to
     359              :      * analyze how successful the advice was.
     360              :      */
     361        86897 :     if (generate_advice_feedback)
     362              :     {
     363        43436 :         List       *feedback = NIL;
     364              : 
     365              :         /*
     366              :          * Inject a Node-tree representation of all the trove-entry flags into
     367              :          * the PlannedStmt.
     368              :          */
     369        43436 :         feedback = pgpa_planner_append_feedback(feedback,
     370              :                                                 trove,
     371              :                                                 PGPA_TROVE_LOOKUP_SCAN,
     372              :                                                 rt_identifiers, &walker);
     373        43436 :         feedback = pgpa_planner_append_feedback(feedback,
     374              :                                                 trove,
     375              :                                                 PGPA_TROVE_LOOKUP_JOIN,
     376              :                                                 rt_identifiers, &walker);
     377        43436 :         feedback = pgpa_planner_append_feedback(feedback,
     378              :                                                 trove,
     379              :                                                 PGPA_TROVE_LOOKUP_REL,
     380              :                                                 rt_identifiers, &walker);
     381              : 
     382        43436 :         pgpa_items = lappend(pgpa_items, makeDefElem("feedback",
     383              :                                                      (Node *) feedback, -1));
     384              : 
     385              :         /* If we were asked to generate feedback warnings, do so. */
     386        43436 :         if (pg_plan_advice_feedback_warnings)
     387        43313 :             pgpa_planner_feedback_warning(feedback);
     388              :     }
     389              : 
     390              :     /* Push whatever data we're saving into the PlannedStmt. */
     391        86897 :     if (pgpa_items != NIL)
     392        86838 :         pstmt->extension_state =
     393        86838 :             lappend(pstmt->extension_state,
     394        86838 :                     makeDefElem("pg_plan_advice", (Node *) pgpa_items, -1));
     395              : 
     396              :     /* Pass call to previous hook. */
     397        86897 :     if (prev_planner_shutdown)
     398            0 :         (*prev_planner_shutdown) (glob, parse, query_string, pstmt);
     399        86897 : }
     400              : 
     401              : /*
     402              :  * Hook function for build_simple_rel().
     403              :  */
     404              : static void
     405       158225 : pgpa_build_simple_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
     406              : {
     407              :     pgpa_planner_state *pps;
     408       158225 :     pgpa_planner_info *proot = NULL;
     409              : 
     410              :     /* Fetch our private state, set up by pgpa_planner_setup(). */
     411       158225 :     pps = GetPlannerGlobalExtensionState(root->glob, planner_extension_id);
     412              : 
     413              :     /*
     414              :      * Look up the pgpa_planner_info for this subquery, and make sure we've
     415              :      * saved a range table identifier.
     416              :      */
     417       158225 :     if (pps != NULL)
     418              :     {
     419       154420 :         proot = pgpa_planner_get_proot(pps, root);
     420       154420 :         pgpa_compute_rt_identifier(proot, root, rel);
     421              :     }
     422              : 
     423              :     /* If query advice was provided, search for relevant entries. */
     424       158225 :     if (pps != NULL && pps->trove != NULL)
     425              :     {
     426              :         pgpa_identifier *rid;
     427              :         pgpa_trove_result tresult_scan;
     428              :         pgpa_trove_result tresult_rel;
     429              : 
     430              :         /* Search for scan advice and general rel advice. */
     431        77252 :         rid = &proot->rid_array[rel->relid - 1];
     432        77252 :         pgpa_trove_lookup(pps->trove, PGPA_TROVE_LOOKUP_SCAN, 1, rid,
     433              :                           &tresult_scan);
     434        77252 :         pgpa_trove_lookup(pps->trove, PGPA_TROVE_LOOKUP_REL, 1, rid,
     435              :                           &tresult_rel);
     436              : 
     437              :         /* If relevant entries were found, apply them. */
     438        77252 :         if (tresult_scan.indexes != NULL || tresult_rel.indexes != NULL)
     439              :         {
     440        74483 :             uint64      original_mask = rel->pgs_mask;
     441              : 
     442        74483 :             pgpa_planner_apply_scan_advice(rel,
     443              :                                            tresult_scan.entries,
     444              :                                            tresult_scan.indexes,
     445              :                                            tresult_rel.entries,
     446              :                                            tresult_rel.indexes);
     447              : 
     448              :             /* Emit debugging message, if enabled. */
     449        74483 :             if (pg_plan_advice_trace_mask && original_mask != rel->pgs_mask)
     450              :             {
     451            0 :                 if (root->plan_name != NULL)
     452            0 :                     ereport(WARNING,
     453              :                             (errmsg("strategy mask for RTI %u in subplan \"%s\" changed from 0x%" PRIx64 " to 0x%" PRIx64,
     454              :                                     rel->relid, root->plan_name,
     455              :                                     original_mask, rel->pgs_mask)));
     456              :                 else
     457            0 :                     ereport(WARNING,
     458              :                             (errmsg("strategy mask for RTI %u changed from 0x%" PRIx64 " to 0x%" PRIx64,
     459              :                                     rel->relid, original_mask,
     460              :                                     rel->pgs_mask)));
     461              :             }
     462              :         }
     463              :     }
     464              : 
     465              :     /* Pass call to previous hook. */
     466       158225 :     if (prev_build_simple_rel)
     467            0 :         (*prev_build_simple_rel) (root, rel, rte);
     468       158225 : }
     469              : 
     470              : /*
     471              :  * Enforce any provided advice that is relevant to any method of implementing
     472              :  * this join.
     473              :  *
     474              :  * Although we're passed the outerrel and innerrel here, those are just
     475              :  * whatever values happened to prompt the creation of this joinrel; they
     476              :  * shouldn't really influence our choice of what advice to apply.
     477              :  */
     478              : static void
     479        50148 : pgpa_joinrel_setup(PlannerInfo *root, RelOptInfo *joinrel,
     480              :                    RelOptInfo *outerrel, RelOptInfo *innerrel,
     481              :                    SpecialJoinInfo *sjinfo, List *restrictlist)
     482              : {
     483              :     pgpa_join_state *pjs;
     484              : 
     485              :     Assert(bms_membership(joinrel->relids) == BMS_MULTIPLE);
     486              : 
     487              :     /* Get our private state information for this join. */
     488        50148 :     pjs = pgpa_get_join_state(root, joinrel, outerrel, innerrel);
     489              : 
     490              :     /* If there is relevant advice, call a helper function to apply it. */
     491        50148 :     if (pjs != NULL)
     492              :     {
     493        24046 :         uint64      original_mask = joinrel->pgs_mask;
     494              : 
     495        24046 :         pgpa_planner_apply_joinrel_advice(&joinrel->pgs_mask,
     496              :                                           root->plan_name,
     497              :                                           pjs);
     498              : 
     499              :         /* Emit debugging message, if enabled. */
     500        24046 :         if (pg_plan_advice_trace_mask && original_mask != joinrel->pgs_mask)
     501              :         {
     502            0 :             if (root->plan_name != NULL)
     503            0 :                 ereport(WARNING,
     504              :                         (errmsg("strategy mask for join on RTIs %s in subplan \"%s\" changed from 0x%" PRIx64 " to 0x%" PRIx64,
     505              :                                 pgpa_bms_to_cstring(joinrel->relids),
     506              :                                 root->plan_name,
     507              :                                 original_mask,
     508              :                                 joinrel->pgs_mask)));
     509              :             else
     510            0 :                 ereport(WARNING,
     511              :                         (errmsg("strategy mask for join on RTIs %s changed from 0x%" PRIx64 " to 0x%" PRIx64,
     512              :                                 pgpa_bms_to_cstring(joinrel->relids),
     513              :                                 original_mask,
     514              :                                 joinrel->pgs_mask)));
     515              :         }
     516              :     }
     517              : 
     518              :     /* Pass call to previous hook. */
     519        50148 :     if (prev_joinrel_setup)
     520            0 :         (*prev_joinrel_setup) (root, joinrel, outerrel, innerrel,
     521              :                                sjinfo, restrictlist);
     522        50148 : }
     523              : 
     524              : /*
     525              :  * Enforce any provided advice that is relevant to this particular method of
     526              :  * implementing this particular join.
     527              :  */
     528              : static void
     529       155296 : pgpa_join_path_setup(PlannerInfo *root, RelOptInfo *joinrel,
     530              :                      RelOptInfo *outerrel, RelOptInfo *innerrel,
     531              :                      JoinType jointype, JoinPathExtraData *extra)
     532              : {
     533              :     pgpa_join_state *pjs;
     534              : 
     535              :     Assert(bms_membership(joinrel->relids) == BMS_MULTIPLE);
     536              : 
     537              :     /*
     538              :      * If we're considering implementing a semijoin by making one side unique,
     539              :      * make a note of it in the pgpa_planner_state.
     540              :      */
     541       155296 :     if (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER)
     542              :     {
     543              :         pgpa_planner_state *pps;
     544              :         RelOptInfo *uniquerel;
     545              : 
     546         3200 :         uniquerel = jointype == JOIN_UNIQUE_OUTER ? outerrel : innerrel;
     547         3200 :         pps = GetPlannerGlobalExtensionState(root->glob, planner_extension_id);
     548         3200 :         if (pps != NULL &&
     549         3200 :             (pps->generate_advice_string || pps->generate_advice_feedback))
     550              :         {
     551              :             pgpa_planner_info *proot;
     552              :             MemoryContext oldcontext;
     553              : 
     554              :             /*
     555              :              * Get or create a pgpa_planner_info object, and then add the
     556              :              * relids from the unique side to proot->sj_unique_rels.
     557              :              *
     558              :              * We must be careful here to use a sufficiently long-lived
     559              :              * context, since we might have been called by GEQO. We want all
     560              :              * the data we store here (including the proot, if we create it)
     561              :              * to last for as long as the pgpa_planner_state.
     562              :              */
     563         3200 :             oldcontext = MemoryContextSwitchTo(pps->mcxt);
     564         3200 :             proot = pgpa_planner_get_proot(pps, root);
     565         3200 :             if (!list_member(proot->sj_unique_rels, uniquerel->relids))
     566         1442 :                 proot->sj_unique_rels = lappend(proot->sj_unique_rels,
     567         1442 :                                                 bms_copy(uniquerel->relids));
     568         3200 :             MemoryContextSwitchTo(oldcontext);
     569              :         }
     570              :     }
     571              : 
     572              :     /* Get our private state information for this join. */
     573       155296 :     pjs = pgpa_get_join_state(root, joinrel, outerrel, innerrel);
     574              : 
     575              :     /* If there is relevant advice, call a helper function to apply it. */
     576       155296 :     if (pjs != NULL)
     577              :     {
     578        73698 :         uint64      original_mask = extra->pgs_mask;
     579              : 
     580        73698 :         pgpa_planner_apply_join_path_advice(jointype,
     581              :                                             &extra->pgs_mask,
     582              :                                             root->plan_name,
     583              :                                             pjs);
     584              : 
     585              :         /* Emit debugging message, if enabled. */
     586        73698 :         if (pg_plan_advice_trace_mask && original_mask != extra->pgs_mask)
     587              :         {
     588            0 :             if (root->plan_name != NULL)
     589            0 :                 ereport(WARNING,
     590              :                         (errmsg("strategy mask for %s join on %s with outer %s and inner %s in subplan \"%s\" changed from 0x%" PRIx64 " to 0x%" PRIx64,
     591              :                                 pgpa_jointype_to_cstring(jointype),
     592              :                                 pgpa_bms_to_cstring(joinrel->relids),
     593              :                                 pgpa_bms_to_cstring(outerrel->relids),
     594              :                                 pgpa_bms_to_cstring(innerrel->relids),
     595              :                                 root->plan_name,
     596              :                                 original_mask,
     597              :                                 extra->pgs_mask)));
     598              :             else
     599            0 :                 ereport(WARNING,
     600              :                         (errmsg("strategy mask for %s join on %s with outer %s and inner %s changed from 0x%" PRIx64 " to 0x%" PRIx64,
     601              :                                 pgpa_jointype_to_cstring(jointype),
     602              :                                 pgpa_bms_to_cstring(joinrel->relids),
     603              :                                 pgpa_bms_to_cstring(outerrel->relids),
     604              :                                 pgpa_bms_to_cstring(innerrel->relids),
     605              :                                 original_mask,
     606              :                                 extra->pgs_mask)));
     607              :         }
     608              :     }
     609              : 
     610              :     /* Pass call to previous hook. */
     611       155296 :     if (prev_join_path_setup)
     612            0 :         (*prev_join_path_setup) (root, joinrel, outerrel, innerrel,
     613              :                                  jointype, extra);
     614       155296 : }
     615              : 
     616              : /*
     617              :  * Search for advice pertaining to a proposed join.
     618              :  */
     619              : static pgpa_join_state *
     620       205444 : pgpa_get_join_state(PlannerInfo *root, RelOptInfo *joinrel,
     621              :                     RelOptInfo *outerrel, RelOptInfo *innerrel)
     622              : {
     623              :     pgpa_planner_state *pps;
     624              :     pgpa_join_state *pjs;
     625       205444 :     bool        new_pjs = false;
     626              : 
     627              :     /* Fetch our private state, set up by pgpa_planner_setup(). */
     628       205444 :     pps = GetPlannerGlobalExtensionState(root->glob, planner_extension_id);
     629       205444 :     if (pps == NULL || pps->trove == NULL)
     630              :     {
     631              :         /* No advice applies to this query, hence none to this joinrel. */
     632       102445 :         return NULL;
     633              :     }
     634              : 
     635              :     /*
     636              :      * See whether we've previously associated a pgpa_join_state with this
     637              :      * joinrel. If we have not, we need to try to construct one. If we have,
     638              :      * then there are two cases: (a) if innerrel and outerrel are unchanged,
     639              :      * we can simply use it, and (b) if they have changed, we need to rejigger
     640              :      * the array of identifiers but can still skip the trove lookup.
     641              :      */
     642       102999 :     pjs = GetRelOptInfoExtensionState(joinrel, planner_extension_id);
     643       102999 :     if (pjs != NULL)
     644              :     {
     645        77852 :         if (pjs->join_indexes == NULL && pjs->rel_indexes == NULL)
     646              :         {
     647              :             /*
     648              :              * If there's no potentially relevant advice, then the presence of
     649              :              * this pgpa_join_state acts like a negative cache entry: it tells
     650              :              * us not to bother searching the trove for advice, because we
     651              :              * will not find any.
     652              :              */
     653         4154 :             return NULL;
     654              :         }
     655              : 
     656        73698 :         if (pjs->outerrel == outerrel && pjs->innerrel == innerrel)
     657              :         {
     658              :             /* No updates required, so just return. */
     659              :             /* XXX. Does this need to do something different under GEQO? */
     660        22903 :             return pjs;
     661              :         }
     662              :     }
     663              : 
     664              :     /*
     665              :      * If there's no pgpa_join_state yet, we need to allocate one. Trove keys
     666              :      * will not get built for RTE_JOIN RTEs, so the array may end up being
     667              :      * larger than needed. It's not worth trying to compute a perfectly
     668              :      * accurate count here.
     669              :      */
     670        75942 :     if (pjs == NULL)
     671              :     {
     672        25147 :         int         pessimistic_count = bms_num_members(joinrel->relids);
     673              : 
     674        25147 :         pjs = palloc0_object(pgpa_join_state);
     675        25147 :         pjs->rids = palloc_array(pgpa_identifier, pessimistic_count);
     676        25147 :         new_pjs = true;
     677              :     }
     678              : 
     679              :     /*
     680              :      * Either we just allocated a new pgpa_join_state, or the existing one
     681              :      * needs reconfiguring for a new innerrel and outerrel. The required array
     682              :      * size can't change, so we can overwrite the existing one.
     683              :      */
     684        75942 :     pjs->outerrel = outerrel;
     685        75942 :     pjs->innerrel = innerrel;
     686        75942 :     pjs->outer_count =
     687        75942 :         pgpa_compute_identifiers_by_relids(root, outerrel->relids, pjs->rids);
     688        75942 :     pjs->inner_count =
     689        75942 :         pgpa_compute_identifiers_by_relids(root, innerrel->relids,
     690        75942 :                                            pjs->rids + pjs->outer_count);
     691              : 
     692              :     /*
     693              :      * If we allocated a new pgpa_join_state, search our trove of advice for
     694              :      * relevant entries. The trove lookup will return the same results for
     695              :      * every outerrel/innerrel combination, so we don't need to repeat that
     696              :      * work every time.
     697              :      */
     698        75942 :     if (new_pjs)
     699              :     {
     700              :         pgpa_trove_result tresult;
     701              : 
     702              :         /* Find join entries. */
     703        25147 :         pgpa_trove_lookup(pps->trove, PGPA_TROVE_LOOKUP_JOIN,
     704        25147 :                           pjs->outer_count + pjs->inner_count,
     705              :                           pjs->rids, &tresult);
     706        25147 :         pjs->join_entries = tresult.entries;
     707        25147 :         pjs->join_indexes = tresult.indexes;
     708              : 
     709              :         /* Find rel entries. */
     710        25147 :         pgpa_trove_lookup(pps->trove, PGPA_TROVE_LOOKUP_REL,
     711        25147 :                           pjs->outer_count + pjs->inner_count,
     712              :                           pjs->rids, &tresult);
     713        25147 :         pjs->rel_entries = tresult.entries;
     714        25147 :         pjs->rel_indexes = tresult.indexes;
     715              : 
     716              :         /* Now that the new pgpa_join_state is fully valid, save a pointer. */
     717        25147 :         SetRelOptInfoExtensionState(joinrel, planner_extension_id, pjs);
     718              : 
     719              :         /*
     720              :          * If there was no relevant advice found, just return NULL. This
     721              :          * pgpa_join_state will stick around as a sort of negative cache
     722              :          * entry, so that future calls for this same joinrel quickly return
     723              :          * NULL.
     724              :          */
     725        25147 :         if (pjs->join_indexes == NULL && pjs->rel_indexes == NULL)
     726         1101 :             return NULL;
     727              :     }
     728              : 
     729        74841 :     return pjs;
     730              : }
     731              : 
     732              : /*
     733              :  * Enforce overall restrictions on a join relation that apply uniformly
     734              :  * regardless of the choice of inner and outer rel.
     735              :  */
     736              : static void
     737        24046 : pgpa_planner_apply_joinrel_advice(uint64 *pgs_mask_p, char *plan_name,
     738              :                                   pgpa_join_state *pjs)
     739              : {
     740        24046 :     int         i = -1;
     741              :     int         flags;
     742        24046 :     bool        gather_conflict = false;
     743        24046 :     uint64      gather_mask = 0;
     744        24046 :     Bitmapset  *gather_partial_match = NULL;
     745        24046 :     Bitmapset  *gather_full_match = NULL;
     746        24046 :     bool        partitionwise_conflict = false;
     747        24046 :     int         partitionwise_outcome = 0;
     748        24046 :     Bitmapset  *partitionwise_partial_match = NULL;
     749        24046 :     Bitmapset  *partitionwise_full_match = NULL;
     750              : 
     751              :     /* Iterate over all possibly-relevant advice. */
     752        79449 :     while ((i = bms_next_member(pjs->rel_indexes, i)) >= 0)
     753              :     {
     754        55403 :         pgpa_trove_entry *entry = &pjs->rel_entries[i];
     755              :         pgpa_itm_type itm;
     756        55403 :         bool        full_match = false;
     757        55403 :         uint64      my_gather_mask = 0;
     758        55403 :         int         my_partitionwise_outcome = 0;   /* >0 yes, <0 no */
     759              : 
     760              :         /*
     761              :          * For GATHER and GATHER_MERGE, if the specified relations exactly
     762              :          * match this joinrel, do whatever the advice says; otherwise, don't
     763              :          * allow Gather or Gather Merge at this level. For NO_GATHER, there
     764              :          * must be a single target relation which must be included in this
     765              :          * joinrel, so just don't allow Gather or Gather Merge here, full
     766              :          * stop.
     767              :          */
     768        55403 :         if (entry->tag == PGPA_TAG_NO_GATHER)
     769              :         {
     770        54457 :             my_gather_mask = PGS_CONSIDER_NONPARTIAL;
     771        54457 :             full_match = true;
     772              :         }
     773              :         else
     774              :         {
     775              :             int         total_count;
     776              : 
     777          946 :             total_count = pjs->outer_count + pjs->inner_count;
     778          946 :             itm = pgpa_identifiers_match_target(total_count, pjs->rids,
     779              :                                                 entry->target);
     780              :             Assert(itm != PGPA_ITM_DISJOINT);
     781              : 
     782          946 :             if (itm == PGPA_ITM_EQUAL)
     783              :             {
     784          276 :                 full_match = true;
     785          276 :                 if (entry->tag == PGPA_TAG_PARTITIONWISE)
     786          204 :                     my_partitionwise_outcome = 1;
     787           72 :                 else if (entry->tag == PGPA_TAG_GATHER)
     788           65 :                     my_gather_mask = PGS_GATHER;
     789            7 :                 else if (entry->tag == PGPA_TAG_GATHER_MERGE)
     790            7 :                     my_gather_mask = PGS_GATHER_MERGE;
     791              :                 else
     792            0 :                     elog(ERROR, "unexpected advice tag: %d",
     793              :                          (int) entry->tag);
     794              :             }
     795              :             else
     796              :             {
     797              :                 /*
     798              :                  * If specified relations don't exactly match this joinrel,
     799              :                  * then we should do the opposite of whatever the advice says.
     800              :                  * For instance, if we have PARTITIONWISE((a b c)) or
     801              :                  * GATHER((a b c)) and this joinrel covers {a, b} or {a, b, c,
     802              :                  * d} or {a, d}, we shouldn't plan it partitionwise or put a
     803              :                  * Gather or Gather Merge on it here.
     804              :                  *
     805              :                  * Also, we can't put a Gather or Gather Merge at this level
     806              :                  * if there is PARTITIONWISE advice that overlaps with it,
     807              :                  * unless the PARTITIONWISE advice covers a subset of the
     808              :                  * relations in the joinrel. To continue the previous example,
     809              :                  * PARTITIONWISE((a b c)) is logically incompatible with
     810              :                  * GATHER((a b)) or GATHER((a d)), but not with GATHER((a b c
     811              :                  * d)).
     812              :                  *
     813              :                  * Conversely, we can't proceed partitionwise at this level if
     814              :                  * there is overlapping GATHER or GATHER_MERGE advice, unless
     815              :                  * that advice covers a superset of the relations in this
     816              :                  * joinrel. This is just the flip side of the preceding point.
     817              :                  */
     818          670 :                 if (entry->tag == PGPA_TAG_PARTITIONWISE)
     819              :                 {
     820          620 :                     my_partitionwise_outcome = -1;
     821          620 :                     if (itm != PGPA_ITM_TARGETS_ARE_SUBSET)
     822           86 :                         my_gather_mask = PGS_CONSIDER_NONPARTIAL;
     823              :                 }
     824           50 :                 else if (entry->tag == PGPA_TAG_GATHER ||
     825           19 :                          entry->tag == PGPA_TAG_GATHER_MERGE)
     826              :                 {
     827           50 :                     my_gather_mask = PGS_CONSIDER_NONPARTIAL;
     828           50 :                     if (itm != PGPA_ITM_KEYS_ARE_SUBSET)
     829           42 :                         my_partitionwise_outcome = -1;
     830              :                 }
     831              :                 else
     832            0 :                     elog(ERROR, "unexpected advice tag: %d",
     833              :                          (int) entry->tag);
     834              :             }
     835              :         }
     836              : 
     837              :         /*
     838              :          * If we set my_gather_mask up above, then we (1) make a note if the
     839              :          * advice conflicted, (2) remember the mask value, and (3) remember
     840              :          * whether this was a full or partial match.
     841              :          */
     842        55403 :         if (my_gather_mask != 0)
     843              :         {
     844        54665 :             if (gather_mask != 0 && gather_mask != my_gather_mask)
     845            1 :                 gather_conflict = true;
     846        54665 :             gather_mask = my_gather_mask;
     847        54665 :             if (full_match)
     848        54529 :                 gather_full_match = bms_add_member(gather_full_match, i);
     849              :             else
     850          136 :                 gather_partial_match = bms_add_member(gather_partial_match, i);
     851              :         }
     852              : 
     853              :         /*
     854              :          * Likewise, if we set my_partitionwise_outcome up above, then we (1)
     855              :          * make a note if the advice conflicted, (2) remember what the desired
     856              :          * outcome was, and (3) remember whether this was a full or partial
     857              :          * match.
     858              :          */
     859        55403 :         if (my_partitionwise_outcome != 0)
     860              :         {
     861          866 :             if (partitionwise_outcome != 0 &&
     862              :                 partitionwise_outcome != my_partitionwise_outcome)
     863            2 :                 partitionwise_conflict = true;
     864          866 :             partitionwise_outcome = my_partitionwise_outcome;
     865          866 :             if (full_match)
     866              :                 partitionwise_full_match =
     867          204 :                     bms_add_member(partitionwise_full_match, i);
     868              :             else
     869              :                 partitionwise_partial_match =
     870          662 :                     bms_add_member(partitionwise_partial_match, i);
     871              :         }
     872              :     }
     873              : 
     874              :     /*
     875              :      * Mark every Gather-related piece of advice as partially matched, and if
     876              :      * the set of targets exactly matched this relation, fully matched. If
     877              :      * there was a conflict, mark them all as conflicting.
     878              :      */
     879        24046 :     flags = PGPA_TE_MATCH_PARTIAL;
     880        24046 :     if (gather_conflict)
     881            1 :         flags |= PGPA_TE_CONFLICTING;
     882        24046 :     pgpa_trove_set_flags(pjs->rel_entries, gather_partial_match, flags);
     883        24046 :     flags |= PGPA_TE_MATCH_FULL;
     884        24046 :     pgpa_trove_set_flags(pjs->rel_entries, gather_full_match, flags);
     885              : 
     886              :     /* Likewise for partitionwise advice. */
     887        24046 :     flags = PGPA_TE_MATCH_PARTIAL;
     888        24046 :     if (partitionwise_conflict)
     889            2 :         flags |= PGPA_TE_CONFLICTING;
     890        24046 :     pgpa_trove_set_flags(pjs->rel_entries, partitionwise_partial_match, flags);
     891        24046 :     flags |= PGPA_TE_MATCH_FULL;
     892        24046 :     pgpa_trove_set_flags(pjs->rel_entries, partitionwise_full_match, flags);
     893              : 
     894              :     /*
     895              :      * Enforce restrictions on the Gather/Gather Merge.  Only clear bits here,
     896              :      * so that we still respect the enable_* GUCs. Do nothing if the advice
     897              :      * conflicts.
     898              :      */
     899        24046 :     if (gather_mask != 0 && !gather_conflict)
     900              :     {
     901              :         uint64      all_gather_mask;
     902              : 
     903        23388 :         all_gather_mask =
     904              :             PGS_GATHER | PGS_GATHER_MERGE | PGS_CONSIDER_NONPARTIAL;
     905        23388 :         *pgs_mask_p &= ~(all_gather_mask & ~gather_mask);
     906              :     }
     907              : 
     908              :     /*
     909              :      * As above, but for partitionwise advice.
     910              :      *
     911              :      * To induce a partitionwise join, we disable all the ordinary means of
     912              :      * performing a join, so that an Append or MergeAppend path will hopefully
     913              :      * be chosen.
     914              :      *
     915              :      * To prevent one, we just disable Append and MergeAppend.  Note that we
     916              :      * must not unset PGS_CONSIDER_PARTITIONWISE even when we don't want a
     917              :      * partitionwise join here, because we might want one at a higher level
     918              :      * that will construct its own paths using the ones from this level.
     919              :      */
     920        24046 :     if (partitionwise_outcome != 0 && !partitionwise_conflict)
     921              :     {
     922          691 :         if (partitionwise_outcome > 0)
     923          202 :             *pgs_mask_p = (*pgs_mask_p & ~PGS_JOIN_ANY);
     924              :         else
     925          489 :             *pgs_mask_p &= ~(PGS_APPEND | PGS_MERGE_APPEND);
     926              :     }
     927        24046 : }
     928              : 
     929              : /*
     930              :  * Enforce restrictions on the join order or join method.
     931              :  */
     932              : static void
     933        73698 : pgpa_planner_apply_join_path_advice(JoinType jointype, uint64 *pgs_mask_p,
     934              :                                     char *plan_name,
     935              :                                     pgpa_join_state *pjs)
     936              : {
     937        73698 :     int         i = -1;
     938        73698 :     Bitmapset  *jo_permit_indexes = NULL;
     939        73698 :     Bitmapset  *jo_deny_indexes = NULL;
     940        73698 :     Bitmapset  *jo_deny_rel_indexes = NULL;
     941        73698 :     Bitmapset  *jm_indexes = NULL;
     942        73698 :     bool        jm_conflict = false;
     943        73698 :     uint64      join_mask = 0;
     944        73698 :     Bitmapset  *sj_permit_indexes = NULL;
     945        73698 :     Bitmapset  *sj_deny_indexes = NULL;
     946              : 
     947              :     /*
     948              :      * Reconsider PARTITIONWISE(...) advice.
     949              :      *
     950              :      * We already thought about this for the joinrel as a whole, but in some
     951              :      * cases, partitionwise advice can also constrain the join order. For
     952              :      * instance, if the advice says PARTITIONWISE((t1 t2)), we shouldn't build
     953              :      * join paths for any joinrel that includes t1 or t2 unless it also
     954              :      * includes the other. In general, the partitionwise operation must have
     955              :      * already been completed within one side of the current join or the
     956              :      * other, else the join order is impermissible.
     957              :      *
     958              :      * NB: It might seem tempting to try to deal with PARTITIONWISE advice
     959              :      * entirely in this function, but that doesn't work. Here, we can only
     960              :      * affect the pgs_mask within a particular JoinPathExtraData, that is, for
     961              :      * a particular choice of innerrel and outerrel. Partitionwise paths are
     962              :      * not built that way, so we must set pgs_mask for the RelOptInfo, which
     963              :      * is best done in pgpa_planner_apply_joinrel_advice.
     964              :      */
     965       264370 :     while ((i = bms_next_member(pjs->rel_indexes, i)) >= 0)
     966              :     {
     967       190672 :         pgpa_trove_entry *entry = &pjs->rel_entries[i];
     968              :         pgpa_itm_type inner_itm;
     969              :         pgpa_itm_type outer_itm;
     970              : 
     971       190672 :         if (entry->tag != PGPA_TAG_PARTITIONWISE)
     972       188572 :             continue;
     973              : 
     974         2100 :         outer_itm = pgpa_identifiers_match_target(pjs->outer_count,
     975              :                                                   pjs->rids, entry->target);
     976         2100 :         if (outer_itm == PGPA_ITM_EQUAL ||
     977              :             outer_itm == PGPA_ITM_TARGETS_ARE_SUBSET)
     978          694 :             continue;
     979              : 
     980         1406 :         inner_itm = pgpa_identifiers_match_target(pjs->inner_count,
     981         1406 :                                                   pjs->rids + pjs->outer_count,
     982              :                                                   entry->target);
     983         1406 :         if (inner_itm == PGPA_ITM_EQUAL ||
     984              :             inner_itm == PGPA_ITM_TARGETS_ARE_SUBSET)
     985          694 :             continue;
     986              : 
     987          712 :         jo_deny_rel_indexes = bms_add_member(jo_deny_rel_indexes, i);
     988              :     }
     989              : 
     990              :     /* Iterate over advice that pertains to the join order and method. */
     991        73698 :     i = -1;
     992       285150 :     while ((i = bms_next_member(pjs->join_indexes, i)) >= 0)
     993              :     {
     994       211452 :         pgpa_trove_entry *entry = &pjs->join_entries[i];
     995              :         uint64      my_join_mask;
     996              : 
     997              :         /* Handle join order advice. */
     998       211452 :         if (entry->tag == PGPA_TAG_JOIN_ORDER)
     999        72294 :         {
    1000              :             pgpa_jo_outcome jo_outcome;
    1001              : 
    1002        72294 :             jo_outcome = pgpa_join_order_permits_join(pjs->outer_count,
    1003              :                                                       pjs->inner_count,
    1004              :                                                       pjs->rids,
    1005              :                                                       entry);
    1006        72294 :             if (jo_outcome == PGPA_JO_PERMITTED)
    1007        20378 :                 jo_permit_indexes = bms_add_member(jo_permit_indexes, i);
    1008        51916 :             else if (jo_outcome == PGPA_JO_DENIED)
    1009        51870 :                 jo_deny_indexes = bms_add_member(jo_deny_indexes, i);
    1010        72294 :             continue;
    1011              :         }
    1012              : 
    1013              :         /* Handle join method advice. */
    1014       139158 :         my_join_mask = pgpa_join_strategy_mask_from_advice_tag(entry->tag);
    1015       139158 :         if (my_join_mask != 0)
    1016       135594 :         {
    1017              :             bool        permit;
    1018              :             bool        restrict_method;
    1019              : 
    1020       135594 :             if (entry->tag == PGPA_TAG_FOREIGN_JOIN)
    1021            2 :                 permit = pgpa_opaque_join_permits_join(pjs->outer_count,
    1022              :                                                        pjs->inner_count,
    1023              :                                                        pjs->rids,
    1024              :                                                        entry,
    1025              :                                                        &restrict_method);
    1026              :             else
    1027       135592 :                 permit = pgpa_join_method_permits_join(pjs->outer_count,
    1028              :                                                        pjs->inner_count,
    1029              :                                                        pjs->rids,
    1030              :                                                        entry,
    1031              :                                                        &restrict_method);
    1032       135594 :             if (!permit)
    1033        41303 :                 jo_deny_indexes = bms_add_member(jo_deny_indexes, i);
    1034        94291 :             else if (restrict_method)
    1035              :             {
    1036        32927 :                 jm_indexes = bms_add_member(jm_indexes, i);
    1037        32927 :                 if (join_mask != 0 && join_mask != my_join_mask)
    1038            1 :                     jm_conflict = true;
    1039        32927 :                 join_mask = my_join_mask;
    1040              :             }
    1041       135594 :             continue;
    1042              :         }
    1043              : 
    1044              :         /* Handle semijoin uniqueness advice. */
    1045         3564 :         if (entry->tag == PGPA_TAG_SEMIJOIN_UNIQUE ||
    1046         2840 :             entry->tag == PGPA_TAG_SEMIJOIN_NON_UNIQUE)
    1047         3564 :         {
    1048              :             bool        outer_side_nullable;
    1049              :             bool        restrict_method;
    1050              : 
    1051              :             /* Planner has nullable side of the semijoin on the outer side? */
    1052         3564 :             outer_side_nullable = (jointype == JOIN_UNIQUE_OUTER ||
    1053              :                                    jointype == JOIN_RIGHT_SEMI);
    1054              : 
    1055         3564 :             if (!pgpa_semijoin_permits_join(pjs->outer_count,
    1056              :                                             pjs->inner_count,
    1057              :                                             pjs->rids,
    1058              :                                             entry,
    1059              :                                             outer_side_nullable,
    1060              :                                             &restrict_method))
    1061            9 :                 jo_deny_indexes = bms_add_member(jo_deny_indexes, i);
    1062         3555 :             else if (restrict_method)
    1063              :             {
    1064              :                 bool        advice_unique;
    1065              :                 bool        jt_unique;
    1066              :                 bool        jt_non_unique;
    1067              : 
    1068              :                 /* Advice wants to unique-ify and use a regular join? */
    1069         3013 :                 advice_unique = (entry->tag == PGPA_TAG_SEMIJOIN_UNIQUE);
    1070              : 
    1071              :                 /* Planner is trying to unique-ify and use a regular join? */
    1072         3013 :                 jt_unique = (jointype == JOIN_UNIQUE_INNER ||
    1073              :                              jointype == JOIN_UNIQUE_OUTER);
    1074              : 
    1075              :                 /* Planner is trying a semi-join, without unique-ifying? */
    1076         3013 :                 jt_non_unique = (jointype == JOIN_SEMI ||
    1077              :                                  jointype == JOIN_RIGHT_SEMI);
    1078              : 
    1079         3013 :                 if (!jt_unique && !jt_non_unique)
    1080              :                 {
    1081              :                     /*
    1082              :                      * This doesn't seem to be a semijoin to which SJ_UNIQUE
    1083              :                      * or SJ_NON_UNIQUE can be applied.
    1084              :                      */
    1085            1 :                     entry->flags |= PGPA_TE_INAPPLICABLE;
    1086              :                 }
    1087         3012 :                 else if (advice_unique != jt_unique)
    1088         1482 :                     sj_deny_indexes = bms_add_member(sj_deny_indexes, i);
    1089              :                 else
    1090         1530 :                     sj_permit_indexes = bms_add_member(sj_permit_indexes, i);
    1091              :             }
    1092         3564 :             continue;
    1093              :         }
    1094              :     }
    1095              : 
    1096              :     /*
    1097              :      * If the advice indicates both that this join order is permissible and
    1098              :      * also that it isn't, then mark advice related to the join order as
    1099              :      * conflicting.
    1100              :      */
    1101        73698 :     if (jo_permit_indexes != NULL &&
    1102        20375 :         (jo_deny_indexes != NULL || jo_deny_rel_indexes != NULL))
    1103              :     {
    1104            3 :         pgpa_trove_set_flags(pjs->join_entries, jo_permit_indexes,
    1105              :                              PGPA_TE_CONFLICTING);
    1106            3 :         pgpa_trove_set_flags(pjs->join_entries, jo_deny_indexes,
    1107              :                              PGPA_TE_CONFLICTING);
    1108            3 :         pgpa_trove_set_flags(pjs->rel_entries, jo_deny_rel_indexes,
    1109              :                              PGPA_TE_CONFLICTING);
    1110              :     }
    1111              : 
    1112              :     /*
    1113              :      * If more than one join method specification is relevant here and they
    1114              :      * differ, mark them all as conflicting.
    1115              :      */
    1116        73698 :     if (jm_conflict)
    1117            1 :         pgpa_trove_set_flags(pjs->join_entries, jm_indexes,
    1118              :                              PGPA_TE_CONFLICTING);
    1119              : 
    1120              :     /* If semijoin advice says both yes and no, mark it all as conflicting. */
    1121        73698 :     if (sj_permit_indexes != NULL && sj_deny_indexes != NULL)
    1122              :     {
    1123            4 :         pgpa_trove_set_flags(pjs->join_entries, sj_permit_indexes,
    1124              :                              PGPA_TE_CONFLICTING);
    1125            4 :         pgpa_trove_set_flags(pjs->join_entries, sj_deny_indexes,
    1126              :                              PGPA_TE_CONFLICTING);
    1127              :     }
    1128              : 
    1129              :     /*
    1130              :      * Enforce restrictions on the join order and join method, and any
    1131              :      * semijoin-related restrictions. Only clear bits here, so that we still
    1132              :      * respect the enable_* GUCs. Do nothing in cases where the advice on a
    1133              :      * single topic conflicts.
    1134              :      */
    1135        73698 :     if ((jo_deny_indexes != NULL || jo_deny_rel_indexes != NULL) &&
    1136              :         jo_permit_indexes == NULL)
    1137        52580 :         *pgs_mask_p &= ~PGS_JOIN_ANY;
    1138        73698 :     if (join_mask != 0 && !jm_conflict)
    1139        32925 :         *pgs_mask_p &= ~(PGS_JOIN_ANY & ~join_mask);
    1140        73698 :     if (sj_deny_indexes != NULL && sj_permit_indexes == NULL)
    1141         1478 :         *pgs_mask_p &= ~PGS_JOIN_ANY;
    1142        73698 : }
    1143              : 
    1144              : /*
    1145              :  * Translate an advice tag into a path generation strategy mask.
    1146              :  *
    1147              :  * This function can be called with tag types that don't represent join
    1148              :  * strategies. In such cases, we just return 0, which can't be confused with
    1149              :  * a valid mask.
    1150              :  */
    1151              : static uint64
    1152       139158 : pgpa_join_strategy_mask_from_advice_tag(pgpa_advice_tag_type tag)
    1153              : {
    1154       139158 :     switch (tag)
    1155              :     {
    1156            2 :         case PGPA_TAG_FOREIGN_JOIN:
    1157            2 :             return PGS_FOREIGNJOIN;
    1158         5932 :         case PGPA_TAG_MERGE_JOIN_PLAIN:
    1159         5932 :             return PGS_MERGEJOIN_PLAIN;
    1160          176 :         case PGPA_TAG_MERGE_JOIN_MATERIALIZE:
    1161          176 :             return PGS_MERGEJOIN_MATERIALIZE;
    1162        82756 :         case PGPA_TAG_NESTED_LOOP_PLAIN:
    1163        82756 :             return PGS_NESTLOOP_PLAIN;
    1164         3026 :         case PGPA_TAG_NESTED_LOOP_MATERIALIZE:
    1165         3026 :             return PGS_NESTLOOP_MATERIALIZE;
    1166         1558 :         case PGPA_TAG_NESTED_LOOP_MEMOIZE:
    1167         1558 :             return PGS_NESTLOOP_MEMOIZE;
    1168        42144 :         case PGPA_TAG_HASH_JOIN:
    1169        42144 :             return PGS_HASHJOIN;
    1170         3564 :         default:
    1171         3564 :             return 0;
    1172              :     }
    1173              : }
    1174              : 
    1175              : /*
    1176              :  * Does a certain item of join order advice permit a certain join?
    1177              :  *
    1178              :  * Returns PGPA_JO_DENIED if the advice is incompatible with the proposed
    1179              :  * join order.
    1180              :  *
    1181              :  * Returns PGPA_JO_PERMITTED if the advice specifies exactly the proposed
    1182              :  * join order. This implies that a partitionwise join should not be
    1183              :  * performed at this level; rather, one of the traditional join methods
    1184              :  * should be used.
    1185              :  *
    1186              :  * Returns PGPA_JO_INDIFFERENT if the advice does not care what happens.
    1187              :  * We use this for unordered JOIN_ORDER sublists, which are compatible with
    1188              :  * partitionwise join but do not mandate it.
    1189              :  */
    1190              : static pgpa_jo_outcome
    1191        72294 : pgpa_join_order_permits_join(int outer_count, int inner_count,
    1192              :                              pgpa_identifier *rids,
    1193              :                              pgpa_trove_entry *entry)
    1194              : {
    1195        72294 :     bool        loop = true;
    1196        72294 :     bool        sublist = false;
    1197              :     int         length;
    1198              :     int         outer_length;
    1199        72294 :     pgpa_advice_target *target = entry->target;
    1200              :     pgpa_advice_target *prefix_target;
    1201              : 
    1202              :     /* We definitely have at least a partial match for this trove entry. */
    1203        72294 :     entry->flags |= PGPA_TE_MATCH_PARTIAL;
    1204              : 
    1205              :     /*
    1206              :      * Find the innermost sublist that contains all keys; if no sublist does,
    1207              :      * then continue processing with the toplevel list.
    1208              :      *
    1209              :      * For example, if the advice says JOIN_ORDER(t1 t2 (t3 t4 t5)), then we
    1210              :      * should evaluate joins that only involve t3, t4, and/or t5 against the
    1211              :      * (t3 t4 t5) sublist, and others against the full list.
    1212              :      *
    1213              :      * Note that (1) outermost sublist is always ordered and (2) whenever we
    1214              :      * zoom into an unordered sublist, we instantly return
    1215              :      * PGPA_JO_INDIFFERENT.
    1216              :      */
    1217       147568 :     while (loop)
    1218              :     {
    1219              :         Assert(target->ttype == PGPA_TARGET_ORDERED_LIST);
    1220              : 
    1221        75320 :         loop = false;
    1222       376998 :         foreach_ptr(pgpa_advice_target, child_target, target->children)
    1223              :         {
    1224              :             pgpa_itm_type itm;
    1225              : 
    1226       229476 :             if (child_target->ttype == PGPA_TARGET_IDENTIFIER)
    1227       208236 :                 continue;
    1228              : 
    1229        21240 :             itm = pgpa_identifiers_match_target(outer_count + inner_count,
    1230              :                                                 rids, child_target);
    1231        21240 :             if (itm == PGPA_ITM_EQUAL || itm == PGPA_ITM_KEYS_ARE_SUBSET)
    1232              :             {
    1233         3072 :                 if (child_target->ttype == PGPA_TARGET_ORDERED_LIST)
    1234              :                 {
    1235         3026 :                     target = child_target;
    1236         3026 :                     sublist = true;
    1237         3026 :                     loop = true;
    1238         3026 :                     break;
    1239              :                 }
    1240              :                 else
    1241              :                 {
    1242              :                     Assert(child_target->ttype == PGPA_TARGET_UNORDERED_LIST);
    1243           46 :                     return PGPA_JO_INDIFFERENT;
    1244              :                 }
    1245              :             }
    1246              :         }
    1247              :     }
    1248              : 
    1249              :     /*
    1250              :      * Try to find a prefix of the selected join order list that is exactly
    1251              :      * equal to the outer side of the proposed join.
    1252              :      */
    1253        72248 :     length = list_length(target->children);
    1254        72248 :     prefix_target = palloc0_object(pgpa_advice_target);
    1255        72248 :     prefix_target->ttype = PGPA_TARGET_ORDERED_LIST;
    1256        84569 :     for (outer_length = 1; outer_length <= length; ++outer_length)
    1257              :     {
    1258              :         pgpa_itm_type itm;
    1259              : 
    1260              :         /* Avoid leaking memory in every loop iteration. */
    1261        84566 :         if (prefix_target->children != NULL)
    1262        12318 :             list_free(prefix_target->children);
    1263        84566 :         prefix_target->children = list_copy_head(target->children,
    1264              :                                                  outer_length);
    1265              : 
    1266              :         /* Search, hoping to find an exact match. */
    1267        84566 :         itm = pgpa_identifiers_match_target(outer_count, rids, prefix_target);
    1268        84566 :         if (itm == PGPA_ITM_EQUAL)
    1269        26399 :             break;
    1270              : 
    1271              :         /*
    1272              :          * If the prefix of the join order list that we're considering
    1273              :          * includes some but not all of the outer rels, we can make the prefix
    1274              :          * longer to find an exact match. But if the advice hasn't mentioned
    1275              :          * everything that's part of our outer rel yet, but has mentioned
    1276              :          * things that are not, then this join doesn't match the join order
    1277              :          * list.
    1278              :          */
    1279        58167 :         if (itm != PGPA_ITM_TARGETS_ARE_SUBSET)
    1280        45846 :             return PGPA_JO_DENIED;
    1281              :     }
    1282              : 
    1283              :     /*
    1284              :      * If the previous loop stopped before the prefix_target included the
    1285              :      * entire join order list, then the next member of the join order list
    1286              :      * must exactly match the inner side of the join.
    1287              :      *
    1288              :      * Example: Given JOIN_ORDER(t1 t2 (t3 t4 t5)), if the outer side of the
    1289              :      * current join includes only t1, then the inner side must be exactly t2;
    1290              :      * if the outer side includes both t1 and t2, then the inner side must
    1291              :      * include exactly t3, t4, and t5.
    1292              :      */
    1293        26402 :     if (outer_length < length)
    1294              :     {
    1295              :         pgpa_advice_target *inner_target;
    1296              :         pgpa_itm_type itm;
    1297              : 
    1298        26385 :         inner_target = list_nth(target->children, outer_length);
    1299              : 
    1300        26385 :         itm = pgpa_identifiers_match_target(inner_count, rids + outer_count,
    1301              :                                             inner_target);
    1302              : 
    1303              :         /*
    1304              :          * Before returning, consider whether we need to mark this entry as
    1305              :          * fully matched. If we're considering the full list rather than a
    1306              :          * sublist, and if we found every item but one on the outer side of
    1307              :          * the join and the last item on the inner side of the join, then the
    1308              :          * answer is yes.
    1309              :          */
    1310        26385 :         if (!sublist && outer_length + 1 == length && itm == PGPA_ITM_EQUAL)
    1311        16329 :             entry->flags |= PGPA_TE_MATCH_FULL;
    1312              : 
    1313        26385 :         return (itm == PGPA_ITM_EQUAL) ? PGPA_JO_PERMITTED : PGPA_JO_DENIED;
    1314              :     }
    1315              : 
    1316              :     /*
    1317              :      * If we get here, then the outer side of the join includes the entirety
    1318              :      * of the join order list. In this case, we behave differently depending
    1319              :      * on whether we're looking at the top-level join order list or sublist.
    1320              :      * At the top-level, we treat the specified list as mandating that the
    1321              :      * actual join order has the given list as a prefix, but a sublist
    1322              :      * requires an exact match.
    1323              :      *
    1324              :      * Example: Given JOIN_ORDER(t1 t2 (t3 t4 t5)), we must start by joining
    1325              :      * all five of those relations and in that sequence, but once that is
    1326              :      * done, it's OK to join any other rels that are part of the join problem.
    1327              :      * This allows a user to specify the driving table and perhaps the first
    1328              :      * few things to which it should be joined while leaving the rest of the
    1329              :      * join order up the optimizer. But it seems like it would be surprising,
    1330              :      * given that specification, if the user could add t6 to the (t3 t4 t5)
    1331              :      * sub-join, so we don't allow that. If we did want to allow it, the logic
    1332              :      * earlier in this function would require substantial adjustment: we could
    1333              :      * allow the t3-t4-t5-t6 join to be built here, but the next step of
    1334              :      * joining t1-t2 to the result would still be rejected.
    1335              :      */
    1336           17 :     if (!sublist)
    1337           17 :         entry->flags |= PGPA_TE_MATCH_FULL;
    1338           17 :     return sublist ? PGPA_JO_DENIED : PGPA_JO_PERMITTED;
    1339              : }
    1340              : 
    1341              : /*
    1342              :  * Does a certain item of join method advice permit a certain join?
    1343              :  *
    1344              :  * Advice such as HASH_JOIN((x y)) means that there should be a hash join with
    1345              :  * exactly x and y on the inner side. Obviously, this means that if we are
    1346              :  * considering a join with exactly x and y on the inner side, we should enforce
    1347              :  * the use of a hash join. However, it also means that we must reject some
    1348              :  * incompatible join orders entirely.  For example, a join with exactly x
    1349              :  * and y on the outer side shouldn't be allowed, because such paths might win
    1350              :  * over the advice-driven path on cost.
    1351              :  *
    1352              :  * To accommodate these requirements, this function returns true if the join
    1353              :  * should be allowed and false if it should not. Furthermore, *restrict_method
    1354              :  * is set to true if the join method should be enforced and false if not.
    1355              :  */
    1356              : static bool
    1357       135592 : pgpa_join_method_permits_join(int outer_count, int inner_count,
    1358              :                               pgpa_identifier *rids,
    1359              :                               pgpa_trove_entry *entry,
    1360              :                               bool *restrict_method)
    1361              : {
    1362       135592 :     pgpa_advice_target *target = entry->target;
    1363              :     pgpa_itm_type inner_itm;
    1364              :     pgpa_itm_type outer_itm;
    1365              :     pgpa_itm_type join_itm;
    1366              : 
    1367              :     /* We definitely have at least a partial match for this trove entry. */
    1368       135592 :     entry->flags |= PGPA_TE_MATCH_PARTIAL;
    1369              : 
    1370       135592 :     *restrict_method = false;
    1371              : 
    1372              :     /*
    1373              :      * If our inner rel mentions exactly the same relations as the advice
    1374              :      * target, allow the join and enforce the join method restriction.
    1375              :      *
    1376              :      * If our inner rel mentions a superset of the target relations, allow the
    1377              :      * join. The join we care about has already taken place, and this advice
    1378              :      * imposes no further restrictions.
    1379              :      */
    1380       135592 :     inner_itm = pgpa_identifiers_match_target(inner_count,
    1381       135592 :                                               rids + outer_count,
    1382              :                                               target);
    1383       135592 :     if (inner_itm == PGPA_ITM_EQUAL)
    1384              :     {
    1385        32925 :         entry->flags |= PGPA_TE_MATCH_FULL;
    1386        32925 :         *restrict_method = true;
    1387        32925 :         return true;
    1388              :     }
    1389       102667 :     else if (inner_itm == PGPA_ITM_TARGETS_ARE_SUBSET)
    1390        29151 :         return true;
    1391              : 
    1392              :     /*
    1393              :      * If our outer rel mentions a superset of the relations in the advice
    1394              :      * target, no restrictions apply, because the join we care about has
    1395              :      * already taken place.
    1396              :      *
    1397              :      * On the other hand, if our outer rel mentions exactly the relations
    1398              :      * mentioned in the advice target, the planner is trying to reverse the
    1399              :      * sides of the join as compared with our desired outcome. Reject that.
    1400              :      */
    1401        73516 :     outer_itm = pgpa_identifiers_match_target(outer_count,
    1402              :                                               rids, target);
    1403        73516 :     if (outer_itm == PGPA_ITM_TARGETS_ARE_SUBSET)
    1404        29151 :         return true;
    1405        44365 :     else if (outer_itm == PGPA_ITM_EQUAL)
    1406        32925 :         return false;
    1407              : 
    1408              :     /*
    1409              :      * If the advice target mentions only a single relation, the test below
    1410              :      * cannot ever pass, so save some work by exiting now.
    1411              :      */
    1412        11440 :     if (target->ttype == PGPA_TARGET_IDENTIFIER)
    1413            0 :         return false;
    1414              : 
    1415              :     /*
    1416              :      * If everything in the joinrel appears in the advice target, we're below
    1417              :      * the level of the join we want to control.
    1418              :      *
    1419              :      * For example, HASH_JOIN((x y)) doesn't restrict how x and y can be
    1420              :      * joined.
    1421              :      *
    1422              :      * This lookup shouldn't return PGPA_ITM_DISJOINT, because any such advice
    1423              :      * should not have been returned from the trove in the first place.
    1424              :      */
    1425        11440 :     join_itm = pgpa_identifiers_match_target(outer_count + inner_count,
    1426              :                                              rids, target);
    1427              :     Assert(join_itm != PGPA_ITM_DISJOINT);
    1428        11440 :     if (join_itm == PGPA_ITM_KEYS_ARE_SUBSET ||
    1429              :         join_itm == PGPA_ITM_EQUAL)
    1430         3062 :         return true;
    1431              : 
    1432              :     /*
    1433              :      * We've already permitted all allowable cases, so reject this.
    1434              :      *
    1435              :      * If we reach this point, then the advice overlaps with this join but
    1436              :      * isn't entirely contained within either side, and there's also at least
    1437              :      * one relation present in the join that isn't mentioned by the advice.
    1438              :      *
    1439              :      * For instance, in the HASH_JOIN((x y)) example, we would reach here if x
    1440              :      * were on one side of the join, y on the other, and at least one of the
    1441              :      * two sides also included some other relation, say t. In that case,
    1442              :      * accepting this join would allow the (x y t) joinrel to contain
    1443              :      * non-disabled paths that do not put (x y) on the inner side of a hash
    1444              :      * join; we could instead end up with something like (x JOIN t) JOIN y.
    1445              :      */
    1446         8378 :     return false;
    1447              : }
    1448              : 
    1449              : /*
    1450              :  * Does advice concerning an opaque join permit a certain join?
    1451              :  *
    1452              :  * By an opaque join, we mean one where the exact mechanism by which the
    1453              :  * join is performed is not visible to PostgreSQL. Currently this is the
    1454              :  * case only for foreign joins: FOREIGN_JOIN((x y z)) means that x, y, and
    1455              :  * z are joined on the remote side, but we know nothing about the join order
    1456              :  * or join methods used over there.
    1457              :  *
    1458              :  * The logic here needs to differ from pgpa_join_method_permits_join because,
    1459              :  * for other join types, the advice target is the set of inner rels; here, it
    1460              :  * includes both inner and outer rels.
    1461              :  */
    1462              : static bool
    1463            2 : pgpa_opaque_join_permits_join(int outer_count, int inner_count,
    1464              :                               pgpa_identifier *rids,
    1465              :                               pgpa_trove_entry *entry,
    1466              :                               bool *restrict_method)
    1467              : {
    1468            2 :     pgpa_advice_target *target = entry->target;
    1469              :     pgpa_itm_type join_itm;
    1470              : 
    1471              :     /* We definitely have at least a partial match for this trove entry. */
    1472            2 :     entry->flags |= PGPA_TE_MATCH_PARTIAL;
    1473              : 
    1474            2 :     *restrict_method = false;
    1475              : 
    1476            2 :     join_itm = pgpa_identifiers_match_target(outer_count + inner_count,
    1477              :                                              rids, target);
    1478            2 :     if (join_itm == PGPA_ITM_EQUAL)
    1479              :     {
    1480              :         /*
    1481              :          * We have an exact match, and should therefore allow the join and
    1482              :          * enforce the use of the relevant opaque join method.
    1483              :          */
    1484            2 :         entry->flags |= PGPA_TE_MATCH_FULL;
    1485            2 :         *restrict_method = true;
    1486            2 :         return true;
    1487              :     }
    1488              : 
    1489            0 :     if (join_itm == PGPA_ITM_KEYS_ARE_SUBSET ||
    1490              :         join_itm == PGPA_ITM_TARGETS_ARE_SUBSET)
    1491              :     {
    1492              :         /*
    1493              :          * If join_itm == PGPA_ITM_TARGETS_ARE_SUBSET, then the join we care
    1494              :          * about has already taken place and no further restrictions apply.
    1495              :          *
    1496              :          * If join_itm == PGPA_ITM_KEYS_ARE_SUBSET, we're still building up to
    1497              :          * the join we care about and have not introduced any extraneous
    1498              :          * relations not named in the advice. Note that ForeignScan paths for
    1499              :          * joins are built up from ForeignScan paths from underlying joins and
    1500              :          * scans, so we must not disable this join when considering a subset
    1501              :          * of the relations we ultimately want.
    1502              :          */
    1503            0 :         return true;
    1504              :     }
    1505              : 
    1506              :     /*
    1507              :      * The advice overlaps the join, but at least one relation is present in
    1508              :      * the join that isn't mentioned by the advice. We want to disable such
    1509              :      * paths so that we actually push down the join as intended.
    1510              :      */
    1511            0 :     return false;
    1512              : }
    1513              : 
    1514              : /*
    1515              :  * Does advice concerning a semijoin permit a certain join?
    1516              :  *
    1517              :  * Unlike join method advice, which lists the rels on the inner side of the
    1518              :  * join, semijoin uniqueness advice lists the rels on the nullable side of the
    1519              :  * join. Those can be the same, if the join type is JOIN_UNIQUE_INNER or
    1520              :  * JOIN_SEMI, or they can be different, in case of JOIN_UNIQUE_OUTER or
    1521              :  * JOIN_RIGHT_SEMI.
    1522              :  *
    1523              :  * We don't know here whether the caller specified SEMIJOIN_UNIQUE or
    1524              :  * SEMIJOIN_NON_UNIQUE. The caller should check the join type against the
    1525              :  * advice type if and only if we set *restrict_method to true.
    1526              :  */
    1527              : static bool
    1528         3564 : pgpa_semijoin_permits_join(int outer_count, int inner_count,
    1529              :                            pgpa_identifier *rids,
    1530              :                            pgpa_trove_entry *entry,
    1531              :                            bool outer_is_nullable,
    1532              :                            bool *restrict_method)
    1533              : {
    1534         3564 :     pgpa_advice_target *target = entry->target;
    1535              :     pgpa_itm_type join_itm;
    1536              :     pgpa_itm_type inner_itm;
    1537              :     pgpa_itm_type outer_itm;
    1538              : 
    1539         3564 :     *restrict_method = false;
    1540              : 
    1541              :     /* We definitely have at least a partial match for this trove entry. */
    1542         3564 :     entry->flags |= PGPA_TE_MATCH_PARTIAL;
    1543              : 
    1544              :     /*
    1545              :      * If outer rel is the nullable side and contains exactly the same
    1546              :      * relations as the advice target, then the join order is allowable, but
    1547              :      * the caller must check whether the advice tag (either SEMIJOIN_UNIQUE or
    1548              :      * SEMIJOIN_NON_UNIQUE) matches the join type.
    1549              :      *
    1550              :      * If the outer rel is a superset of the target relations, the join we
    1551              :      * care about has already taken place, so we should impose no further
    1552              :      * restrictions.
    1553              :      */
    1554         3564 :     outer_itm = pgpa_identifiers_match_target(outer_count,
    1555              :                                               rids, target);
    1556         3564 :     if (outer_itm == PGPA_ITM_EQUAL)
    1557              :     {
    1558         1511 :         entry->flags |= PGPA_TE_MATCH_FULL;
    1559         1511 :         if (outer_is_nullable)
    1560              :         {
    1561         1506 :             *restrict_method = true;
    1562         1506 :             return true;
    1563              :         }
    1564              :     }
    1565         2053 :     else if (outer_itm == PGPA_ITM_TARGETS_ARE_SUBSET)
    1566          203 :         return true;
    1567              : 
    1568              :     /* As above, but for the inner rel. */
    1569         1855 :     inner_itm = pgpa_identifiers_match_target(inner_count,
    1570         1855 :                                               rids + outer_count,
    1571              :                                               target);
    1572         1855 :     if (inner_itm == PGPA_ITM_EQUAL)
    1573              :     {
    1574         1511 :         entry->flags |= PGPA_TE_MATCH_FULL;
    1575         1511 :         if (!outer_is_nullable)
    1576              :         {
    1577         1507 :             *restrict_method = true;
    1578         1507 :             return true;
    1579              :         }
    1580              :     }
    1581          344 :     else if (inner_itm == PGPA_ITM_TARGETS_ARE_SUBSET)
    1582          203 :         return true;
    1583              : 
    1584              :     /*
    1585              :      * If everything in the joinrel appears in the advice target, we're below
    1586              :      * the level of the join we want to control.
    1587              :      */
    1588          145 :     join_itm = pgpa_identifiers_match_target(outer_count + inner_count,
    1589              :                                              rids, target);
    1590              :     Assert(join_itm != PGPA_ITM_DISJOINT);
    1591          145 :     if (join_itm == PGPA_ITM_KEYS_ARE_SUBSET ||
    1592              :         join_itm == PGPA_ITM_EQUAL)
    1593          136 :         return true;
    1594              : 
    1595              :     /*
    1596              :      * We've tested for all allowable possibilities, and so must reject this
    1597              :      * join order. This can happen in two ways.
    1598              :      *
    1599              :      * First, we might be considering a semijoin that overlaps incompletely
    1600              :      * with one or both sides of the join. For example, if the user has
    1601              :      * specified SEMIJOIN_UNIQUE((t1 t2)) or SEMIJOIN_NON_UNIQUE((t1 t2)), we
    1602              :      * should reject a proposed t2-t3 join, since that could not result in a
    1603              :      * final plan compatible with the advice.
    1604              :      *
    1605              :      * Second, we might be considering a semijoin where the advice target
    1606              :      * perfectly matches one side of the join, but it's the wrong one. For
    1607              :      * example, in the example above, we might see a 3-way join between t1,
    1608              :      * t2, and t3, with (t1 t2) on the non-nullable side. That, too, would be
    1609              :      * incompatible with the advice.
    1610              :      */
    1611            9 :     return false;
    1612              : }
    1613              : 
    1614              : /*
    1615              :  * Apply scan advice to a RelOptInfo.
    1616              :  */
    1617              : static void
    1618        74483 : pgpa_planner_apply_scan_advice(RelOptInfo *rel,
    1619              :                                pgpa_trove_entry *scan_entries,
    1620              :                                Bitmapset *scan_indexes,
    1621              :                                pgpa_trove_entry *rel_entries,
    1622              :                                Bitmapset *rel_indexes)
    1623              : {
    1624        74483 :     const uint64 all_scan_mask = PGS_SCAN_ANY | PGS_APPEND |
    1625              :         PGS_MERGE_APPEND | PGS_CONSIDER_INDEXONLY;
    1626        74483 :     bool        gather_conflict = false;
    1627        74483 :     Bitmapset  *gather_partial_match = NULL;
    1628        74483 :     Bitmapset  *gather_full_match = NULL;
    1629        74483 :     int         i = -1;
    1630        74483 :     pgpa_trove_entry *scan_entry = NULL;
    1631              :     int         flags;
    1632        74483 :     bool        scan_type_conflict = false;
    1633        74483 :     Bitmapset  *scan_type_indexes = NULL;
    1634        74483 :     Bitmapset  *scan_type_rel_indexes = NULL;
    1635        74483 :     uint64      gather_mask = 0;
    1636        74483 :     uint64      scan_type = all_scan_mask;  /* sentinel: no advice yet */
    1637              : 
    1638              :     /* Scrutinize available scan advice. */
    1639       118522 :     while ((i = bms_next_member(scan_indexes, i)) >= 0)
    1640              :     {
    1641        44039 :         pgpa_trove_entry *my_entry = &scan_entries[i];
    1642        44039 :         uint64      my_scan_type = all_scan_mask;
    1643              : 
    1644              :         /* Translate our advice tags to a scan strategy advice value. */
    1645        44039 :         if (my_entry->tag == PGPA_TAG_DO_NOT_SCAN)
    1646          416 :             my_scan_type = 0;
    1647        43623 :         else if (my_entry->tag == PGPA_TAG_BITMAP_HEAP_SCAN)
    1648              :         {
    1649              :             /*
    1650              :              * Currently, PGS_CONSIDER_INDEXONLY can suppress Bitmap Heap
    1651              :              * Scans, so don't clear it when such a scan is requested. This
    1652              :              * happens because build_index_scan() thinks that the possibility
    1653              :              * of an index-only scan is a sufficient reason to consider using
    1654              :              * an otherwise-useless index, and get_index_paths() thinks that
    1655              :              * the same paths that are useful for index or index-only scans
    1656              :              * should also be considered for bitmap scans. Perhaps that logic
    1657              :              * should be tightened up, but until then we need to include
    1658              :              * PGS_CONSIDER_INDEXONLY in my_scan_type here.
    1659              :              */
    1660         2764 :             my_scan_type = PGS_BITMAPSCAN | PGS_CONSIDER_INDEXONLY;
    1661              :         }
    1662        40859 :         else if (my_entry->tag == PGPA_TAG_INDEX_ONLY_SCAN)
    1663         1908 :             my_scan_type = PGS_INDEXONLYSCAN | PGS_CONSIDER_INDEXONLY;
    1664        38951 :         else if (my_entry->tag == PGPA_TAG_INDEX_SCAN)
    1665        11804 :             my_scan_type = PGS_INDEXSCAN;
    1666        27147 :         else if (my_entry->tag == PGPA_TAG_SEQ_SCAN)
    1667        26727 :             my_scan_type = PGS_SEQSCAN;
    1668          420 :         else if (my_entry->tag == PGPA_TAG_TID_SCAN)
    1669          420 :             my_scan_type = PGS_TIDSCAN;
    1670              : 
    1671              :         /*
    1672              :          * If this is understandable scan advice, hang on to the entry, the
    1673              :          * inferred scan type, and the index at which we found it.
    1674              :          *
    1675              :          * Also make a note if we see conflicting scan type advice. Note that
    1676              :          * we regard two index specifications as conflicting unless they match
    1677              :          * exactly. In theory, perhaps we could regard INDEX_SCAN(a c) and
    1678              :          * INDEX_SCAN(a b.c) as non-conflicting if it happens that the only
    1679              :          * index named c is in schema b, but it doesn't seem worth the code.
    1680              :          */
    1681        44039 :         if (my_scan_type != all_scan_mask)
    1682              :         {
    1683        44039 :             if (scan_type != all_scan_mask && scan_type != my_scan_type)
    1684            0 :                 scan_type_conflict = true;
    1685        44039 :             if (!scan_type_conflict && scan_entry != NULL &&
    1686            2 :                 my_entry->target->itarget != NULL &&
    1687            2 :                 scan_entry->target->itarget != NULL &&
    1688            2 :                 !pgpa_index_targets_equal(scan_entry->target->itarget,
    1689            2 :                                           my_entry->target->itarget))
    1690            1 :                 scan_type_conflict = true;
    1691        44039 :             scan_entry = my_entry;
    1692        44039 :             scan_type = my_scan_type;
    1693        44039 :             scan_type_indexes = bms_add_member(scan_type_indexes, i);
    1694              :         }
    1695              :     }
    1696              : 
    1697              :     /* Scrutinize available gather-related and partitionwise advice. */
    1698        74483 :     i = -1;
    1699       148124 :     while ((i = bms_next_member(rel_indexes, i)) >= 0)
    1700              :     {
    1701        73641 :         pgpa_trove_entry *my_entry = &rel_entries[i];
    1702        73641 :         uint64      my_gather_mask = 0;
    1703              :         bool        just_one_rel;
    1704              : 
    1705       147282 :         just_one_rel = my_entry->target->ttype == PGPA_TARGET_IDENTIFIER
    1706        73641 :             || list_length(my_entry->target->children) == 1;
    1707              : 
    1708              :         /*
    1709              :          * PARTITIONWISE behaves like a scan type, except that if there's more
    1710              :          * than one relation targeted, it has no effect at this level.
    1711              :          */
    1712        73641 :         if (my_entry->tag == PGPA_TAG_PARTITIONWISE)
    1713              :         {
    1714         2419 :             if (just_one_rel)
    1715              :             {
    1716         1966 :                 const uint64 my_scan_type = PGS_APPEND | PGS_MERGE_APPEND;
    1717              : 
    1718         1966 :                 if (scan_type != all_scan_mask && scan_type != my_scan_type)
    1719            0 :                     scan_type_conflict = true;
    1720         1966 :                 scan_entry = my_entry;
    1721         1966 :                 scan_type = my_scan_type;
    1722              :                 scan_type_rel_indexes =
    1723         1966 :                     bms_add_member(scan_type_rel_indexes, i);
    1724              :             }
    1725         2419 :             continue;
    1726              :         }
    1727              : 
    1728              :         /*
    1729              :          * GATHER and GATHER_MERGE applied to a single rel mean that we should
    1730              :          * use the corresponding strategy here, while applying either to more
    1731              :          * than one rel means we should not use those strategies here, but
    1732              :          * rather at the level of the joinrel that corresponds to what was
    1733              :          * specified. NO_GATHER can only be applied to single rels.
    1734              :          *
    1735              :          * Note that setting PGS_CONSIDER_NONPARTIAL in my_gather_mask is
    1736              :          * equivalent to allowing the non-use of either form of Gather here.
    1737              :          */
    1738        71222 :         if (my_entry->tag == PGPA_TAG_GATHER ||
    1739        70984 :             my_entry->tag == PGPA_TAG_GATHER_MERGE)
    1740              :         {
    1741          308 :             if (!just_one_rel)
    1742          148 :                 my_gather_mask = PGS_CONSIDER_NONPARTIAL;
    1743          160 :             else if (my_entry->tag == PGPA_TAG_GATHER)
    1744          106 :                 my_gather_mask = PGS_GATHER;
    1745              :             else
    1746           54 :                 my_gather_mask = PGS_GATHER_MERGE;
    1747              :         }
    1748        70914 :         else if (my_entry->tag == PGPA_TAG_NO_GATHER)
    1749              :         {
    1750              :             Assert(just_one_rel);
    1751        70914 :             my_gather_mask = PGS_CONSIDER_NONPARTIAL;
    1752              :         }
    1753              : 
    1754              :         /*
    1755              :          * If we set my_gather_mask up above, then we (1) make a note if the
    1756              :          * advice conflicted, (2) remember the mask value, and (3) remember
    1757              :          * whether this was a full or partial match.
    1758              :          */
    1759        71222 :         if (my_gather_mask != 0)
    1760              :         {
    1761        71222 :             if (gather_mask != 0 && gather_mask != my_gather_mask)
    1762            0 :                 gather_conflict = true;
    1763        71222 :             gather_mask = my_gather_mask;
    1764        71222 :             if (just_one_rel)
    1765        71074 :                 gather_full_match = bms_add_member(gather_full_match, i);
    1766              :             else
    1767          148 :                 gather_partial_match = bms_add_member(gather_partial_match, i);
    1768              :         }
    1769              :     }
    1770              : 
    1771              :     /* Enforce choice of index. */
    1772        74483 :     if (scan_entry != NULL && !scan_type_conflict &&
    1773        46002 :         (scan_entry->tag == PGPA_TAG_INDEX_SCAN ||
    1774        34201 :          scan_entry->tag == PGPA_TAG_INDEX_ONLY_SCAN))
    1775              :     {
    1776        13709 :         pgpa_index_target *itarget = scan_entry->target->itarget;
    1777        13709 :         IndexOptInfo *matched_index = NULL;
    1778              : 
    1779        41721 :         foreach_node(IndexOptInfo, index, rel->indexlist)
    1780              :         {
    1781        28009 :             char       *relname = get_rel_name(index->indexoid);
    1782        28009 :             Oid         nspoid = get_rel_namespace(index->indexoid);
    1783        28009 :             char       *relnamespace = get_namespace_name_or_temp(nspoid);
    1784              : 
    1785        28009 :             if (strcmp(itarget->indname, relname) == 0 &&
    1786        13707 :                 (itarget->indnamespace == NULL ||
    1787        13695 :                  strcmp(itarget->indnamespace, relnamespace) == 0))
    1788              :             {
    1789        13706 :                 matched_index = index;
    1790        13706 :                 break;
    1791              :             }
    1792              :         }
    1793              : 
    1794        13709 :         if (matched_index == NULL)
    1795              :         {
    1796              :             /* Don't force the scan type if the index doesn't exist. */
    1797            3 :             scan_type = all_scan_mask;
    1798              : 
    1799              :             /* Mark advice as inapplicable. */
    1800            3 :             pgpa_trove_set_flags(scan_entries, scan_type_indexes,
    1801              :                                  PGPA_TE_INAPPLICABLE);
    1802              :         }
    1803              :         else
    1804              :         {
    1805              :             /* Disable every other index. */
    1806        60815 :             foreach_node(IndexOptInfo, index, rel->indexlist)
    1807              :             {
    1808        33403 :                 if (index != matched_index)
    1809        19697 :                     index->disabled = true;
    1810              :             }
    1811              :         }
    1812              :     }
    1813              : 
    1814              :     /*
    1815              :      * Mark all the scan method entries as fully matched; and if they specify
    1816              :      * different things, mark them all as conflicting.
    1817              :      */
    1818        74483 :     flags = PGPA_TE_MATCH_PARTIAL | PGPA_TE_MATCH_FULL;
    1819        74483 :     if (scan_type_conflict)
    1820            1 :         flags |= PGPA_TE_CONFLICTING;
    1821        74483 :     pgpa_trove_set_flags(scan_entries, scan_type_indexes, flags);
    1822        74483 :     pgpa_trove_set_flags(rel_entries, scan_type_rel_indexes, flags);
    1823              : 
    1824              :     /*
    1825              :      * Mark every Gather-related piece of advice as partially matched. Mark
    1826              :      * the ones that included this relation as a target by itself as fully
    1827              :      * matched. If there was a conflict, mark them all as conflicting.
    1828              :      */
    1829        74483 :     flags = PGPA_TE_MATCH_PARTIAL;
    1830        74483 :     if (gather_conflict)
    1831            0 :         flags |= PGPA_TE_CONFLICTING;
    1832        74483 :     pgpa_trove_set_flags(rel_entries, gather_partial_match, flags);
    1833        74483 :     flags |= PGPA_TE_MATCH_FULL;
    1834        74483 :     pgpa_trove_set_flags(rel_entries, gather_full_match, flags);
    1835              : 
    1836              :     /*
    1837              :      * Enforce restrictions on the scan type and use of Gather/Gather Merge.
    1838              :      * Only clear bits here, so that we still respect the enable_* GUCs. Do
    1839              :      * nothing in cases where the advice on a single topic conflicts.
    1840              :      */
    1841        74483 :     if (scan_type != all_scan_mask && !scan_type_conflict)
    1842        45999 :         rel->pgs_mask &= ~(all_scan_mask & ~scan_type);
    1843        74483 :     if (gather_mask != 0 && !gather_conflict)
    1844              :     {
    1845              :         uint64      all_gather_mask;
    1846              : 
    1847        71221 :         all_gather_mask =
    1848              :             PGS_GATHER | PGS_GATHER_MERGE | PGS_CONSIDER_NONPARTIAL;
    1849        71221 :         rel->pgs_mask &= ~(all_gather_mask & ~gather_mask);
    1850              :     }
    1851        74483 : }
    1852              : 
    1853              : /*
    1854              :  * Add feedback entries for one trove slice to the provided list and
    1855              :  * return the resulting list.
    1856              :  *
    1857              :  * Feedback entries are generated from the trove entry's flags. It's assumed
    1858              :  * that the caller has already set all relevant flags with the exception of
    1859              :  * PGPA_TE_FAILED. We set that flag here if appropriate.
    1860              :  */
    1861              : static List *
    1862       130308 : pgpa_planner_append_feedback(List *list, pgpa_trove *trove,
    1863              :                              pgpa_trove_lookup_type type,
    1864              :                              pgpa_identifier *rt_identifiers,
    1865              :                              pgpa_plan_walker_context *walker)
    1866              : {
    1867              :     pgpa_trove_entry *entries;
    1868              :     int         nentries;
    1869              : 
    1870       130308 :     pgpa_trove_lookup_all(trove, type, &entries, &nentries);
    1871       274322 :     for (int i = 0; i < nentries; ++i)
    1872              :     {
    1873       144014 :         pgpa_trove_entry *entry = &entries[i];
    1874              :         DefElem    *item;
    1875              : 
    1876              :         /*
    1877              :          * If this entry was fully matched, check whether generating advice
    1878              :          * from this plan would produce such an entry. If not, label the entry
    1879              :          * as failed.
    1880              :          */
    1881       144014 :         if ((entry->flags & PGPA_TE_MATCH_FULL) != 0 &&
    1882       143990 :             !pgpa_walker_would_advise(walker, rt_identifiers,
    1883              :                                       entry->tag, entry->target))
    1884           31 :             entry->flags |= PGPA_TE_FAILED;
    1885              : 
    1886       144014 :         item = makeDefElem(pgpa_cstring_trove_entry(entry),
    1887       144014 :                            (Node *) makeInteger(entry->flags), -1);
    1888       144014 :         list = lappend(list, item);
    1889              :     }
    1890              : 
    1891       130308 :     return list;
    1892              : }
    1893              : 
    1894              : /*
    1895              :  * Emit a WARNING to tell the user about a problem with the supplied plan
    1896              :  * advice.
    1897              :  */
    1898              : static void
    1899        43313 : pgpa_planner_feedback_warning(List *feedback)
    1900              : {
    1901              :     StringInfoData detailbuf;
    1902              :     StringInfoData flagbuf;
    1903              : 
    1904              :     /* Quick exit if there's no feedback. */
    1905        43313 :     if (feedback == NIL)
    1906            0 :         return;
    1907              : 
    1908              :     /* Initialize buffers. */
    1909        43313 :     initStringInfo(&detailbuf);
    1910        43313 :     initStringInfo(&flagbuf);
    1911              : 
    1912              :     /* Main loop. */
    1913       230497 :     foreach_node(DefElem, item, feedback)
    1914              :     {
    1915       143871 :         int         flags = defGetInt32(item);
    1916              : 
    1917              :         /*
    1918              :          * Don't emit anything if it was fully matched with no problems found.
    1919              :          *
    1920              :          * NB: Feedback should never be marked fully matched without also
    1921              :          * being marked partially matched.
    1922              :          */
    1923       143871 :         if (flags == (PGPA_TE_MATCH_PARTIAL | PGPA_TE_MATCH_FULL))
    1924       143871 :             continue;
    1925              : 
    1926              :         /*
    1927              :          * Terminate each detail line except the last with a newline. This is
    1928              :          * also a convenient place to reset flagbuf.
    1929              :          */
    1930            0 :         if (detailbuf.len > 0)
    1931              :         {
    1932            0 :             appendStringInfoChar(&detailbuf, '\n');
    1933            0 :             resetStringInfo(&flagbuf);
    1934              :         }
    1935              : 
    1936              :         /* Generate output. */
    1937            0 :         pgpa_trove_append_flags(&flagbuf, flags);
    1938            0 :         appendStringInfo(&detailbuf, "advice %s feedback is \"%s\"",
    1939              :                          item->defname, flagbuf.data);
    1940              :     }
    1941              : 
    1942              :     /* Emit the warning, if any problems were found. */
    1943        43313 :     if (detailbuf.len > 0)
    1944            0 :         ereport(WARNING,
    1945              :                 errmsg("supplied plan advice was not enforced"),
    1946              :                 errdetail("%s", detailbuf.data));
    1947              : }
    1948              : 
    1949              : /*
    1950              :  * Get or create the pgpa_planner_info for the given PlannerInfo.
    1951              :  */
    1952              : static pgpa_planner_info *
    1953       157620 : pgpa_planner_get_proot(pgpa_planner_state *pps, PlannerInfo *root)
    1954              : {
    1955              :     pgpa_planner_info *new_proot;
    1956              : 
    1957              :     /*
    1958              :      * If pps->last_proot isn't populated, there are no pgpa_planner_info
    1959              :      * objects yet, so we can drop through and create a new one. Otherwise,
    1960              :      * search for an object with a matching name, and drop through only if
    1961              :      * none is found.
    1962              :      */
    1963       157620 :     if (pps->last_proot != NULL)
    1964              :     {
    1965        70708 :         if (root->plan_name == NULL)
    1966              :         {
    1967        47487 :             if (pps->last_proot->plan_name == NULL)
    1968        38455 :                 return pps->last_proot;
    1969              : 
    1970        23154 :             foreach_ptr(pgpa_planner_info, proot, pps->proots)
    1971              :             {
    1972        12102 :                 if (proot->plan_name == NULL)
    1973              :                 {
    1974         3506 :                     pps->last_proot = proot;
    1975         3506 :                     return proot;
    1976              :                 }
    1977              :             }
    1978              :         }
    1979              :         else
    1980              :         {
    1981        23221 :             if (pps->last_proot->plan_name != NULL &&
    1982        16665 :                 strcmp(pps->last_proot->plan_name, root->plan_name) == 0)
    1983        11404 :                 return pps->last_proot;
    1984              : 
    1985        46018 :             foreach_ptr(pgpa_planner_info, proot, pps->proots)
    1986              :             {
    1987        22934 :                 if (proot->plan_name != NULL &&
    1988        14314 :                     strcmp(proot->plan_name, root->plan_name) == 0)
    1989              :                 {
    1990          275 :                     pps->last_proot = proot;
    1991          275 :                     return proot;
    1992              :                 }
    1993              :             }
    1994              :         }
    1995              :     }
    1996              : 
    1997              :     /* Create new object. */
    1998       103980 :     new_proot = palloc0_object(pgpa_planner_info);
    1999              : 
    2000              :     /* Set plan name and alternative plan name. */
    2001       103980 :     new_proot->plan_name = root->plan_name;
    2002       103980 :     new_proot->alternative_plan_name = root->alternative_plan_name;
    2003              : 
    2004              :     /*
    2005              :      * If the newly-created proot shares an alternative_plan_name with one or
    2006              :      * more others, all should have the is_alternative_plan flag set.
    2007              :      */
    2008       238805 :     foreach_ptr(pgpa_planner_info, other_proot, pps->proots)
    2009              :     {
    2010        30845 :         if (strings_equal_or_both_null(new_proot->alternative_plan_name,
    2011        30845 :                                        other_proot->alternative_plan_name))
    2012              :         {
    2013          828 :             new_proot->is_alternative_plan = true;
    2014          828 :             other_proot->is_alternative_plan = true;
    2015              :         }
    2016              :     }
    2017              : 
    2018              :     /*
    2019              :      * Outermost query level always has rtoffset 0; other rtoffset values are
    2020              :      * computed later.
    2021              :      */
    2022       103980 :     if (root->plan_name == NULL)
    2023              :     {
    2024        86911 :         new_proot->has_rtoffset = true;
    2025        86911 :         new_proot->rtoffset = 0;
    2026              :     }
    2027              : 
    2028              :     /* Add to list and make it most recently used. */
    2029       103980 :     pps->proots = lappend(pps->proots, new_proot);
    2030       103980 :     pps->last_proot = new_proot;
    2031              : 
    2032       103980 :     return new_proot;
    2033              : }
    2034              : 
    2035              : /*
    2036              :  * Compute the range table identifier for one relation and save it for future
    2037              :  * use.
    2038              :  */
    2039              : static void
    2040       154420 : pgpa_compute_rt_identifier(pgpa_planner_info *proot, PlannerInfo *root,
    2041              :                            RelOptInfo *rel)
    2042              : {
    2043              :     pgpa_identifier *rid;
    2044              : 
    2045              :     /* Allocate or extend the proot's rid_array as necessary. */
    2046       154420 :     if (proot->rid_array_size < rel->relid)
    2047              :     {
    2048       105156 :         int         new_size = pg_nextpower2_32(Max(rel->relid, 8));
    2049              : 
    2050       105156 :         if (proot->rid_array_size == 0)
    2051       103980 :             proot->rid_array = palloc0_array(pgpa_identifier, new_size);
    2052              :         else
    2053         1176 :             proot->rid_array = repalloc0_array(proot->rid_array,
    2054              :                                                pgpa_identifier,
    2055              :                                                proot->rid_array_size,
    2056              :                                                new_size);
    2057       105156 :         proot->rid_array_size = new_size;
    2058              :     }
    2059              : 
    2060              :     /* Save relation identifier details for this RTI if not already done. */
    2061       154420 :     rid = &proot->rid_array[rel->relid - 1];
    2062       154420 :     if (rid->alias_name == NULL)
    2063       154420 :         pgpa_compute_identifier_by_rti(root, rel->relid, rid);
    2064       154420 : }
    2065              : 
    2066              : /*
    2067              :  * Compute the range table offset for each pgpa_planner_info for which it
    2068              :  * is possible to meaningfully do so.
    2069              :  */
    2070              : static void
    2071        86839 : pgpa_compute_rt_offsets(pgpa_planner_state *pps, PlannedStmt *pstmt)
    2072              : {
    2073       277583 :     foreach_ptr(pgpa_planner_info, proot, pps->proots)
    2074              :     {
    2075              :         /* For the top query level, we've previously set rtoffset 0. */
    2076       103905 :         if (proot->plan_name == NULL)
    2077              :         {
    2078              :             Assert(proot->has_rtoffset);
    2079        86839 :             continue;
    2080              :         }
    2081              : 
    2082              :         /*
    2083              :          * It's not guaranteed that every plan name we saw during planning has
    2084              :          * a SubPlanInfo, but any that do not certainly don't appear in the
    2085              :          * final range table.
    2086              :          */
    2087        47802 :         foreach_node(SubPlanRTInfo, rtinfo, pstmt->subrtinfos)
    2088              :         {
    2089        30518 :             if (strcmp(proot->plan_name, rtinfo->plan_name) == 0)
    2090              :             {
    2091              :                 /*
    2092              :                  * If rtinfo->dummy is set, then the subquery's range table
    2093              :                  * will only have been partially copied to the final range
    2094              :                  * table. Specifically, only RTE_RELATION entries and
    2095              :                  * RTE_SUBQUERY entries that were once RTE_RELATION entries
    2096              :                  * will be copied, as per add_rtes_to_flat_rtable. Therefore,
    2097              :                  * there's no fixed rtoffset that we can apply to the RTIs
    2098              :                  * used during planning to locate the corresponding relations.
    2099              :                  */
    2100        16848 :                 if (rtinfo->dummy)
    2101              :                 {
    2102              :                     /*
    2103              :                      * It will not be possible to make any effective use of
    2104              :                      * the sj_unique_rels list in this case, and it also won't
    2105              :                      * be important to do so. So just throw the list away to
    2106              :                      * avoid confusing pgpa_plan_walker.
    2107              :                      */
    2108           82 :                     proot->sj_unique_rels = NIL;
    2109           82 :                     break;
    2110              :                 }
    2111              :                 Assert(!proot->has_rtoffset);
    2112        16766 :                 proot->has_rtoffset = true;
    2113        16766 :                 proot->rtoffset = rtinfo->rtoffset;
    2114        16766 :                 break;
    2115              :             }
    2116              :         }
    2117              :     }
    2118        86839 : }
    2119              : 
    2120              : /*
    2121              :  * Validate that the range table identifiers we were able to generate during
    2122              :  * planning match the ones we generated from the final plan.
    2123              :  */
    2124              : static void
    2125        86839 : pgpa_validate_rt_identifiers(pgpa_planner_state *pps, PlannedStmt *pstmt)
    2126              : {
    2127              : #ifdef USE_ASSERT_CHECKING
    2128              :     pgpa_identifier *rt_identifiers;
    2129              :     Index       rtable_length = list_length(pstmt->rtable);
    2130              : 
    2131              :     /* Create identifiers from the planned statement. */
    2132              :     rt_identifiers = pgpa_create_identifiers_for_planned_stmt(pstmt);
    2133              : 
    2134              :     /* Iterate over identifiers created during planning, so we can compare. */
    2135              :     foreach_ptr(pgpa_planner_info, proot, pps->proots)
    2136              :     {
    2137              :         if (!proot->has_rtoffset)
    2138              :             continue;
    2139              : 
    2140              :         for (int rti = 1; rti <= proot->rid_array_size; ++rti)
    2141              :         {
    2142              :             Index       flat_rti = proot->rtoffset + rti;
    2143              :             pgpa_identifier *rid1 = &proot->rid_array[rti - 1];
    2144              :             pgpa_identifier *rid2;
    2145              : 
    2146              :             if (rid1->alias_name == NULL)
    2147              :                 continue;
    2148              : 
    2149              :             Assert(flat_rti <= rtable_length);
    2150              :             rid2 = &rt_identifiers[flat_rti - 1];
    2151              :             Assert(strcmp(rid1->alias_name, rid2->alias_name) == 0);
    2152              :             Assert(rid1->occurrence == rid2->occurrence);
    2153              :             Assert(strings_equal_or_both_null(rid1->partnsp, rid2->partnsp));
    2154              :             Assert(strings_equal_or_both_null(rid1->partrel, rid2->partrel));
    2155              :             Assert(strings_equal_or_both_null(rid1->plan_name,
    2156              :                                               rid2->plan_name));
    2157              :         }
    2158              :     }
    2159              : #endif
    2160        86839 : }
    2161              : 
    2162              : /*
    2163              :  * Convert a bitmapset to a C string of comma-separated integers.
    2164              :  */
    2165              : static char *
    2166            0 : pgpa_bms_to_cstring(Bitmapset *bms)
    2167              : {
    2168              :     StringInfoData buf;
    2169            0 :     int         x = -1;
    2170              : 
    2171            0 :     if (bms_is_empty(bms))
    2172            0 :         return "none";
    2173              : 
    2174            0 :     initStringInfo(&buf);
    2175            0 :     while ((x = bms_next_member(bms, x)) >= 0)
    2176              :     {
    2177            0 :         if (buf.len > 0)
    2178            0 :             appendStringInfo(&buf, ", %d", x);
    2179              :         else
    2180            0 :             appendStringInfo(&buf, "%d", x);
    2181              :     }
    2182              : 
    2183            0 :     return buf.data;
    2184              : }
    2185              : 
    2186              : /*
    2187              :  * Convert a JoinType to a C string.
    2188              :  */
    2189              : static const char *
    2190            0 : pgpa_jointype_to_cstring(JoinType jointype)
    2191              : {
    2192            0 :     switch (jointype)
    2193              :     {
    2194            0 :         case JOIN_INNER:
    2195            0 :             return "inner";
    2196            0 :         case JOIN_LEFT:
    2197            0 :             return "left";
    2198            0 :         case JOIN_FULL:
    2199            0 :             return "full";
    2200            0 :         case JOIN_RIGHT:
    2201            0 :             return "right";
    2202            0 :         case JOIN_SEMI:
    2203            0 :             return "semi";
    2204            0 :         case JOIN_ANTI:
    2205            0 :             return "anti";
    2206            0 :         case JOIN_RIGHT_SEMI:
    2207            0 :             return "right semi";
    2208            0 :         case JOIN_RIGHT_ANTI:
    2209            0 :             return "right anti";
    2210            0 :         case JOIN_UNIQUE_OUTER:
    2211            0 :             return "unique outer";
    2212            0 :         case JOIN_UNIQUE_INNER:
    2213            0 :             return "unique inner";
    2214              :     }
    2215            0 :     return "???";
    2216              : }
        

Generated by: LCOV version 2.0-1