Branch data Line data Source code
1 : : /*-------------------------------------------------------------------------
2 : : *
3 : : * pgpa_output.c
4 : : * produce textual output from the results of a plan tree walk
5 : : *
6 : : * Copyright (c) 2016-2025, 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 : : */
34 : : typedef struct pgpa_output_context
35 : : {
36 : : const char **rid_strings;
37 : : StringInfo buf;
38 : : int wrap_column;
39 : : } pgpa_output_context;
40 : :
41 : : static void pgpa_output_unrolled_join(pgpa_output_context *context,
42 : : pgpa_unrolled_join *join);
43 : : static void pgpa_output_join_member(pgpa_output_context *context,
44 : : pgpa_join_member *member);
45 : : static void pgpa_output_scan_strategy(pgpa_output_context *context,
46 : : pgpa_scan_strategy strategy,
47 : : List *scans);
48 : : static void pgpa_output_bitmap_index_details(pgpa_output_context *context,
49 : : Plan *plan);
50 : : static void pgpa_output_relation_name(pgpa_output_context *context, Oid relid);
51 : : static void pgpa_output_query_feature(pgpa_output_context *context,
52 : : pgpa_qf_type type,
53 : : List *query_features);
54 : : static void pgpa_output_simple_strategy(pgpa_output_context *context,
55 : : char *strategy,
56 : : List *relid_sets);
57 : : static void pgpa_output_no_gather(pgpa_output_context *context,
58 : : Bitmapset *relids);
59 : : static void pgpa_output_relations(pgpa_output_context *context, StringInfo buf,
60 : : Bitmapset *relids);
61 : :
62 : : static char *pgpa_cstring_join_strategy(pgpa_join_strategy strategy);
63 : : static char *pgpa_cstring_scan_strategy(pgpa_scan_strategy strategy);
64 : : static char *pgpa_cstring_query_feature_type(pgpa_qf_type type);
65 : :
66 : : static void pgpa_maybe_linebreak(StringInfo buf, int wrap_column);
67 : :
68 : : /*
69 : : * Append query advice to the provided buffer.
70 : : *
71 : : * Before calling this function, 'walker' must be used to iterate over the
72 : : * main plan tree and all subplans from the PlannedStmt.
73 : : *
74 : : * 'rt_identifiers' is a table of unique identifiers, one for each RTI.
75 : : * See pgpa_create_identifiers_for_planned_stmt().
76 : : *
77 : : * Results will be appended to 'buf'.
78 : : */
79 : : void
80 : 43515 : pgpa_output_advice(StringInfo buf, pgpa_plan_walker_context *walker,
81 : : pgpa_identifier *rt_identifiers)
82 : : {
83 : 43515 : Index rtable_length = list_length(walker->pstmt->rtable);
84 : 43515 : ListCell *lc;
85 : 43515 : pgpa_output_context context;
86 : :
87 : : /* Basic initialization. */
88 : 43515 : memset(&context, 0, sizeof(pgpa_output_context));
89 : 43515 : context.buf = buf;
90 : :
91 : : /*
92 : : * Convert identifiers to string form. Note that the loop variable here is
93 : : * not an RTI, because RTIs are 1-based. Some RTIs will have no
94 : : * identifier, either because the reloptkind is RTE_JOIN or because that
95 : : * portion of the query didn't make it into the final plan.
96 : : */
97 : 43515 : context.rid_strings = palloc0_array(const char *, rtable_length);
98 [ + + ]: 137862 : for (int i = 0; i < rtable_length; ++i)
99 [ + + ]: 180222 : if (rt_identifiers[i].alias_name != NULL)
100 : 85875 : context.rid_strings[i] = pgpa_identifier_string(&rt_identifiers[i]);
101 : :
102 : : /*
103 : : * If the user chooses to use EXPLAIN (PLAN_ADVICE) in an 80-column window
104 : : * from a psql client with default settings, psql will add one space to
105 : : * the left of the output and EXPLAIN will add two more to the left of the
106 : : * advice. Thus, lines of more than 77 characters will wrap. We set the
107 : : * wrap limit to 76 here so that the output won't reach all the way to the
108 : : * very last column of the terminal.
109 : : *
110 : : * Of course, this is fairly arbitrary set of assumptions, and one could
111 : : * well make an argument for a different wrap limit, or for a configurable
112 : : * one.
113 : : */
114 : 43515 : context.wrap_column = 76;
115 : :
116 : : /*
117 : : * Each piece of JOIN_ORDER() advice fully describes the join order for a
118 : : * a single unrolled join. Merging is not permitted, because that would
119 : : * change the meaning, e.g. SEQ_SCAN(a b c d) means simply that sequential
120 : : * scans should be used for all of those relations, and is thus equivalent
121 : : * to SEQ_SCAN(a b) SEQ_SCAN(c d), but JOIN_ORDER(a b c d) means that "a"
122 : : * is the driving table which is then joined to "b" then "c" then "d",
123 : : * which is totally different from JOIN_ORDER(a b) and JOIN_ORDER(c d).
124 : : */
125 [ + + + + : 53271 : foreach(lc, walker->toplevel_unrolled_joins)
+ + ]
126 : : {
127 : 9756 : pgpa_unrolled_join *ujoin = lfirst(lc);
128 : :
129 [ + + ]: 9756 : if (buf->len > 0)
130 : 1581 : appendStringInfoChar(buf, '\n');
131 : 9756 : appendStringInfo(context.buf, "JOIN_ORDER(");
132 : 9756 : pgpa_output_unrolled_join(&context, ujoin);
133 : 9756 : appendStringInfoChar(context.buf, ')');
134 : 9756 : pgpa_maybe_linebreak(context.buf, context.wrap_column);
135 : 9756 : }
136 : :
137 : : /* Emit join strategy advice. */
138 [ + + ]: 304605 : for (int s = 0; s < NUM_PGPA_JOIN_STRATEGY; ++s)
139 : : {
140 : 261090 : char *strategy = pgpa_cstring_join_strategy(s);
141 : :
142 : 261090 : pgpa_output_simple_strategy(&context,
143 : 261090 : strategy,
144 : 261090 : walker->join_strategies[s]);
145 : 261090 : }
146 : :
147 : : /*
148 : : * Emit scan strategy advice (but not for ordinary scans, which are
149 : : * definitionally uninteresting).
150 : : */
151 [ + + ]: 391635 : for (int c = 0; c < NUM_PGPA_SCAN_STRATEGY; ++c)
152 [ + + ]: 652725 : if (c != PGPA_SCAN_ORDINARY)
153 : 304605 : pgpa_output_scan_strategy(&context, c, walker->scans[c]);
154 : :
155 : : /* Emit query feature advice. */
156 [ + + ]: 217575 : for (int t = 0; t < NUM_PGPA_QF_TYPES; ++t)
157 : 174060 : pgpa_output_query_feature(&context, t, walker->query_features[t]);
158 : :
159 : : /* Emit NO_GATHER advice. */
160 : 43515 : pgpa_output_no_gather(&context, walker->no_gather_scans);
161 : 43515 : }
162 : :
163 : : /*
164 : : * Output the members of an unrolled join, first the outermost member, and
165 : : * then the inner members one by one, as part of JOIN_ORDER() advice.
166 : : */
167 : : static void
168 : 10166 : pgpa_output_unrolled_join(pgpa_output_context *context,
169 : : pgpa_unrolled_join *join)
170 : : {
171 : 10166 : pgpa_output_join_member(context, &join->outer);
172 : :
173 [ + + ]: 23146 : for (int k = 0; k < join->ninner; ++k)
174 : : {
175 : 12980 : pgpa_join_member *member = &join->inner[k];
176 : :
177 : 12980 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
178 : 12980 : appendStringInfoChar(context->buf, ' ');
179 : 12980 : pgpa_output_join_member(context, member);
180 : 12980 : }
181 : 10166 : }
182 : :
183 : : /*
184 : : * Output a single member of an unrolled join as part of JOIN_ORDER() advice.
185 : : */
186 : : static void
187 : 23146 : pgpa_output_join_member(pgpa_output_context *context,
188 : : pgpa_join_member *member)
189 : : {
190 [ + + ]: 23146 : if (member->unrolled_join != NULL)
191 : : {
192 : 410 : appendStringInfoChar(context->buf, '(');
193 : 410 : pgpa_output_unrolled_join(context, member->unrolled_join);
194 : 410 : appendStringInfoChar(context->buf, ')');
195 : 410 : }
196 : : else
197 : : {
198 : 22736 : pgpa_scan *scan = member->scan;
199 : :
200 [ + - ]: 22736 : Assert(scan != NULL);
201 [ + + ]: 22736 : if (bms_membership(scan->relids) == BMS_SINGLETON)
202 : 22720 : pgpa_output_relations(context, context->buf, scan->relids);
203 : : else
204 : : {
205 : 16 : appendStringInfoChar(context->buf, '{');
206 : 16 : pgpa_output_relations(context, context->buf, scan->relids);
207 : 16 : appendStringInfoChar(context->buf, '}');
208 : : }
209 : 22736 : }
210 : 23146 : }
211 : :
212 : : /*
213 : : * Output advice for a List of pgpa_scan objects.
214 : : *
215 : : * All the scans must use the strategy specified by the "strategy" argument.
216 : : */
217 : : static void
218 : 304605 : pgpa_output_scan_strategy(pgpa_output_context *context,
219 : : pgpa_scan_strategy strategy,
220 : : List *scans)
221 : : {
222 : 304605 : bool first = true;
223 : :
224 [ + + ]: 304605 : if (scans == NIL)
225 : 274118 : return;
226 : :
227 [ + + ]: 30487 : if (context->buf->len > 0)
228 : 14397 : appendStringInfoChar(context->buf, '\n');
229 : 60974 : appendStringInfo(context->buf, "%s(",
230 : 30487 : pgpa_cstring_scan_strategy(strategy));
231 : :
232 [ + + + - : 105612 : foreach_ptr(pgpa_scan, scan, scans)
+ + + + ]
233 : : {
234 : 44638 : Plan *plan = scan->plan;
235 : :
236 [ + + ]: 44638 : if (first)
237 : 30487 : first = false;
238 : : else
239 : : {
240 : 14151 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
241 : 14151 : appendStringInfoChar(context->buf, ' ');
242 : : }
243 : :
244 : : /* Output the relation identifiers. */
245 [ + + ]: 44638 : if (bms_membership(scan->relids) == BMS_SINGLETON)
246 : 44433 : pgpa_output_relations(context, context->buf, scan->relids);
247 : : else
248 : : {
249 : 205 : appendStringInfoChar(context->buf, '(');
250 : 205 : pgpa_output_relations(context, context->buf, scan->relids);
251 : 205 : appendStringInfoChar(context->buf, ')');
252 : : }
253 : :
254 : : /* For scans involving indexes, output index information. */
255 [ + + ]: 44638 : if (strategy == PGPA_SCAN_INDEX)
256 : : {
257 [ - + ]: 11684 : Assert(IsA(plan, IndexScan));
258 : 11684 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
259 : 11684 : appendStringInfoChar(context->buf, ' ');
260 : 11684 : pgpa_output_relation_name(context, ((IndexScan *) plan)->indexid);
261 : 11684 : }
262 [ + + ]: 32954 : else if (strategy == PGPA_SCAN_INDEX_ONLY)
263 : : {
264 [ - + ]: 1841 : Assert(IsA(plan, IndexOnlyScan));
265 : 1841 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
266 : 1841 : appendStringInfoChar(context->buf, ' ');
267 : 3682 : pgpa_output_relation_name(context,
268 : 1841 : ((IndexOnlyScan *) plan)->indexid);
269 : 1841 : }
270 [ + + ]: 31113 : else if (strategy == PGPA_SCAN_BITMAP_HEAP)
271 : : {
272 : 2656 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
273 : 2656 : appendStringInfoChar(context->buf, ' ');
274 : 2656 : pgpa_output_bitmap_index_details(context, plan->lefttree);
275 : 2656 : }
276 : 75125 : }
277 : :
278 : 30487 : appendStringInfoChar(context->buf, ')');
279 : 30487 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
280 [ - + ]: 304605 : }
281 : :
282 : : /*
283 : : * Output information about which index or indexes power a BitmapHeapScan.
284 : : *
285 : : * We emit &&(i1 i2 i3) for a BitmapAnd between indexes i1, i2, and i3;
286 : : * and likewise ||(i1 i2 i3) for a similar BitmapOr operation.
287 : : */
288 : : static void
289 : 2757 : pgpa_output_bitmap_index_details(pgpa_output_context *context, Plan *plan)
290 : : {
291 : 2757 : char *operator;
292 : 2757 : List *bitmapplans;
293 : 2757 : bool first = true;
294 : :
295 [ + + ]: 2757 : if (IsA(plan, BitmapIndexScan))
296 : : {
297 : 2707 : BitmapIndexScan *bitmapindexscan = (BitmapIndexScan *) plan;
298 : :
299 : 2707 : pgpa_output_relation_name(context, bitmapindexscan->indexid);
300 : : return;
301 : 2707 : }
302 : :
303 [ + + ]: 50 : if (IsA(plan, BitmapOr))
304 : : {
305 : 33 : operator = "||";
306 : 33 : bitmapplans = ((BitmapOr *) plan)->bitmapplans;
307 : 33 : }
308 [ + - ]: 17 : else if (IsA(plan, BitmapAnd))
309 : : {
310 : 17 : operator = "&&";
311 : 17 : bitmapplans = ((BitmapAnd *) plan)->bitmapplans;
312 : 17 : }
313 : : else
314 [ # # # # ]: 0 : elog(ERROR, "unexpected node type: %d", (int) nodeTag(plan));
315 : :
316 : 50 : appendStringInfo(context->buf, "%s(", operator);
317 [ + + + - : 201 : foreach_ptr(Plan, child_plan, bitmapplans)
+ + + + ]
318 : : {
319 [ + + ]: 101 : if (first)
320 : 50 : first = false;
321 : : else
322 : : {
323 : 51 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
324 : 51 : appendStringInfoChar(context->buf, ' ');
325 : : }
326 : 101 : pgpa_output_bitmap_index_details(context, child_plan);
327 : 151 : }
328 : 50 : appendStringInfoChar(context->buf, ')');
329 [ - + ]: 2757 : }
330 : :
331 : : /*
332 : : * Output a schema-qualified relation name.
333 : : */
334 : : static void
335 : 16232 : pgpa_output_relation_name(pgpa_output_context *context, Oid relid)
336 : : {
337 : 16232 : Oid nspoid = get_rel_namespace(relid);
338 : 16232 : char *relnamespace = get_namespace_name_or_temp(nspoid);
339 : 16232 : char *relname = get_rel_name(relid);
340 : :
341 : 16232 : appendStringInfoString(context->buf, quote_identifier(relnamespace));
342 : 16232 : appendStringInfoChar(context->buf, '.');
343 : 16232 : appendStringInfoString(context->buf, quote_identifier(relname));
344 : 16232 : }
345 : :
346 : : /*
347 : : * Output advice for a List of pgpa_query_feature objects.
348 : : *
349 : : * All features must be of the type specified by the "type" argument.
350 : : */
351 : : static void
352 : 174060 : pgpa_output_query_feature(pgpa_output_context *context, pgpa_qf_type type,
353 : : List *query_features)
354 : : {
355 : 174060 : bool first = true;
356 : :
357 [ + + ]: 174060 : if (query_features == NIL)
358 : 173172 : return;
359 : :
360 [ + + ]: 888 : if (context->buf->len > 0)
361 : 870 : appendStringInfoChar(context->buf, '\n');
362 : 1776 : appendStringInfo(context->buf, "%s(",
363 : 888 : pgpa_cstring_query_feature_type(type));
364 : :
365 [ + + + - : 2734 : foreach_ptr(pgpa_query_feature, qf, query_features)
+ + + + ]
366 : : {
367 [ + + ]: 958 : if (first)
368 : 888 : first = false;
369 : : else
370 : : {
371 : 70 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
372 : 70 : appendStringInfoChar(context->buf, ' ');
373 : : }
374 : :
375 [ + + ]: 958 : if (bms_membership(qf->relids) == BMS_SINGLETON)
376 : 846 : pgpa_output_relations(context, context->buf, qf->relids);
377 : : else
378 : : {
379 : 112 : appendStringInfoChar(context->buf, '(');
380 : 112 : pgpa_output_relations(context, context->buf, qf->relids);
381 : 112 : appendStringInfoChar(context->buf, ')');
382 : : }
383 : 1846 : }
384 : :
385 : 888 : appendStringInfoChar(context->buf, ')');
386 : 888 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
387 [ - + ]: 174060 : }
388 : :
389 : : /*
390 : : * Output "simple" advice for a List of Bitmapset objects each of which
391 : : * contains one or more RTIs.
392 : : *
393 : : * By simple, we just mean that the advice emitted follows the most
394 : : * straightforward pattern: the strategy name, followed by a list of items
395 : : * separated by spaces and surrounded by parentheses. Individual items in
396 : : * the list are a single relation identifier for a Bitmapset that contains
397 : : * just one member, or a sub-list again separated by spaces and surrounded
398 : : * by parentheses for a Bitmapset with multiple members. Bitmapsets with
399 : : * no members probably shouldn't occur here, but if they do they'll be
400 : : * rendered as an empty sub-list.
401 : : */
402 : : static void
403 : 261090 : pgpa_output_simple_strategy(pgpa_output_context *context, char *strategy,
404 : : List *relid_sets)
405 : : {
406 : 261090 : bool first = true;
407 : :
408 [ + + ]: 261090 : if (relid_sets == NIL)
409 : 251971 : return;
410 : :
411 [ - + ]: 9119 : if (context->buf->len > 0)
412 : 9119 : appendStringInfoChar(context->buf, '\n');
413 : 9119 : appendStringInfo(context->buf, "%s(", strategy);
414 : :
415 [ + + + - : 31218 : foreach_node(Bitmapset, relids, relid_sets)
+ + + + ]
416 : : {
417 [ + + ]: 12980 : if (first)
418 : 9119 : first = false;
419 : : else
420 : : {
421 : 3861 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
422 : 3861 : appendStringInfoChar(context->buf, ' ');
423 : : }
424 : :
425 [ + + ]: 12980 : if (bms_membership(relids) == BMS_SINGLETON)
426 : 12559 : pgpa_output_relations(context, context->buf, relids);
427 : : else
428 : : {
429 : 421 : appendStringInfoChar(context->buf, '(');
430 : 421 : pgpa_output_relations(context, context->buf, relids);
431 : 421 : appendStringInfoChar(context->buf, ')');
432 : : }
433 : 22099 : }
434 : :
435 : 9119 : appendStringInfoChar(context->buf, ')');
436 : 9119 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
437 [ - + ]: 261090 : }
438 : :
439 : : /*
440 : : * Output NO_GATHER advice for all relations not appearing beneath any
441 : : * Gather or Gather Merge node.
442 : : */
443 : : static void
444 : 43515 : pgpa_output_no_gather(pgpa_output_context *context, Bitmapset *relids)
445 : : {
446 [ + + ]: 43515 : if (relids == NULL)
447 : 19439 : return;
448 [ + + ]: 24076 : if (context->buf->len > 0)
449 : 23929 : appendStringInfoChar(context->buf, '\n');
450 : 24076 : appendStringInfoString(context->buf, "NO_GATHER(");
451 : 24076 : pgpa_output_relations(context, context->buf, relids);
452 : 24076 : appendStringInfoChar(context->buf, ')');
453 : 43515 : }
454 : :
455 : : /*
456 : : * Output the identifiers for each RTI in the provided set.
457 : : *
458 : : * Identifiers are separated by spaces, and a line break is possible after
459 : : * each one.
460 : : */
461 : : static void
462 : 105388 : pgpa_output_relations(pgpa_output_context *context, StringInfo buf,
463 : : Bitmapset *relids)
464 : : {
465 : 105388 : int rti = -1;
466 : 105388 : bool first = true;
467 : :
468 [ + + ]: 229961 : while ((rti = bms_next_member(relids, rti)) >= 0)
469 : : {
470 : 124573 : const char *rid_string = context->rid_strings[rti - 1];
471 : :
472 [ + - ]: 124573 : if (rid_string == NULL)
473 [ # # # # ]: 0 : elog(ERROR, "no identifier for RTI %d", rti);
474 : :
475 [ + + ]: 124573 : if (first)
476 : : {
477 : 105388 : first = false;
478 : 105388 : appendStringInfoString(buf, rid_string);
479 : 105388 : }
480 : : else
481 : : {
482 : 19185 : pgpa_maybe_linebreak(buf, context->wrap_column);
483 : 19185 : appendStringInfo(buf, " %s", rid_string);
484 : : }
485 : 124573 : }
486 : 105388 : }
487 : :
488 : : /*
489 : : * Get a C string that corresponds to the specified join strategy.
490 : : */
491 : : static char *
492 : 261090 : pgpa_cstring_join_strategy(pgpa_join_strategy strategy)
493 : : {
494 [ + + + + : 261090 : switch (strategy)
+ - + ]
495 : : {
496 : : case JSTRAT_MERGE_JOIN_PLAIN:
497 : 43515 : return "MERGE_JOIN_PLAIN";
498 : : case JSTRAT_MERGE_JOIN_MATERIALIZE:
499 : 43515 : return "MERGE_JOIN_MATERIALIZE";
500 : : case JSTRAT_NESTED_LOOP_PLAIN:
501 : 43515 : return "NESTED_LOOP_PLAIN";
502 : : case JSTRAT_NESTED_LOOP_MATERIALIZE:
503 : 43515 : return "NESTED_LOOP_MATERIALIZE";
504 : : case JSTRAT_NESTED_LOOP_MEMOIZE:
505 : 43515 : return "NESTED_LOOP_MEMOIZE";
506 : : case JSTRAT_HASH_JOIN:
507 : 43515 : return "HASH_JOIN";
508 : : }
509 : :
510 : 0 : pg_unreachable();
511 : : return NULL;
512 : 261090 : }
513 : :
514 : : /*
515 : : * Get a C string that corresponds to the specified scan strategy.
516 : : */
517 : : static char *
518 : 30487 : pgpa_cstring_scan_strategy(pgpa_scan_strategy strategy)
519 : : {
520 [ - + + - : 30487 : switch (strategy)
+ - + +
+ ]
521 : : {
522 : : case PGPA_SCAN_ORDINARY:
523 : 0 : return "ORDINARY_SCAN";
524 : : case PGPA_SCAN_SEQ:
525 : 17381 : return "SEQ_SCAN";
526 : : case PGPA_SCAN_BITMAP_HEAP:
527 : 2496 : return "BITMAP_HEAP_SCAN";
528 : : case PGPA_SCAN_FOREIGN:
529 : 0 : return "FOREIGN_JOIN";
530 : : case PGPA_SCAN_INDEX:
531 : 6979 : return "INDEX_SCAN";
532 : : case PGPA_SCAN_INDEX_ONLY:
533 : 1631 : return "INDEX_ONLY_SCAN";
534 : : case PGPA_SCAN_PARTITIONWISE:
535 : 1599 : return "PARTITIONWISE";
536 : : case PGPA_SCAN_TID:
537 : 401 : return "TID_SCAN";
538 : : }
539 : :
540 : 0 : pg_unreachable();
541 : : return NULL;
542 : 30487 : }
543 : :
544 : : /*
545 : : * Get a C string that corresponds to the specified scan strategy.
546 : : */
547 : : static char *
548 : 888 : pgpa_cstring_query_feature_type(pgpa_qf_type type)
549 : : {
550 [ + + - + : 888 : switch (type)
+ ]
551 : : {
552 : : case PGPAQF_GATHER:
553 : 182 : return "GATHER";
554 : : case PGPAQF_GATHER_MERGE:
555 : 60 : return "GATHER_MERGE";
556 : : case PGPAQF_SEMIJOIN_NON_UNIQUE:
557 : 576 : return "SEMIJOIN_NON_UNIQUE";
558 : : case PGPAQF_SEMIJOIN_UNIQUE:
559 : 70 : return "SEMIJOIN_UNIQUE";
560 : : }
561 : :
562 : :
563 : 0 : pg_unreachable();
564 : : return NULL;
565 : 888 : }
566 : :
567 : : /*
568 : : * Insert a line break into the StringInfoData, if needed.
569 : : *
570 : : * If wrap_column is zero or negative, this does nothing. Otherwise, we
571 : : * consider inserting a newline. We only insert a newline if the length of
572 : : * the last line in the buffer exceeds wrap_column, and not if we'd be
573 : : * inserting a newline at or before the beginning of the current line.
574 : : *
575 : : * The position at which the newline is inserted is simply wherever the
576 : : * buffer ended the last time this function was called. In other words,
577 : : * the caller is expected to call this function every time we reach a good
578 : : * place for a line break.
579 : : */
580 : : static void
581 : 116729 : pgpa_maybe_linebreak(StringInfo buf, int wrap_column)
582 : : {
583 : 116729 : char *trailing_nl;
584 : 116729 : int line_start;
585 : 116729 : int save_cursor;
586 : :
587 : : /* If line wrapping is disabled, exit quickly. */
588 [ - + ]: 116729 : if (wrap_column <= 0)
589 : 0 : return;
590 : :
591 : : /*
592 : : * Set line_start to the byte offset within buf->data of the first
593 : : * character of the current line, where the current line means the last
594 : : * one in the buffer. Note that line_start could be the offset of the
595 : : * trailing '\0' if the last character in the buffer is a line break.
596 : : */
597 : 116729 : trailing_nl = strrchr(buf->data, '\n');
598 [ + + ]: 116729 : if (trailing_nl == NULL)
599 : 41792 : line_start = 0;
600 : : else
601 : 74937 : line_start = (trailing_nl - buf->data) + 1;
602 : :
603 : : /*
604 : : * Remember that the current end of the buffer is a potential location to
605 : : * insert a line break on a future call to this function.
606 : : */
607 : 116729 : save_cursor = buf->cursor;
608 : 116729 : buf->cursor = buf->len;
609 : :
610 : : /* If we haven't passed the wrap column, we don't need a newline. */
611 [ + + ]: 116729 : if (buf->len - line_start <= wrap_column)
612 : 109565 : return;
613 : :
614 : : /*
615 : : * It only makes sense to insert a newline at a position later than the
616 : : * beginning of the current line.
617 : : */
618 [ - + ]: 7164 : if (buf->cursor <= line_start)
619 : 0 : return;
620 : :
621 : : /* Insert a newline at the previous cursor location. */
622 : 7164 : enlargeStringInfo(buf, 1);
623 : 7164 : memmove(&buf->data[save_cursor] + 1, &buf->data[save_cursor],
624 : : buf->len - save_cursor);
625 : 7164 : ++buf->cursor;
626 : 7164 : buf->data[++buf->len] = '\0';
627 : 7164 : buf->data[save_cursor] = '\n';
628 [ - + ]: 116729 : }
|