LCOV - differential code coverage report
Current view: top level - contrib/pg_stash_advice - stashpersist.c (source / functions) Coverage Total Hit UNC UBC GNC CBC DUB DCB
Current: c38de3ae6098ccf1904109f2e550eb4777f66024 vs e352ac8c5c90ed6ce792f044c22145578542a330 Lines: 77.2 % 272 210 1 61 1 209 1 1
Current Date: 2026-11-05 14:14:01 +0100 Functions: 91.7 % 12 11 1 1 10
Baseline: lcov-20261105-baseline Branches: 46.2 % 173 80 93 80
Baseline Date: 2026-11-05 14:14:01 +0100 Line coverage date bins:
Legend: Lines:     hit not hit
Branches: + taken - not taken # not executed
(1,7] days: 50.0 % 2 1 1 1
(30,360] days: 77.4 % 270 209 1 60 1 208
Function coverage date bins:
(30,360] days: 91.7 % 12 11 1 1 10
Branch coverage date bins:
(30,360] days: 46.2 % 173 80 93 80

 Age         Owner                    Branch data    TLA  Line data    Source code
                                  1                 :                : /*-------------------------------------------------------------------------
                                  2                 :                :  *
                                  3                 :                :  * stashpersist.c
                                  4                 :                :  *    Persistence support for pg_stash_advice.
                                  5                 :                :  *
                                  6                 :                :  * Copyright (c) 2016-2026, PostgreSQL Global Development Group
                                  7                 :                :  *
                                  8                 :                :  *    contrib/pg_stash_advice/stashpersist.c
                                  9                 :                :  *
                                 10                 :                :  *-------------------------------------------------------------------------
                                 11                 :                :  */
                                 12                 :                : #include "postgres.h"
                                 13                 :                : 
                                 14                 :                : #include <sys/stat.h>
                                 15                 :                : 
                                 16                 :                : #include "common/hashfn.h"
                                 17                 :                : #include "miscadmin.h"
                                 18                 :                : #include "pg_stash_advice.h"
                                 19                 :                : #include "postmaster/bgworker.h"
                                 20                 :                : #include "postmaster/interrupt.h"
                                 21                 :                : #include "storage/fd.h"
                                 22                 :                : #include "storage/ipc.h"
                                 23                 :                : #include "storage/latch.h"
                                 24                 :                : #include "storage/proc.h"
                                 25                 :                : #include "storage/procsignal.h"
                                 26                 :                : #include "utils/backend_status.h"
                                 27                 :                : #include "utils/guc.h"
                                 28                 :                : #include "utils/memutils.h"
                                 29                 :                : #include "utils/timestamp.h"
                                 30                 :                : 
                                 31                 :                : typedef struct pgsa_writer_context
                                 32                 :                : {
                                 33                 :                :     char        pathname[MAXPGPATH];
                                 34                 :                :     FILE       *file;
                                 35                 :                :     pgsa_stash_name_table_hash *nhash;
                                 36                 :                :     StringInfoData buf;
                                 37                 :                :     int         entries_written;
                                 38                 :                : } pgsa_writer_context;
                                 39                 :                : 
                                 40                 :                : /*
                                 41                 :                :  * A parsed entry line, with pointers into the slurp buffer.
                                 42                 :                :  */
                                 43                 :                : typedef struct pgsa_saved_entry
                                 44                 :                : {
                                 45                 :                :     char       *stash_name;
                                 46                 :                :     int64       queryId;
                                 47                 :                :     char       *advice_string;
                                 48                 :                : } pgsa_saved_entry;
                                 49                 :                : 
                                 50                 :                : /*
                                 51                 :                :  * simplehash for detecting duplicate stash names during parsing.
                                 52                 :                :  * Keyed by stash name (char *), pointing into the slurp buffer.
                                 53                 :                :  */
                                 54                 :                : typedef struct pgsa_saved_stash
                                 55                 :                : {
                                 56                 :                :     uint32      status;
                                 57                 :                :     char       *name;
                                 58                 :                : } pgsa_saved_stash;
                                 59                 :                : 
                                 60                 :                : #define SH_PREFIX pgsa_saved_stash_table
                                 61                 :                : #define SH_ELEMENT_TYPE pgsa_saved_stash
                                 62                 :                : #define SH_KEY_TYPE char *
                                 63                 :                : #define SH_KEY name
                                 64                 :                : #define SH_HASH_KEY(tb, key) hash_bytes((const unsigned char *) (key), strlen(key))
                                 65                 :                : #define SH_EQUAL(tb, a, b) (strcmp(a, b) == 0)
                                 66                 :                : #define SH_SCOPE static inline
                                 67                 :                : #define SH_DEFINE
                                 68                 :                : #define SH_DECLARE
                                 69                 :                : #include "lib/simplehash.h"
                                 70                 :                : 
                                 71                 :                : extern PGDLLEXPORT void pg_stash_advice_worker_main(Datum main_arg);
                                 72                 :                : static void pgsa_append_tsv_escaped_string(StringInfo buf, const char *str);
                                 73                 :                : static void pgsa_detach_shmem(int code, Datum arg);
                                 74                 :                : static char *pgsa_next_tsv_field(char **cursor);
                                 75                 :                : static void pgsa_read_from_disk(void);
                                 76                 :                : static void pgsa_restore_entries(pgsa_saved_entry *entries, int num_entries);
                                 77                 :                : static void pgsa_restore_stashes(pgsa_saved_stash_table_hash *saved_stashes);
                                 78                 :                : static void pgsa_unescape_tsv_field(char *str, const char *filename,
                                 79                 :                :                                     unsigned lineno);
                                 80                 :                : static void pgsa_write_entries(pgsa_writer_context *wctx);
                                 81                 :                : pg_noreturn static void pgsa_write_error(pgsa_writer_context *wctx);
                                 82                 :                : static void pgsa_write_stashes(pgsa_writer_context *wctx);
                                 83                 :                : static void pgsa_write_to_disk(void);
                                 84                 :                : 
                                 85                 :                : /*
                                 86                 :                :  * Background worker entry point for pg_stash_advice persistence.
                                 87                 :                :  *
                                 88                 :                :  * On startup, if stashes_ready is set, we load previously saved
                                 89                 :                :  * stash data from disk.  Then we enter a loop, periodically checking whether
                                 90                 :                :  * any changes have been made (via the change_count atomic counter) and
                                 91                 :                :  * writing them to disk.  On shutdown, we perform a final write.
                                 92                 :                :  */
                                 93                 :                : PGDLLEXPORT void
  212 rhaas@postgresql.org       94                 :CBC           4 : pg_stash_advice_worker_main(Datum main_arg)
                                 95                 :                : {
                                 96                 :                :     uint64      last_change_count;
                                 97                 :              4 :     TimestampTz last_write_time = 0;
                                 98                 :                : 
                                 99                 :                :     /* Establish signal handlers; once that's done, unblock signals. */
                                100                 :              4 :     pqsignal(SIGTERM, SignalHandlerForShutdownRequest);
                                101                 :              4 :     pqsignal(SIGHUP, SignalHandlerForConfigReload);
                                102                 :              4 :     pqsignal(SIGUSR1, procsignal_sigusr1_handler);
                                103                 :              4 :     BackgroundWorkerUnblockSignals();
                                104                 :                : 
                                105                 :                :     /* Log a debug message */
                                106         [ -  + ]:              4 :     ereport(DEBUG1,
                                107                 :                :             errmsg("pg_stash_advice worker started"));
                                108                 :                : 
                                109                 :                :     /* Set up session user so pgstat can report it. */
                                110                 :              4 :     InitializeSessionUserIdStandalone();
                                111                 :                : 
                                112                 :                :     /* Report this worker in pg_stat_activity. */
                                113                 :              4 :     pgstat_beinit();
                                114                 :              4 :     pgstat_bestart_initial();
                                115                 :              4 :     pgstat_bestart_final();
                                116                 :                : 
                                117                 :                :     /* Attach to shared memory structures. */
                                118                 :              4 :     pgsa_attach();
                                119                 :                : 
                                120                 :                :     /* Set on-detach hook so that our PID will be cleared on exit. */
                                121                 :              4 :     before_shmem_exit(pgsa_detach_shmem, 0);
                                122                 :                : 
                                123                 :                :     /*
                                124                 :                :      * Store our PID in shared memory, unless there's already another worker
                                125                 :                :      * running, in which case just exit.
                                126                 :                :      */
                                127                 :              4 :     LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
                                128         [ -  + ]:              4 :     if (pgsa_state->bgworker_pid != InvalidPid)
                                129                 :                :     {
  212 rhaas@postgresql.org      130                 :UBC           0 :         LWLockRelease(&pgsa_state->lock);
                                131         [ #  # ]:              0 :         ereport(LOG,
                                132                 :                :                 (errmsg("pg_stash_advice worker is already running under PID %d",
                                133                 :                :                         (int) pgsa_state->bgworker_pid)));
                                134                 :              0 :         return;
                                135                 :                :     }
  212 rhaas@postgresql.org      136                 :CBC           4 :     pgsa_state->bgworker_pid = MyProcPid;
                                137                 :              4 :     LWLockRelease(&pgsa_state->lock);
                                138                 :                : 
                                139                 :                :     /*
                                140                 :                :      * If pg_stash_advice.persist was set to true during
                                141                 :                :      * process_shared_preload_libraries() and the data has not yet been
                                142                 :                :      * successfully loaded, load it now. At that point, what's in memory
                                143                 :                :      * matches what's on disk, so note the current change count before
                                144                 :                :      * allowing any further changes.
                                145                 :                :      *
                                146                 :                :      * Otherwise, we can't assume that what's in memory has been saved: a
                                147                 :                :      * previous worker may have died while trying to write the file. Since
                                148                 :                :      * change_count starts at zero, comparing against zero makes us write at
                                149                 :                :      * the first opportunity unless nothing has ever been changed.
                                150                 :                :      */
                                151         [ +  - ]:              4 :     if (pg_atomic_unlocked_test_flag(&pgsa_state->stashes_ready))
                                152                 :                :     {
                                153                 :              4 :         pgsa_read_from_disk();
    7                           154                 :              4 :         last_change_count = pg_atomic_read_u64(&pgsa_state->change_count);
  212                           155                 :              4 :         pg_atomic_test_set_flag(&pgsa_state->stashes_ready);
                                156                 :                :     }
                                157                 :                :     else
    7 rhaas@postgresql.org      158                 :UBC           0 :         last_change_count = 0;
                                159                 :                : 
                                160                 :                :     /* Periodically write to disk until terminated. */
  212 rhaas@postgresql.org      161         [ +  + ]:CBC          12 :     while (!ShutdownRequestPending)
                                162                 :                :     {
                                163                 :                :         /* In case of a SIGHUP, just reload the configuration. */
                                164         [ -  + ]:              8 :         if (ConfigReloadPending)
                                165                 :                :         {
  212 rhaas@postgresql.org      166                 :UBC           0 :             ConfigReloadPending = false;
                                167                 :              0 :             ProcessConfigFile(PGC_SIGHUP);
                                168                 :                :         }
                                169                 :                : 
  212 rhaas@postgresql.org      170         [ +  - ]:CBC           8 :         if (pg_stash_advice_persist_interval <= 0)
                                171                 :                :         {
                                172                 :                :             /* Only writing at shutdown, so just wait forever. */
                                173                 :              8 :             (void) WaitLatch(MyLatch,
                                174                 :                :                              WL_LATCH_SET | WL_EXIT_ON_PM_DEATH,
                                175                 :                :                              -1L,
                                176                 :                :                              PG_WAIT_EXTENSION);
                                177                 :                :         }
                                178                 :                :         else
                                179                 :                :         {
                                180                 :                :             TimestampTz next_write_time;
                                181                 :                :             long        delay_in_ms;
                                182                 :                :             uint64      current_change_count;
                                183                 :                : 
                                184                 :                :             /* Compute when the next write should happen. */
  212 rhaas@postgresql.org      185                 :UBC           0 :             next_write_time =
                                186                 :              0 :                 TimestampTzPlusMilliseconds(last_write_time,
                                187                 :                :                                             pg_stash_advice_persist_interval * 1000);
                                188                 :                :             delay_in_ms =
                                189                 :              0 :                 TimestampDifferenceMilliseconds(GetCurrentTimestamp(),
                                190                 :                :                                                 next_write_time);
                                191                 :                : 
                                192                 :                :             /*
                                193                 :                :              * When we reach next_write_time, we always update last_write_time
                                194                 :                :              * (which is really the time at which we last considered writing),
                                195                 :                :              * but we only actually write to disk if something has changed.
                                196                 :                :              */
                                197         [ #  # ]:              0 :             if (delay_in_ms <= 0)
                                198                 :                :             {
                                199                 :                :                 current_change_count =
                                200                 :              0 :                     pg_atomic_read_u64(&pgsa_state->change_count);
                                201         [ #  # ]:              0 :                 if (current_change_count != last_change_count)
                                202                 :                :                 {
                                203                 :              0 :                     pgsa_write_to_disk();
                                204                 :              0 :                     last_change_count = current_change_count;
                                205                 :                :                 }
                                206                 :              0 :                 last_write_time = GetCurrentTimestamp();
                                207                 :              0 :                 continue;
                                208                 :                :             }
                                209                 :                : 
                                210                 :                :             /* Sleep until the next write time. */
                                211                 :              0 :             (void) WaitLatch(MyLatch,
                                212                 :                :                              WL_LATCH_SET | WL_TIMEOUT | WL_EXIT_ON_PM_DEATH,
                                213                 :                :                              delay_in_ms,
                                214                 :                :                              PG_WAIT_EXTENSION);
                                215                 :                :         }
                                216                 :                : 
  212 rhaas@postgresql.org      217                 :CBC           8 :         ResetLatch(MyLatch);
                                218                 :                :     }
                                219                 :                : 
                                220                 :                :     /* Write one last time before exiting. */
                                221                 :              4 :     pgsa_write_to_disk();
                                222                 :                : }
                                223                 :                : 
                                224                 :                : /*
                                225                 :                :  * Clear our PID from shared memory on exit.
                                226                 :                :  */
                                227                 :                : static void
                                228                 :              4 : pgsa_detach_shmem(int code, Datum arg)
                                229                 :                : {
                                230                 :              4 :     LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
                                231         [ +  - ]:              4 :     if (pgsa_state->bgworker_pid == MyProcPid)
                                232                 :              4 :         pgsa_state->bgworker_pid = InvalidPid;
                                233                 :              4 :     LWLockRelease(&pgsa_state->lock);
                                234                 :              4 : }
                                235                 :                : 
                                236                 :                : /*
                                237                 :                :  * Load advice stash data from a dump file on disk, if there is one.
                                238                 :                :  */
                                239                 :                : static void
                                240                 :              4 : pgsa_read_from_disk(void)
                                241                 :                : {
                                242                 :                :     struct stat statbuf;
                                243                 :                :     FILE       *file;
                                244                 :                :     char       *filebuf;
                                245                 :                :     size_t      nread;
                                246                 :                :     char       *p;
                                247                 :                :     unsigned    lineno;
                                248                 :                :     pgsa_saved_stash_table_hash *saved_stashes;
                                249                 :              4 :     int         num_stashes = 0;
                                250                 :                :     pgsa_saved_entry *entries;
                                251                 :              4 :     int         num_entries = 0;
                                252                 :              4 :     int         max_entries = 64;
                                253                 :                :     MemoryContext tmpcxt;
                                254                 :                :     MemoryContext oldcxt;
                                255                 :                : 
                                256         [ -  + ]:              4 :     Assert(pgsa_entry_dshash != NULL);
                                257                 :                : 
                                258                 :                :     /*
                                259                 :                :      * Clear any existing shared memory state.
                                260                 :                :      *
                                261                 :                :      * Normally, there won't be any, but if this function was called before
                                262                 :                :      * and failed after beginning to apply changes to shared memory, then we
                                263                 :                :      * need to get rid of any entries created at that time before trying
                                264                 :                :      * again.
                                265                 :                :      */
                                266                 :              4 :     LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
                                267                 :              4 :     pgsa_reset_all_stashes();
                                268                 :              4 :     LWLockRelease(&pgsa_state->lock);
                                269                 :                : 
                                270                 :                :     /* Open the dump file. If it doesn't exist, we're done. */
                                271                 :              4 :     file = AllocateFile(PGSA_DUMP_FILE, "r");
                                272         [ +  + ]:              4 :     if (!file)
                                273                 :                :     {
                                274         [ +  - ]:              2 :         if (errno == ENOENT)
                                275                 :              2 :             return;
  212 rhaas@postgresql.org      276         [ #  # ]:UBC           0 :         ereport(ERROR,
                                277                 :                :                 (errcode_for_file_access(),
                                278                 :                :                  errmsg("could not open file \"%s\": %m", PGSA_DUMP_FILE)));
                                279                 :                :     }
                                280                 :                : 
                                281                 :                :     /* Use a temporary context for all parse-phase allocations. */
  212 rhaas@postgresql.org      282                 :CBC           2 :     tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
                                283                 :                :                                    "pg_stash_advice load",
                                284                 :                :                                    ALLOCSET_DEFAULT_SIZES);
                                285                 :              2 :     oldcxt = MemoryContextSwitchTo(tmpcxt);
                                286                 :                : 
                                287                 :                :     /* Figure out how long the file is. */
                                288         [ -  + ]:              2 :     if (fstat(fileno(file), &statbuf) != 0)
  212 rhaas@postgresql.org      289         [ #  # ]:UBC           0 :         ereport(ERROR,
                                290                 :                :                 (errcode_for_file_access(),
                                291                 :                :                  errmsg("could not stat file \"%s\": %m", PGSA_DUMP_FILE)));
                                292                 :                : 
                                293                 :                :     /*
                                294                 :                :      * Slurp the entire file into memory all at once.
                                295                 :                :      *
                                296                 :                :      * We could avoid this by reading the file incrementally and applying
                                297                 :                :      * changes to pgsa_stash_dshash and pgsa_entry_dshash as we go. Given the
                                298                 :                :      * lockout mechanism implemented by stashes_ready, that shouldn't have any
                                299                 :                :      * user-visible behavioral consequences, but it would consume shared
                                300                 :                :      * memory to no benefit. It seems better to buffer everything in private
                                301                 :                :      * memory first, and then only apply the changes once the file has been
                                302                 :                :      * successfully parsed in its entirety.
                                303                 :                :      *
                                304                 :                :      * That also has the advantage of possibly being more future-proof: if we
                                305                 :                :      * decide to remove the stashes_ready mechanism in the future, or say
                                306                 :                :      * allow for multiple save files, fully validating the file before
                                307                 :                :      * applying any changes will become much more important.
                                308                 :                :      *
                                309                 :                :      * Of course, this approach does have one major disadvantage, which is
                                310                 :                :      * that we'll temporarily use about twice as much memory as we're
                                311                 :                :      * ultimately going to need, but that seems like it shouldn't be a problem
                                312                 :                :      * in practice. If there's so much stashed advice that parsing the disk
                                313                 :                :      * file runs us out of memory, something has gone terribly wrong. In that
                                314                 :                :      * situation, there probably also isn't enough free memory for the
                                315                 :                :      * workload that the advice is attempting to manipulate to run
                                316                 :                :      * successfully.
                                317                 :                :      */
  212 rhaas@postgresql.org      318                 :CBC           2 :     filebuf = palloc_extended(statbuf.st_size + 1, MCXT_ALLOC_HUGE);
                                319                 :              2 :     nread = fread(filebuf, 1, statbuf.st_size, file);
                                320         [ -  + ]:              2 :     if (ferror(file))
  212 rhaas@postgresql.org      321         [ #  # ]:UBC           0 :         ereport(ERROR,
                                322                 :                :                 (errcode_for_file_access(),
                                323                 :                :                  errmsg("could not read file \"%s\": %m", PGSA_DUMP_FILE)));
  212 rhaas@postgresql.org      324                 :CBC           2 :     FreeFile(file);
                                325                 :              2 :     filebuf[nread] = '\0';
                                326                 :                : 
                                327                 :                :     /* Initial memory allocations. */
                                328                 :              2 :     saved_stashes = pgsa_saved_stash_table_create(tmpcxt, 64, NULL);
   80 michael@paquier.xyz       329                 :GNC           2 :     entries = palloc_array(pgsa_saved_entry, max_entries);
                                330                 :                : 
                                331                 :                :     /*
                                332                 :                :      * For memory and CPU efficiency, we parse the file in place. The end of
                                333                 :                :      * each line gets replaced with a NUL byte, and then the end of each field
                                334                 :                :      * within a line gets the same treatment. The advice string is unescaped
                                335                 :                :      * in place, and stash names and query IDs can't contain any special
                                336                 :                :      * characters. All of the resulting pointers point right back into the
                                337                 :                :      * buffer; we only need additional memory to grow the 'entries' array and
                                338                 :                :      * the 'saved_stashes' hash table.
                                339                 :                :      */
  212 rhaas@postgresql.org      340         [ +  + ]:CBC          15 :     for (p = filebuf, lineno = 1; *p != '\0'; lineno++)
                                341                 :                :     {
                                342                 :             13 :         char       *cursor = p;
                                343                 :                :         char       *eol;
                                344                 :                :         char       *line_type;
                                345                 :                : 
                                346                 :                :         /* Find end of line and NUL-terminate. */
                                347                 :             13 :         eol = strchr(p, '\n');
                                348         [ +  - ]:             13 :         if (eol != NULL)
                                349                 :                :         {
                                350                 :             13 :             *eol = '\0';
                                351                 :             13 :             p = eol + 1;
                                352   [ +  -  -  + ]:             13 :             if (eol > cursor && eol[-1] == '\r')
  212 rhaas@postgresql.org      353                 :UBC           0 :                 eol[-1] = '\0';
                                354                 :                :         }
                                355                 :                :         else
                                356                 :              0 :             p += strlen(p);
                                357                 :                : 
                                358                 :                :         /* Skip empty lines. */
  212 rhaas@postgresql.org      359         [ -  + ]:CBC          13 :         if (*cursor == '\0')
  212 rhaas@postgresql.org      360                 :UBC           0 :             continue;
                                361                 :                : 
                                362                 :                :         /* First field is the type of line, either "stash" or "entry". */
  212 rhaas@postgresql.org      363                 :CBC          13 :         line_type = pgsa_next_tsv_field(&cursor);
                                364         [ +  + ]:             13 :         if (strcmp(line_type, "stash") == 0)
                                365                 :                :         {
                                366                 :                :             char       *name;
                                367                 :                :             bool        found;
                                368                 :                : 
                                369                 :                :             /* Second field should be the stash name. */
                                370                 :              5 :             name = pgsa_next_tsv_field(&cursor);
                                371   [ +  -  -  + ]:              5 :             if (name == NULL || *name == '\0')
  212 rhaas@postgresql.org      372         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                373                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                374                 :                :                          errmsg("syntax error in file \"%s\" line %u: expected stash name",
                                375                 :                :                                 PGSA_DUMP_FILE, lineno)));
                                376                 :                : 
                                377                 :                :             /* No further fields are expected. */
   58 rhaas@postgresql.org      378         [ -  + ]:CBC           5 :             if (cursor != NULL)
  212 rhaas@postgresql.org      379         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                380                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                381                 :                :                          errmsg("syntax error in file \"%s\" line %u: expected end of line",
                                382                 :                :                                 PGSA_DUMP_FILE, lineno)));
                                383                 :                : 
                                384                 :                :             /* Reject overlong stash names. */
  160 rhaas@postgresql.org      385         [ -  + ]:CBC           5 :             if (strlen(name) >= NAMEDATALEN)
  160 rhaas@postgresql.org      386         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                387                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                388                 :                :                          errmsg("syntax error in file \"%s\" line %u: stash name too long",
                                389                 :                :                                 PGSA_DUMP_FILE, lineno)));
                                390                 :                : 
                                391                 :                :             /* Duplicate check. */
  212 rhaas@postgresql.org      392                 :CBC           5 :             (void) pgsa_saved_stash_table_insert(saved_stashes, name, &found);
                                393         [ -  + ]:              5 :             if (found)
  212 rhaas@postgresql.org      394         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                395                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                396                 :                :                          errmsg("syntax error in file \"%s\" line %u: duplicate stash name \"%s\"",
                                397                 :                :                                 PGSA_DUMP_FILE, lineno, name)));
  212 rhaas@postgresql.org      398                 :CBC           5 :             num_stashes++;
                                399                 :                :         }
                                400         [ +  - ]:              8 :         else if (strcmp(line_type, "entry") == 0)
                                401                 :                :         {
                                402                 :                :             char       *stash_name;
                                403                 :                :             char       *queryid_str;
                                404                 :                :             char       *advice_str;
                                405                 :                :             char       *endptr;
                                406                 :                :             int64       queryId;
                                407                 :                : 
                                408                 :                :             /* Second field should be the stash name. */
                                409                 :              8 :             stash_name = pgsa_next_tsv_field(&cursor);
                                410         [ -  + ]:              8 :             if (stash_name == NULL)
  212 rhaas@postgresql.org      411         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                412                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                413                 :                :                          errmsg("syntax error in file \"%s\" line %u: expected stash name",
                                414                 :                :                                 PGSA_DUMP_FILE, lineno)));
                                415                 :                : 
                                416                 :                :             /* Third field should be the query ID. */
  212 rhaas@postgresql.org      417                 :CBC           8 :             queryid_str = pgsa_next_tsv_field(&cursor);
                                418         [ -  + ]:              8 :             if (queryid_str == NULL)
  212 rhaas@postgresql.org      419         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                420                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                421                 :                :                          errmsg("syntax error in file \"%s\" line %u: expected query ID",
                                422                 :                :                                 PGSA_DUMP_FILE, lineno)));
                                423                 :                : 
                                424                 :                :             /* Fourth field should be the advice string. */
  212 rhaas@postgresql.org      425                 :CBC           8 :             advice_str = pgsa_next_tsv_field(&cursor);
                                426         [ -  + ]:              8 :             if (advice_str == NULL)
  212 rhaas@postgresql.org      427         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                428                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                429                 :                :                          errmsg("syntax error in file \"%s\" line %u: expected advice string",
                                430                 :                :                                 PGSA_DUMP_FILE, lineno)));
                                431                 :                : 
                                432                 :                :             /* No further fields are expected. */
   58 rhaas@postgresql.org      433         [ -  + ]:CBC           8 :             if (cursor != NULL)
  212 rhaas@postgresql.org      434         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                435                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                436                 :                :                          errmsg("syntax error in file \"%s\" line %u: expected end of line",
                                437                 :                :                                 PGSA_DUMP_FILE, lineno)));
                                438                 :                : 
                                439                 :                :             /* Make sure the stash is one we've actually seen. */
  212 rhaas@postgresql.org      440         [ -  + ]:CBC           8 :             if (pgsa_saved_stash_table_lookup(saved_stashes,
                                441                 :                :                                               stash_name) == NULL)
  212 rhaas@postgresql.org      442         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                443                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                444                 :                :                          errmsg("syntax error in file \"%s\" line %u: unknown stash \"%s\"",
                                445                 :                :                                 PGSA_DUMP_FILE, lineno, stash_name)));
                                446                 :                : 
                                447                 :                :             /* Parse the query ID. */
  212 rhaas@postgresql.org      448                 :CBC           8 :             errno = 0;
  162 peter@eisentraut.org      449                 :              8 :             queryId = strtoi64(queryid_str, &endptr, 10);
  212 rhaas@postgresql.org      450   [ +  -  +  -  :              8 :             if (*endptr != '\0' || errno != 0 || queryid_str == endptr ||
                                        +  -  -  + ]
                                451                 :                :                 queryId == 0)
  212 rhaas@postgresql.org      452         [ #  # ]:UBC           0 :                 ereport(ERROR,
                                453                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                454                 :                :                          errmsg("syntax error in file \"%s\" line %u: invalid query ID \"%s\"",
                                455                 :                :                                 PGSA_DUMP_FILE, lineno, queryid_str)));
                                456                 :                : 
                                457                 :                :             /* Unescape the advice string. */
  212 rhaas@postgresql.org      458                 :CBC           8 :             pgsa_unescape_tsv_field(advice_str, PGSA_DUMP_FILE, lineno);
                                459                 :                : 
                                460                 :                :             /* Append to the entry array. */
                                461         [ -  + ]:              8 :             if (num_entries >= max_entries)
                                462                 :                :             {
  212 rhaas@postgresql.org      463                 :UBC           0 :                 max_entries *= 2;
   80 michael@paquier.xyz       464                 :UNC           0 :                 entries = repalloc_array(entries, pgsa_saved_entry, max_entries);
                                465                 :                :             }
  212 rhaas@postgresql.org      466                 :CBC           8 :             entries[num_entries].stash_name = stash_name;
                                467                 :              8 :             entries[num_entries].queryId = queryId;
                                468                 :              8 :             entries[num_entries].advice_string = advice_str;
                                469                 :              8 :             num_entries++;
                                470                 :                :         }
                                471                 :                :         else
                                472                 :                :         {
  212 rhaas@postgresql.org      473         [ #  # ]:UBC           0 :             ereport(ERROR,
                                474                 :                :                     (errcode(ERRCODE_DATA_CORRUPTED),
                                475                 :                :                      errmsg("syntax error in file \"%s\" line %u: unrecognized line type",
                                476                 :                :                             PGSA_DUMP_FILE, lineno)));
                                477                 :                :         }
                                478                 :                :     }
                                479                 :                : 
                                480                 :                :     /*
                                481                 :                :      * Parsing succeeded. Apply everything to shared memory.
                                482                 :                :      *
                                483                 :                :      * At this point, we know that the file we just read is fully valid, but
                                484                 :                :      * it's still possible for this to fail if, for example, DSA memory cannot
                                485                 :                :      * be allocated. If that happens, the worker will die, the postmaster will
                                486                 :                :      * eventually restart it, and we'll try again after clearing any data that
                                487                 :                :      * we did manage to put into shared memory. (Note that we call
                                488                 :                :      * pgsa_reset_all_stashes() at the top of this function.)
                                489                 :                :      */
  212 rhaas@postgresql.org      490                 :CBC           2 :     pgsa_restore_stashes(saved_stashes);
                                491                 :              2 :     pgsa_restore_entries(entries, num_entries);
                                492                 :                : 
                                493                 :                :     /* Hooray, it worked! Notify the user. */
                                494         [ +  - ]:              2 :     ereport(LOG,
                                495                 :                :             (errmsg("loaded %d advice stashes and %d entries from \"%s\"",
                                496                 :                :                     num_stashes, num_entries, PGSA_DUMP_FILE)));
                                497                 :                : 
                                498                 :                :     /* Clean up. */
                                499                 :              2 :     MemoryContextSwitchTo(oldcxt);
                                500                 :              2 :     MemoryContextDelete(tmpcxt);
                                501                 :                : }
                                502                 :                : 
                                503                 :                : /*
                                504                 :                :  * Write all advice stash data to disk.
                                505                 :                :  *
                                506                 :                :  * The file format is a simple TSV with a line-type prefix:
                                507                 :                :  *   stash\tstash_name
                                508                 :                :  *   entry\tstash_name\tquery_id\tadvice_string
                                509                 :                :  */
                                510                 :                : static void
                                511                 :              4 : pgsa_write_to_disk(void)
                                512                 :                : {
                                513                 :              4 :     pgsa_writer_context wctx = {0};
                                514                 :                :     MemoryContext tmpcxt;
                                515                 :                :     MemoryContext oldcxt;
                                516                 :                : 
                                517         [ -  + ]:              4 :     Assert(pgsa_entry_dshash != NULL);
                                518                 :                : 
                                519                 :                :     /* Use a temporary context so all allocations are freed at the end. */
                                520                 :              4 :     tmpcxt = AllocSetContextCreate(CurrentMemoryContext,
                                521                 :                :                                    "pg_stash_advice dump",
                                522                 :                :                                    ALLOCSET_DEFAULT_SIZES);
                                523                 :              4 :     oldcxt = MemoryContextSwitchTo(tmpcxt);
                                524                 :                : 
                                525                 :                :     /* Set up the writer context. */
                                526                 :              4 :     snprintf(wctx.pathname, MAXPGPATH, "%s.tmp", PGSA_DUMP_FILE);
                                527                 :              4 :     wctx.file = AllocateFile(wctx.pathname, "w");
                                528         [ -  + ]:              4 :     if (!wctx.file)
  212 rhaas@postgresql.org      529         [ #  # ]:UBC           0 :         ereport(ERROR,
                                530                 :                :                 (errcode_for_file_access(),
                                531                 :                :                  errmsg("could not open file \"%s\": %m", wctx.pathname)));
  212 rhaas@postgresql.org      532                 :CBC           4 :     wctx.nhash = pgsa_stash_name_table_create(tmpcxt, 64, NULL);
                                533                 :              4 :     initStringInfo(&wctx.buf);
                                534                 :                : 
                                535                 :                :     /* Write stash lines, then entry lines. */
                                536                 :              4 :     pgsa_write_stashes(&wctx);
                                537                 :              4 :     pgsa_write_entries(&wctx);
                                538                 :                : 
                                539                 :                :     /*
                                540                 :                :      * If nothing was written, remove both the temp file and any existing dump
                                541                 :                :      * file rather than installing a zero-length file.
                                542                 :                :      */
                                543         [ +  + ]:              4 :     if (wctx.nhash->members == 0)
                                544                 :                :     {
                                545         [ -  + ]:              2 :         ereport(DEBUG1,
                                546                 :                :                 errmsg("there are no advice stashes to save"));
                                547                 :              2 :         FreeFile(wctx.file);
                                548                 :              2 :         unlink(wctx.pathname);
                                549         [ +  + ]:              2 :         if (unlink(PGSA_DUMP_FILE) == 0)
                                550         [ -  + ]:              1 :             ereport(DEBUG1,
                                551                 :                :                     errmsg("removed \"%s\"", PGSA_DUMP_FILE));
                                552                 :                :     }
                                553                 :                :     else
                                554                 :                :     {
                                555         [ -  + ]:              2 :         if (FreeFile(wctx.file) != 0)
                                556                 :                :         {
  212 rhaas@postgresql.org      557                 :UBC           0 :             int         save_errno = errno;
                                558                 :                : 
                                559                 :              0 :             unlink(wctx.pathname);
                                560                 :              0 :             errno = save_errno;
                                561         [ #  # ]:              0 :             ereport(ERROR,
                                562                 :                :                     (errcode_for_file_access(),
                                563                 :                :                      errmsg("could not close file \"%s\": %m",
                                564                 :                :                             wctx.pathname)));
                                565                 :                :         }
  212 rhaas@postgresql.org      566                 :CBC           2 :         (void) durable_rename(wctx.pathname, PGSA_DUMP_FILE, ERROR);
                                567                 :                : 
                                568         [ +  - ]:              2 :         ereport(LOG,
                                569                 :                :                 errmsg("saved %d advice stashes and %d entries to \"%s\"",
                                570                 :                :                        (int) wctx.nhash->members, wctx.entries_written,
                                571                 :                :                        PGSA_DUMP_FILE));
                                572                 :                :     }
                                573                 :                : 
                                574                 :              4 :     MemoryContextSwitchTo(oldcxt);
                                575                 :              4 :     MemoryContextDelete(tmpcxt);
                                576                 :              4 : }
                                577                 :                : 
                                578                 :                : /*
                                579                 :                :  * Append the TSV-escaped form of str to buf.
                                580                 :                :  *
                                581                 :                :  * Backslash, tab, newline, and carriage return are escaped with backslash
                                582                 :                :  * sequences.  All other characters are passed through unchanged.
                                583                 :                :  */
                                584                 :                : static void
                                585                 :              8 : pgsa_append_tsv_escaped_string(StringInfo buf, const char *str)
                                586                 :                : {
                                587         [ +  + ]:            102 :     for (const char *p = str; *p != '\0'; p++)
                                588                 :                :     {
                                589   [ +  +  +  -  :             94 :         switch (*p)
                                                 + ]
                                590                 :                :         {
                                591                 :              2 :             case '\\':
                                592                 :              2 :                 appendStringInfoString(buf, "\\\\");
                                593                 :              2 :                 break;
                                594                 :              2 :             case '\t':
                                595                 :              2 :                 appendStringInfoString(buf, "\\t");
                                596                 :              2 :                 break;
                                597                 :              2 :             case '\n':
                                598                 :              2 :                 appendStringInfoString(buf, "\\n");
                                599                 :              2 :                 break;
  212 rhaas@postgresql.org      600                 :UBC           0 :             case '\r':
                                601                 :              0 :                 appendStringInfoString(buf, "\\r");
                                602                 :              0 :                 break;
  212 rhaas@postgresql.org      603                 :CBC          88 :             default:
                                604                 :             88 :                 appendStringInfoChar(buf, *p);
                                605                 :             88 :                 break;
                                606                 :                :         }
                                607                 :                :     }
                                608                 :              8 : }
                                609                 :                : 
                                610                 :                : /*
                                611                 :                :  * Extract the next tab-delimited field from *cursor.
                                612                 :                :  *
                                613                 :                :  * Returns a pointer to the field data; when there are no remaining fields,
                                614                 :                :  * returns NULL. For fields other than the last, the tab that terminates the
                                615                 :                :  * field is replaced by \0, so that the caller can always interpret the return
                                616                 :                :  * value as a C string.
                                617                 :                :  *
                                618                 :                :  * When this function is first called for a given line of text, *cursor should
                                619                 :                :  * point to the beginning of the line. On return, *cursor will have been
                                620                 :                :  * advanced past the tab that terminates the current field, or set to NULL if
                                621                 :                :  * we just returned the last field.
                                622                 :                :  */
                                623                 :                : static char *
                                624                 :             42 : pgsa_next_tsv_field(char **cursor)
                                625                 :                : {
                                626                 :             42 :     char       *start = *cursor;
                                627                 :                :     char       *p;
                                628                 :                : 
   58                           629         [ -  + ]:             42 :     if (start == NULL)
  212 rhaas@postgresql.org      630                 :UBC           0 :         return NULL;
                                631                 :                : 
   58 rhaas@postgresql.org      632                 :CBC          42 :     p = strchr(start, '\t');
                                633         [ +  + ]:             42 :     if (p != NULL)
                                634                 :                :     {
                                635                 :             29 :         *p = '\0';
                                636                 :             29 :         *cursor = p + 1;
                                637                 :                :     }
                                638                 :                :     else
                                639                 :             13 :         *cursor = NULL;
                                640                 :                : 
  212                           641                 :             42 :     return start;
                                642                 :                : }
                                643                 :                : 
                                644                 :                : /*
                                645                 :                :  * Insert entries into shared memory from the parsed entry array.
                                646                 :                :  */
                                647                 :                : static void
                                648                 :              2 : pgsa_restore_entries(pgsa_saved_entry *entries, int num_entries)
                                649                 :                : {
                                650                 :              2 :     LWLockAcquire(&pgsa_state->lock, LW_SHARED);
                                651         [ +  + ]:             10 :     for (int i = 0; i < num_entries; i++)
                                652                 :                :     {
                                653         [ -  + ]:              8 :         ereport(DEBUG2,
                                654                 :                :                 errmsg("restoring advice stash entry for \"%s\", query ID %" PRId64,
                                655                 :                :                        entries[i].stash_name, entries[i].queryId));
                                656                 :              8 :         pgsa_set_advice_string(entries[i].stash_name,
                                657                 :              8 :                                entries[i].queryId,
                                658                 :              8 :                                entries[i].advice_string);
                                659                 :                :     }
                                660                 :              2 :     LWLockRelease(&pgsa_state->lock);
                                661                 :              2 : }
                                662                 :                : 
                                663                 :                : /*
                                664                 :                :  * Create stashes in shared memory from the parsed stash hash table.
                                665                 :                :  */
                                666                 :                : static void
                                667                 :              2 : pgsa_restore_stashes(pgsa_saved_stash_table_hash *saved_stashes)
                                668                 :                : {
                                669                 :                :     pgsa_saved_stash_table_iterator iter;
                                670                 :                :     pgsa_saved_stash *s;
                                671                 :                : 
                                672                 :              2 :     LWLockAcquire(&pgsa_state->lock, LW_EXCLUSIVE);
                                673                 :              2 :     pgsa_saved_stash_table_start_iterate(saved_stashes, &iter);
                                674                 :              7 :     while ((s = pgsa_saved_stash_table_iterate(saved_stashes,
                                675         [ +  + ]:              7 :                                                &iter)) != NULL)
                                676                 :                :     {
                                677         [ -  + ]:              5 :         ereport(DEBUG2,
                                678                 :                :                 errmsg("restoring advice stash \"%s\"", s->name));
                                679                 :              5 :         pgsa_create_stash(s->name);
                                680                 :                :     }
                                681                 :              2 :     LWLockRelease(&pgsa_state->lock);
                                682                 :              2 : }
                                683                 :                : 
                                684                 :                : /*
                                685                 :                :  * Unescape a TSV field in place.
                                686                 :                :  *
                                687                 :                :  * Recognized escape sequences are \\, \t, \n, and \r.  A trailing backslash
                                688                 :                :  * or an unrecognized escape sequence is a syntax error.
                                689                 :                :  */
                                690                 :                : static void
                                691                 :              8 : pgsa_unescape_tsv_field(char *str, const char *filename, unsigned lineno)
                                692                 :                : {
                                693                 :              8 :     char       *src = str;
                                694                 :              8 :     char       *dst = str;
                                695                 :                : 
                                696         [ +  + ]:            102 :     while (*src != '\0')
                                697                 :                :     {
                                698                 :                :         /* Just pass through anything that's not a backslash-escape. */
                                699         [ +  + ]:             94 :         if (likely(*src != '\\'))
                                700                 :                :         {
                                701                 :             88 :             *dst++ = *src++;
                                702                 :             88 :             continue;
                                703                 :                :         }
                                704                 :                : 
                                705                 :                :         /* Check what sort of escape we've got. */
                                706   [ +  +  +  -  :              6 :         switch (src[1])
                                              -  - ]
                                707                 :                :         {
                                708                 :              2 :             case '\\':
                                709                 :              2 :                 *dst++ = '\\';
                                710                 :              2 :                 break;
                                711                 :              2 :             case 't':
                                712                 :              2 :                 *dst++ = '\t';
                                713                 :              2 :                 break;
                                714                 :              2 :             case 'n':
                                715                 :              2 :                 *dst++ = '\n';
                                716                 :              2 :                 break;
  212 rhaas@postgresql.org      717                 :UBC           0 :             case 'r':
                                718                 :              0 :                 *dst++ = '\r';
                                719                 :              0 :                 break;
                                720                 :              0 :             case '\0':
                                721         [ #  # ]:              0 :                 ereport(ERROR,
                                722                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                723                 :                :                          errmsg("syntax error in file \"%s\" line %u: trailing backslash",
                                724                 :                :                                 filename, lineno)));
                                725                 :                :                 break;
                                726                 :              0 :             default:
                                727         [ #  # ]:              0 :                 ereport(ERROR,
                                728                 :                :                         (errcode(ERRCODE_DATA_CORRUPTED),
                                729                 :                :                          errmsg("syntax error in file \"%s\" line %u: unrecognized escape \"\\%c\"",
                                730                 :                :                                 filename, lineno, src[1])));
                                731                 :                :                 break;
                                732                 :                :         }
                                733                 :                : 
                                734                 :                :         /* We consumed the backslash and the following character. */
  212 rhaas@postgresql.org      735                 :CBC           6 :         src += 2;
                                736                 :                :     }
                                737                 :              8 :     *dst = '\0';
                                738                 :              8 : }
                                739                 :                : 
                                740                 :                : /*
                                741                 :                :  * Write an entry line for each advice entry.
                                742                 :                :  */
                                743                 :                : static void
                                744                 :              4 : pgsa_write_entries(pgsa_writer_context *wctx)
                                745                 :                : {
                                746                 :                :     dshash_seq_status iter;
                                747                 :                :     pgsa_entry *entry;
                                748                 :                : 
                                749                 :              4 :     dshash_seq_init(&iter, pgsa_entry_dshash, false);
                                750         [ +  + ]:             12 :     while ((entry = dshash_seq_next(&iter)) != NULL)
                                751                 :                :     {
                                752                 :                :         pgsa_stash_name *n;
                                753                 :                :         char       *advice_string;
                                754                 :                : 
                                755         [ -  + ]:              8 :         if (entry->advice_string == InvalidDsaPointer)
  212 rhaas@postgresql.org      756                 :UBC           0 :             continue;
                                757                 :                : 
  212 rhaas@postgresql.org      758                 :CBC           8 :         n = pgsa_stash_name_table_lookup(wctx->nhash,
                                759                 :                :                                          entry->key.pgsa_stash_id);
                                760         [ -  + ]:              8 :         if (n == NULL)
  212 rhaas@postgresql.org      761                 :UBC           0 :             continue;           /* orphan entry, skip */
                                762                 :                : 
  212 rhaas@postgresql.org      763                 :CBC           8 :         advice_string = dsa_get_address(pgsa_dsa_area, entry->advice_string);
                                764                 :                : 
                                765                 :              8 :         resetStringInfo(&wctx->buf);
                                766                 :              8 :         appendStringInfo(&wctx->buf, "entry\t%s\t%" PRId64 "\t",
                                767                 :                :                          n->name, entry->key.queryId);
                                768                 :              8 :         pgsa_append_tsv_escaped_string(&wctx->buf, advice_string);
                                769                 :              8 :         appendStringInfoChar(&wctx->buf, '\n');
                                770                 :              8 :         fwrite(wctx->buf.data, 1, wctx->buf.len, wctx->file);
                                771         [ -  + ]:              8 :         if (ferror(wctx->file))
  212 rhaas@postgresql.org      772                 :UBC           0 :             pgsa_write_error(wctx);
  212 rhaas@postgresql.org      773                 :CBC           8 :         wctx->entries_written++;
                                774                 :                :     }
                                775                 :              4 :     dshash_seq_term(&iter);
                                776                 :              4 : }
                                777                 :                : 
                                778                 :                : /*
                                779                 :                :  * Clean up and report a write error.  Does not return.
                                780                 :                :  */
                                781                 :                : static void
  212 rhaas@postgresql.org      782                 :UBC           0 : pgsa_write_error(pgsa_writer_context *wctx)
                                783                 :                : {
                                784                 :              0 :     int         save_errno = errno;
                                785                 :                : 
                                786                 :              0 :     FreeFile(wctx->file);
                                787                 :              0 :     unlink(wctx->pathname);
                                788                 :              0 :     errno = save_errno;
                                789         [ #  # ]:              0 :     ereport(ERROR,
                                790                 :                :             (errcode_for_file_access(),
                                791                 :                :              errmsg("could not write to file \"%s\": %m", wctx->pathname)));
                                792                 :                : }
                                793                 :                : 
                                794                 :                : /*
                                795                 :                :  * Write a stash line for each advice stash, and populate the ID-to-name
                                796                 :                :  * hash table for use by pgsa_write_entries.
                                797                 :                :  */
                                798                 :                : static void
  212 rhaas@postgresql.org      799                 :CBC           4 : pgsa_write_stashes(pgsa_writer_context *wctx)
                                800                 :                : {
                                801                 :                :     dshash_seq_status iter;
                                802                 :                :     pgsa_stash *stash;
                                803                 :                : 
                                804                 :              4 :     dshash_seq_init(&iter, pgsa_stash_dshash, false);
                                805         [ +  + ]:              9 :     while ((stash = dshash_seq_next(&iter)) != NULL)
                                806                 :                :     {
                                807                 :                :         pgsa_stash_name *n;
                                808                 :                :         bool        found;
                                809                 :                : 
                                810                 :              5 :         n = pgsa_stash_name_table_insert(wctx->nhash, stash->pgsa_stash_id,
                                811                 :                :                                          &found);
                                812         [ -  + ]:              5 :         Assert(!found);
                                813                 :              5 :         n->name = pstrdup(stash->name);
                                814                 :                : 
                                815                 :              5 :         resetStringInfo(&wctx->buf);
                                816                 :              5 :         appendStringInfo(&wctx->buf, "stash\t%s\n", n->name);
                                817                 :              5 :         fwrite(wctx->buf.data, 1, wctx->buf.len, wctx->file);
                                818         [ -  + ]:              5 :         if (ferror(wctx->file))
  212 rhaas@postgresql.org      819                 :UBC           0 :             pgsa_write_error(wctx);
                                820                 :                :     }
  212 rhaas@postgresql.org      821                 :CBC           4 :     dshash_seq_term(&iter);
                                822                 :              4 : }
        

Generated by: LCOV version 2.0-1