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-08-15 09:15:43 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                 :       43836 : pgpa_output_advice(StringInfo buf, pgpa_plan_walker_context *walker,
      81                 :             :                    pgpa_identifier *rt_identifiers)
      82                 :             : {
      83                 :       43836 :     Index       rtable_length = list_length(walker->pstmt->rtable);
      84                 :             :     ListCell   *lc;
      85                 :             :     pgpa_output_context context;
      86                 :             : 
      87                 :             :     /* Basic initialization. */
      88                 :       43836 :     memset(&context, 0, sizeof(pgpa_output_context));
      89                 :       43836 :     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                 :       43836 :     context.rid_strings = palloc0_array(const char *, rtable_length);
      98         [ +  + ]:      143456 :     for (Index i = 0; i < rtable_length; ++i)
      99         [ +  + ]:       99620 :         if (rt_identifiers[i].alias_name != NULL)
     100                 :       90350 :             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                 :       43836 :     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   [ +  +  +  +  :       54940 :     foreach(lc, walker->toplevel_unrolled_joins)
                   +  + ]
     126                 :             :     {
     127                 :       11104 :         pgpa_unrolled_join *ujoin = lfirst(lc);
     128                 :             : 
     129         [ +  + ]:       11104 :         if (buf->len > 0)
     130                 :        2213 :             appendStringInfoChar(buf, '\n');
     131                 :       11104 :         appendStringInfoString(context.buf, "JOIN_ORDER(");
     132                 :       11104 :         pgpa_output_unrolled_join(&context, ujoin);
     133                 :       11104 :         appendStringInfoChar(context.buf, ')');
     134                 :       11104 :         pgpa_maybe_linebreak(context.buf, context.wrap_column);
     135                 :             :     }
     136                 :             : 
     137                 :             :     /* Emit join strategy advice. */
     138         [ +  + ]:      306852 :     for (int s = 0; s < NUM_PGPA_JOIN_STRATEGY; ++s)
     139                 :             :     {
     140                 :      263016 :         char       *strategy = pgpa_cstring_join_strategy(s);
     141                 :             : 
     142                 :      263016 :         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         [ +  + ]:      394524 :     for (int c = 0; c < NUM_PGPA_SCAN_STRATEGY; ++c)
     152         [ +  + ]:      350688 :         if (c != PGPA_SCAN_ORDINARY)
     153                 :      306852 :             pgpa_output_scan_strategy(&context, c, walker->scans[c]);
     154                 :             : 
     155                 :             :     /* Emit query feature advice. */
     156         [ +  + ]:      219180 :     for (int t = 0; t < NUM_PGPA_QF_TYPES; ++t)
     157                 :      175344 :         pgpa_output_query_feature(&context, t, walker->query_features[t]);
     158                 :             : 
     159                 :             :     /* Emit NO_GATHER advice. */
     160                 :       43836 :     pgpa_output_no_gather(&context, walker->no_gather_scans);
     161                 :             : 
     162                 :             :     /* Emit DO_NOT_SCAN advice. */
     163                 :       43836 :     pgpa_output_do_not_scan(&context, walker->do_not_scan_identifiers);
     164                 :       43836 : }
     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                 :       11627 : pgpa_output_unrolled_join(pgpa_output_context *context,
     172                 :             :                           pgpa_unrolled_join *join)
     173                 :             : {
     174                 :       11627 :     pgpa_output_join_member(context, &join->outer);
     175                 :             : 
     176         [ +  + ]:       26910 :     for (unsigned k = 0; k < join->ninner; ++k)
     177                 :             :     {
     178                 :       15283 :         pgpa_join_member *member = &join->inner[k];
     179                 :             : 
     180                 :       15283 :         pgpa_maybe_linebreak(context->buf, context->wrap_column);
     181                 :       15283 :         appendStringInfoChar(context->buf, ' ');
     182                 :       15283 :         pgpa_output_join_member(context, member);
     183                 :             :     }
     184                 :       11627 : }
     185                 :             : 
     186                 :             : /*
     187                 :             :  * Output a single member of an unrolled join as part of JOIN_ORDER() advice.
     188                 :             :  */
     189                 :             : static void
     190                 :       26910 : pgpa_output_join_member(pgpa_output_context *context,
     191                 :             :                         pgpa_join_member *member)
     192                 :             : {
     193         [ +  + ]:       26910 :     if (member->unrolled_join != NULL)
     194                 :             :     {
     195                 :         523 :         appendStringInfoChar(context->buf, '(');
     196                 :         523 :         pgpa_output_unrolled_join(context, member->unrolled_join);
     197                 :         523 :         appendStringInfoChar(context->buf, ')');
     198                 :             :     }
     199                 :             :     else
     200                 :             :     {
     201                 :       26387 :         pgpa_scan  *scan = member->scan;
     202                 :             : 
     203                 :             :         Assert(scan != NULL);
     204         [ +  + ]:       26387 :         if (bms_membership(scan->relids) == BMS_SINGLETON)
     205                 :       26373 :             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                 :       26910 : }
     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                 :      306852 : pgpa_output_scan_strategy(pgpa_output_context *context,
     222                 :             :                           pgpa_scan_strategy strategy,
     223                 :             :                           List *scans)
     224                 :             : {
     225                 :      306852 :     bool        first = true;
     226                 :             : 
     227         [ +  + ]:      306852 :     if (scans == NIL)
     228                 :      276471 :         return;
     229                 :             : 
     230         [ +  + ]:       30381 :     if (context->buf->len > 0)
     231                 :       15955 :         appendStringInfoChar(context->buf, '\n');
     232                 :       30381 :     appendStringInfo(context->buf, "%s(",
     233                 :             :                      pgpa_cstring_scan_strategy(strategy));
     234                 :             : 
     235   [ +  -  +  +  :      107148 :     foreach_ptr(pgpa_scan, scan, scans)
                   +  + ]
     236                 :             :     {
     237                 :       46386 :         Plan       *plan = scan->plan;
     238                 :             : 
     239         [ +  + ]:       46386 :         if (first)
     240                 :       30381 :             first = false;
     241                 :             :         else
     242                 :             :         {
     243                 :       16005 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     244                 :       16005 :             appendStringInfoChar(context->buf, ' ');
     245                 :             :         }
     246                 :             : 
     247                 :             :         /* Output the relation identifiers. */
     248         [ +  + ]:       46386 :         if (bms_membership(scan->relids) == BMS_SINGLETON)
     249                 :       46180 :             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         [ +  + ]:       46386 :         if (strategy == PGPA_SCAN_INDEX)
     259                 :             :         {
     260                 :             :             Assert(IsA(plan, IndexScan));
     261                 :       12961 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     262                 :       12961 :             appendStringInfoChar(context->buf, ' ');
     263                 :       12961 :             pgpa_output_relation_name(context, ((IndexScan *) plan)->indexid);
     264                 :             :         }
     265         [ +  + ]:       33425 :         else if (strategy == PGPA_SCAN_INDEX_ONLY)
     266                 :             :         {
     267                 :             :             Assert(IsA(plan, IndexOnlyScan));
     268                 :        1853 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     269                 :        1853 :             appendStringInfoChar(context->buf, ' ');
     270                 :        1853 :             pgpa_output_relation_name(context,
     271                 :             :                                       ((IndexOnlyScan *) plan)->indexid);
     272                 :             :         }
     273                 :             :     }
     274                 :             : 
     275                 :       30381 :     appendStringInfoChar(context->buf, ')');
     276                 :       30381 :     pgpa_maybe_linebreak(context->buf, context->wrap_column);
     277                 :             : }
     278                 :             : 
     279                 :             : /*
     280                 :             :  * Output a schema-qualified relation name.
     281                 :             :  */
     282                 :             : static void
     283                 :       14814 : pgpa_output_relation_name(pgpa_output_context *context, Oid relid)
     284                 :             : {
     285                 :       14814 :     Oid         nspoid = get_rel_namespace(relid);
     286                 :       14814 :     char       *relnamespace = get_namespace_name_or_temp(nspoid);
     287                 :       14814 :     char       *relname = get_rel_name(relid);
     288                 :             : 
     289                 :       14814 :     appendStringInfoString(context->buf, quote_identifier(relnamespace));
     290                 :       14814 :     appendStringInfoChar(context->buf, '.');
     291                 :       14814 :     appendStringInfoString(context->buf, quote_identifier(relname));
     292                 :       14814 : }
     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                 :      175344 : pgpa_output_query_feature(pgpa_output_context *context, pgpa_qf_type type,
     301                 :             :                           List *query_features)
     302                 :             : {
     303                 :      175344 :     bool        first = true;
     304                 :             : 
     305         [ +  + ]:      175344 :     if (query_features == NIL)
     306                 :      174466 :         return;
     307                 :             : 
     308         [ +  + ]:         878 :     if (context->buf->len > 0)
     309                 :         877 :         appendStringInfoChar(context->buf, '\n');
     310                 :         878 :     appendStringInfo(context->buf, "%s(",
     311                 :             :                      pgpa_cstring_query_feature_type(type));
     312                 :             : 
     313   [ +  -  +  +  :        2704 :     foreach_ptr(pgpa_query_feature, qf, query_features)
                   +  + ]
     314                 :             :     {
     315         [ +  + ]:         948 :         if (first)
     316                 :         878 :             first = false;
     317                 :             :         else
     318                 :             :         {
     319                 :          70 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     320                 :          70 :             appendStringInfoChar(context->buf, ' ');
     321                 :             :         }
     322                 :             : 
     323         [ +  + ]:         948 :         if (bms_membership(qf->relids) == BMS_SINGLETON)
     324                 :         844 :             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                 :         878 :     appendStringInfoChar(context->buf, ')');
     334                 :         878 :     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                 :      263016 : pgpa_output_simple_strategy(pgpa_output_context *context, char *strategy,
     352                 :             :                             List *relid_sets)
     353                 :             : {
     354                 :      263016 :     bool        first = true;
     355                 :             : 
     356         [ +  + ]:      263016 :     if (relid_sets == NIL)
     357                 :      253043 :         return;
     358                 :             : 
     359         [ +  - ]:        9973 :     if (context->buf->len > 0)
     360                 :        9973 :         appendStringInfoChar(context->buf, '\n');
     361                 :        9973 :     appendStringInfo(context->buf, "%s(", strategy);
     362                 :             : 
     363   [ +  -  +  +  :       35229 :     foreach_node(Bitmapset, relids, relid_sets)
                   +  + ]
     364                 :             :     {
     365         [ +  + ]:       15283 :         if (first)
     366                 :        9973 :             first = false;
     367                 :             :         else
     368                 :             :         {
     369                 :        5310 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     370                 :        5310 :             appendStringInfoChar(context->buf, ' ');
     371                 :             :         }
     372                 :             : 
     373         [ +  + ]:       15283 :         if (bms_membership(relids) == BMS_SINGLETON)
     374                 :       14749 :             pgpa_output_relations(context, context->buf, relids);
     375                 :             :         else
     376                 :             :         {
     377                 :         534 :             appendStringInfoChar(context->buf, '(');
     378                 :         534 :             pgpa_output_relations(context, context->buf, relids);
     379                 :         534 :             appendStringInfoChar(context->buf, ')');
     380                 :             :         }
     381                 :             :     }
     382                 :             : 
     383                 :        9973 :     appendStringInfoChar(context->buf, ')');
     384                 :        9973 :     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                 :       43836 : pgpa_output_no_gather(pgpa_output_context *context, Bitmapset *relids)
     393                 :             : {
     394         [ +  + ]:       43836 :     if (relids == NULL)
     395                 :         179 :         return;
     396         [ +  + ]:       43657 :     if (context->buf->len > 0)
     397                 :       23146 :         appendStringInfoChar(context->buf, '\n');
     398                 :       43657 :     appendStringInfoString(context->buf, "NO_GATHER(");
     399                 :       43657 :     pgpa_output_relations(context, context->buf, relids);
     400                 :       43657 :     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                 :       43836 : pgpa_output_do_not_scan(pgpa_output_context *context, List *identifiers)
     409                 :             : {
     410                 :       43836 :     bool        first = true;
     411                 :             : 
     412         [ +  + ]:       43836 :     if (identifiers == NIL)
     413                 :       43661 :         return;
     414         [ +  - ]:         175 :     if (context->buf->len > 0)
     415                 :         175 :         appendStringInfoChar(context->buf, '\n');
     416                 :         175 :     appendStringInfoString(context->buf, "DO_NOT_SCAN(");
     417                 :             : 
     418   [ +  -  +  +  :         684 :     foreach_ptr(pgpa_identifier, rid, identifiers)
                   +  + ]
     419                 :             :     {
     420         [ +  + ]:         334 :         if (first)
     421                 :         175 :             first = false;
     422                 :             :         else
     423                 :             :         {
     424                 :         159 :             pgpa_maybe_linebreak(context->buf, context->wrap_column);
     425                 :         159 :             appendStringInfoChar(context->buf, ' ');
     426                 :             :         }
     427                 :         334 :         appendStringInfoString(context->buf, pgpa_identifier_string(rid));
     428                 :             :     }
     429                 :             : 
     430                 :         175 :     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                 :      132661 : pgpa_output_relations(pgpa_output_context *context, StringInfo buf,
     441                 :             :                       Bitmapset *relids)
     442                 :             : {
     443                 :      132661 :     int         rti = -1;
     444                 :      132661 :     bool        first = true;
     445                 :             : 
     446         [ +  + ]:      294801 :     while ((rti = bms_next_member(relids, rti)) >= 0)
     447                 :             :     {
     448                 :      162140 :         const char *rid_string = context->rid_strings[rti - 1];
     449                 :             : 
     450         [ -  + ]:      162140 :         if (rid_string == NULL)
     451         [ #  # ]:           0 :             elog(ERROR, "no identifier for RTI %d", rti);
     452                 :             : 
     453         [ +  + ]:      162140 :         if (first)
     454                 :             :         {
     455                 :      132661 :             first = false;
     456                 :      132661 :             appendStringInfoString(buf, rid_string);
     457                 :             :         }
     458                 :             :         else
     459                 :             :         {
     460                 :       29479 :             pgpa_maybe_linebreak(buf, context->wrap_column);
     461                 :       29479 :             appendStringInfo(buf, " %s", rid_string);
     462                 :             :         }
     463                 :             :     }
     464                 :      132661 : }
     465                 :             : 
     466                 :             : /*
     467                 :             :  * Get a C string that corresponds to the specified join strategy.
     468                 :             :  */
     469                 :             : static char *
     470                 :      263016 : pgpa_cstring_join_strategy(pgpa_join_strategy strategy)
     471                 :             : {
     472   [ +  +  +  +  :      263016 :     switch (strategy)
                +  +  - ]
     473                 :             :     {
     474                 :       43836 :         case JSTRAT_MERGE_JOIN_PLAIN:
     475                 :       43836 :             return "MERGE_JOIN_PLAIN";
     476                 :       43836 :         case JSTRAT_MERGE_JOIN_MATERIALIZE:
     477                 :       43836 :             return "MERGE_JOIN_MATERIALIZE";
     478                 :       43836 :         case JSTRAT_NESTED_LOOP_PLAIN:
     479                 :       43836 :             return "NESTED_LOOP_PLAIN";
     480                 :       43836 :         case JSTRAT_NESTED_LOOP_MATERIALIZE:
     481                 :       43836 :             return "NESTED_LOOP_MATERIALIZE";
     482                 :       43836 :         case JSTRAT_NESTED_LOOP_MEMOIZE:
     483                 :       43836 :             return "NESTED_LOOP_MEMOIZE";
     484                 :       43836 :         case JSTRAT_HASH_JOIN:
     485                 :       43836 :             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                 :       30381 : pgpa_cstring_scan_strategy(pgpa_scan_strategy strategy)
     497                 :             : {
     498   [ -  +  +  +  :       30381 :     switch (strategy)
             +  +  +  +  
                      - ]
     499                 :             :     {
     500                 :           0 :         case PGPA_SCAN_ORDINARY:
     501                 :           0 :             return "ORDINARY_SCAN";
     502                 :       16569 :         case PGPA_SCAN_SEQ:
     503                 :       16569 :             return "SEQ_SCAN";
     504                 :        2340 :         case PGPA_SCAN_BITMAP_HEAP:
     505                 :        2340 :             return "BITMAP_HEAP_SCAN";
     506                 :           1 :         case PGPA_SCAN_FOREIGN:
     507                 :           1 :             return "FOREIGN_JOIN";
     508                 :        7749 :         case PGPA_SCAN_INDEX:
     509                 :        7749 :             return "INDEX_SCAN";
     510                 :        1631 :         case PGPA_SCAN_INDEX_ONLY:
     511                 :        1631 :             return "INDEX_ONLY_SCAN";
     512                 :        1689 :         case PGPA_SCAN_PARTITIONWISE:
     513                 :        1689 :             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                 :         878 : pgpa_cstring_query_feature_type(pgpa_qf_type type)
     527                 :             : {
     528   [ +  +  +  +  :         878 :     switch (type)
                      - ]
     529                 :             :     {
     530                 :         160 :         case PGPAQF_GATHER:
     531                 :         160 :             return "GATHER";
     532                 :          56 :         case PGPAQF_GATHER_MERGE:
     533                 :          56 :             return "GATHER_MERGE";
     534                 :         591 :         case PGPAQF_SEMIJOIN_NON_UNIQUE:
     535                 :         591 :             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                 :      133456 : 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         [ -  + ]:      133456 :     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                 :      133456 :     trailing_nl = strrchr(buf->data, '\n');
     576         [ +  + ]:      133456 :     if (trailing_nl == NULL)
     577                 :       40824 :         line_start = 0;
     578                 :             :     else
     579                 :       92632 :         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                 :      133456 :     save_cursor = buf->cursor;
     586                 :      133456 :     buf->cursor = buf->len;
     587                 :             : 
     588                 :             :     /* If we haven't passed the wrap column, we don't need a newline. */
     589         [ +  + ]:      133456 :     if (buf->len - line_start <= wrap_column)
     590                 :      124235 :         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         [ -  + ]:        9221 :     if (save_cursor <= line_start)
     597                 :           0 :         return;
     598                 :             : 
     599                 :             :     /* Insert a newline at the previous cursor location. */
     600                 :        9221 :     enlargeStringInfo(buf, 1);
     601                 :        9221 :     memmove(&buf->data[save_cursor] + 1, &buf->data[save_cursor],
     602                 :        9221 :             buf->len - save_cursor);
     603                 :        9221 :     ++buf->cursor;
     604                 :        9221 :     buf->data[++buf->len] = '\0';
     605                 :        9221 :     buf->data[save_cursor] = '\n';
     606                 :             : }
        

Generated by: LCOV version 2.0-1