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

Generated by: LCOV version 2.0-1