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