LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_output.c (source / functions) Coverage Total Hit
Test: PostgreSQL 19devel Lines: 95.5 % 221 211
Test Date: 2026-04-06 23:16:20 Functions: 100.0 % 14 14
Legend: Lines:     hit not hit

            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
      80        43505 : pgpa_output_advice(StringInfo buf, pgpa_plan_walker_context *walker,
      81              :                    pgpa_identifier *rt_identifiers)
      82              : {
      83        43505 :     Index       rtable_length = list_length(walker->pstmt->rtable);
      84              :     ListCell   *lc;
      85              :     pgpa_output_context context;
      86              : 
      87              :     /* Basic initialization. */
      88        43505 :     memset(&context, 0, sizeof(pgpa_output_context));
      89        43505 :     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        43505 :     context.rid_strings = palloc0_array(const char *, rtable_length);
      98       142119 :     for (int i = 0; i < rtable_length; ++i)
      99        98614 :         if (rt_identifiers[i].alias_name != NULL)
     100        89278 :             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        43505 :     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        54705 :     foreach(lc, walker->toplevel_unrolled_joins)
     126              :     {
     127        11200 :         pgpa_unrolled_join *ujoin = lfirst(lc);
     128              : 
     129        11200 :         if (buf->len > 0)
     130         2202 :             appendStringInfoChar(buf, '\n');
     131        11200 :         appendStringInfo(context.buf, "JOIN_ORDER(");
     132        11200 :         pgpa_output_unrolled_join(&context, ujoin);
     133        11200 :         appendStringInfoChar(context.buf, ')');
     134        11200 :         pgpa_maybe_linebreak(context.buf, context.wrap_column);
     135              :     }
     136              : 
     137              :     /* Emit join strategy advice. */
     138       304535 :     for (int s = 0; s < NUM_PGPA_JOIN_STRATEGY; ++s)
     139              :     {
     140       261030 :         char       *strategy = pgpa_cstring_join_strategy(s);
     141              : 
     142       261030 :         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       391545 :     for (int c = 0; c < NUM_PGPA_SCAN_STRATEGY; ++c)
     152       348040 :         if (c != PGPA_SCAN_ORDINARY)
     153       304535 :             pgpa_output_scan_strategy(&context, c, walker->scans[c]);
     154              : 
     155              :     /* Emit query feature advice. */
     156       217525 :     for (int t = 0; t < NUM_PGPA_QF_TYPES; ++t)
     157       174020 :         pgpa_output_query_feature(&context, t, walker->query_features[t]);
     158              : 
     159              :     /* Emit NO_GATHER advice. */
     160        43505 :     pgpa_output_no_gather(&context, walker->no_gather_scans);
     161              : 
     162              :     /* Emit DO_NOT_SCAN advice. */
     163        43505 :     pgpa_output_do_not_scan(&context, walker->do_not_scan_identifiers);
     164        43505 : }
     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        11726 : pgpa_output_unrolled_join(pgpa_output_context *context,
     172              :                           pgpa_unrolled_join *join)
     173              : {
     174        11726 :     pgpa_output_join_member(context, &join->outer);
     175              : 
     176        26621 :     for (int k = 0; k < join->ninner; ++k)
     177              :     {
     178        14895 :         pgpa_join_member *member = &join->inner[k];
     179              : 
     180        14895 :         pgpa_maybe_linebreak(context->buf, context->wrap_column);
     181        14895 :         appendStringInfoChar(context->buf, ' ');
     182        14895 :         pgpa_output_join_member(context, member);
     183              :     }
     184        11726 : }
     185              : 
     186              : /*
     187              :  * Output a single member of an unrolled join as part of JOIN_ORDER() advice.
     188              :  */
     189              : static void
     190        26621 : pgpa_output_join_member(pgpa_output_context *context,
     191              :                         pgpa_join_member *member)
     192              : {
     193        26621 :     if (member->unrolled_join != NULL)
     194              :     {
     195          526 :         appendStringInfoChar(context->buf, '(');
     196          526 :         pgpa_output_unrolled_join(context, member->unrolled_join);
     197          526 :         appendStringInfoChar(context->buf, ')');
     198              :     }
     199              :     else
     200              :     {
     201        26095 :         pgpa_scan  *scan = member->scan;
     202              : 
     203              :         Assert(scan != NULL);
     204        26095 :         if (bms_membership(scan->relids) == BMS_SINGLETON)
     205        26081 :             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        26621 : }
     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       304535 : pgpa_output_scan_strategy(pgpa_output_context *context,
     222              :                           pgpa_scan_strategy strategy,
     223              :                           List *scans)
     224              : {
     225       304535 :     bool        first = true;
     226              : 
     227       304535 :     if (scans == NIL)
     228       274815 :         return;
     229              : 
     230        29720 :     if (context->buf->len > 0)
     231        15472 :         appendStringInfoChar(context->buf, '\n');
     232        29720 :     appendStringInfo(context->buf, "%s(",
     233              :                      pgpa_cstring_scan_strategy(strategy));
     234              : 
     235       105449 :     foreach_ptr(pgpa_scan, scan, scans)
     236              :     {
     237        46009 :         Plan       *plan = scan->plan;
     238              : 
     239        46009 :         if (first)
     240        29720 :             first = false;
     241              :         else
     242              :         {
     243        16289 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     244        16289 :             appendStringInfoChar(context->buf, ' ');
     245              :         }
     246              : 
     247              :         /* Output the relation identifiers. */
     248        46009 :         if (bms_membership(scan->relids) == BMS_SINGLETON)
     249        45804 :             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        46009 :         if (strategy == PGPA_SCAN_INDEX)
     259              :         {
     260              :             Assert(IsA(plan, IndexScan));
     261        11907 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     262        11907 :             appendStringInfoChar(context->buf, ' ');
     263        11907 :             pgpa_output_relation_name(context, ((IndexScan *) plan)->indexid);
     264              :         }
     265        34102 :         else if (strategy == PGPA_SCAN_INDEX_ONLY)
     266              :         {
     267              :             Assert(IsA(plan, IndexOnlyScan));
     268         1883 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     269         1883 :             appendStringInfoChar(context->buf, ' ');
     270         1883 :             pgpa_output_relation_name(context,
     271              :                                       ((IndexOnlyScan *) plan)->indexid);
     272              :         }
     273              :     }
     274              : 
     275        29720 :     appendStringInfoChar(context->buf, ')');
     276        29720 :     pgpa_maybe_linebreak(context->buf, context->wrap_column);
     277              : }
     278              : 
     279              : /*
     280              :  * Output a schema-qualified relation name.
     281              :  */
     282              : static void
     283        13790 : pgpa_output_relation_name(pgpa_output_context *context, Oid relid)
     284              : {
     285        13790 :     Oid         nspoid = get_rel_namespace(relid);
     286        13790 :     char       *relnamespace = get_namespace_name_or_temp(nspoid);
     287        13790 :     char       *relname = get_rel_name(relid);
     288              : 
     289        13790 :     appendStringInfoString(context->buf, quote_identifier(relnamespace));
     290        13790 :     appendStringInfoChar(context->buf, '.');
     291        13790 :     appendStringInfoString(context->buf, quote_identifier(relname));
     292        13790 : }
     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       174020 : pgpa_output_query_feature(pgpa_output_context *context, pgpa_qf_type type,
     301              :                           List *query_features)
     302              : {
     303       174020 :     bool        first = true;
     304              : 
     305       174020 :     if (query_features == NIL)
     306       173152 :         return;
     307              : 
     308          868 :     if (context->buf->len > 0)
     309          867 :         appendStringInfoChar(context->buf, '\n');
     310          868 :     appendStringInfo(context->buf, "%s(",
     311              :                      pgpa_cstring_query_feature_type(type));
     312              : 
     313         2674 :     foreach_ptr(pgpa_query_feature, qf, query_features)
     314              :     {
     315          938 :         if (first)
     316          868 :             first = false;
     317              :         else
     318              :         {
     319           70 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     320           70 :             appendStringInfoChar(context->buf, ' ');
     321              :         }
     322              : 
     323          938 :         if (bms_membership(qf->relids) == BMS_SINGLETON)
     324          835 :             pgpa_output_relations(context, context->buf, qf->relids);
     325              :         else
     326              :         {
     327          103 :             appendStringInfoChar(context->buf, '(');
     328          103 :             pgpa_output_relations(context, context->buf, qf->relids);
     329          103 :             appendStringInfoChar(context->buf, ')');
     330              :         }
     331              :     }
     332              : 
     333          868 :     appendStringInfoChar(context->buf, ')');
     334          868 :     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       261030 : pgpa_output_simple_strategy(pgpa_output_context *context, char *strategy,
     352              :                             List *relid_sets)
     353              : {
     354       261030 :     bool        first = true;
     355              : 
     356       261030 :     if (relid_sets == NIL)
     357       250969 :         return;
     358              : 
     359        10061 :     if (context->buf->len > 0)
     360        10061 :         appendStringInfoChar(context->buf, '\n');
     361        10061 :     appendStringInfo(context->buf, "%s(", strategy);
     362              : 
     363        35017 :     foreach_node(Bitmapset, relids, relid_sets)
     364              :     {
     365        14895 :         if (first)
     366        10061 :             first = false;
     367              :         else
     368              :         {
     369         4834 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     370         4834 :             appendStringInfoChar(context->buf, ' ');
     371              :         }
     372              : 
     373        14895 :         if (bms_membership(relids) == BMS_SINGLETON)
     374        14358 :             pgpa_output_relations(context, context->buf, relids);
     375              :         else
     376              :         {
     377          537 :             appendStringInfoChar(context->buf, '(');
     378          537 :             pgpa_output_relations(context, context->buf, relids);
     379          537 :             appendStringInfoChar(context->buf, ')');
     380              :         }
     381              :     }
     382              : 
     383        10061 :     appendStringInfoChar(context->buf, ')');
     384        10061 :     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        43505 : pgpa_output_no_gather(pgpa_output_context *context, Bitmapset *relids)
     393              : {
     394        43505 :     if (relids == NULL)
     395          177 :         return;
     396        43328 :     if (context->buf->len > 0)
     397        23075 :         appendStringInfoChar(context->buf, '\n');
     398        43328 :     appendStringInfoString(context->buf, "NO_GATHER(");
     399        43328 :     pgpa_output_relations(context, context->buf, relids);
     400        43328 :     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
     408        43505 : pgpa_output_do_not_scan(pgpa_output_context *context, List *identifiers)
     409              : {
     410        43505 :     bool        first = true;
     411              : 
     412        43505 :     if (identifiers == NIL)
     413        43292 :         return;
     414          213 :     if (context->buf->len > 0)
     415          213 :         appendStringInfoChar(context->buf, '\n');
     416          213 :     appendStringInfoString(context->buf, "DO_NOT_SCAN(");
     417              : 
     418          841 :     foreach_ptr(pgpa_identifier, rid, identifiers)
     419              :     {
     420          415 :         if (first)
     421          213 :             first = false;
     422              :         else
     423              :         {
     424          202 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     425          202 :             appendStringInfoChar(context->buf, ' ');
     426              :         }
     427          415 :         appendStringInfoString(context->buf, pgpa_identifier_string(rid));
     428              :     }
     429              : 
     430          213 :     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
     440       131265 : pgpa_output_relations(pgpa_output_context *context, StringInfo buf,
     441              :                       Bitmapset *relids)
     442              : {
     443       131265 :     int         rti = -1;
     444       131265 :     bool        first = true;
     445              : 
     446       291512 :     while ((rti = bms_next_member(relids, rti)) >= 0)
     447              :     {
     448       160247 :         const char *rid_string = context->rid_strings[rti - 1];
     449              : 
     450       160247 :         if (rid_string == NULL)
     451            0 :             elog(ERROR, "no identifier for RTI %d", rti);
     452              : 
     453       160247 :         if (first)
     454              :         {
     455       131265 :             first = false;
     456       131265 :             appendStringInfoString(buf, rid_string);
     457              :         }
     458              :         else
     459              :         {
     460        28982 :             pgpa_maybe_linebreak(buf, context->wrap_column);
     461        28982 :             appendStringInfo(buf, " %s", rid_string);
     462              :         }
     463              :     }
     464       131265 : }
     465              : 
     466              : /*
     467              :  * Get a C string that corresponds to the specified join strategy.
     468              :  */
     469              : static char *
     470       261030 : pgpa_cstring_join_strategy(pgpa_join_strategy strategy)
     471              : {
     472       261030 :     switch (strategy)
     473              :     {
     474        43505 :         case JSTRAT_MERGE_JOIN_PLAIN:
     475        43505 :             return "MERGE_JOIN_PLAIN";
     476        43505 :         case JSTRAT_MERGE_JOIN_MATERIALIZE:
     477        43505 :             return "MERGE_JOIN_MATERIALIZE";
     478        43505 :         case JSTRAT_NESTED_LOOP_PLAIN:
     479        43505 :             return "NESTED_LOOP_PLAIN";
     480        43505 :         case JSTRAT_NESTED_LOOP_MATERIALIZE:
     481        43505 :             return "NESTED_LOOP_MATERIALIZE";
     482        43505 :         case JSTRAT_NESTED_LOOP_MEMOIZE:
     483        43505 :             return "NESTED_LOOP_MEMOIZE";
     484        43505 :         case JSTRAT_HASH_JOIN:
     485        43505 :             return "HASH_JOIN";
     486              :     }
     487              : 
     488            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 *
     496        29720 : pgpa_cstring_scan_strategy(pgpa_scan_strategy strategy)
     497              : {
     498        29720 :     switch (strategy)
     499              :     {
     500            0 :         case PGPA_SCAN_ORDINARY:
     501            0 :             return "ORDINARY_SCAN";
     502        16378 :         case PGPA_SCAN_SEQ:
     503        16378 :             return "SEQ_SCAN";
     504         2612 :         case PGPA_SCAN_BITMAP_HEAP:
     505         2612 :             return "BITMAP_HEAP_SCAN";
     506            0 :         case PGPA_SCAN_FOREIGN:
     507            0 :             return "FOREIGN_JOIN";
     508         7001 :         case PGPA_SCAN_INDEX:
     509         7001 :             return "INDEX_SCAN";
     510         1667 :         case PGPA_SCAN_INDEX_ONLY:
     511         1667 :             return "INDEX_ONLY_SCAN";
     512         1662 :         case PGPA_SCAN_PARTITIONWISE:
     513         1662 :             return "PARTITIONWISE";
     514          400 :         case PGPA_SCAN_TID:
     515          400 :             return "TID_SCAN";
     516              :     }
     517              : 
     518            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 *
     526          868 : pgpa_cstring_query_feature_type(pgpa_qf_type type)
     527              : {
     528          868 :     switch (type)
     529              :     {
     530          160 :         case PGPAQF_GATHER:
     531          160 :             return "GATHER";
     532           56 :         case PGPAQF_GATHER_MERGE:
     533           56 :             return "GATHER_MERGE";
     534          582 :         case PGPAQF_SEMIJOIN_NON_UNIQUE:
     535          582 :             return "SEMIJOIN_NON_UNIQUE";
     536           70 :         case PGPAQF_SEMIJOIN_UNIQUE:
     537           70 :             return "SEMIJOIN_UNIQUE";
     538              :     }
     539              : 
     540              : 
     541            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
     559       130911 : 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       130911 :     if (wrap_column <= 0)
     567            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              :      */
     575       130911 :     trailing_nl = strrchr(buf->data, '\n');
     576       130911 :     if (trailing_nl == NULL)
     577        40910 :         line_start = 0;
     578              :     else
     579        90001 :         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       130911 :     save_cursor = buf->cursor;
     586       130911 :     buf->cursor = buf->len;
     587              : 
     588              :     /* If we haven't passed the wrap column, we don't need a newline. */
     589       130911 :     if (buf->len - line_start <= wrap_column)
     590       122470 :         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         8441 :     if (save_cursor <= line_start)
     597            0 :         return;
     598              : 
     599              :     /* Insert a newline at the previous cursor location. */
     600         8441 :     enlargeStringInfo(buf, 1);
     601         8441 :     memmove(&buf->data[save_cursor] + 1, &buf->data[save_cursor],
     602         8441 :             buf->len - save_cursor);
     603         8441 :     ++buf->cursor;
     604         8441 :     buf->data[++buf->len] = '\0';
     605         8441 :     buf->data[save_cursor] = '\n';
     606              : }
        

Generated by: LCOV version 2.0-1