PostgreSQL Source Code git master
Loading...
Searching...
No Matches
pgpa_output.c
Go to the documentation of this file.
1/*-------------------------------------------------------------------------
2 *
3 * pgpa_output.c
4 * produce textual output from the results of a plan tree walk
5 *
6 * Copyright (c) 2016-2026, PostgreSQL Global Development Group
7 *
8 * contrib/pg_plan_advice/pgpa_output.c
9 *
10 *-------------------------------------------------------------------------
11 */
12
13#include "postgres.h"
14
15#include "pgpa_output.h"
16#include "pgpa_scan.h"
17
18#include "nodes/parsenodes.h"
19#include "parser/parsetree.h"
20#include "utils/builtins.h"
21#include "utils/lsyscache.h"
22
23/*
24 * Context object for textual advice generation.
25 *
26 * rt_identifiers is the caller-provided array of range table identifiers.
27 * See the comments at the top of pgpa_identifier.c for more details.
28 *
29 * buf is the caller-provided output buffer.
30 *
31 * wrap_column is the wrap column, so that we don't create output that is
32 * too wide. See pgpa_maybe_linebreak() and comments in pgpa_output_advice.
33 */
40
42 pgpa_unrolled_join *join);
44 pgpa_join_member *member);
46 pgpa_scan_strategy strategy,
47 List *scans);
48static void pgpa_output_relation_name(pgpa_output_context *context, Oid relid);
51 List *query_features);
53 char *strategy,
56 Bitmapset *relids);
58 Bitmapset *relids);
59
63
64static void pgpa_maybe_linebreak(StringInfo buf, int wrap_column);
65
66/*
67 * Append query advice to the provided buffer.
68 *
69 * Before calling this function, 'walker' must be used to iterate over the
70 * main plan tree and all subplans from the PlannedStmt.
71 *
72 * 'rt_identifiers' is a table of unique identifiers, one for each RTI.
73 * See pgpa_create_identifiers_for_planned_stmt().
74 *
75 * Results will be appended to 'buf'.
76 */
77void
80{
81 Index rtable_length = list_length(walker->pstmt->rtable);
82 ListCell *lc;
83 pgpa_output_context context;
84
85 /* Basic initialization. */
86 memset(&context, 0, sizeof(pgpa_output_context));
87 context.buf = buf;
88
89 /*
90 * Convert identifiers to string form. Note that the loop variable here is
91 * not an RTI, because RTIs are 1-based. Some RTIs will have no
92 * identifier, either because the reloptkind is RTE_JOIN or because that
93 * portion of the query didn't make it into the final plan.
94 */
95 context.rid_strings = palloc0_array(const char *, rtable_length);
96 for (int i = 0; i < rtable_length; ++i)
97 if (rt_identifiers[i].alias_name != NULL)
99
100 /*
101 * If the user chooses to use EXPLAIN (PLAN_ADVICE) in an 80-column window
102 * from a psql client with default settings, psql will add one space to
103 * the left of the output and EXPLAIN will add two more to the left of the
104 * advice. Thus, lines of more than 77 characters will wrap. We set the
105 * wrap limit to 76 here so that the output won't reach all the way to the
106 * very last column of the terminal.
107 *
108 * Of course, this is fairly arbitrary set of assumptions, and one could
109 * well make an argument for a different wrap limit, or for a configurable
110 * one.
111 */
112 context.wrap_column = 76;
113
114 /*
115 * Each piece of JOIN_ORDER() advice fully describes the join order for a
116 * a single unrolled join. Merging is not permitted, because that would
117 * change the meaning, e.g. SEQ_SCAN(a b c d) means simply that sequential
118 * scans should be used for all of those relations, and is thus equivalent
119 * to SEQ_SCAN(a b) SEQ_SCAN(c d), but JOIN_ORDER(a b c d) means that "a"
120 * is the driving table which is then joined to "b" then "c" then "d",
121 * which is totally different from JOIN_ORDER(a b) and JOIN_ORDER(c d).
122 */
123 foreach(lc, walker->toplevel_unrolled_joins)
124 {
126
127 if (buf->len > 0)
129 appendStringInfo(context.buf, "JOIN_ORDER(");
131 appendStringInfoChar(context.buf, ')');
132 pgpa_maybe_linebreak(context.buf, context.wrap_column);
133 }
134
135 /* Emit join strategy advice. */
136 for (int s = 0; s < NUM_PGPA_JOIN_STRATEGY; ++s)
137 {
138 char *strategy = pgpa_cstring_join_strategy(s);
139
141 strategy,
142 walker->join_strategies[s]);
143 }
144
145 /*
146 * Emit scan strategy advice (but not for ordinary scans, which are
147 * definitionally uninteresting).
148 */
149 for (int c = 0; c < NUM_PGPA_SCAN_STRATEGY; ++c)
150 if (c != PGPA_SCAN_ORDINARY)
151 pgpa_output_scan_strategy(&context, c, walker->scans[c]);
152
153 /* Emit query feature advice. */
154 for (int t = 0; t < NUM_PGPA_QF_TYPES; ++t)
155 pgpa_output_query_feature(&context, t, walker->query_features[t]);
156
157 /* Emit NO_GATHER advice. */
158 pgpa_output_no_gather(&context, walker->no_gather_scans);
159}
160
161/*
162 * Output the members of an unrolled join, first the outermost member, and
163 * then the inner members one by one, as part of JOIN_ORDER() advice.
164 */
165static void
167 pgpa_unrolled_join *join)
168{
169 pgpa_output_join_member(context, &join->outer);
170
171 for (int k = 0; k < join->ninner; ++k)
172 {
173 pgpa_join_member *member = &join->inner[k];
174
175 pgpa_maybe_linebreak(context->buf, context->wrap_column);
176 appendStringInfoChar(context->buf, ' ');
177 pgpa_output_join_member(context, member);
178 }
179}
180
181/*
182 * Output a single member of an unrolled join as part of JOIN_ORDER() advice.
183 */
184static void
186 pgpa_join_member *member)
187{
188 if (member->unrolled_join != NULL)
189 {
190 appendStringInfoChar(context->buf, '(');
192 appendStringInfoChar(context->buf, ')');
193 }
194 else
195 {
196 pgpa_scan *scan = member->scan;
197
198 Assert(scan != NULL);
199 if (bms_membership(scan->relids) == BMS_SINGLETON)
200 pgpa_output_relations(context, context->buf, scan->relids);
201 else
202 {
203 appendStringInfoChar(context->buf, '{');
204 pgpa_output_relations(context, context->buf, scan->relids);
205 appendStringInfoChar(context->buf, '}');
206 }
207 }
208}
209
210/*
211 * Output advice for a List of pgpa_scan objects.
212 *
213 * All the scans must use the strategy specified by the "strategy" argument.
214 */
215static void
217 pgpa_scan_strategy strategy,
218 List *scans)
219{
220 bool first = true;
221
222 if (scans == NIL)
223 return;
224
225 if (context->buf->len > 0)
226 appendStringInfoChar(context->buf, '\n');
227 appendStringInfo(context->buf, "%s(",
229
230 foreach_ptr(pgpa_scan, scan, scans)
231 {
232 Plan *plan = scan->plan;
233
234 if (first)
235 first = false;
236 else
237 {
238 pgpa_maybe_linebreak(context->buf, context->wrap_column);
239 appendStringInfoChar(context->buf, ' ');
240 }
241
242 /* Output the relation identifiers. */
243 if (bms_membership(scan->relids) == BMS_SINGLETON)
244 pgpa_output_relations(context, context->buf, scan->relids);
245 else
246 {
247 appendStringInfoChar(context->buf, '(');
248 pgpa_output_relations(context, context->buf, scan->relids);
249 appendStringInfoChar(context->buf, ')');
250 }
251
252 /* For index or index-only scans, output index information. */
253 if (strategy == PGPA_SCAN_INDEX)
254 {
256 pgpa_maybe_linebreak(context->buf, context->wrap_column);
257 appendStringInfoChar(context->buf, ' ');
258 pgpa_output_relation_name(context, ((IndexScan *) plan)->indexid);
259 }
260 else if (strategy == PGPA_SCAN_INDEX_ONLY)
261 {
263 pgpa_maybe_linebreak(context->buf, context->wrap_column);
264 appendStringInfoChar(context->buf, ' ');
266 ((IndexOnlyScan *) plan)->indexid);
267 }
268 }
269
270 appendStringInfoChar(context->buf, ')');
271 pgpa_maybe_linebreak(context->buf, context->wrap_column);
272}
273
274/*
275 * Output a schema-qualified relation name.
276 */
277static void
279{
281 char *relnamespace = get_namespace_name_or_temp(nspoid);
282 char *relname = get_rel_name(relid);
283
284 appendStringInfoString(context->buf, quote_identifier(relnamespace));
285 appendStringInfoChar(context->buf, '.');
287}
288
289/*
290 * Output advice for a List of pgpa_query_feature objects.
291 *
292 * All features must be of the type specified by the "type" argument.
293 */
294static void
296 List *query_features)
297{
298 bool first = true;
299
300 if (query_features == NIL)
301 return;
302
303 if (context->buf->len > 0)
304 appendStringInfoChar(context->buf, '\n');
305 appendStringInfo(context->buf, "%s(",
307
308 foreach_ptr(pgpa_query_feature, qf, query_features)
309 {
310 if (first)
311 first = false;
312 else
313 {
314 pgpa_maybe_linebreak(context->buf, context->wrap_column);
315 appendStringInfoChar(context->buf, ' ');
316 }
317
318 if (bms_membership(qf->relids) == BMS_SINGLETON)
319 pgpa_output_relations(context, context->buf, qf->relids);
320 else
321 {
322 appendStringInfoChar(context->buf, '(');
323 pgpa_output_relations(context, context->buf, qf->relids);
324 appendStringInfoChar(context->buf, ')');
325 }
326 }
327
328 appendStringInfoChar(context->buf, ')');
329 pgpa_maybe_linebreak(context->buf, context->wrap_column);
330}
331
332/*
333 * Output "simple" advice for a List of Bitmapset objects each of which
334 * contains one or more RTIs.
335 *
336 * By simple, we just mean that the advice emitted follows the most
337 * straightforward pattern: the strategy name, followed by a list of items
338 * separated by spaces and surrounded by parentheses. Individual items in
339 * the list are a single relation identifier for a Bitmapset that contains
340 * just one member, or a sub-list again separated by spaces and surrounded
341 * by parentheses for a Bitmapset with multiple members. Bitmapsets with
342 * no members probably shouldn't occur here, but if they do they'll be
343 * rendered as an empty sub-list.
344 */
345static void
348{
349 bool first = true;
350
351 if (relid_sets == NIL)
352 return;
353
354 if (context->buf->len > 0)
355 appendStringInfoChar(context->buf, '\n');
356 appendStringInfo(context->buf, "%s(", strategy);
357
359 {
360 if (first)
361 first = false;
362 else
363 {
364 pgpa_maybe_linebreak(context->buf, context->wrap_column);
365 appendStringInfoChar(context->buf, ' ');
366 }
367
368 if (bms_membership(relids) == BMS_SINGLETON)
369 pgpa_output_relations(context, context->buf, relids);
370 else
371 {
372 appendStringInfoChar(context->buf, '(');
373 pgpa_output_relations(context, context->buf, relids);
374 appendStringInfoChar(context->buf, ')');
375 }
376 }
377
378 appendStringInfoChar(context->buf, ')');
379 pgpa_maybe_linebreak(context->buf, context->wrap_column);
380}
381
382/*
383 * Output NO_GATHER advice for all relations not appearing beneath any
384 * Gather or Gather Merge node.
385 */
386static void
388{
389 if (relids == NULL)
390 return;
391 if (context->buf->len > 0)
392 appendStringInfoChar(context->buf, '\n');
393 appendStringInfoString(context->buf, "NO_GATHER(");
394 pgpa_output_relations(context, context->buf, relids);
395 appendStringInfoChar(context->buf, ')');
396}
397
398/*
399 * Output the identifiers for each RTI in the provided set.
400 *
401 * Identifiers are separated by spaces, and a line break is possible after
402 * each one.
403 */
404static void
406 Bitmapset *relids)
407{
408 int rti = -1;
409 bool first = true;
410
411 while ((rti = bms_next_member(relids, rti)) >= 0)
412 {
413 const char *rid_string = context->rid_strings[rti - 1];
414
415 if (rid_string == NULL)
416 elog(ERROR, "no identifier for RTI %d", rti);
417
418 if (first)
419 {
420 first = false;
422 }
423 else
424 {
427 }
428 }
429}
430
431/*
432 * Get a C string that corresponds to the specified join strategy.
433 */
434static char *
436{
437 switch (strategy)
438 {
440 return "MERGE_JOIN_PLAIN";
442 return "MERGE_JOIN_MATERIALIZE";
444 return "NESTED_LOOP_PLAIN";
446 return "NESTED_LOOP_MATERIALIZE";
448 return "NESTED_LOOP_MEMOIZE";
449 case JSTRAT_HASH_JOIN:
450 return "HASH_JOIN";
451 }
452
454 return NULL;
455}
456
457/*
458 * Get a C string that corresponds to the specified scan strategy.
459 */
460static char *
462{
463 switch (strategy)
464 {
466 return "ORDINARY_SCAN";
467 case PGPA_SCAN_SEQ:
468 return "SEQ_SCAN";
470 return "BITMAP_HEAP_SCAN";
472 return "FOREIGN_JOIN";
473 case PGPA_SCAN_INDEX:
474 return "INDEX_SCAN";
476 return "INDEX_ONLY_SCAN";
478 return "PARTITIONWISE";
479 case PGPA_SCAN_TID:
480 return "TID_SCAN";
481 }
482
484 return NULL;
485}
486
487/*
488 * Get a C string that corresponds to the query feature type.
489 */
490static char *
492{
493 switch (type)
494 {
495 case PGPAQF_GATHER:
496 return "GATHER";
498 return "GATHER_MERGE";
500 return "SEMIJOIN_NON_UNIQUE";
502 return "SEMIJOIN_UNIQUE";
503 }
504
505
507 return NULL;
508}
509
510/*
511 * Insert a line break into the StringInfoData, if needed.
512 *
513 * If wrap_column is zero or negative, this does nothing. Otherwise, we
514 * consider inserting a newline. We only insert a newline if the length of
515 * the last line in the buffer exceeds wrap_column, and not if we'd be
516 * inserting a newline at or before the beginning of the current line.
517 *
518 * The position at which the newline is inserted is simply wherever the
519 * buffer ended the last time this function was called. In other words,
520 * the caller is expected to call this function every time we reach a good
521 * place for a line break.
522 */
523static void
525{
526 char *trailing_nl;
527 int line_start;
528 int save_cursor;
529
530 /* If line wrapping is disabled, exit quickly. */
531 if (wrap_column <= 0)
532 return;
533
534 /*
535 * Set line_start to the byte offset within buf->data of the first
536 * character of the current line, where the current line means the last
537 * one in the buffer. Note that line_start could be the offset of the
538 * trailing '\0' if the last character in the buffer is a line break.
539 */
540 trailing_nl = strrchr(buf->data, '\n');
541 if (trailing_nl == NULL)
542 line_start = 0;
543 else
544 line_start = (trailing_nl - buf->data) + 1;
545
546 /*
547 * Remember that the current end of the buffer is a potential location to
548 * insert a line break on a future call to this function.
549 */
550 save_cursor = buf->cursor;
551 buf->cursor = buf->len;
552
553 /* If we haven't passed the wrap column, we don't need a newline. */
554 if (buf->len - line_start <= wrap_column)
555 return;
556
557 /*
558 * It only makes sense to insert a newline at a position later than the
559 * beginning of the current line.
560 */
561 if (save_cursor <= line_start)
562 return;
563
564 /* Insert a newline at the previous cursor location. */
566 memmove(&buf->data[save_cursor] + 1, &buf->data[save_cursor],
567 buf->len - save_cursor);
568 ++buf->cursor;
569 buf->data[++buf->len] = '\0';
570 buf->data[save_cursor] = '\n';
571}
int bms_next_member(const Bitmapset *a, int prevbit)
Definition bitmapset.c:1290
BMS_Membership bms_membership(const Bitmapset *a)
Definition bitmapset.c:765
@ BMS_SINGLETON
Definition bitmapset.h:72
#define Assert(condition)
Definition c.h:927
#define pg_unreachable()
Definition c.h:361
unsigned int Index
Definition c.h:682
#define ERROR
Definition elog.h:39
#define elog(elevel,...)
Definition elog.h:226
#define palloc0_array(type, count)
Definition fe_memutils.h:77
int i
Definition isn.c:77
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 IsA(nodeptr, _type_)
Definition nodes.h:164
NameData relname
Definition pg_class.h:40
#define lfirst(lc)
Definition pg_list.h:172
static int list_length(const List *l)
Definition pg_list.h:152
#define NIL
Definition pg_list.h:68
#define foreach_ptr(type, var, lst)
Definition pg_list.h:469
#define foreach_node(type, var, lst)
Definition pg_list.h:496
#define plan(x)
Definition pg_regress.c:161
static char buf[DEFAULT_XLOG_SEG_SIZE]
const char * pgpa_identifier_string(const pgpa_identifier *rid)
pgpa_join_strategy
Definition pgpa_join.h:28
@ JSTRAT_MERGE_JOIN_PLAIN
Definition pgpa_join.h:29
@ JSTRAT_NESTED_LOOP_MATERIALIZE
Definition pgpa_join.h:32
@ JSTRAT_NESTED_LOOP_MEMOIZE
Definition pgpa_join.h:33
@ JSTRAT_HASH_JOIN
Definition pgpa_join.h:34
@ JSTRAT_NESTED_LOOP_PLAIN
Definition pgpa_join.h:31
@ JSTRAT_MERGE_JOIN_MATERIALIZE
Definition pgpa_join.h:30
#define NUM_PGPA_JOIN_STRATEGY
Definition pgpa_join.h:38
void pgpa_output_advice(StringInfo buf, pgpa_plan_walker_context *walker, pgpa_identifier *rt_identifiers)
Definition pgpa_output.c:78
static void pgpa_output_simple_strategy(pgpa_output_context *context, char *strategy, List *relid_sets)
static char * pgpa_cstring_join_strategy(pgpa_join_strategy strategy)
static void pgpa_output_relations(pgpa_output_context *context, StringInfo buf, Bitmapset *relids)
static void pgpa_output_no_gather(pgpa_output_context *context, Bitmapset *relids)
static void pgpa_maybe_linebreak(StringInfo buf, int wrap_column)
static void pgpa_output_unrolled_join(pgpa_output_context *context, pgpa_unrolled_join *join)
static void pgpa_output_relation_name(pgpa_output_context *context, Oid relid)
static void pgpa_output_scan_strategy(pgpa_output_context *context, pgpa_scan_strategy strategy, List *scans)
static void pgpa_output_query_feature(pgpa_output_context *context, pgpa_qf_type type, List *query_features)
static void pgpa_output_join_member(pgpa_output_context *context, pgpa_join_member *member)
static char * pgpa_cstring_scan_strategy(pgpa_scan_strategy strategy)
static char * pgpa_cstring_query_feature_type(pgpa_qf_type type)
pgpa_scan_strategy
Definition pgpa_scan.h:56
@ PGPA_SCAN_SEQ
Definition pgpa_scan.h:58
@ PGPA_SCAN_INDEX
Definition pgpa_scan.h:61
@ PGPA_SCAN_INDEX_ONLY
Definition pgpa_scan.h:62
@ PGPA_SCAN_BITMAP_HEAP
Definition pgpa_scan.h:59
@ PGPA_SCAN_FOREIGN
Definition pgpa_scan.h:60
@ PGPA_SCAN_TID
Definition pgpa_scan.h:64
@ PGPA_SCAN_PARTITIONWISE
Definition pgpa_scan.h:63
@ PGPA_SCAN_ORDINARY
Definition pgpa_scan.h:57
#define NUM_PGPA_SCAN_STRATEGY
Definition pgpa_scan.h:68
pgpa_qf_type
Definition pgpa_walker.h:62
@ PGPAQF_GATHER
Definition pgpa_walker.h:63
@ PGPAQF_GATHER_MERGE
Definition pgpa_walker.h:64
@ PGPAQF_SEMIJOIN_UNIQUE
Definition pgpa_walker.h:66
@ PGPAQF_SEMIJOIN_NON_UNIQUE
Definition pgpa_walker.h:65
#define NUM_PGPA_QF_TYPES
Definition pgpa_walker.h:70
unsigned int Oid
char * c
static int fb(int x)
const char * quote_identifier(const char *ident)
void appendStringInfo(StringInfo str, const char *fmt,...)
Definition stringinfo.c:145
void enlargeStringInfo(StringInfo str, int needed)
Definition stringinfo.c:337
void appendStringInfoString(StringInfo str, const char *s)
Definition stringinfo.c:230
void appendStringInfoChar(StringInfo str, char ch)
Definition stringinfo.c:242
Definition pg_list.h:54
struct pgpa_scan * scan
Definition pgpa_join.h:60
pgpa_unrolled_join * unrolled_join
Definition pgpa_join.h:61
const char ** rid_strings
Definition pgpa_output.c:36
Bitmapset * relids
Definition pgpa_scan.h:77
pgpa_join_member * inner
Definition pgpa_join.h:83
pgpa_join_member outer
Definition pgpa_join.h:74
const char * type