Age Owner Branch data TLA 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-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 : : */
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_relation_name(pgpa_output_context *context, Oid relid);
49 : : static void pgpa_output_query_feature(pgpa_output_context *context,
50 : : pgpa_qf_type type,
51 : : List *query_features);
52 : : static void pgpa_output_simple_strategy(pgpa_output_context *context,
53 : : char *strategy,
54 : : List *relid_sets);
55 : : static void pgpa_output_no_gather(pgpa_output_context *context,
56 : : Bitmapset *relids);
57 : : static void pgpa_output_do_not_scan(pgpa_output_context *context,
58 : : List *identifiers);
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
54 rhaas@postgresql.org 80 :GNC 43623 : pgpa_output_advice(StringInfo buf, pgpa_plan_walker_context *walker,
81 : : pgpa_identifier *rt_identifiers)
82 : : {
83 : 43623 : Index rtable_length = list_length(walker->pstmt->rtable);
84 : : ListCell *lc;
85 : : pgpa_output_context context;
86 : :
87 : : /* Basic initialization. */
88 : 43623 : memset(&context, 0, sizeof(pgpa_output_context));
89 : 43623 : 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 : 43623 : context.rid_strings = palloc0_array(const char *, rtable_length);
98 [ + + ]: 142643 : for (int i = 0; i < rtable_length; ++i)
99 [ + + ]: 99020 : if (rt_identifiers[i].alias_name != NULL)
100 : 89662 : 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 : 43623 : context.wrap_column = 76;
115 : :
116 : : /*
117 : : * Each piece of JOIN_ORDER() advice fully describes the join order for a
118 : : * 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 [ + + + + : 54860 : foreach(lc, walker->toplevel_unrolled_joins)
+ + ]
126 : : {
127 : 11237 : pgpa_unrolled_join *ujoin = lfirst(lc);
128 : :
129 [ + + ]: 11237 : if (buf->len > 0)
130 : 2202 : appendStringInfoChar(buf, '\n');
22 drowley@postgresql.o 131 : 11237 : appendStringInfoString(context.buf, "JOIN_ORDER(");
54 rhaas@postgresql.org 132 : 11237 : pgpa_output_unrolled_join(&context, ujoin);
133 : 11237 : appendStringInfoChar(context.buf, ')');
134 : 11237 : pgpa_maybe_linebreak(context.buf, context.wrap_column);
135 : : }
136 : :
137 : : /* Emit join strategy advice. */
138 [ + + ]: 305361 : for (int s = 0; s < NUM_PGPA_JOIN_STRATEGY; ++s)
139 : : {
140 : 261738 : char *strategy = pgpa_cstring_join_strategy(s);
141 : :
142 : 261738 : pgpa_output_simple_strategy(&context,
143 : : strategy,
144 : : walker->join_strategies[s]);
145 : : }
146 : :
147 : : /*
148 : : * Emit scan strategy advice (but not for ordinary scans, which are
149 : : * definitionally uninteresting).
150 : : */
151 [ + + ]: 392607 : for (int c = 0; c < NUM_PGPA_SCAN_STRATEGY; ++c)
152 [ + + ]: 348984 : if (c != PGPA_SCAN_ORDINARY)
153 : 305361 : pgpa_output_scan_strategy(&context, c, walker->scans[c]);
154 : :
155 : : /* Emit query feature advice. */
156 [ + + ]: 218115 : for (int t = 0; t < NUM_PGPA_QF_TYPES; ++t)
157 : 174492 : pgpa_output_query_feature(&context, t, walker->query_features[t]);
158 : :
159 : : /* Emit NO_GATHER advice. */
160 : 43623 : pgpa_output_no_gather(&context, walker->no_gather_scans);
161 : :
162 : : /* Emit DO_NOT_SCAN advice. */
40 163 : 43623 : pgpa_output_do_not_scan(&context, walker->do_not_scan_identifiers);
54 164 : 43623 : }
165 : :
166 : : /*
167 : : * Output the members of an unrolled join, first the outermost member, and
168 : : * then the inner members one by one, as part of JOIN_ORDER() advice.
169 : : */
170 : : static void
171 : 11754 : pgpa_output_unrolled_join(pgpa_output_context *context,
172 : : pgpa_unrolled_join *join)
173 : : {
174 : 11754 : pgpa_output_join_member(context, &join->outer);
175 : :
176 [ + + ]: 26690 : for (int k = 0; k < join->ninner; ++k)
177 : : {
178 : 14936 : pgpa_join_member *member = &join->inner[k];
179 : :
180 : 14936 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
181 : 14936 : appendStringInfoChar(context->buf, ' ');
182 : 14936 : pgpa_output_join_member(context, member);
183 : : }
184 : 11754 : }
185 : :
186 : : /*
187 : : * Output a single member of an unrolled join as part of JOIN_ORDER() advice.
188 : : */
189 : : static void
190 : 26690 : pgpa_output_join_member(pgpa_output_context *context,
191 : : pgpa_join_member *member)
192 : : {
193 [ + + ]: 26690 : if (member->unrolled_join != NULL)
194 : : {
195 : 517 : appendStringInfoChar(context->buf, '(');
196 : 517 : pgpa_output_unrolled_join(context, member->unrolled_join);
197 : 517 : appendStringInfoChar(context->buf, ')');
198 : : }
199 : : else
200 : : {
201 : 26173 : pgpa_scan *scan = member->scan;
202 : :
203 [ - + ]: 26173 : Assert(scan != NULL);
204 [ + + ]: 26173 : if (bms_membership(scan->relids) == BMS_SINGLETON)
205 : 26159 : pgpa_output_relations(context, context->buf, scan->relids);
206 : : else
207 : : {
208 : 14 : appendStringInfoChar(context->buf, '{');
209 : 14 : pgpa_output_relations(context, context->buf, scan->relids);
210 : 14 : appendStringInfoChar(context->buf, '}');
211 : : }
212 : : }
213 : 26690 : }
214 : :
215 : : /*
216 : : * Output advice for a List of pgpa_scan objects.
217 : : *
218 : : * All the scans must use the strategy specified by the "strategy" argument.
219 : : */
220 : : static void
221 : 305361 : pgpa_output_scan_strategy(pgpa_output_context *context,
222 : : pgpa_scan_strategy strategy,
223 : : List *scans)
224 : : {
225 : 305361 : bool first = true;
226 : :
227 [ + + ]: 305361 : if (scans == NIL)
228 : 275467 : return;
229 : :
230 [ + + ]: 29894 : if (context->buf->len > 0)
231 : 15570 : appendStringInfoChar(context->buf, '\n');
232 : 29894 : appendStringInfo(context->buf, "%s(",
233 : : pgpa_cstring_scan_strategy(strategy));
234 : :
235 [ + - + + : 105966 : foreach_ptr(pgpa_scan, scan, scans)
+ + ]
236 : : {
237 : 46178 : Plan *plan = scan->plan;
238 : :
239 [ + + ]: 46178 : if (first)
240 : 29894 : first = false;
241 : : else
242 : : {
243 : 16284 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
244 : 16284 : appendStringInfoChar(context->buf, ' ');
245 : : }
246 : :
247 : : /* Output the relation identifiers. */
248 [ + + ]: 46178 : if (bms_membership(scan->relids) == BMS_SINGLETON)
249 : 45973 : pgpa_output_relations(context, context->buf, scan->relids);
250 : : else
251 : : {
252 : 205 : appendStringInfoChar(context->buf, '(');
253 : 205 : pgpa_output_relations(context, context->buf, scan->relids);
254 : 205 : appendStringInfoChar(context->buf, ')');
255 : : }
256 : :
257 : : /* For index or index-only scans, output index information. */
258 [ + + ]: 46178 : if (strategy == PGPA_SCAN_INDEX)
259 : : {
260 [ - + ]: 12102 : Assert(IsA(plan, IndexScan));
261 : 12102 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
262 : 12102 : appendStringInfoChar(context->buf, ' ');
263 : 12102 : pgpa_output_relation_name(context, ((IndexScan *) plan)->indexid);
264 : : }
265 [ + + ]: 34076 : else if (strategy == PGPA_SCAN_INDEX_ONLY)
266 : : {
267 [ - + ]: 1927 : Assert(IsA(plan, IndexOnlyScan));
268 : 1927 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
269 : 1927 : appendStringInfoChar(context->buf, ' ');
270 : 1927 : pgpa_output_relation_name(context,
271 : : ((IndexOnlyScan *) plan)->indexid);
272 : : }
273 : : }
274 : :
275 : 29894 : appendStringInfoChar(context->buf, ')');
276 : 29894 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
277 : : }
278 : :
279 : : /*
280 : : * Output a schema-qualified relation name.
281 : : */
282 : : static void
283 : 14029 : pgpa_output_relation_name(pgpa_output_context *context, Oid relid)
284 : : {
285 : 14029 : Oid nspoid = get_rel_namespace(relid);
286 : 14029 : char *relnamespace = get_namespace_name_or_temp(nspoid);
287 : 14029 : char *relname = get_rel_name(relid);
288 : :
289 : 14029 : appendStringInfoString(context->buf, quote_identifier(relnamespace));
290 : 14029 : appendStringInfoChar(context->buf, '.');
291 : 14029 : appendStringInfoString(context->buf, quote_identifier(relname));
292 : 14029 : }
293 : :
294 : : /*
295 : : * Output advice for a List of pgpa_query_feature objects.
296 : : *
297 : : * All features must be of the type specified by the "type" argument.
298 : : */
299 : : static void
300 : 174492 : pgpa_output_query_feature(pgpa_output_context *context, pgpa_qf_type type,
301 : : List *query_features)
302 : : {
303 : 174492 : bool first = true;
304 : :
305 [ + + ]: 174492 : if (query_features == NIL)
306 : 173618 : return;
307 : :
308 [ + + ]: 874 : if (context->buf->len > 0)
309 : 873 : appendStringInfoChar(context->buf, '\n');
310 : 874 : appendStringInfo(context->buf, "%s(",
311 : : pgpa_cstring_query_feature_type(type));
312 : :
313 [ + - + + : 2692 : foreach_ptr(pgpa_query_feature, qf, query_features)
+ + ]
314 : : {
315 [ + + ]: 944 : if (first)
316 : 874 : first = false;
317 : : else
318 : : {
319 : 70 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
320 : 70 : appendStringInfoChar(context->buf, ' ');
321 : : }
322 : :
323 [ + + ]: 944 : if (bms_membership(qf->relids) == BMS_SINGLETON)
324 : 840 : pgpa_output_relations(context, context->buf, qf->relids);
325 : : else
326 : : {
327 : 104 : appendStringInfoChar(context->buf, '(');
328 : 104 : pgpa_output_relations(context, context->buf, qf->relids);
329 : 104 : appendStringInfoChar(context->buf, ')');
330 : : }
331 : : }
332 : :
333 : 874 : appendStringInfoChar(context->buf, ')');
334 : 874 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
335 : : }
336 : :
337 : : /*
338 : : * Output "simple" advice for a List of Bitmapset objects each of which
339 : : * contains one or more RTIs.
340 : : *
341 : : * By simple, we just mean that the advice emitted follows the most
342 : : * straightforward pattern: the strategy name, followed by a list of items
343 : : * separated by spaces and surrounded by parentheses. Individual items in
344 : : * the list are a single relation identifier for a Bitmapset that contains
345 : : * just one member, or a sub-list again separated by spaces and surrounded
346 : : * by parentheses for a Bitmapset with multiple members. Bitmapsets with
347 : : * no members probably shouldn't occur here, but if they do they'll be
348 : : * rendered as an empty sub-list.
349 : : */
350 : : static void
351 : 261738 : pgpa_output_simple_strategy(pgpa_output_context *context, char *strategy,
352 : : List *relid_sets)
353 : : {
354 : 261738 : bool first = true;
355 : :
356 [ + + ]: 261738 : if (relid_sets == NIL)
357 : 251640 : return;
358 : :
359 [ + - ]: 10098 : if (context->buf->len > 0)
360 : 10098 : appendStringInfoChar(context->buf, '\n');
361 : 10098 : appendStringInfo(context->buf, "%s(", strategy);
362 : :
363 [ + - + + : 35132 : foreach_node(Bitmapset, relids, relid_sets)
+ + ]
364 : : {
365 [ + + ]: 14936 : if (first)
366 : 10098 : first = false;
367 : : else
368 : : {
369 : 4838 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
370 : 4838 : appendStringInfoChar(context->buf, ' ');
371 : : }
372 : :
373 [ + + ]: 14936 : if (bms_membership(relids) == BMS_SINGLETON)
374 : 14408 : pgpa_output_relations(context, context->buf, relids);
375 : : else
376 : : {
377 : 528 : appendStringInfoChar(context->buf, '(');
378 : 528 : pgpa_output_relations(context, context->buf, relids);
379 : 528 : appendStringInfoChar(context->buf, ')');
380 : : }
381 : : }
382 : :
383 : 10098 : appendStringInfoChar(context->buf, ')');
384 : 10098 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
385 : : }
386 : :
387 : : /*
388 : : * Output NO_GATHER advice for all relations not appearing beneath any
389 : : * Gather or Gather Merge node.
390 : : */
391 : : static void
392 : 43623 : pgpa_output_no_gather(pgpa_output_context *context, Bitmapset *relids)
393 : : {
394 [ + + ]: 43623 : if (relids == NULL)
395 : 177 : return;
396 [ + + ]: 43446 : if (context->buf->len > 0)
397 : 23188 : appendStringInfoChar(context->buf, '\n');
398 : 43446 : appendStringInfoString(context->buf, "NO_GATHER(");
399 : 43446 : pgpa_output_relations(context, context->buf, relids);
400 : 43446 : appendStringInfoChar(context->buf, ')');
401 : : }
402 : :
403 : : /*
404 : : * Output DO_NOT_SCAN advice for all relations in the provided list of
405 : : * identifiers.
406 : : */
407 : : static void
40 408 : 43623 : pgpa_output_do_not_scan(pgpa_output_context *context, List *identifiers)
409 : : {
410 : 43623 : bool first = true;
411 : :
412 [ + + ]: 43623 : if (identifiers == NIL)
413 : 43399 : return;
414 [ + - ]: 224 : if (context->buf->len > 0)
415 : 224 : appendStringInfoChar(context->buf, '\n');
416 : 224 : appendStringInfoString(context->buf, "DO_NOT_SCAN(");
417 : :
418 [ + - + + : 880 : foreach_ptr(pgpa_identifier, rid, identifiers)
+ + ]
419 : : {
420 [ + + ]: 432 : if (first)
421 : 224 : first = false;
422 : : else
423 : : {
424 : 208 : pgpa_maybe_linebreak(context->buf, context->wrap_column);
425 : 208 : appendStringInfoChar(context->buf, ' ');
426 : : }
427 : 432 : appendStringInfoString(context->buf, pgpa_identifier_string(rid));
428 : : }
429 : :
430 : 224 : appendStringInfoChar(context->buf, ')');
431 : : }
432 : :
433 : : /*
434 : : * Output the identifiers for each RTI in the provided set.
435 : : *
436 : : * Identifiers are separated by spaces, and a line break is possible after
437 : : * each one.
438 : : */
439 : : static void
54 440 : 131677 : pgpa_output_relations(pgpa_output_context *context, StringInfo buf,
441 : : Bitmapset *relids)
442 : : {
443 : 131677 : int rti = -1;
444 : 131677 : bool first = true;
445 : :
446 [ + + ]: 292410 : while ((rti = bms_next_member(relids, rti)) >= 0)
447 : : {
448 : 160733 : const char *rid_string = context->rid_strings[rti - 1];
449 : :
450 [ - + ]: 160733 : if (rid_string == NULL)
54 rhaas@postgresql.org 451 [ # # ]:UNC 0 : elog(ERROR, "no identifier for RTI %d", rti);
452 : :
54 rhaas@postgresql.org 453 [ + + ]:GNC 160733 : if (first)
454 : : {
455 : 131677 : first = false;
456 : 131677 : appendStringInfoString(buf, rid_string);
457 : : }
458 : : else
459 : : {
460 : 29056 : pgpa_maybe_linebreak(buf, context->wrap_column);
461 : 29056 : appendStringInfo(buf, " %s", rid_string);
462 : : }
463 : : }
464 : 131677 : }
465 : :
466 : : /*
467 : : * Get a C string that corresponds to the specified join strategy.
468 : : */
469 : : static char *
470 : 261738 : pgpa_cstring_join_strategy(pgpa_join_strategy strategy)
471 : : {
472 [ + + + + : 261738 : switch (strategy)
+ + - ]
473 : : {
474 : 43623 : case JSTRAT_MERGE_JOIN_PLAIN:
475 : 43623 : return "MERGE_JOIN_PLAIN";
476 : 43623 : case JSTRAT_MERGE_JOIN_MATERIALIZE:
477 : 43623 : return "MERGE_JOIN_MATERIALIZE";
478 : 43623 : case JSTRAT_NESTED_LOOP_PLAIN:
479 : 43623 : return "NESTED_LOOP_PLAIN";
480 : 43623 : case JSTRAT_NESTED_LOOP_MATERIALIZE:
481 : 43623 : return "NESTED_LOOP_MATERIALIZE";
482 : 43623 : case JSTRAT_NESTED_LOOP_MEMOIZE:
483 : 43623 : return "NESTED_LOOP_MEMOIZE";
484 : 43623 : case JSTRAT_HASH_JOIN:
485 : 43623 : return "HASH_JOIN";
486 : : }
487 : :
54 rhaas@postgresql.org 488 :UNC 0 : pg_unreachable();
489 : : return NULL;
490 : : }
491 : :
492 : : /*
493 : : * Get a C string that corresponds to the specified scan strategy.
494 : : */
495 : : static char *
54 rhaas@postgresql.org 496 :GNC 29894 : pgpa_cstring_scan_strategy(pgpa_scan_strategy strategy)
497 : : {
498 [ - + + - : 29894 : switch (strategy)
+ + + +
- ]
499 : : {
54 rhaas@postgresql.org 500 :UNC 0 : case PGPA_SCAN_ORDINARY:
501 : 0 : return "ORDINARY_SCAN";
54 rhaas@postgresql.org 502 :GNC 16587 : case PGPA_SCAN_SEQ:
503 : 16587 : return "SEQ_SCAN";
504 : 2217 : case PGPA_SCAN_BITMAP_HEAP:
505 : 2217 : return "BITMAP_HEAP_SCAN";
54 rhaas@postgresql.org 506 :UNC 0 : case PGPA_SCAN_FOREIGN:
507 : 0 : return "FOREIGN_JOIN";
54 rhaas@postgresql.org 508 :GNC 7306 : case PGPA_SCAN_INDEX:
509 : 7306 : return "INDEX_SCAN";
510 : 1709 : case PGPA_SCAN_INDEX_ONLY:
511 : 1709 : return "INDEX_ONLY_SCAN";
512 : 1673 : case PGPA_SCAN_PARTITIONWISE:
513 : 1673 : return "PARTITIONWISE";
514 : 402 : case PGPA_SCAN_TID:
515 : 402 : return "TID_SCAN";
516 : : }
517 : :
54 rhaas@postgresql.org 518 :UNC 0 : pg_unreachable();
519 : : return NULL;
520 : : }
521 : :
522 : : /*
523 : : * Get a C string that corresponds to the query feature type.
524 : : */
525 : : static char *
54 rhaas@postgresql.org 526 :GNC 874 : pgpa_cstring_query_feature_type(pgpa_qf_type type)
527 : : {
528 [ + + + + : 874 : switch (type)
- ]
529 : : {
530 : 162 : case PGPAQF_GATHER:
531 : 162 : return "GATHER";
532 : 55 : case PGPAQF_GATHER_MERGE:
533 : 55 : return "GATHER_MERGE";
534 : 586 : case PGPAQF_SEMIJOIN_NON_UNIQUE:
535 : 586 : return "SEMIJOIN_NON_UNIQUE";
536 : 71 : case PGPAQF_SEMIJOIN_UNIQUE:
537 : 71 : return "SEMIJOIN_UNIQUE";
538 : : }
539 : :
540 : :
54 rhaas@postgresql.org 541 :UNC 0 : pg_unreachable();
542 : : return NULL;
543 : : }
544 : :
545 : : /*
546 : : * Insert a line break into the StringInfoData, if needed.
547 : : *
548 : : * If wrap_column is zero or negative, this does nothing. Otherwise, we
549 : : * consider inserting a newline. We only insert a newline if the length of
550 : : * the last line in the buffer exceeds wrap_column, and not if we'd be
551 : : * inserting a newline at or before the beginning of the current line.
552 : : *
553 : : * The position at which the newline is inserted is simply wherever the
554 : : * buffer ended the last time this function was called. In other words,
555 : : * the caller is expected to call this function every time we reach a good
556 : : * place for a line break.
557 : : */
558 : : static void
54 rhaas@postgresql.org 559 :GNC 131524 : pgpa_maybe_linebreak(StringInfo buf, int wrap_column)
560 : : {
561 : : char *trailing_nl;
562 : : int line_start;
563 : : int save_cursor;
564 : :
565 : : /* If line wrapping is disabled, exit quickly. */
566 [ - + ]: 131524 : if (wrap_column <= 0)
54 rhaas@postgresql.org 567 :UNC 0 : return;
568 : :
569 : : /*
570 : : * Set line_start to the byte offset within buf->data of the first
571 : : * character of the current line, where the current line means the last
572 : : * one in the buffer. Note that line_start could be the offset of the
573 : : * trailing '\0' if the last character in the buffer is a line break.
574 : : */
54 rhaas@postgresql.org 575 :GNC 131524 : trailing_nl = strrchr(buf->data, '\n');
576 [ + + ]: 131524 : if (trailing_nl == NULL)
577 : 41104 : line_start = 0;
578 : : else
579 : 90420 : line_start = (trailing_nl - buf->data) + 1;
580 : :
581 : : /*
582 : : * Remember that the current end of the buffer is a potential location to
583 : : * insert a line break on a future call to this function.
584 : : */
585 : 131524 : save_cursor = buf->cursor;
586 : 131524 : buf->cursor = buf->len;
587 : :
588 : : /* If we haven't passed the wrap column, we don't need a newline. */
589 [ + + ]: 131524 : if (buf->len - line_start <= wrap_column)
590 : 123077 : return;
591 : :
592 : : /*
593 : : * It only makes sense to insert a newline at a position later than the
594 : : * beginning of the current line.
595 : : */
596 [ - + ]: 8447 : if (save_cursor <= line_start)
54 rhaas@postgresql.org 597 :UNC 0 : return;
598 : :
599 : : /* Insert a newline at the previous cursor location. */
54 rhaas@postgresql.org 600 :GNC 8447 : enlargeStringInfo(buf, 1);
601 : 8447 : memmove(&buf->data[save_cursor] + 1, &buf->data[save_cursor],
602 : 8447 : buf->len - save_cursor);
603 : 8447 : ++buf->cursor;
604 : 8447 : buf->data[++buf->len] = '\0';
605 : 8447 : buf->data[save_cursor] = '\n';
606 : : }
|