LCOV - code coverage report
Current view: top level - contrib/pg_plan_advice - pgpa_output.c (source / functions) Coverage Total Hit
Test: Code coverage Lines: 96.4 % 252 243
Test Date: 2026-01-26 10:56:24 Functions: 100.0 % 14 14
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
Branches: 81.1 % 159 129

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

Generated by: LCOV version 2.3.2-1