PostgreSQL Source Code git master
Loading...
Searching...
No Matches
pgpa_planner.c File Reference
#include "postgres.h"
#include "pg_plan_advice.h"
#include "pgpa_identifier.h"
#include "pgpa_output.h"
#include "pgpa_planner.h"
#include "pgpa_trove.h"
#include "pgpa_walker.h"
#include "commands/defrem.h"
#include "common/hashfn_unstable.h"
#include "nodes/makefuncs.h"
#include "optimizer/extendplan.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/plancat.h"
#include "optimizer/planner.h"
#include "parser/parsetree.h"
#include "utils/lsyscache.h"
Include dependency graph for pgpa_planner.c:

Go to the source code of this file.

Data Structures

struct  pgpa_planner_state
 
struct  pgpa_join_state
 

Typedefs

typedef enum pgpa_jo_outcome pgpa_jo_outcome
 
typedef struct pgpa_planner_state pgpa_planner_state
 
typedef struct pgpa_join_state pgpa_join_state
 

Enumerations

enum  pgpa_jo_outcome { PGPA_JO_PERMITTED , PGPA_JO_DENIED , PGPA_JO_INDIFFERENT }
 

Functions

static void pgpa_planner_setup (PlannerGlobal *glob, Query *parse, const char *query_string, int cursorOptions, double *tuple_fraction, ExplainState *es)
 
static void pgpa_planner_shutdown (PlannerGlobal *glob, Query *parse, const char *query_string, PlannedStmt *pstmt)
 
static void pgpa_build_simple_rel (PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 
static void pgpa_joinrel_setup (PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, SpecialJoinInfo *sjinfo, List *restrictlist)
 
static void pgpa_join_path_setup (PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, JoinPathExtraData *extra)
 
static pgpa_join_statepgpa_get_join_state (PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel)
 
static void pgpa_planner_apply_joinrel_advice (uint64 *pgs_mask_p, char *plan_name, pgpa_join_state *pjs)
 
static void pgpa_planner_apply_join_path_advice (JoinType jointype, uint64 *pgs_mask_p, char *plan_name, pgpa_join_state *pjs)
 
static void pgpa_planner_apply_scan_advice (RelOptInfo *rel, pgpa_trove_entry *scan_entries, Bitmapset *scan_indexes, pgpa_trove_entry *rel_entries, Bitmapset *rel_indexes)
 
static uint64 pgpa_join_strategy_mask_from_advice_tag (pgpa_advice_tag_type tag)
 
static pgpa_jo_outcome pgpa_join_order_permits_join (int outer_count, int inner_count, pgpa_identifier *rids, pgpa_trove_entry *entry)
 
static bool pgpa_join_method_permits_join (int outer_count, int inner_count, pgpa_identifier *rids, pgpa_trove_entry *entry, bool *restrict_method)
 
static bool pgpa_opaque_join_permits_join (int outer_count, int inner_count, pgpa_identifier *rids, pgpa_trove_entry *entry, bool *restrict_method)
 
static bool pgpa_semijoin_permits_join (int outer_count, int inner_count, pgpa_identifier *rids, pgpa_trove_entry *entry, bool outer_is_nullable, bool *restrict_method)
 
static Listpgpa_planner_append_feedback (List *list, pgpa_trove *trove, pgpa_trove_lookup_type type, pgpa_identifier *rt_identifiers, pgpa_plan_walker_context *walker)
 
static void pgpa_planner_feedback_warning (List *feedback)
 
static void pgpa_ri_checker_save (pgpa_planner_state *pps, PlannerInfo *root, RelOptInfo *rel)
 
static void pgpa_ri_checker_validate (pgpa_planner_state *pps, PlannedStmt *pstmt)
 
static charpgpa_bms_to_cstring (Bitmapset *bms)
 
static const charpgpa_jointype_to_cstring (JoinType jointype)
 
void pgpa_planner_install_hooks (void)
 

Variables

static build_simple_rel_hook_type prev_build_simple_rel = NULL
 
static join_path_setup_hook_type prev_join_path_setup = NULL
 
static joinrel_setup_hook_type prev_joinrel_setup = NULL
 
static planner_setup_hook_type prev_planner_setup = NULL
 
static planner_shutdown_hook_type prev_planner_shutdown = NULL
 
int pgpa_planner_generate_advice = 0
 
static int planner_extension_id = -1
 

Typedef Documentation

◆ pgpa_jo_outcome

◆ pgpa_join_state

◆ pgpa_planner_state

Enumeration Type Documentation

◆ pgpa_jo_outcome

Enumerator
PGPA_JO_PERMITTED 
PGPA_JO_DENIED 
PGPA_JO_INDIFFERENT 

Definition at line 84 of file pgpa_planner.c.

85{
86 PGPA_JO_PERMITTED, /* permit this join order */
87 PGPA_JO_DENIED, /* deny this join order */
88 PGPA_JO_INDIFFERENT /* do neither */
pgpa_jo_outcome
@ PGPA_JO_PERMITTED
@ PGPA_JO_DENIED
@ PGPA_JO_INDIFFERENT

Function Documentation

◆ pgpa_bms_to_cstring()

static char * pgpa_bms_to_cstring ( Bitmapset bms)
static

Definition at line 2148 of file pgpa_planner.c.

2149{
2151 int x = -1;
2152
2153 if (bms_is_empty(bms))
2154 return "none";
2155
2157 while ((x = bms_next_member(bms, x)) >= 0)
2158 {
2159 if (buf.len > 0)
2160 appendStringInfo(&buf, ", %d", x);
2161 else
2162 appendStringInfo(&buf, "%d", x);
2163 }
2164
2165 return buf.data;
2166}
int bms_next_member(const Bitmapset *a, int prevbit)
Definition bitmapset.c:1290
#define bms_is_empty(a)
Definition bitmapset.h:118
int x
Definition isn.c:75
static char buf[DEFAULT_XLOG_SEG_SIZE]
static int fb(int x)
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition stringinfo.c:145
void initStringInfo(StringInfo str)
Definition stringinfo.c:97

References appendStringInfo(), bms_is_empty, bms_next_member(), buf, fb(), initStringInfo(), and x.

Referenced by pgpa_join_path_setup(), and pgpa_joinrel_setup().

◆ pgpa_build_simple_rel()

static void pgpa_build_simple_rel ( PlannerInfo root,
RelOptInfo rel,
RangeTblEntry rte 
)
static

Definition at line 459 of file pgpa_planner.c.

460{
462
463 /* Fetch our private state, set up by pgpa_planner_setup(). */
465
466 /* Save details needed for range table identifier cross-checking. */
467 if (pps != NULL)
469
470 /* If query advice was provided, search for relevant entries. */
471 if (pps != NULL && pps->trove != NULL)
472 {
473 pgpa_identifier rid;
476
477 /* Search for scan advice and general rel advice. */
480 &tresult_scan);
482 &tresult_rel);
483
484 /* If relevant entries were found, apply them. */
485 if (tresult_scan.indexes != NULL || tresult_rel.indexes != NULL)
486 {
488
490 tresult_scan.entries,
491 tresult_scan.indexes,
492 tresult_rel.entries,
493 tresult_rel.indexes);
494
495 /* Emit debugging message, if enabled. */
497 {
498 if (root->plan_name != NULL)
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
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. */
514 (*prev_build_simple_rel) (root, rel, rte);
515}
uint64_t uint64
Definition c.h:601
#define WARNING
Definition elog.h:36
#define ereport(elevel,...)
Definition elog.h:150
static void * GetPlannerGlobalExtensionState(PlannerGlobal *glob, int extension_id)
Definition extendplan.h:25
static char * errmsg
bool pg_plan_advice_trace_mask
void pgpa_compute_identifier_by_rti(PlannerInfo *root, Index rti, pgpa_identifier *rid)
static void pgpa_planner_apply_scan_advice(RelOptInfo *rel, pgpa_trove_entry *scan_entries, Bitmapset *scan_indexes, pgpa_trove_entry *rel_entries, Bitmapset *rel_indexes)
static build_simple_rel_hook_type prev_build_simple_rel
static void pgpa_ri_checker_save(pgpa_planner_state *pps, PlannerInfo *root, RelOptInfo *rel)
static int planner_extension_id
void pgpa_trove_lookup(pgpa_trove *trove, pgpa_trove_lookup_type type, int nrids, pgpa_identifier *rids, pgpa_trove_result *result)
Definition pgpa_trove.c:233
@ PGPA_TROVE_LOOKUP_SCAN
Definition pgpa_trove.h:85
@ PGPA_TROVE_LOOKUP_REL
Definition pgpa_trove.h:84
tree ctl root
Definition radixtree.h:1857
Index relid
Definition pathnodes.h:1057
uint64 pgs_mask
Definition pathnodes.h:1027

References ereport, errmsg, fb(), GetPlannerGlobalExtensionState(), pg_plan_advice_trace_mask, pgpa_compute_identifier_by_rti(), pgpa_planner_apply_scan_advice(), pgpa_ri_checker_save(), pgpa_trove_lookup(), PGPA_TROVE_LOOKUP_REL, PGPA_TROVE_LOOKUP_SCAN, RelOptInfo::pgs_mask, planner_extension_id, prev_build_simple_rel, RelOptInfo::relid, root, and WARNING.

Referenced by pgpa_planner_install_hooks().

◆ pgpa_get_join_state()

static pgpa_join_state * pgpa_get_join_state ( PlannerInfo root,
RelOptInfo joinrel,
RelOptInfo outerrel,
RelOptInfo innerrel 
)
static

Definition at line 676 of file pgpa_planner.c.

678{
681 bool new_pjs = false;
682
683 /* Fetch our private state, set up by pgpa_planner_setup(). */
685 if (pps == NULL || pps->trove == NULL)
686 {
687 /* No advice applies to this query, hence none to this joinrel. */
688 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 */
699 if (pjs != NULL)
700 {
701 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 return NULL;
710 }
711
712 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 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 if (pjs == NULL)
727 {
729
732 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 pjs->outerrel = outerrel;
741 pjs->innerrel = innerrel;
742 pjs->outer_count =
744 pjs->inner_count =
746 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 if (new_pjs)
755 {
756 pgpa_trove_result tresult;
757
758 /* Find join entries. */
760 pjs->outer_count + pjs->inner_count,
761 pjs->rids, &tresult);
762 pjs->join_entries = tresult.entries;
763 pjs->join_indexes = tresult.indexes;
764
765 /* Find rel entries. */
767 pjs->outer_count + pjs->inner_count,
768 pjs->rids, &tresult);
769 pjs->rel_entries = tresult.entries;
770 pjs->rel_indexes = tresult.indexes;
771
772 /* Now that the new pgpa_join_state is fully valid, save a pointer. */
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 if (pjs->join_indexes == NULL && pjs->rel_indexes == NULL)
782 return NULL;
783 }
784
785 return pjs;
786}
int bms_num_members(const Bitmapset *a)
Definition bitmapset.c:744
void SetRelOptInfoExtensionState(RelOptInfo *rel, int extension_id, void *opaque)
Definition extendplan.c:147
static void * GetRelOptInfoExtensionState(RelOptInfo *rel, int extension_id)
Definition extendplan.h:53
#define palloc_array(type, count)
Definition fe_memutils.h:76
#define palloc0_object(type)
Definition fe_memutils.h:75
int pgpa_compute_identifiers_by_relids(PlannerInfo *root, Bitmapset *relids, pgpa_identifier *rids)
@ PGPA_TROVE_LOOKUP_JOIN
Definition pgpa_trove.h:83
Relids relids
Definition pathnodes.h:1009
pgpa_trove_entry * entries
Definition pgpa_trove.h:95
Bitmapset * indexes
Definition pgpa_trove.h:96

References bms_num_members(), pgpa_trove_result::entries, fb(), GetPlannerGlobalExtensionState(), GetRelOptInfoExtensionState(), pgpa_trove_result::indexes, palloc0_object, palloc_array, pgpa_compute_identifiers_by_relids(), pgpa_trove_lookup(), PGPA_TROVE_LOOKUP_JOIN, PGPA_TROVE_LOOKUP_REL, planner_extension_id, RelOptInfo::relids, root, and SetRelOptInfoExtensionState().

Referenced by pgpa_join_path_setup(), and pgpa_joinrel_setup().

◆ pgpa_join_method_permits_join()

static bool pgpa_join_method_permits_join ( int  outer_count,
int  inner_count,
pgpa_identifier rids,
pgpa_trove_entry entry,
bool restrict_method 
)
static

Definition at line 1413 of file pgpa_planner.c.

1417{
1418 pgpa_advice_target *target = entry->target;
1422
1423 /* We definitely have at least a partial match for this trove entry. */
1424 entry->flags |= PGPA_TE_MATCH_PARTIAL;
1425
1426 *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 */
1437 rids + outer_count,
1438 target);
1440 {
1441 entry->flags |= PGPA_TE_MATCH_FULL;
1442 *restrict_method = true;
1443 return true;
1444 }
1446 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 */
1458 rids, target);
1460 return true;
1461 else if (outer_itm == PGPA_ITM_EQUAL)
1462 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 if (target->ttype == PGPA_TARGET_IDENTIFIER)
1469 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 join_itm = pgpa_identifiers_match_target(outer_count + inner_count,
1482 rids, target);
1486 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 return false;
1503}
#define Assert(condition)
Definition c.h:927
pgpa_itm_type pgpa_identifiers_match_target(int nrids, pgpa_identifier *rids, pgpa_advice_target *target)
Definition pgpa_ast.c:277
@ PGPA_TARGET_IDENTIFIER
Definition pgpa_ast.h:27
pgpa_itm_type
Definition pgpa_ast.h:141
@ PGPA_ITM_EQUAL
Definition pgpa_ast.h:142
@ PGPA_ITM_DISJOINT
Definition pgpa_ast.h:146
@ PGPA_ITM_KEYS_ARE_SUBSET
Definition pgpa_ast.h:143
@ PGPA_ITM_TARGETS_ARE_SUBSET
Definition pgpa_ast.h:144
#define PGPA_TE_MATCH_FULL
Definition pgpa_trove.h:47
#define PGPA_TE_MATCH_PARTIAL
Definition pgpa_trove.h:46
pgpa_target_type ttype
Definition pgpa_ast.h:49
pgpa_advice_target * target
Definition pgpa_trove.h:59
int flags
Definition pgpa_trove.h:60

