LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_output.c (source / functions) Coverage Total Hit
Test: PostgreSQL 20devel Lines: 96.4 % 221 213
Test Date: 2026-07-25 22:15:46 Functions: 100.0 % 14 14
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 88.0 % 125 110

             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-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                 :       43842 : pgpa_output_advice(StringInfo buf, pgpa_plan_walker_context *walker,
      81                 :             :                    pgpa_identifier *rt_identifiers)
      82                 :             : {
      83                 :       43842 :     Index       rtable_length = list_length(walker->pstmt->rtable);
      84                 :             :     ListCell   *lc;
      85                 :             :     pgpa_output_context context;
      86                 :             : 
      87                 :             :     /* Basic initialization. */
      88                 :       43842 :     memset(&context, 0, sizeof(pgpa_output_context));
      89                 :       43842 :     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                 :       43842 :     context.rid_strings = palloc0_array(const char *, rtable_length);
      98         [ +  + ]:      143154 :     for (Index i = 0; i < rtable_length; ++i)
      99         [ +  + ]:       99312 :         if (rt_identifiers[i].alias_name != NULL)
     100                 :       90105 :             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                 :       43842 :     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   [ +  +  +  +  :       54868 :     foreach(lc, walker->toplevel_unrolled_joins)
                   +  + ]
     126                 :             :     {
     127                 :       11026 :         pgpa_unrolled_join *ujoin = lfirst(lc);
     128                 :             : 
     129         [ +  + ]:       11026 :         if (buf->len > 0)
     130                 :        2195 :             appendStringInfoChar(buf, '\n');
     131                 :       11026 :         appendStringInfoString(context.buf, "JOIN_ORDER(");
     132                 :       11026 :         pgpa_output_unrolled_join(&context, ujoin);
     133                 :       11026 :         appendStringInfoChar(context.buf, ')');
     134                 :       11026 :         pgpa_maybe_linebreak(context.buf, context.wrap_column);
     135                 :             :     }
     136                 :             : 
     137                 :             :     /* Emit join strategy advice. */
     138         [ +  + ]:      306894 :     for (int s = 0; s < NUM_PGPA_JOIN_STRATEGY; ++s)
     139                 :             :     {
     140                 :      263052 :         char       *strategy = pgpa_cstring_join_strategy(s);
     141                 :             : 
     142                 :      263052 :         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         [ +  + ]:      394578 :     for (int c = 0; c < NUM_PGPA_SCAN_STRATEGY; ++c)
     152         [ +  + ]:      350736 :         if (c != PGPA_SCAN_ORDINARY)
     153                 :      306894 :             pgpa_output_scan_strategy(&context, c, walker->scans[c]);
     154                 :             : 
     155                 :             :     /* Emit query feature advice. */
     156         [ +  + ]:      219210 :     for (int t = 0; t < NUM_PGPA_QF_TYPES; ++t)
     157                 :      175368 :         pgpa_output_query_feature(&context, t, walker->query_features[t]);
     158                 :             : 
     159                 :             :     /* Emit NO_GATHER advice. */
     160                 :       43842 :     pgpa_output_no_gather(&context, walker->no_gather_scans);
     161                 :             : 
     162                 :             :     /* Emit DO_NOT_SCAN advice. */
     163                 :       43842 :     pgpa_output_do_not_scan(&context, walker->do_not_scan_identifiers);
     164                 :       43842 : }
     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                 :       11557 : pgpa_output_unrolled_join(pgpa_output_context *context,
     172                 :             :                           pgpa_unrolled_join *join)
     173                 :             : {
     174                 :       11557 :     pgpa_output_join_member(context, &join->outer);
     175                 :             : 
     176         [ +  + ]:       26740 :     for (unsigned k = 0; k < join->ninner; ++k)
     177                 :             :     {
     178                 :       15183 :         pgpa_join_member *member = &join->inner[k];
     179                 :             : 
     180                 :       15183 :         pgpa_maybe_linebreak(context->buf, context->wrap_column);
     181                 :       15183 :         appendStringInfoChar(context->buf, ' ');
     182                 :       15183 :         pgpa_output_join_member(context, member);
     183                 :             :     }
     184                 :       11557 : }
     185                 :             : 
     186                 :             : /*
     187                 :             :  * Output a single member of an unrolled join as part of JOIN_ORDER() advice.
     188                 :             :  */
     189                 :             : static void
     190                 :       26740 : pgpa_output_join_member(pgpa_output_context *context,
     191                 :             :                         pgpa_join_member *member)
     192                 :             : {
     193         [ +  + ]:       26740 :     if (member->unrolled_join != NULL)
     194                 :             :     {
     195                 :         531 :         appendStringInfoChar(context->buf, '(');
     196                 :         531 :         pgpa_output_unrolled_join(context, member->unrolled_join);
     197                 :         531 :         appendStringInfoChar(context->buf, ')');
     198                 :             :     }
     199                 :             :     else
     200                 :             :     {
     201                 :       26209 :         pgpa_scan  *scan = member->scan;
     202                 :             : 
     203                 :             :         Assert(scan != NULL);
     204         [ +  + ]:       26209 :         if (bms_membership(scan->relids) == BMS_SINGLETON)
     205                 :       26195 :             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                 :       26740 : }
     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                 :      306894 : pgpa_output_scan_strategy(pgpa_output_context *context,
     222                 :             :                           pgpa_scan_strategy strategy,
     223                 :             :                           List *scans)
     224                 :             : {
     225                 :      306894 :     bool        first = true;
     226                 :             : 
     227         [ +  + ]:      306894 :     if (scans == NIL)
     228                 :      276733 :         return;
     229                 :             : 
     230         [ +  + ]:       30161 :     if (context->buf->len > 0)
     231                 :       15777 :         appendStringInfoChar(context->buf, '\n');
     232                 :       30161 :     appendStringInfo(context->buf, "%s(",
     233                 :             :                      pgpa_cstring_scan_strategy(strategy));
     234                 :             : 
     235   [ +  -  +  +  :      106477 :     foreach_ptr(pgpa_scan, scan, scans)
                   +  + ]
     236                 :             :     {
     237                 :       46155 :         Plan       *plan = scan->plan;
     238                 :             : 
     239         [ +  + ]:       46155 :         if (first)
     240                 :       30161 :             first = false;
     241                 :             :         else
     242                 :             :         {
     243                 :       15994 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     244                 :       15994 :             appendStringInfoChar(context->buf, ' ');
     245                 :             :         }
     246                 :             : 
     247                 :             :         /* Output the relation identifiers. */
     248         [ +  + ]:       46155 :         if (bms_membership(scan->relids) == BMS_SINGLETON)
     249                 :       45949 :             pgpa_output_relations(context, context->buf, scan->relids);
     250                 :             :         else
     251                 :             :         {
     252                 :         206 :             appendStringInfoChar(context->buf, '(');
     253                 :         206 :             pgpa_output_relations(context, context->buf, scan->relids);
     254                 :         206 :             appendStringInfoChar(context->buf, ')');
     255                 :             :         }
     256                 :             : 
     257                 :             :         /* For index or index-only scans, output index information. */
     258         [ +  + ]:       46155 :         if (strategy == PGPA_SCAN_INDEX)
     259                 :             :         {
     260                 :             :             Assert(IsA(plan, IndexScan));
     261                 :       12707 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     262                 :       12707 :             appendStringInfoChar(context->buf, ' ');
     263                 :       12707 :             pgpa_output_relation_name(context, ((IndexScan *) plan)->indexid);
     264                 :             :         }
     265         [ +  + ]:       33448 :         else if (strategy == PGPA_SCAN_INDEX_ONLY)
     266                 :             :         {
     267                 :             :             Assert(IsA(plan, IndexOnlyScan));
     268                 :        1787 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     269                 :        1787 :             appendStringInfoChar(context->buf, ' ');
     270                 :        1787 :             pgpa_output_relation_name(context,
     271                 :             :                                       ((IndexOnlyScan *) plan)->indexid);
     272                 :             :         }
     273                 :             :     }
     274                 :             : 
     275                 :       30161 :     appendStringInfoChar(context->buf, ')');
     276                 :       30161 :     pgpa_maybe_linebreak(context->buf, context->wrap_column);
     277                 :             : }
     278                 :             : 
     279                 :             : /*
     280                 :             :  * Output a schema-qualified relation name.
     281                 :             :  */
     282                 :             : static void
     283                 :       14494 : pgpa_output_relation_name(pgpa_output_context *context, Oid relid)
     284                 :             : {
     285                 :       14494 :     Oid         nspoid = get_rel_namespace(relid);
     286                 :       14494 :     char       *relnamespace = get_namespace_name_or_temp(nspoid);
     287                 :       14494 :     char       *relname = get_rel_name(relid);
     288                 :             : 
     289                 :       14494 :     appendStringInfoString(context->buf, quote_identifier(relnamespace));
     290                 :       14494 :     appendStringInfoChar(context->buf, '.');
     291                 :       14494 :     appendStringInfoString(context->buf, quote_identifier(relname));
     292                 :       14494 : }
     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                 :      175368 : pgpa_output_query_feature(pgpa_output_context *context, pgpa_qf_type type,
     301                 :             :                           List *query_features)
     302                 :             : {
     303                 :      175368 :     bool        first = true;
     304                 :             : 
     305         [ +  + ]:      175368 :     if (query_features == NIL)
     306                 :      174496 :         return;
     307                 :             : 
     308         [ +  + ]:         872 :     if (context->buf->len > 0)
     309                 :         871 :         appendStringInfoChar(context->buf, '\n');
     310                 :         872 :     appendStringInfo(context->buf, "%s(",
     311                 :             :                      pgpa_cstring_query_feature_type(type));
     312                 :             : 
     313   [ +  -  +  +  :        2686 :     foreach_ptr(pgpa_query_feature, qf, query_features)
                   +  + ]
     314                 :             :     {
     315         [ +  + ]:         942 :         if (first)
     316                 :         872 :             first = false;
     317                 :             :         else
     318                 :             :         {
     319                 :          70 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     320                 :          70 :             appendStringInfoChar(context->buf, ' ');
     321                 :             :         }
     322                 :             : 
     323         [ +  + ]:         942 :         if (bms_membership(qf->relids) == BMS_SINGLETON)
     324                 :         838 :             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                 :         872 :     appendStringInfoChar(context->buf, ')');
     334                 :         872 :     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                 :      263052 : pgpa_output_simple_strategy(pgpa_output_context *context, char *strategy,
     352                 :             :                             List *relid_sets)
     353                 :             : {
     354                 :      263052 :     bool        first = true;
     355                 :             : 
     356         [ +  + ]:      263052 :     if (relid_sets == NIL)
     357                 :      253140 :         return;
     358                 :             : 
     359         [ +  - ]:        9912 :     if (context->buf->len > 0)
     360                 :        9912 :         appendStringInfoChar(context->buf, '\n');
     361                 :        9912 :     appendStringInfo(context->buf, "%s(", strategy);
     362                 :             : 
     363   [ +  -  +  +  :       35007 :     foreach_node(Bitmapset, relids, relid_sets)
                   +  + ]
     364                 :             :     {
     365         [ +  + ]:       15183 :         if (first)
     366                 :        9912 :             first = false;
     367                 :             :         else
     368                 :             :         {
     369                 :        5271 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     370                 :        5271 :             appendStringInfoChar(context->buf, ' ');
     371                 :             :         }
     372                 :             : 
     373         [ +  + ]:       15183 :         if (bms_membership(relids) == BMS_SINGLETON)
     374                 :       14641 :             pgpa_output_relations(context, context->buf, relids);
     375                 :             :         else
     376                 :             :         {
     377                 :         542 :             appendStringInfoChar(context->buf, '(');
     378                 :         542 :             pgpa_output_relations(context, context->buf, relids);
     379                 :         542 :             appendStringInfoChar(context->buf, ')');
     380                 :             :         }
     381                 :             :     }
     382                 :             : 
     383                 :        9912 :     appendStringInfoChar(context->buf, ')');
     384                 :        9912 :     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                 :       43842 : pgpa_output_no_gather(pgpa_output_context *context, Bitmapset *relids)
     393                 :             : {
     394         [ +  + ]:       43842 :     if (relids == NULL)
     395                 :         179 :         return;
     396         [ +  + ]:       43663 :     if (context->buf->len > 0)
     397                 :       23044 :         appendStringInfoChar(context->buf, '\n');
     398                 :       43663 :     appendStringInfoString(context->buf, "NO_GATHER(");
     399                 :       43663 :     pgpa_output_relations(context, context->buf, relids);
     400                 :       43663 :     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                 :       43842 : pgpa_output_do_not_scan(pgpa_output_context *context, List *identifiers)
     409                 :             : {
     410                 :       43842 :     bool        first = true;
     411                 :             : 
     412         [ +  + ]:       43842 :     if (identifiers == NIL)
     413                 :       43668 :         return;
     414         [ +  - ]:         174 :     if (context->buf->len > 0)
     415                 :         174 :         appendStringInfoChar(context->buf, '\n');
     416                 :         174 :     appendStringInfoString(context->buf, "DO_NOT_SCAN(");
     417                 :             : 
     418   [ +  -  +  +  :         680 :     foreach_ptr(pgpa_identifier, rid, identifiers)
                   +  + ]
     419                 :             :     {
     420         [ +  + ]:         332 :         if (first)
     421                 :         174 :             first = false;
     422                 :             :         else
     423                 :             :         {
     424                 :         158 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     425                 :         158 :             appendStringInfoChar(context->buf, ' ');
     426                 :             :         }
     427                 :         332 :         appendStringInfoString(context->buf, pgpa_identifier_string(rid));
     428                 :             :     }
     429                 :             : 
     430                 :         174 :     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                 :      132152 : pgpa_output_relations(pgpa_output_context *context, StringInfo buf,
     441                 :             :                       Bitmapset *relids)
     442                 :             : {
     443                 :      132152 :     int         rti = -1;
     444                 :      132152 :     bool        first = true;
     445                 :             : 
     446         [ +  + ]:      293620 :     while ((rti = bms_next_member(relids, rti)) >= 0)
     447                 :             :     {
     448                 :      161468 :         const char *rid_string = context->rid_strings[rti - 1];
     449                 :             : 
     450         [ -  + ]:      161468 :         if (rid_string == NULL)
     451         [ #  # ]:           0 :             elog(ERROR, "no identifier for RTI %d", rti);
     452                 :             : 
     453         [ +  + ]:      161468 :         if (first)
     454                 :             :         {
     455                 :      132152 :             first = false;
     456                 :      132152 :             appendStringInfoString(buf, rid_string);
     457                 :             :         }
     458                 :             :         else
     459                 :             :         {
     460                 :       29316 :             pgpa_maybe_linebreak(buf, context->wrap_column);
     461                 :       29316 :             appendStringInfo(buf, " %s", rid_string);
     462                 :             :         }
     463                 :             :     }
     464                 :      132152 : }
     465                 :             : 
     466                 :             : /*
     467                 :             :  * Get a C string that corresponds to the specified join strategy.
     468                 :             :  */
     469                 :             : static char *
     470                 :      263052 : pgpa_cstring_join_strategy(pgpa_join_strategy strategy)
     471                 :             : {
     472   [ +  +  +  +  :      263052 :     switch (strategy)
                +  +  - ]
     473                 :             :     {
     474                 :       43842 :         case JSTRAT_MERGE_JOIN_PLAIN:
     475                 :       43842 :             return "MERGE_JOIN_PLAIN";
     476                 :       43842 :         case JSTRAT_MERGE_JOIN_MATERIALIZE:
     477                 :       43842 :             return "MERGE_JOIN_MATERIALIZE";
     478                 :       43842 :         case JSTRAT_NESTED_LOOP_PLAIN:
     479                 :       43842 :             return "NESTED_LOOP_PLAIN";
     480                 :       43842 :         case JSTRAT_NESTED_LOOP_MATERIALIZE:
     481                 :       43842 :             return "NESTED_LOOP_MATERIALIZE";
     482                 :       43842 :         case JSTRAT_NESTED_LOOP_MEMOIZE:
     483                 :       43842 :             return "NESTED_LOOP_MEMOIZE";
     484                 :       43842 :         case JSTRAT_HASH_JOIN:
     485                 :       43842 :             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                 :       30161 : pgpa_cstring_scan_strategy(pgpa_scan_strategy strategy)
     497                 :             : {
     498   [ -  +  +  +  :       30161 :     switch (strategy)
             +  +  +  +  
                      - ]
     499                 :             :     {
     500                 :           0 :         case PGPA_SCAN_ORDINARY:
     501                 :           0 :             return "ORDINARY_SCAN";
     502                 :       16435 :         case PGPA_SCAN_SEQ:
     503                 :       16435 :             return "SEQ_SCAN";
     504                 :        2573 :         case PGPA_SCAN_BITMAP_HEAP:
     505                 :        2573 :             return "BITMAP_HEAP_SCAN";
     506                 :           1 :         case PGPA_SCAN_FOREIGN:
     507                 :           1 :             return "FOREIGN_JOIN";
     508                 :        7500 :         case PGPA_SCAN_INDEX:
     509                 :        7500 :             return "INDEX_SCAN";
     510                 :        1565 :         case PGPA_SCAN_INDEX_ONLY:
     511                 :        1565 :             return "INDEX_ONLY_SCAN";
     512                 :        1685 :         case PGPA_SCAN_PARTITIONWISE:
     513                 :        1685 :             return "PARTITIONWISE";
     514                 :         402 :         case PGPA_SCAN_TID:
     515                 :         402 :             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                 :         872 : pgpa_cstring_query_feature_type(pgpa_qf_type type)
     527                 :             : {
     528   [ +  +  +  +  :         872 :     switch (type)
                      - ]
     529                 :             :     {
     530                 :         160 :         case PGPAQF_GATHER:
     531                 :         160 :             return "GATHER";
     532                 :          56 :         case PGPAQF_GATHER_MERGE:
     533                 :          56 :             return "GATHER_MERGE";
     534                 :         585 :         case PGPAQF_SEMIJOIN_NON_UNIQUE:
     535                 :         585 :             return "SEMIJOIN_NON_UNIQUE";
     536                 :          71 :         case PGPAQF_SEMIJOIN_UNIQUE:
     537                 :          71 :             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                 :      132457 : 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         [ -  + ]:      132457 :     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                 :      132457 :     trailing_nl = strrchr(buf->data, '\n');
     576         [ +  + ]:      132457 :     if (trailing_nl == NULL)
     577                 :       40618 :         line_start = 0;
     578                 :             :     else
     579                 :       91839 :         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                 :      132457 :     save_cursor = buf->cursor;
     586                 :      132457 :     buf->cursor = buf->len;
     587                 :             : 
     588                 :             :     /* If we haven't passed the wrap column, we don't need a newline. */
     589         [ +  + ]:      132457 :     if (buf->len - line_start <= wrap_column)
     590                 :      123257 :         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         [ -  + ]:        9200 :     if (save_cursor <= line_start)
     597                 :           0 :         return;
     598                 :             : 
     599                 :             :     /* Insert a newline at the previous cursor location. */
     600                 :        9200 :     enlargeStringInfo(buf, 1);
     601                 :        9200 :     memmove(&buf->data[save_cursor] + 1, &buf->data[save_cursor],
     602                 :        9200 :             buf->len - save_cursor);
     603                 :        9200 :     ++buf->cursor;
     604                 :        9200 :     buf->data[++buf->len] = '\0';
     605                 :        9200 :     buf->data[save_cursor] = '\n';
     606                 :             : }
        

Generated by: LCOV version 2.0-1