References Assert, fb(), pgpa_trove_entry::flags, pgpa_identifiers_match_target(), PGPA_ITM_DISJOINT, PGPA_ITM_EQUAL, PGPA_ITM_KEYS_ARE_SUBSET, PGPA_ITM_TARGETS_ARE_SUBSET, PGPA_TARGET_IDENTIFIER, PGPA_TE_MATCH_FULL, PGPA_TE_MATCH_PARTIAL, pgpa_trove_entry::target, and pgpa_advice_target::ttype.

Referenced by pgpa_planner_apply_join_path_advice().

◆ pgpa_join_order_permits_join()

static pgpa_jo_outcome pgpa_join_order_permits_join ( int  outer_count,
int  inner_count,
pgpa_identifier rids,
pgpa_trove_entry entry 
)
static

Definition at line 1247 of file pgpa_planner.c.

1250{
1251 bool loop = true;
1252 bool sublist = false;
1253 int length;
1254 int outer_length;
1255 pgpa_advice_target *target = entry->target;
1257
1258 /* We definitely have at least a partial match for this trove entry. */
1259 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 while (loop)
1274 {
1276
1277 loop = false;
1279 {
1281
1283 continue;
1284
1285 itm = pgpa_identifiers_match_target(outer_count + inner_count,
1286 rids, child_target);
1288 {
1290 {
1291 target = child_target;
1292 sublist = true;
1293 loop = true;
1294 break;
1295 }
1296 else
1297 {
1299 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 length = list_length(target->children);
1312 for (outer_length = 1; outer_length <= length; ++outer_length)
1313 {
1315
1316 /* Avoid leaking memory in every loop iteration. */
1317 if (prefix_target->children != NULL)
1318 list_free(prefix_target->children);
1319 prefix_target->children = list_copy_head(target->children,
1320 outer_length);
1321
1322 /* Search, hoping to find an exact match. */
1323 itm = pgpa_identifiers_match_target(outer_count, rids, prefix_target);
1324 if (itm == PGPA_ITM_EQUAL)
1325 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 */
1336 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 if (outer_length < length)
1350 {
1353
1355
1356 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 if (!sublist && outer_length + 1 == length && itm == PGPA_ITM_EQUAL)
1367 entry->flags |= PGPA_TE_MATCH_FULL;
1368
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 if (!sublist)
1393 entry->flags |= PGPA_TE_MATCH_FULL;
1395}
void list_free(List *list)
Definition list.c:1546
List * list_copy_head(const List *oldlist, int len)
Definition list.c:1593
static int list_length(const List *l)
Definition pg_list.h:152
#define foreach_ptr(type, var, lst)
Definition pg_list.h:469
static void * list_nth(const List *list, int n)
Definition pg_list.h:299
@ PGPA_TARGET_UNORDERED_LIST
Definition pgpa_ast.h:29
@ PGPA_TARGET_ORDERED_LIST
Definition pgpa_ast.h:28

References Assert, pgpa_advice_target::children, fb(), pgpa_trove_entry::flags, foreach_ptr, list_copy_head(), list_free(), list_length(), list_nth(), palloc0_object, pgpa_identifiers_match_target(), PGPA_ITM_EQUAL, PGPA_ITM_KEYS_ARE_SUBSET, PGPA_ITM_TARGETS_ARE_SUBSET, PGPA_JO_DENIED, PGPA_JO_INDIFFERENT, PGPA_JO_PERMITTED, PGPA_TARGET_IDENTIFIER, PGPA_TARGET_ORDERED_LIST, PGPA_TARGET_UNORDERED_LIST, PGPA_TE_MATCH_FULL, PGPA_TE_MATCH_PARTIAL, pgpa_trove_entry::target, and pgpa_advice_target::ttype.

Referenced by pgpa_planner_apply_join_path_advice().

◆ pgpa_join_path_setup()

static void pgpa_join_path_setup ( PlannerInfo root,
RelOptInfo joinrel,
RelOptInfo outerrel,
RelOptInfo innerrel,
JoinType  jointype,
JoinPathExtraData extra 
)
static

Definition at line 576 of file pgpa_planner.c.

579{
581
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 if (jointype == JOIN_UNIQUE_OUTER || jointype == JOIN_UNIQUE_INNER)
590 {
593
594 uniquerel = jointype == JOIN_UNIQUE_OUTER ? outerrel : innerrel;
596 if (pps != NULL &&
597 (pps->generate_advice_string || pps->generate_advice_feedback))
598 {
599 bool found = false;
600
601 /* Avoid adding duplicates. */
602 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 if (root->plan_name == ur->plan_name &&
609 bms_equal(uniquerel->relids, ur->relids))
610 {
611 found = true;
612 break;
613 }
614 }
615
616 /* If not a duplicate, append to the list. */
617 if (!found)
618 {
620
621 ur->plan_name = root->plan_name;
622 ur->relids = uniquerel->relids;
623 pps->sj_unique_rels = lappend(pps->sj_unique_rels, ur);
624 }
625 }
626 }
627
628 /* Get our private state information for this join. */
629 pjs = pgpa_get_join_state(root, joinrel, outerrel, innerrel);
630
631 /* If there is relevant advice, call a helper function to apply it. */
632 if (pjs != NULL)
633 {
635
637 &extra->pgs_mask,
638 root->plan_name,
639 pjs);
640
641 /* Emit debugging message, if enabled. */
643 {
644 if (root->plan_name != NULL)
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,
653 extra->pgs_mask)));
654 else
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),
662 extra->pgs_mask)));
663 }
664 }
665
666 /* Pass call to previous hook. */
668 (*prev_join_path_setup) (root, joinrel, outerrel, innerrel,
669 jointype, extra);
670}
bool bms_equal(const Bitmapset *a, const Bitmapset *b)
Definition bitmapset.c:142
BMS_Membership bms_membership(const Bitmapset *a)
Definition bitmapset.c:765
@ BMS_MULTIPLE
Definition bitmapset.h:73
#define palloc_object(type)
Definition fe_memutils.h:74
List * lappend(List *list, void *datum)
Definition list.c:339
@ JOIN_UNIQUE_OUTER
Definition nodes.h:326
@ JOIN_UNIQUE_INNER
Definition nodes.h:327
static pgpa_join_state * pgpa_get_join_state(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel)
static char * pgpa_bms_to_cstring(Bitmapset *bms)
static const char * pgpa_jointype_to_cstring(JoinType jointype)
static void pgpa_planner_apply_join_path_advice(JoinType jointype, uint64 *pgs_mask_p, char *plan_name, pgpa_join_state *pjs)
static join_path_setup_hook_type prev_join_path_setup

References Assert, bms_equal(), bms_membership(), BMS_MULTIPLE, ereport, errmsg, fb(), foreach_ptr, GetPlannerGlobalExtensionState(), JOIN_UNIQUE_INNER, JOIN_UNIQUE_OUTER, lappend(), palloc_object, pg_plan_advice_trace_mask, pgpa_bms_to_cstring(), pgpa_get_join_state(), pgpa_jointype_to_cstring(), pgpa_planner_apply_join_path_advice(), JoinPathExtraData::pgs_mask, planner_extension_id, prev_join_path_setup, RelOptInfo::relids, root, and WARNING.

Referenced by pgpa_planner_install_hooks().

◆ pgpa_join_strategy_mask_from_advice_tag()

static uint64 pgpa_join_strategy_mask_from_advice_tag ( pgpa_advice_tag_type  tag)
static

Definition at line 1208 of file pgpa_planner.c.

1209{
1210 switch (tag)
1211 {
1213 return PGS_FOREIGNJOIN;
1215 return PGS_MERGEJOIN_PLAIN;
1219 return PGS_NESTLOOP_PLAIN;
1223 return PGS_NESTLOOP_MEMOIZE;
1224 case PGPA_TAG_HASH_JOIN:
1225 return PGS_HASHJOIN;
1226 default:
1227 return 0;
1228 }
1229}
#define PGS_NESTLOOP_MEMOIZE
Definition pathnodes.h:76
#define PGS_FOREIGNJOIN
Definition pathnodes.h:71
#define PGS_NESTLOOP_MATERIALIZE
Definition pathnodes.h:75
#define PGS_MERGEJOIN_PLAIN
Definition pathnodes.h:72
#define PGS_MERGEJOIN_MATERIALIZE
Definition pathnodes.h:73
#define PGS_HASHJOIN
Definition pathnodes.h:77
#define PGS_NESTLOOP_PLAIN
Definition pathnodes.h:74
@ PGPA_TAG_NESTED_LOOP_MATERIALIZE
Definition pgpa_ast.h:92
@ PGPA_TAG_MERGE_JOIN_PLAIN
Definition pgpa_ast.h:91
@ PGPA_TAG_NESTED_LOOP_MEMOIZE
Definition pgpa_ast.h:93
@ PGPA_TAG_HASH_JOIN
Definition pgpa_ast.h:86
@ PGPA_TAG_FOREIGN_JOIN
Definition pgpa_ast.h:83
@ PGPA_TAG_NESTED_LOOP_PLAIN
Definition pgpa_ast.h:94
@ PGPA_TAG_MERGE_JOIN_MATERIALIZE
Definition pgpa_ast.h:90

References PGPA_TAG_FOREIGN_JOIN, PGPA_TAG_HASH_JOIN, PGPA_TAG_MERGE_JOIN_MATERIALIZE, PGPA_TAG_MERGE_JOIN_PLAIN, PGPA_TAG_NESTED_LOOP_MATERIALIZE, PGPA_TAG_NESTED_LOOP_MEMOIZE, PGPA_TAG_NESTED_LOOP_PLAIN, PGS_FOREIGNJOIN, PGS_HASHJOIN, PGS_MERGEJOIN_MATERIALIZE, PGS_MERGEJOIN_PLAIN, PGS_NESTLOOP_MATERIALIZE, PGS_NESTLOOP_MEMOIZE, and PGS_NESTLOOP_PLAIN.

Referenced by pgpa_planner_apply_join_path_advice().

◆ pgpa_joinrel_setup()

static void pgpa_joinrel_setup ( PlannerInfo root,
RelOptInfo joinrel,
RelOptInfo outerrel,
RelOptInfo innerrel,
SpecialJoinInfo sjinfo,
List restrictlist 
)
static

Definition at line 526 of file pgpa_planner.c.

529{
531
533
534 /* Get our private state information for this join. */
535 pjs = pgpa_get_join_state(root, joinrel, outerrel, innerrel);
536
537 /* If there is relevant advice, call a helper function to apply it. */
538 if (pjs != NULL)
539 {
540 uint64 original_mask = joinrel->pgs_mask;
541
543 root->plan_name,
544 pjs);
545
546 /* Emit debugging message, if enabled. */
548 {
549 if (root->plan_name != NULL)
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,
555 joinrel->pgs_mask)));
556 else
558 (errmsg("strategy mask for join on RTIs %s changed from 0x%" PRIx64 " to 0x%" PRIx64,
559 pgpa_bms_to_cstring(joinrel->relids),
561 joinrel->pgs_mask)));
562 }
563 }
564
565 /* Pass call to previous hook. */
567 (*prev_joinrel_setup) (root, joinrel, outerrel, innerrel,
568 sjinfo, restrictlist);
569}
static joinrel_setup_hook_type prev_joinrel_setup
static void pgpa_planner_apply_joinrel_advice(uint64 *pgs_mask_p, char *plan_name, pgpa_join_state *pjs)

References Assert, bms_membership(), BMS_MULTIPLE, ereport, errmsg, fb(), pg_plan_advice_trace_mask, pgpa_bms_to_cstring(), pgpa_get_join_state(), pgpa_planner_apply_joinrel_advice(), RelOptInfo::pgs_mask, prev_joinrel_setup, RelOptInfo::relids, root, and WARNING.

Referenced by pgpa_planner_install_hooks().

◆ pgpa_jointype_to_cstring()

static const char * pgpa_jointype_to_cstring ( JoinType  jointype)
static

Definition at line 2172 of file pgpa_planner.c.

2173{
2174 switch (jointype)
2175 {
2176 case JOIN_INNER:
2177 return "inner";
2178 case JOIN_LEFT:
2179 return "left";
2180 case JOIN_FULL:
2181 return "full";
2182 case JOIN_RIGHT:
2183 return "right";
2184 case JOIN_SEMI:
2185 return "semi";
2186 case JOIN_ANTI:
2187 return "anti";
2188 case JOIN_RIGHT_SEMI:
2189 return "right semi";
2190 case JOIN_RIGHT_ANTI:
2191 return "right anti";
2192 case JOIN_UNIQUE_OUTER:
2193 return "unique outer";
2194 case JOIN_UNIQUE_INNER:
2195 return "unique inner";
2196 }
2197 return "???";
2198}
@ JOIN_SEMI
Definition nodes.h:317
@ JOIN_FULL
Definition nodes.h:305
@ JOIN_INNER
Definition nodes.h:303
@ JOIN_RIGHT
Definition nodes.h:306
@ JOIN_RIGHT_SEMI
Definition nodes.h:319
@ JOIN_LEFT
Definition nodes.h:304
@ JOIN_RIGHT_ANTI
Definition nodes.h:320
@ JOIN_ANTI
Definition nodes.h:318

References JOIN_ANTI, JOIN_FULL, JOIN_INNER, JOIN_LEFT, JOIN_RIGHT, JOIN_RIGHT_ANTI, JOIN_RIGHT_SEMI, JOIN_SEMI, JOIN_UNIQUE_INNER, and JOIN_UNIQUE_OUTER.

Referenced by pgpa_join_path_setup().

◆ pgpa_opaque_join_permits_join()

static bool pgpa_opaque_join_permits_join ( int  outer_count,
int  inner_count,
pgpa_identifier rids,
pgpa_trove_entry entry,
bool restrict_method 
)
static

Definition at line 1519 of file pgpa_planner.c.

1523{
1524 pgpa_advice_target *target = entry->target;
1526
1527 /* We definitely have at least a partial match for this trove entry. */
1528 entry->flags |= PGPA_TE_MATCH_PARTIAL;
1529
1530 *restrict_method = false;
1531
1532 join_itm = pgpa_identifiers_match_target(outer_count + inner_count,
1533 rids, target);
1534 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 entry->flags |= PGPA_TE_MATCH_FULL;
1541 *restrict_method = true;
1542 return true;
1543 }
1544
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 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 return false;
1568}

References fb(), pgpa_trove_entry::flags, pgpa_identifiers_match_target(), PGPA_ITM_EQUAL, PGPA_ITM_KEYS_ARE_SUBSET, PGPA_ITM_TARGETS_ARE_SUBSET, PGPA_TE_MATCH_FULL, PGPA_TE_MATCH_PARTIAL, and pgpa_trove_entry::target.

Referenced by pgpa_planner_apply_join_path_advice().

◆ pgpa_planner_append_feedback()

static List * pgpa_planner_append_feedback ( List list,
pgpa_trove trove,
pgpa_trove_lookup_type  type,
pgpa_identifier rt_identifiers,
pgpa_plan_walker_context walker 
)
static

Definition at line 1920 of file pgpa_planner.c.

1924{
1925 pgpa_trove_entry *entries;
1926 int nentries;
1927
1928 pgpa_trove_lookup_all(trove, type, &entries, &nentries);
1929 for (int i = 0; i < nentries; ++i)
1930 {
1931 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 if ((entry->flags & PGPA_TE_MATCH_FULL) != 0 &&
1941 entry->tag, entry->target))
1942 entry->flags |= PGPA_TE_FAILED;
1943
1945 (Node *) makeInteger(entry->flags), -1);
1946 list = lappend(list, item);
1947 }
1948
1949 return list;
1950}
int i
Definition isn.c:77
DefElem * makeDefElem(char *name, Node *arg, int location)
Definition makefuncs.c:637
void pgpa_trove_lookup_all(pgpa_trove *trove, pgpa_trove_lookup_type type, pgpa_trove_entry **entries, int *nentries)
Definition pgpa_trove.c:275
char * pgpa_cstring_trove_entry(pgpa_trove_entry *entry)
Definition pgpa_trove.c:295
#define PGPA_TE_FAILED
Definition pgpa_trove.h:50
bool pgpa_walker_would_advise(pgpa_plan_walker_context *walker, pgpa_identifier *rt_identifiers, pgpa_advice_tag_type tag, pgpa_advice_target *target)
Definition nodes.h:135
Definition pgpa_trove.h:57
pgpa_advice_tag_type tag
Definition pgpa_trove.h:58
Integer * makeInteger(int i)
Definition value.c:23
const char * type

References fb(), pgpa_trove_entry::flags, i, lappend(), makeDefElem(), makeInteger(), pgpa_cstring_trove_entry(), PGPA_TE_FAILED, PGPA_TE_MATCH_FULL, pgpa_trove_lookup_all(), pgpa_walker_would_advise(), pgpa_trove_entry::tag, pgpa_trove_entry::target, and type.

Referenced by pgpa_planner_shutdown().

◆ pgpa_planner_apply_join_path_advice()

static void pgpa_planner_apply_join_path_advice ( JoinType  jointype,
uint64 pgs_mask_p,
char plan_name,
pgpa_join_state pjs 
)
static

Definition at line 989 of file pgpa_planner.c.

992{
993 int i = -1;
998 bool jm_conflict = false;
999 uint32 join_mask = 0;
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 while ((i = bms_next_member(pjs->rel_indexes, i)) >= 0)
1022 {
1023 pgpa_trove_entry *entry = &pjs->rel_entries[i];
1026
1027 if (entry->tag != PGPA_TAG_PARTITIONWISE)
1028 continue;
1029
1031 pjs->rids, entry->target);
1032 if (outer_itm == PGPA_ITM_EQUAL ||
1034 continue;
1035
1037 pjs->rids + pjs->outer_count,
1038 entry->target);
1039 if (inner_itm == PGPA_ITM_EQUAL ||
1041 continue;
1042
1044 }
1045
1046 /* Iterate over advice that pertains to the join order and method. */
1047 i = -1;
1048 while ((i = bms_next_member(pjs->join_indexes, i)) >= 0)
1049 {
1050 pgpa_trove_entry *entry = &pjs->join_entries[i];
1052
1053 /* Handle join order advice. */
1054 if (entry->tag == PGPA_TAG_JOIN_ORDER)
1055 {
1057
1059 pjs->inner_count,
1060 pjs->rids,
1061 entry);
1064 else if (jo_outcome == PGPA_JO_DENIED)
1066 continue;
1067 }
1068
1069 /* Handle join method advice. */
1071 if (my_join_mask != 0)
1072 {
1073 bool permit;
1074 bool restrict_method;
1075
1076 if (entry->tag == PGPA_TAG_FOREIGN_JOIN)
1078 pjs->inner_count,
1079 pjs->rids,
1080 entry,
1082 else
1084 pjs->inner_count,
1085 pjs->rids,
1086 entry,
1088 if (!permit)
1090 else if (restrict_method)
1091 {
1093 if (join_mask != 0 && join_mask != my_join_mask)
1094 jm_conflict = true;
1096 }
1097 continue;
1098 }
1099
1100 /* Handle semijoin uniqueness advice. */
1101 if (entry->tag == PGPA_TAG_SEMIJOIN_UNIQUE ||
1103 {
1105 bool restrict_method;
1106
1107 /* Planner has nullable side of the semijoin on the outer side? */
1108 outer_side_nullable = (jointype == JOIN_UNIQUE_OUTER ||
1109 jointype == JOIN_RIGHT_SEMI);
1110
1111 if (!pgpa_semijoin_permits_join(pjs->outer_count,
1112 pjs->inner_count,
1113 pjs->rids,
1114 entry,
1118 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? */
1126
1127 /* Planner is trying to unique-ify and use a regular join? */
1128 jt_unique = (jointype == JOIN_UNIQUE_INNER ||
1129 jointype == JOIN_UNIQUE_OUTER);
1130
1131 /* Planner is trying a semi-join, without unique-ifying? */
1132 jt_non_unique = (jointype == JOIN_SEMI ||
1133 jointype == JOIN_RIGHT_SEMI);
1134
1135 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 entry->flags |= PGPA_TE_INAPPLICABLE;
1142 }
1143 else if (advice_unique != jt_unique)
1145 else
1147 }
1148 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 if (jo_permit_indexes != NULL &&
1159 {
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 if (jm_conflict)
1173 pgpa_trove_set_flags(pjs->join_entries, jm_indexes,
1175
1176 /* If semijoin advice says both yes and no, mark it all as conflicting. */
1178 {
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 */
1194 if (join_mask != 0 && !jm_conflict)
1198}
Bitmapset * bms_add_member(Bitmapset *a, int x)
Definition bitmapset.c:799
uint32_t uint32
Definition c.h:600
#define PGS_JOIN_ANY
Definition pathnodes.h:96
@ PGPA_TAG_SEMIJOIN_NON_UNIQUE
Definition pgpa_ast.h:97
@ PGPA_TAG_PARTITIONWISE
Definition pgpa_ast.h:96
@ PGPA_TAG_SEMIJOIN_UNIQUE
Definition pgpa_ast.h:98
@ PGPA_TAG_JOIN_ORDER
Definition pgpa_ast.h:89
static bool pgpa_opaque_join_permits_join(int outer_count, int inner_count, pgpa_identifier *rids, pgpa_trove_entry *entry, bool *restrict_method)
static pgpa_jo_outcome pgpa_join_order_permits_join(int outer_count, int inner_count, pgpa_identifier *rids, pgpa_trove_entry *entry)
static bool pgpa_join_method_permits_join(int outer_count, int inner_count, pgpa_identifier *rids, pgpa_trove_entry *entry, bool *restrict_method)
static uint64 pgpa_join_strategy_mask_from_advice_tag(pgpa_advice_tag_type tag)
static bool pgpa_semijoin_permits_join(int outer_count, int inner_count, pgpa_identifier *rids, pgpa_trove_entry *entry, bool outer_is_nullable, bool *restrict_method)
void pgpa_trove_set_flags(pgpa_trove_entry *entries, Bitmapset *indexes, int flags)
Definition pgpa_trove.c:326
#define PGPA_TE_INAPPLICABLE
Definition pgpa_trove.h:48
#define PGPA_TE_CONFLICTING
Definition pgpa_trove.h:49

References bms_add_member(), bms_next_member(), fb(), pgpa_trove_entry::flags, i, JOIN_RIGHT_SEMI, JOIN_SEMI, JOIN_UNIQUE_INNER, JOIN_UNIQUE_OUTER, pgpa_identifiers_match_target(), PGPA_ITM_EQUAL, PGPA_ITM_TARGETS_ARE_SUBSET, PGPA_JO_DENIED, PGPA_JO_PERMITTED, pgpa_join_method_permits_join(), pgpa_join_order_permits_join(), pgpa_join_strategy_mask_from_advice_tag(), pgpa_opaque_join_permits_join(), pgpa_semijoin_permits_join(), PGPA_TAG_FOREIGN_JOIN, PGPA_TAG_JOIN_ORDER, PGPA_TAG_PARTITIONWISE, PGPA_TAG_SEMIJOIN_NON_UNIQUE, PGPA_TAG_SEMIJOIN_UNIQUE, PGPA_TE_CONFLICTING, PGPA_TE_INAPPLICABLE, pgpa_trove_set_flags(), PGS_JOIN_ANY, pgpa_trove_entry::tag, and pgpa_trove_entry::target.

Referenced by pgpa_join_path_setup().

◆ pgpa_planner_apply_joinrel_advice()

static void pgpa_planner_apply_joinrel_advice ( uint64 pgs_mask_p,
char plan_name,
pgpa_join_state pjs 
)
static

Definition at line 793 of file pgpa_planner.c.

795{
796 int i = -1;
797 int flags;
798 bool gather_conflict = false;
802 bool partitionwise_conflict = false;
803 int partitionwise_outcome = 0;
806
807 /* Iterate over all possibly-relevant advice. */
808 while ((i = bms_next_member(pjs->rel_indexes, i)) >= 0)
809 {
810 pgpa_trove_entry *entry = &pjs->rel_entries[i];
812 bool full_match = false;
814 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 if (entry->tag == PGPA_TAG_NO_GATHER)
825 {
827 full_match = true;
828 }
829 else
830 {
831 int total_count;
832
833 total_count = pjs->outer_count + pjs->inner_count;
835 entry->target);
837
838 if (itm == PGPA_ITM_EQUAL)
839 {
840 full_match = true;
841 if (entry->tag == PGPA_TAG_PARTITIONWISE)
843 else if (entry->tag == PGPA_TAG_GATHER)
845 else if (entry->tag == PGPA_TAG_GATHER_MERGE)
847 else
848 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 if (entry->tag == PGPA_TAG_PARTITIONWISE)
875 {
879 }
880 else if (entry->tag == PGPA_TAG_GATHER ||
881 entry->tag == PGPA_TAG_GATHER_MERGE)
882 {
886 }
887 else
888 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 if (my_gather_mask != 0)
899 {
901 gather_conflict = true;
903 if (full_match)
905 else
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 */
916 {
917 if (partitionwise_outcome != 0 &&
921 if (full_match)
924 else
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 flags = PGPA_TE_MATCH_PARTIAL;
936 if (gather_conflict)
937 flags |= PGPA_TE_CONFLICTING;
938 pgpa_trove_set_flags(pjs->rel_entries, gather_partial_match, flags);
939 flags |= PGPA_TE_MATCH_FULL;
940 pgpa_trove_set_flags(pjs->rel_entries, gather_full_match, flags);
941
942 /* Likewise for partitionwise advice. */
943 flags = PGPA_TE_MATCH_PARTIAL;
945 flags |= PGPA_TE_CONFLICTING;
947 flags |= PGPA_TE_MATCH_FULL;
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 if (gather_mask != 0 && !gather_conflict)
956 {
958
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 */
977 {
978 if (partitionwise_outcome > 0)
980 else
982 }
983}
#define ERROR
Definition elog.h:39
#define elog(elevel,...)
Definition elog.h:226
#define PGS_APPEND
Definition pathnodes.h:78
#define PGS_MERGE_APPEND
Definition pathnodes.h:79
#define PGS_CONSIDER_NONPARTIAL
Definition pathnodes.h:84
#define PGS_GATHER
Definition pathnodes.h:80
#define PGS_GATHER_MERGE
Definition pathnodes.h:81
@ PGPA_TAG_GATHER_MERGE
Definition pgpa_ast.h:85
@ PGPA_TAG_GATHER
Definition pgpa_ast.h:84
@ PGPA_TAG_NO_GATHER
Definition pgpa_ast.h:95

References Assert, bms_add_member(), bms_next_member(), elog, ERROR, fb(), i, pgpa_identifiers_match_target(), PGPA_ITM_DISJOINT, PGPA_ITM_EQUAL, PGPA_ITM_KEYS_ARE_SUBSET, PGPA_ITM_TARGETS_ARE_SUBSET, PGPA_TAG_GATHER, PGPA_TAG_GATHER_MERGE, PGPA_TAG_NO_GATHER, PGPA_TAG_PARTITIONWISE, PGPA_TE_CONFLICTING, PGPA_TE_MATCH_FULL, PGPA_TE_MATCH_PARTIAL, pgpa_trove_set_flags(), PGS_APPEND, PGS_CONSIDER_NONPARTIAL, PGS_GATHER, PGS_GATHER_MERGE, PGS_MERGE_APPEND, pgpa_trove_entry::tag, and pgpa_trove_entry::target.

Referenced by pgpa_joinrel_setup().

◆ pgpa_planner_apply_scan_advice()

static void pgpa_planner_apply_scan_advice ( RelOptInfo rel,
pgpa_trove_entry scan_entries,
Bitmapset scan_indexes,
pgpa_trove_entry rel_entries,
Bitmapset rel_indexes 
)
static

Definition at line 1674 of file pgpa_planner.c.

1679{
1680 bool gather_conflict = false;
1683 int i = -1;
1685 int flags;
1686 bool scan_type_conflict = false;
1689 uint64 gather_mask = 0;
1690 uint64 scan_type = 0;
1691
1692 /* Scrutinize available scan advice. */
1693 while ((i = bms_next_member(scan_indexes, i)) >= 0)
1694 {
1696 uint64 my_scan_type = 0;
1697
1698 /* Translate our advice tags to a scan strategy advice value. */
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 */
1713 }
1714 else if (my_entry->tag == PGPA_TAG_INDEX_ONLY_SCAN)
1716 else if (my_entry->tag == PGPA_TAG_INDEX_SCAN)
1718 else if (my_entry->tag == PGPA_TAG_SEQ_SCAN)
1720 else if (my_entry->tag == PGPA_TAG_TID_SCAN)
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 if (my_scan_type != 0)
1734 {
1735 if (scan_type != 0 && scan_type != my_scan_type)
1736 scan_type_conflict = true;
1737 if (!scan_type_conflict && scan_entry != NULL &&
1738 my_entry->target->itarget != NULL &&
1739 scan_entry->target->itarget != NULL &&
1740 !pgpa_index_targets_equal(scan_entry->target->itarget,
1741 my_entry->target->itarget))
1742 scan_type_conflict = true;
1746 }
1747 }
1748
1749 /* Scrutinize available gather-related and partitionwise advice. */
1750 i = -1;
1751 while ((i = bms_next_member(rel_indexes, i)) >= 0)
1752 {
1753 pgpa_trove_entry *my_entry = &rel_entries[i];
1755 bool just_one_rel;
1756
1758 || 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 if (my_entry->tag == PGPA_TAG_PARTITIONWISE)
1765 {
1766 if (just_one_rel)
1767 {
1769
1770 if (scan_type != 0 && scan_type != my_scan_type)
1771 scan_type_conflict = true;
1776 }
1777 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 if (my_entry->tag == PGPA_TAG_GATHER ||
1792 {
1793 if (!just_one_rel)
1795 else if (my_entry->tag == PGPA_TAG_GATHER)
1797 else
1799 }
1800 else if (my_entry->tag == PGPA_TAG_NO_GATHER)
1801 {
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 if (my_gather_mask != 0)
1812 {
1813 if (gather_mask != 0 && gather_mask != my_gather_mask)
1814 gather_conflict = true;
1816 if (just_one_rel)
1818 else
1820 }
1821 }
1822
1823 /* Enforce choice of index. */
1824 if (scan_entry != NULL && !scan_type_conflict &&
1827 {
1828 pgpa_index_target *itarget = scan_entry->target->itarget;
1830
1832 {
1833 char *relname = get_rel_name(index->indexoid);
1834 Oid nspoid = get_rel_namespace(index->indexoid);
1835 char *relnamespace = get_namespace_name_or_temp(nspoid);
1836
1837 if (strcmp(itarget->indname, relname) == 0 &&
1838 (itarget->indnamespace == NULL ||
1839 strcmp(itarget->indnamespace, relnamespace) == 0))
1840 {
1842 break;
1843 }
1844 }
1845
1846 if (matched_index == NULL)
1847 {
1848 /* Don't force the scan type if the index doesn't exist. */
1849 scan_type = 0;
1850
1851 /* Mark advice as inapplicable. */
1854 }
1855 else
1856 {
1857 /* Disable every other index. */
1859 {
1860 if (index != matched_index)
1861 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 */
1872 flags |= PGPA_TE_CONFLICTING;
1874 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 flags = PGPA_TE_MATCH_PARTIAL;
1882 if (gather_conflict)
1883 flags |= PGPA_TE_CONFLICTING;
1884 pgpa_trove_set_flags(rel_entries, gather_partial_match, flags);
1885 flags |= PGPA_TE_MATCH_FULL;
1886 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 if (scan_type != 0 && !scan_type_conflict)
1894 {
1896
1899 rel->pgs_mask &= ~(all_scan_mask & ~scan_type);
1900 }
1901 if (gather_mask != 0 && !gather_conflict)
1902 {
1904
1908 }
1909}
char * get_rel_name(Oid relid)
Definition lsyscache.c:2146
Oid get_rel_namespace(Oid relid)
Definition lsyscache.c:2170
char * get_namespace_name_or_temp(Oid nspid)
Definition lsyscache.c:3610
#define PGS_TIDSCAN
Definition pathnodes.h:70
#define PGS_SEQSCAN
Definition pathnodes.h:66
#define PGS_CONSIDER_INDEXONLY
Definition pathnodes.h:82
#define PGS_BITMAPSCAN
Definition pathnodes.h:69
#define PGS_SCAN_ANY
Definition pathnodes.h:89
#define PGS_INDEXONLYSCAN
Definition pathnodes.h:68
#define PGS_INDEXSCAN
Definition pathnodes.h:67
NameData relname
Definition pg_class.h:40
#define foreach_node(type, var, lst)
Definition pg_list.h:496
bool pgpa_index_targets_equal(pgpa_index_target *i1, pgpa_index_target *i2)
Definition pgpa_ast.c:212
@ PGPA_TAG_INDEX_SCAN
Definition pgpa_ast.h:88
@ PGPA_TAG_BITMAP_HEAP_SCAN
Definition pgpa_ast.h:82
@ PGPA_TAG_INDEX_ONLY_SCAN
Definition pgpa_ast.h:87
@ PGPA_TAG_SEQ_SCAN
Definition pgpa_ast.h:99
@ PGPA_TAG_TID_SCAN
Definition pgpa_ast.h:100
unsigned int Oid
List * indexlist
Definition pathnodes.h:1079
Definition type.h:96
char * indnamespace
Definition pgpa_ast.h:38

References Assert, bms_add_member(), bms_next_member(), fb(), foreach_node, get_namespace_name_or_temp(), get_rel_name(), get_rel_namespace(), i, RelOptInfo::indexlist, pgpa_index_target::indname, pgpa_index_target::indnamespace, list_length(), pgpa_index_targets_equal(), PGPA_TAG_BITMAP_HEAP_SCAN, PGPA_TAG_GATHER, PGPA_TAG_GATHER_MERGE, PGPA_TAG_INDEX_ONLY_SCAN, PGPA_TAG_INDEX_SCAN, PGPA_TAG_NO_GATHER, PGPA_TAG_PARTITIONWISE, PGPA_TAG_SEQ_SCAN, PGPA_TAG_TID_SCAN, PGPA_TARGET_IDENTIFIER, PGPA_TE_CONFLICTING, PGPA_TE_INAPPLICABLE, PGPA_TE_MATCH_FULL, PGPA_TE_MATCH_PARTIAL, pgpa_trove_set_flags(), PGS_APPEND, PGS_BITMAPSCAN, PGS_CONSIDER_INDEXONLY, PGS_CONSIDER_NONPARTIAL, PGS_GATHER, PGS_GATHER_MERGE, PGS_INDEXONLYSCAN, PGS_INDEXSCAN, RelOptInfo::pgs_mask, PGS_MERGE_APPEND, PGS_SCAN_ANY, PGS_SEQSCAN, PGS_TIDSCAN, relname, pgpa_trove_entry::target, and pgpa_advice_target::ttype.

Referenced by pgpa_build_simple_rel().

◆ pgpa_planner_feedback_warning()

static void pgpa_planner_feedback_warning ( List feedback)
static

Definition at line 1957 of file pgpa_planner.c.

1958{
1961
1962 /* Quick exit if there's no feedback. */
1963 if (feedback == NIL)
1964 return;
1965
1966 /* Initialize buffers. */
1969
1970 /* Main loop. */
1972 {
1973 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 */
1982 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 if (detailbuf.len > 0)
1989 {
1992 }
1993
1994 /* Generate output. */
1996 appendStringInfo(&detailbuf, "advice %s feedback is \"%s\"",
1997 item->defname, flagbuf.data);
1998 }
1999
2000 /* Emit the warning, if any problems were found. */
2001 if (detailbuf.len > 0)
2003 errmsg("supplied plan advice was not enforced"),
2004 errdetail("%s", detailbuf.data));
2005}
int32 defGetInt32(DefElem *def)
Definition define.c:148
int errdetail(const char *fmt,...) pg_attribute_printf(1
#define NIL
Definition pg_list.h:68
void pgpa_trove_append_flags(StringInfo buf, int flags)
Definition pgpa_trove.c:343
void resetStringInfo(StringInfo str)
Definition stringinfo.c:126
void appendStringInfoChar(StringInfo str, char ch)
Definition stringinfo.c:242

References appendStringInfo(), appendStringInfoChar(), defGetInt32(), ereport, errdetail(), errmsg, fb(), foreach_node, initStringInfo(), NIL, PGPA_TE_MATCH_FULL, PGPA_TE_MATCH_PARTIAL, pgpa_trove_append_flags(), resetStringInfo(), and WARNING.

Referenced by pgpa_planner_shutdown().

◆ pgpa_planner_install_hooks()

void pgpa_planner_install_hooks ( void  )

Definition at line 226 of file pgpa_planner.c.

227{
239}
int GetPlannerExtensionId(const char *extension_name)
Definition extendplan.c:41
join_path_setup_hook_type join_path_setup_hook
Definition joinpath.c:32
static void pgpa_planner_shutdown(PlannerGlobal *glob, Query *parse, const char *query_string, PlannedStmt *pstmt)
static planner_setup_hook_type prev_planner_setup
static void pgpa_build_simple_rel(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
static void pgpa_planner_setup(PlannerGlobal *glob, Query *parse, const char *query_string, int cursorOptions, double *tuple_fraction, ExplainState *es)
static planner_shutdown_hook_type prev_planner_shutdown
static void pgpa_join_path_setup(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, JoinType jointype, JoinPathExtraData *extra)
static void pgpa_joinrel_setup(PlannerInfo *root, RelOptInfo *joinrel, RelOptInfo *outerrel, RelOptInfo *innerrel, SpecialJoinInfo *sjinfo, List *restrictlist)
planner_shutdown_hook_type planner_shutdown_hook
Definition planner.c:80
planner_setup_hook_type planner_setup_hook
Definition planner.c:77
joinrel_setup_hook_type joinrel_setup_hook
Definition relnode.c:54
build_simple_rel_hook_type build_simple_rel_hook
Definition relnode.c:51

References build_simple_rel_hook, GetPlannerExtensionId(), join_path_setup_hook, joinrel_setup_hook, pgpa_build_simple_rel(), pgpa_join_path_setup(), pgpa_joinrel_setup(), pgpa_planner_setup(), pgpa_planner_shutdown(), planner_extension_id, planner_setup_hook, planner_shutdown_hook, prev_build_simple_rel, prev_join_path_setup, prev_joinrel_setup, prev_planner_setup, and prev_planner_shutdown.

Referenced by _PG_init().

◆ pgpa_planner_setup()

static void pgpa_planner_setup ( PlannerGlobal glob,
Query parse,
const char query_string,
int  cursorOptions,
double tuple_fraction,
ExplainState es 
)
static

Definition at line 245 of file pgpa_planner.c.

248{
249 pgpa_trove *trove = NULL;
251 char *supplied_advice;
252 bool generate_advice_feedback = false;
253 bool generate_advice_string = false;
254 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 generate_advice_string = (pg_plan_advice_always_store_advice_details ||
265 if (generate_advice_string)
266 needs_pps = true;
267
268 /*
269 * If any advice was provided, build a trove of advice for use during
270 * planning.
271 */
273 query_string,
274 cursorOptions,
275 es);
276 if (supplied_advice != NULL && supplied_advice[0] != '\0')
277 {
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 */
290 if (error)
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 if (advice_items != NIL)
299 {
301 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 */
309 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 if (needs_pps)
329 {
331 pps->generate_advice_feedback = generate_advice_feedback;
332 pps->generate_advice_string = generate_advice_string;
333 pps->trove = trove;
334#ifdef USE_ASSERT_CHECKING
335 pps->ri_check_hash =
337#endif
339 }
340
341 /* Pass call to previous hook. */
343 (*prev_planner_setup) (glob, parse, query_string, cursorOptions,
344 tuple_fraction, es);
345}
void SetPlannerGlobalExtensionState(PlannerGlobal *glob, int extension_id, void *opaque)
Definition extendplan.c:77
void parse(int)
Definition parse.c:49
MemoryContext CurrentMemoryContext
Definition mcxt.c:160
bool pg_plan_advice_always_store_advice_details
bool pg_plan_advice_should_explain(ExplainState *es)
char * pg_plan_advice_get_supplied_query_advice(PlannerGlobal *glob, Query *parse, const char *query_string, int cursorOptions, ExplainState *es)
bool pg_plan_advice_feedback_warnings
List * pgpa_parse(const char *advice_string, char **error_p)
int pgpa_planner_generate_advice
pgpa_trove * pgpa_build_trove(List *advice_items)
Definition pgpa_trove.c:132
static void error(void)
Definition pg_list.h:54

References CurrentMemoryContext, ereport, errmsg, error(), fb(), NIL, palloc0_object, parse(), pg_plan_advice_always_store_advice_details, pg_plan_advice_feedback_warnings, pg_plan_advice_get_supplied_query_advice(), pg_plan_advice_should_explain(), pgpa_build_trove(), pgpa_parse(), pgpa_planner_generate_advice, planner_extension_id, prev_planner_setup, SetPlannerGlobalExtensionState(), and WARNING.

Referenced by pgpa_planner_install_hooks().

◆ pgpa_planner_shutdown()

static void pgpa_planner_shutdown ( PlannerGlobal glob,
Query parse,
const char query_string,
PlannedStmt pstmt 
)
static

Definition at line 351 of file pgpa_planner.c.

353{
355 pgpa_trove *trove = NULL;
356 pgpa_plan_walker_context walker = {0}; /* placate compiler */
357 bool generate_advice_feedback = false;
358 bool generate_advice_string = false;
361
362 /* Fetch our private state, set up by pgpa_planner_setup(). */
364 if (pps != NULL)
365 {
366 trove = pps->trove;
367 generate_advice_feedback = pps->generate_advice_feedback;
368 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 if (generate_advice_string || generate_advice_feedback)
377 {
378 pgpa_plan_walker(&walker, pstmt, pps->sj_unique_rels);
380 }
381
382 /* Generate the advice string, if we need to do so. */
383 if (generate_advice_string)
384 {
385 char *advice_string;
387
388 /* Generate a textual advice string. */
391 advice_string = buf.data;
392
393 /* Save the advice string in the final plan. */
395 makeDefElem("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 if (generate_advice_feedback)
405 {
406 List *feedback = NIL;
407
408 /*
409 * Inject a Node-tree representation of all the trove-entry flags into
410 * the PlannedStmt.
411 */
413 trove,
417 trove,
421 trove,
424
426 (Node *) feedback, -1));
427
428 /* If we were asked to generate feedback warnings, do so. */
431 }
432
433 /* Push whatever data we're saving into the PlannedStmt. */
434 if (pgpa_items != NIL)
435 pstmt->extension_state =
437 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 if (pps != NULL)
445
446 /* Pass call to previous hook. */
448 (*prev_planner_shutdown) (glob, parse, query_string, pstmt);
449}
pgpa_identifier * pgpa_create_identifiers_for_planned_stmt(PlannedStmt *pstmt)
void pgpa_output_advice(StringInfo buf, pgpa_plan_walker_context *walker, pgpa_identifier *rt_identifiers)
Definition pgpa_output.c:78
static void pgpa_ri_checker_validate(pgpa_planner_state *pps, PlannedStmt *pstmt)
static List * pgpa_planner_append_feedback(List *list, pgpa_trove *trove, pgpa_trove_lookup_type type, pgpa_identifier *rt_identifiers, pgpa_plan_walker_context *walker)
static void pgpa_planner_feedback_warning(List *feedback)
void pgpa_plan_walker(pgpa_plan_walker_context *walker, PlannedStmt *pstmt, List *sj_unique_rels)
Definition pgpa_walker.c:71
List * extension_state
Definition plannodes.h:165
String * makeString(char *str)
Definition value.c:63

References buf, PlannedStmt::extension_state, fb(), GetPlannerGlobalExtensionState(), initStringInfo(), lappend(), makeDefElem(), makeString(), NIL, parse(), pg_plan_advice_feedback_warnings, pgpa_create_identifiers_for_planned_stmt(), pgpa_output_advice(), pgpa_plan_walker(), pgpa_planner_append_feedback(), pgpa_planner_feedback_warning(), pgpa_ri_checker_validate(), PGPA_TROVE_LOOKUP_JOIN, PGPA_TROVE_LOOKUP_REL, PGPA_TROVE_LOOKUP_SCAN, planner_extension_id, and prev_planner_shutdown.

Referenced by pgpa_planner_install_hooks().

◆ pgpa_ri_checker_save()

static void pgpa_ri_checker_save ( pgpa_planner_state pps,
PlannerInfo root,
RelOptInfo rel 
)
inlinestatic

Definition at line 2039 of file pgpa_planner.c.

2041{
2042#ifdef USE_ASSERT_CHECKING
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;
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}
int bms_singleton_member(const Bitmapset *a)
Definition bitmapset.c:665
const char * pgpa_identifier_string(const pgpa_identifier *rid)

References Assert, bms_singleton_member(), fb(), pgpa_compute_identifier_by_rti(), pgpa_identifier_string(), RelOptInfo::relids, and root.

Referenced by pgpa_build_simple_rel().

◆ pgpa_ri_checker_validate()

static void pgpa_ri_checker_validate ( pgpa_planner_state pps,
PlannedStmt pstmt 
)
static

Definition at line 2064 of file pgpa_planner.c.

2065{
2066#ifdef USE_ASSERT_CHECKING
2069 pgpa_ri_checker *check;
2070
2071 /* Create identifiers from the planned statement. */
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;
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 {
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. */
2139 Assert(strcmp(rid_string, check->rid_string) == 0);
2140 }
2141#endif
2142}
unsigned int Index
Definition c.h:682
List * subrtinfos
Definition plannodes.h:135
List * rtable
Definition plannodes.h:109

References Assert, fb(), foreach_node, list_length(), pgpa_create_identifiers_for_planned_stmt(), pgpa_identifier_string(), PlannedStmt::rtable, and PlannedStmt::subrtinfos.

Referenced by pgpa_planner_shutdown().

◆ pgpa_semijoin_permits_join()

static bool pgpa_semijoin_permits_join ( int  outer_count,
int  inner_count,
pgpa_identifier rids,
pgpa_trove_entry entry,
bool  outer_is_nullable,
bool restrict_method 
)
static

Definition at line 1584 of file pgpa_planner.c.

1589{
1590 pgpa_advice_target *target = entry->target;
1594
1595 *restrict_method = false;
1596
1597 /* We definitely have at least a partial match for this trove entry. */
1598 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 */
1611 rids, target);
1613 {
1614 entry->flags |= PGPA_TE_MATCH_FULL;
1616 {
1617 *restrict_method = true;
1618 return true;
1619 }
1620 }
1622 return true;
1623
1624 /* As above, but for the inner rel. */
1626 rids + outer_count,
1627 target);
1629 {
1630 entry->flags |= PGPA_TE_MATCH_FULL;
1631 if (!outer_is_nullable)
1632 {
1633 *restrict_method = true;
1634 return true;
1635 }
1636 }
1638 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 join_itm = pgpa_identifiers_match_target(outer_count + inner_count,
1645 rids, target);
1649 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 return false;
1668}

References Assert, fb(), pgpa_trove_entry::flags, pgpa_identifiers_match_target(), PGPA_ITM_DISJOINT, PGPA_ITM_EQUAL, PGPA_ITM_KEYS_ARE_SUBSET, PGPA_ITM_TARGETS_ARE_SUBSET, PGPA_TE_MATCH_FULL, PGPA_TE_MATCH_PARTIAL, and pgpa_trove_entry::target.

Referenced by pgpa_planner_apply_join_path_advice().

Variable Documentation

◆ pgpa_planner_generate_advice

int pgpa_planner_generate_advice = 0

Definition at line 146 of file pgpa_planner.c.

Referenced by pg_plan_advice_request_advice_generation(), and pgpa_planner_setup().

◆ planner_extension_id

◆ prev_build_simple_rel

build_simple_rel_hook_type prev_build_simple_rel = NULL
static

Definition at line 139 of file pgpa_planner.c.

Referenced by pgpa_build_simple_rel(), and pgpa_planner_install_hooks().

◆ prev_join_path_setup

join_path_setup_hook_type prev_join_path_setup = NULL
static

Definition at line 140 of file pgpa_planner.c.

Referenced by pgpa_join_path_setup(), and pgpa_planner_install_hooks().

◆ prev_joinrel_setup

joinrel_setup_hook_type prev_joinrel_setup = NULL
static

Definition at line 141 of file pgpa_planner.c.

Referenced by pgpa_joinrel_setup(), and pgpa_planner_install_hooks().

◆ prev_planner_setup

planner_setup_hook_type prev_planner_setup = NULL
static

Definition at line 142 of file pgpa_planner.c.

Referenced by pgpa_planner_install_hooks(), and pgpa_planner_setup().

◆ prev_planner_shutdown

planner_shutdown_hook_type prev_planner_shutdown = NULL
static

Definition at line 143 of file pgpa_planner.c.

Referenced by pgpa_planner_install_hooks(), and pgpa_planner_shutdown().