LCOV - code coverage report
Current view: top level - contrib/pg_stat_statements - pg_stat_statements.c (source / functions) Hit Total Coverage
Test: PostgreSQL 12beta2 Lines: 667 1121 59.5 %
Date: 2019-06-19 14:06:47 Functions: 33 42 78.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : /*-------------------------------------------------------------------------
       2             :  *
       3             :  * pg_stat_statements.c
       4             :  *      Track statement execution times across a whole database cluster.
       5             :  *
       6             :  * Execution costs are totalled for each distinct source query, and kept in
       7             :  * a shared hashtable.  (We track only as many distinct queries as will fit
       8             :  * in the designated amount of shared memory.)
       9             :  *
      10             :  * As of Postgres 9.2, this module normalizes query entries.  Normalization
      11             :  * is a process whereby similar queries, typically differing only in their
      12             :  * constants (though the exact rules are somewhat more subtle than that) are
      13             :  * recognized as equivalent, and are tracked as a single entry.  This is
      14             :  * particularly useful for non-prepared queries.
      15             :  *
      16             :  * Normalization is implemented by fingerprinting queries, selectively
      17             :  * serializing those fields of each query tree's nodes that are judged to be
      18             :  * essential to the query.  This is referred to as a query jumble.  This is
      19             :  * distinct from a regular serialization in that various extraneous
      20             :  * information is ignored as irrelevant or not essential to the query, such
      21             :  * as the collations of Vars and, most notably, the values of constants.
      22             :  *
      23             :  * This jumble is acquired at the end of parse analysis of each query, and
      24             :  * a 64-bit hash of it is stored into the query's Query.queryId field.
      25             :  * The server then copies this value around, making it available in plan
      26             :  * tree(s) generated from the query.  The executor can then use this value
      27             :  * to blame query costs on the proper queryId.
      28             :  *
      29             :  * To facilitate presenting entries to users, we create "representative" query
      30             :  * strings in which constants are replaced with parameter symbols ($n), to
      31             :  * make it clearer what a normalized entry can represent.  To save on shared
      32             :  * memory, and to avoid having to truncate oversized query strings, we store
      33             :  * these strings in a temporary external query-texts file.  Offsets into this
      34             :  * file are kept in shared memory.
      35             :  *
      36             :  * Note about locking issues: to create or delete an entry in the shared
      37             :  * hashtable, one must hold pgss->lock exclusively.  Modifying any field
      38             :  * in an entry except the counters requires the same.  To look up an entry,
      39             :  * one must hold the lock shared.  To read or update the counters within
      40             :  * an entry, one must hold the lock shared or exclusive (so the entry doesn't
      41             :  * disappear!) and also take the entry's mutex spinlock.
      42             :  * The shared state variable pgss->extent (the next free spot in the external
      43             :  * query-text file) should be accessed only while holding either the
      44             :  * pgss->mutex spinlock, or exclusive lock on pgss->lock.  We use the mutex to
      45             :  * allow reserving file space while holding only shared lock on pgss->lock.
      46             :  * Rewriting the entire external query-text file, eg for garbage collection,
      47             :  * requires holding pgss->lock exclusively; this allows individual entries
      48             :  * in the file to be read or written while holding only shared lock.
      49             :  *
      50             :  *
      51             :  * Copyright (c) 2008-2019, PostgreSQL Global Development Group
      52             :  *
      53             :  * IDENTIFICATION
      54             :  *    contrib/pg_stat_statements/pg_stat_statements.c
      55             :  *
      56             :  *-------------------------------------------------------------------------
      57             :  */
      58             : #include "postgres.h"
      59             : 
      60             : #include <math.h>
      61             : #include <sys/stat.h>
      62             : #include <unistd.h>
      63             : 
      64             : #include "catalog/pg_authid.h"
      65             : #include "executor/instrument.h"
      66             : #include "funcapi.h"
      67             : #include "mb/pg_wchar.h"
      68             : #include "miscadmin.h"
      69             : #include "parser/analyze.h"
      70             : #include "parser/parsetree.h"
      71             : #include "parser/scanner.h"
      72             : #include "parser/scansup.h"
      73             : #include "pgstat.h"
      74             : #include "storage/fd.h"
      75             : #include "storage/ipc.h"
      76             : #include "storage/spin.h"
      77             : #include "tcop/utility.h"
      78             : #include "utils/acl.h"
      79             : #include "utils/builtins.h"
      80             : #include "utils/hashutils.h"
      81             : #include "utils/memutils.h"
      82             : 
      83           4 : PG_MODULE_MAGIC;
      84             : 
      85             : /* Location of permanent stats file (valid when database is shut down) */
      86             : #define PGSS_DUMP_FILE  PGSTAT_STAT_PERMANENT_DIRECTORY "/pg_stat_statements.stat"
      87             : 
      88             : /*
      89             :  * Location of external query text file.  We don't keep it in the core
      90             :  * system's stats_temp_directory.  The core system can safely use that GUC
      91             :  * setting, because the statistics collector temp file paths are set only once
      92             :  * as part of changing the GUC, but pg_stat_statements has no way of avoiding
      93             :  * race conditions.  Besides, we only expect modest, infrequent I/O for query
      94             :  * strings, so placing the file on a faster filesystem is not compelling.
      95             :  */
      96             : #define PGSS_TEXT_FILE  PG_STAT_TMP_DIR "/pgss_query_texts.stat"
      97             : 
      98             : /* Magic number identifying the stats file format */
      99             : static const uint32 PGSS_FILE_HEADER = 0x20171004;
     100             : 
     101             : /* PostgreSQL major version number, changes in which invalidate all entries */
     102             : static const uint32 PGSS_PG_MAJOR_VERSION = PG_VERSION_NUM / 100;
     103             : 
     104             : /* XXX: Should USAGE_EXEC reflect execution time and/or buffer usage? */
     105             : #define USAGE_EXEC(duration)    (1.0)
     106             : #define USAGE_INIT              (1.0)   /* including initial planning */
     107             : #define ASSUMED_MEDIAN_INIT     (10.0)  /* initial assumed median usage */
     108             : #define ASSUMED_LENGTH_INIT     1024    /* initial assumed mean query length */
     109             : #define USAGE_DECREASE_FACTOR   (0.99)  /* decreased every entry_dealloc */
     110             : #define STICKY_DECREASE_FACTOR  (0.50)  /* factor for sticky entries */
     111             : #define USAGE_DEALLOC_PERCENT   5   /* free this % of entries at once */
     112             : 
     113             : #define JUMBLE_SIZE             1024    /* query serialization buffer size */
     114             : 
     115             : /*
     116             :  * Extension version number, for supporting older extension versions' objects
     117             :  */
     118             : typedef enum pgssVersion
     119             : {
     120             :     PGSS_V1_0 = 0,
     121             :     PGSS_V1_1,
     122             :     PGSS_V1_2,
     123             :     PGSS_V1_3
     124             : } pgssVersion;
     125             : 
     126             : /*
     127             :  * Hashtable key that defines the identity of a hashtable entry.  We separate
     128             :  * queries by user and by database even if they are otherwise identical.
     129             :  *
     130             :  * Right now, this structure contains no padding.  If you add any, make sure
     131             :  * to teach pgss_store() to zero the padding bytes.  Otherwise, things will
     132             :  * break, because pgss_hash is created using HASH_BLOBS, and thus tag_hash
     133             :  * is used to hash this.
     134             :  */
     135             : typedef struct pgssHashKey
     136             : {
     137             :     Oid         userid;         /* user OID */
     138             :     Oid         dbid;           /* database OID */
     139             :     uint64      queryid;        /* query identifier */
     140             : } pgssHashKey;
     141             : 
     142             : /*
     143             :  * The actual stats counters kept within pgssEntry.
     144             :  */
     145             : typedef struct Counters
     146             : {
     147             :     int64       calls;          /* # of times executed */
     148             :     double      total_time;     /* total execution time, in msec */
     149             :     double      min_time;       /* minimum execution time in msec */
     150             :     double      max_time;       /* maximum execution time in msec */
     151             :     double      mean_time;      /* mean execution time in msec */
     152             :     double      sum_var_time;   /* sum of variances in execution time in msec */
     153             :     int64       rows;           /* total # of retrieved or affected rows */
     154             :     int64       shared_blks_hit;    /* # of shared buffer hits */
     155             :     int64       shared_blks_read;   /* # of shared disk blocks read */
     156             :     int64       shared_blks_dirtied;    /* # of shared disk blocks dirtied */
     157             :     int64       shared_blks_written;    /* # of shared disk blocks written */
     158             :     int64       local_blks_hit; /* # of local buffer hits */
     159             :     int64       local_blks_read;    /* # of local disk blocks read */
     160             :     int64       local_blks_dirtied; /* # of local disk blocks dirtied */
     161             :     int64       local_blks_written; /* # of local disk blocks written */
     162             :     int64       temp_blks_read; /* # of temp blocks read */
     163             :     int64       temp_blks_written;  /* # of temp blocks written */
     164             :     double      blk_read_time;  /* time spent reading, in msec */
     165             :     double      blk_write_time; /* time spent writing, in msec */
     166             :     double      usage;          /* usage factor */
     167             : } Counters;
     168             : 
     169             : /*
     170             :  * Statistics per statement
     171             :  *
     172             :  * Note: in event of a failure in garbage collection of the query text file,
     173             :  * we reset query_offset to zero and query_len to -1.  This will be seen as
     174             :  * an invalid state by qtext_fetch().
     175             :  */
     176             : typedef struct pgssEntry
     177             : {
     178             :     pgssHashKey key;            /* hash key of entry - MUST BE FIRST */
     179             :     Counters    counters;       /* the statistics for this query */
     180             :     Size        query_offset;   /* query text offset in external file */
     181             :     int         query_len;      /* # of valid bytes in query string, or -1 */
     182             :     int         encoding;       /* query text encoding */
     183             :     slock_t     mutex;          /* protects the counters only */
     184             : } pgssEntry;
     185             : 
     186             : /*
     187             :  * Global shared state
     188             :  */
     189             : typedef struct pgssSharedState
     190             : {
     191             :     LWLock     *lock;           /* protects hashtable search/modification */
     192             :     double      cur_median_usage;   /* current median usage in hashtable */
     193             :     Size        mean_query_len; /* current mean entry text length */
     194             :     slock_t     mutex;          /* protects following fields only: */
     195             :     Size        extent;         /* current extent of query file */
     196             :     int         n_writers;      /* number of active writers to query file */
     197             :     int         gc_count;       /* query file garbage collection cycle count */
     198             : } pgssSharedState;
     199             : 
     200             : /*
     201             :  * Struct for tracking locations/lengths of constants during normalization
     202             :  */
     203             : typedef struct pgssLocationLen
     204             : {
     205             :     int         location;       /* start offset in query text */
     206             :     int         length;         /* length in bytes, or -1 to ignore */
     207             : } pgssLocationLen;
     208             : 
     209             : /*
     210             :  * Working state for computing a query jumble and producing a normalized
     211             :  * query string
     212             :  */
     213             : typedef struct pgssJumbleState
     214             : {
     215             :     /* Jumble of current query tree */
     216             :     unsigned char *jumble;
     217             : 
     218             :     /* Number of bytes used in jumble[] */
     219             :     Size        jumble_len;
     220             : 
     221             :     /* Array of locations of constants that should be removed */
     222             :     pgssLocationLen *clocations;
     223             : 
     224             :     /* Allocated length of clocations array */
     225             :     int         clocations_buf_size;
     226             : 
     227             :     /* Current number of valid entries in clocations array */
     228             :     int         clocations_count;
     229             : 
     230             :     /* highest Param id we've seen, in order to start normalization correctly */
     231             :     int         highest_extern_param_id;
     232             : } pgssJumbleState;
     233             : 
     234             : /*---- Local variables ----*/
     235             : 
     236             : /* Current nesting depth of ExecutorRun+ProcessUtility calls */
     237             : static int  nested_level = 0;
     238             : 
     239             : /* Saved hook values in case of unload */
     240             : static shmem_startup_hook_type prev_shmem_startup_hook = NULL;
     241             : static post_parse_analyze_hook_type prev_post_parse_analyze_hook = NULL;
     242             : static ExecutorStart_hook_type prev_ExecutorStart = NULL;
     243             : static ExecutorRun_hook_type prev_ExecutorRun = NULL;
     244             : static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
     245             : static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
     246             : static ProcessUtility_hook_type prev_ProcessUtility = NULL;
     247             : 
     248             : /* Links to shared memory state */
     249             : static pgssSharedState *pgss = NULL;
     250             : static HTAB *pgss_hash = NULL;
     251             : 
     252             : /*---- GUC variables ----*/
     253             : 
     254             : typedef enum
     255             : {
     256             :     PGSS_TRACK_NONE,            /* track no statements */
     257             :     PGSS_TRACK_TOP,             /* only top level statements */
     258             :     PGSS_TRACK_ALL              /* all statements, including nested ones */
     259             : }           PGSSTrackLevel;
     260             : 
     261             : static const struct config_enum_entry track_options[] =
     262             : {
     263             :     {"none", PGSS_TRACK_NONE, false},
     264             :     {"top", PGSS_TRACK_TOP, false},
     265             :     {"all", PGSS_TRACK_ALL, false},
     266             :     {NULL, 0, false}
     267             : };
     268             : 
     269             : static int  pgss_max;           /* max # statements to track */
     270             : static int  pgss_track;         /* tracking level */
     271             : static bool pgss_track_utility; /* whether to track utility commands */
     272             : static bool pgss_save;          /* whether to save stats across shutdown */
     273             : 
     274             : 
     275             : #define pgss_enabled() \
     276             :     (pgss_track == PGSS_TRACK_ALL || \
     277             :     (pgss_track == PGSS_TRACK_TOP && nested_level == 0))
     278             : 
     279             : #define record_gc_qtexts() \
     280             :     do { \
     281             :         volatile pgssSharedState *s = (volatile pgssSharedState *) pgss; \
     282             :         SpinLockAcquire(&s->mutex); \
     283             :         s->gc_count++; \
     284             :         SpinLockRelease(&s->mutex); \
     285             :     } while(0)
     286             : 
     287             : /*---- Function declarations ----*/
     288             : 
     289             : void        _PG_init(void);
     290             : void        _PG_fini(void);
     291             : 
     292           4 : PG_FUNCTION_INFO_V1(pg_stat_statements_reset);
     293           6 : PG_FUNCTION_INFO_V1(pg_stat_statements_reset_1_7);
     294           0 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_2);
     295           6 : PG_FUNCTION_INFO_V1(pg_stat_statements_1_3);
     296           0 : PG_FUNCTION_INFO_V1(pg_stat_statements);
     297             : 
     298             : static void pgss_shmem_startup(void);
     299             : static void pgss_shmem_shutdown(int code, Datum arg);
     300             : static void pgss_post_parse_analyze(ParseState *pstate, Query *query);
     301             : static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags);
     302             : static void pgss_ExecutorRun(QueryDesc *queryDesc,
     303             :                              ScanDirection direction,
     304             :                              uint64 count, bool execute_once);
     305             : static void pgss_ExecutorFinish(QueryDesc *queryDesc);
     306             : static void pgss_ExecutorEnd(QueryDesc *queryDesc);
     307             : static void pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
     308             :                                 ProcessUtilityContext context, ParamListInfo params,
     309             :                                 QueryEnvironment *queryEnv,
     310             :                                 DestReceiver *dest, char *completionTag);
     311             : static uint64 pgss_hash_string(const char *str, int len);
     312             : static void pgss_store(const char *query, uint64 queryId,
     313             :                        int query_location, int query_len,
     314             :                        double total_time, uint64 rows,
     315             :                        const BufferUsage *bufusage,
     316             :                        pgssJumbleState *jstate);
     317             : static void pg_stat_statements_internal(FunctionCallInfo fcinfo,
     318             :                                         pgssVersion api_version,
     319             :                                         bool showtext);
     320             : static Size pgss_memsize(void);
     321             : static pgssEntry *entry_alloc(pgssHashKey *key, Size query_offset, int query_len,
     322             :                               int encoding, bool sticky);
     323             : static void entry_dealloc(void);
     324             : static bool qtext_store(const char *query, int query_len,
     325             :                         Size *query_offset, int *gc_count);
     326             : static char *qtext_load_file(Size *buffer_size);
     327             : static char *qtext_fetch(Size query_offset, int query_len,
     328             :                          char *buffer, Size buffer_size);
     329             : static bool need_gc_qtexts(void);
     330             : static void gc_qtexts(void);
     331             : static void entry_reset(Oid userid, Oid dbid, uint64 queryid);
     332             : static void AppendJumble(pgssJumbleState *jstate,
     333             :                          const unsigned char *item, Size size);
     334             : static void JumbleQuery(pgssJumbleState *jstate, Query *query);
     335             : static void JumbleRangeTable(pgssJumbleState *jstate, List *rtable);
     336             : static void JumbleExpr(pgssJumbleState *jstate, Node *node);
     337             : static void RecordConstLocation(pgssJumbleState *jstate, int location);
     338             : static char *generate_normalized_query(pgssJumbleState *jstate, const char *query,
     339             :                                        int query_loc, int *query_len_p, int encoding);
     340             : static void fill_in_constant_lengths(pgssJumbleState *jstate, const char *query,
     341             :                                      int query_loc);
     342             : static int  comp_location(const void *a, const void *b);
     343             : 
     344             : 
     345             : /*
     346             :  * Module load callback
     347             :  */
     348             : void
     349           4 : _PG_init(void)
     350             : {
     351             :     /*
     352             :      * In order to create our shared memory area, we have to be loaded via
     353             :      * shared_preload_libraries.  If not, fall out without hooking into any of
     354             :      * the main system.  (We don't throw error here because it seems useful to
     355             :      * allow the pg_stat_statements functions to be created even when the
     356             :      * module isn't active.  The functions must protect themselves against
     357             :      * being called then, however.)
     358             :      */
     359           4 :     if (!process_shared_preload_libraries_in_progress)
     360           2 :         return;
     361             : 
     362             :     /*
     363             :      * Define (or redefine) custom GUC variables.
     364             :      */
     365           2 :     DefineCustomIntVariable("pg_stat_statements.max",
     366             :                             "Sets the maximum number of statements tracked by pg_stat_statements.",
     367             :                             NULL,
     368             :                             &pgss_max,
     369             :                             5000,
     370             :                             100,
     371             :                             INT_MAX,
     372             :                             PGC_POSTMASTER,
     373             :                             0,
     374             :                             NULL,
     375             :                             NULL,
     376             :                             NULL);
     377             : 
     378           2 :     DefineCustomEnumVariable("pg_stat_statements.track",
     379             :                              "Selects which statements are tracked by pg_stat_statements.",
     380             :                              NULL,
     381             :                              &pgss_track,
     382             :                              PGSS_TRACK_TOP,
     383             :                              track_options,
     384             :                              PGC_SUSET,
     385             :                              0,
     386             :                              NULL,
     387             :                              NULL,
     388             :                              NULL);
     389             : 
     390           2 :     DefineCustomBoolVariable("pg_stat_statements.track_utility",
     391             :                              "Selects whether utility commands are tracked by pg_stat_statements.",
     392             :                              NULL,
     393             :                              &pgss_track_utility,
     394             :                              true,
     395             :                              PGC_SUSET,
     396             :                              0,
     397             :                              NULL,
     398             :                              NULL,
     399             :                              NULL);
     400             : 
     401           2 :     DefineCustomBoolVariable("pg_stat_statements.save",
     402             :                              "Save pg_stat_statements statistics across server shutdowns.",
     403             :                              NULL,
     404             :                              &pgss_save,
     405             :                              true,
     406             :                              PGC_SIGHUP,
     407             :                              0,
     408             :                              NULL,
     409             :                              NULL,
     410             :                              NULL);
     411             : 
     412           2 :     EmitWarningsOnPlaceholders("pg_stat_statements");
     413             : 
     414             :     /*
     415             :      * Request additional shared resources.  (These are no-ops if we're not in
     416             :      * the postmaster process.)  We'll allocate or attach to the shared
     417             :      * resources in pgss_shmem_startup().
     418             :      */
     419           2 :     RequestAddinShmemSpace(pgss_memsize());
     420           2 :     RequestNamedLWLockTranche("pg_stat_statements", 1);
     421             : 
     422             :     /*
     423             :      * Install hooks.
     424             :      */
     425           2 :     prev_shmem_startup_hook = shmem_startup_hook;
     426           2 :     shmem_startup_hook = pgss_shmem_startup;
     427           2 :     prev_post_parse_analyze_hook = post_parse_analyze_hook;
     428           2 :     post_parse_analyze_hook = pgss_post_parse_analyze;
     429           2 :     prev_ExecutorStart = ExecutorStart_hook;
     430           2 :     ExecutorStart_hook = pgss_ExecutorStart;
     431           2 :     prev_ExecutorRun = ExecutorRun_hook;
     432           2 :     ExecutorRun_hook = pgss_ExecutorRun;
     433           2 :     prev_ExecutorFinish = ExecutorFinish_hook;
     434           2 :     ExecutorFinish_hook = pgss_ExecutorFinish;
     435           2 :     prev_ExecutorEnd = ExecutorEnd_hook;
     436           2 :     ExecutorEnd_hook = pgss_ExecutorEnd;
     437           2 :     prev_ProcessUtility = ProcessUtility_hook;
     438           2 :     ProcessUtility_hook = pgss_ProcessUtility;
     439             : }
     440             : 
     441             : /*
     442             :  * Module unload callback
     443             :  */
     444             : void
     445           0 : _PG_fini(void)
     446             : {
     447             :     /* Uninstall hooks. */
     448           0 :     shmem_startup_hook = prev_shmem_startup_hook;
     449           0 :     post_parse_analyze_hook = prev_post_parse_analyze_hook;
     450           0 :     ExecutorStart_hook = prev_ExecutorStart;
     451           0 :     ExecutorRun_hook = prev_ExecutorRun;
     452           0 :     ExecutorFinish_hook = prev_ExecutorFinish;
     453           0 :     ExecutorEnd_hook = prev_ExecutorEnd;
     454           0 :     ProcessUtility_hook = prev_ProcessUtility;
     455           0 : }
     456             : 
     457             : /*
     458             :  * shmem_startup hook: allocate or attach to shared memory,
     459             :  * then load any pre-existing statistics from file.
     460             :  * Also create and load the query-texts file, which is expected to exist
     461             :  * (even if empty) while the module is enabled.
     462             :  */
     463             : static void
     464           2 : pgss_shmem_startup(void)
     465             : {
     466             :     bool        found;
     467             :     HASHCTL     info;
     468           2 :     FILE       *file = NULL;
     469           2 :     FILE       *qfile = NULL;
     470             :     uint32      header;
     471             :     int32       num;
     472             :     int32       pgver;
     473             :     int32       i;
     474             :     int         buffer_size;
     475           2 :     char       *buffer = NULL;
     476             : 
     477           2 :     if (prev_shmem_startup_hook)
     478           0 :         prev_shmem_startup_hook();
     479             : 
     480             :     /* reset in case this is a restart within the postmaster */
     481           2 :     pgss = NULL;
     482           2 :     pgss_hash = NULL;
     483             : 
     484             :     /*
     485             :      * Create or attach to the shared memory state, including hash table
     486             :      */
     487           2 :     LWLockAcquire(AddinShmemInitLock, LW_EXCLUSIVE);
     488             : 
     489           2 :     pgss = ShmemInitStruct("pg_stat_statements",
     490             :                            sizeof(pgssSharedState),
     491             :                            &found);
     492             : 
     493           2 :     if (!found)
     494             :     {
     495             :         /* First time through ... */
     496           2 :         pgss->lock = &(GetNamedLWLockTranche("pg_stat_statements"))->lock;
     497           2 :         pgss->cur_median_usage = ASSUMED_MEDIAN_INIT;
     498           2 :         pgss->mean_query_len = ASSUMED_LENGTH_INIT;
     499           2 :         SpinLockInit(&pgss->mutex);
     500           2 :         pgss->extent = 0;
     501           2 :         pgss->n_writers = 0;
     502           2 :         pgss->gc_count = 0;
     503             :     }
     504             : 
     505           2 :     memset(&info, 0, sizeof(info));
     506           2 :     info.keysize = sizeof(pgssHashKey);
     507           2 :     info.entrysize = sizeof(pgssEntry);
     508           2 :     pgss_hash = ShmemInitHash("pg_stat_statements hash",
     509             :                               pgss_max, pgss_max,
     510             :                               &info,
     511             :                               HASH_ELEM | HASH_BLOBS);
     512             : 
     513           2 :     LWLockRelease(AddinShmemInitLock);
     514             : 
     515             :     /*
     516             :      * If we're in the postmaster (or a standalone backend...), set up a shmem
     517             :      * exit hook to dump the statistics to disk.
     518             :      */
     519           2 :     if (!IsUnderPostmaster)
     520           2 :         on_shmem_exit(pgss_shmem_shutdown, (Datum) 0);
     521             : 
     522             :     /*
     523             :      * Done if some other process already completed our initialization.
     524             :      */
     525           2 :     if (found)
     526           2 :         return;
     527             : 
     528             :     /*
     529             :      * Note: we don't bother with locks here, because there should be no other
     530             :      * processes running when this code is reached.
     531             :      */
     532             : 
     533             :     /* Unlink query text file possibly left over from crash */
     534           2 :     unlink(PGSS_TEXT_FILE);
     535             : 
     536             :     /* Allocate new query text temp file */
     537           2 :     qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
     538           2 :     if (qfile == NULL)
     539           0 :         goto write_error;
     540             : 
     541             :     /*
     542             :      * If we were told not to load old statistics, we're done.  (Note we do
     543             :      * not try to unlink any old dump file in this case.  This seems a bit
     544             :      * questionable but it's the historical behavior.)
     545             :      */
     546           2 :     if (!pgss_save)
     547             :     {
     548           0 :         FreeFile(qfile);
     549           0 :         return;
     550             :     }
     551             : 
     552             :     /*
     553             :      * Attempt to load old statistics from the dump file.
     554             :      */
     555           2 :     file = AllocateFile(PGSS_DUMP_FILE, PG_BINARY_R);
     556           2 :     if (file == NULL)
     557             :     {
     558           2 :         if (errno != ENOENT)
     559           0 :             goto read_error;
     560             :         /* No existing persisted stats file, so we're done */
     561           2 :         FreeFile(qfile);
     562           2 :         return;
     563             :     }
     564             : 
     565           0 :     buffer_size = 2048;
     566           0 :     buffer = (char *) palloc(buffer_size);
     567             : 
     568           0 :     if (fread(&header, sizeof(uint32), 1, file) != 1 ||
     569           0 :         fread(&pgver, sizeof(uint32), 1, file) != 1 ||
     570           0 :         fread(&num, sizeof(int32), 1, file) != 1)
     571             :         goto read_error;
     572             : 
     573           0 :     if (header != PGSS_FILE_HEADER ||
     574           0 :         pgver != PGSS_PG_MAJOR_VERSION)
     575             :         goto data_error;
     576             : 
     577           0 :     for (i = 0; i < num; i++)
     578             :     {
     579             :         pgssEntry   temp;
     580             :         pgssEntry  *entry;
     581             :         Size        query_offset;
     582             : 
     583           0 :         if (fread(&temp, sizeof(pgssEntry), 1, file) != 1)
     584           0 :             goto read_error;
     585             : 
     586             :         /* Encoding is the only field we can easily sanity-check */
     587           0 :         if (!PG_VALID_BE_ENCODING(temp.encoding))
     588             :             goto data_error;
     589             : 
     590             :         /* Resize buffer as needed */
     591           0 :         if (temp.query_len >= buffer_size)
     592             :         {
     593           0 :             buffer_size = Max(buffer_size * 2, temp.query_len + 1);
     594           0 :             buffer = repalloc(buffer, buffer_size);
     595             :         }
     596             : 
     597           0 :         if (fread(buffer, 1, temp.query_len + 1, file) != temp.query_len + 1)
     598           0 :             goto read_error;
     599             : 
     600             :         /* Should have a trailing null, but let's make sure */
     601           0 :         buffer[temp.query_len] = '\0';
     602             : 
     603             :         /* Skip loading "sticky" entries */
     604           0 :         if (temp.counters.calls == 0)
     605           0 :             continue;
     606             : 
     607             :         /* Store the query text */
     608           0 :         query_offset = pgss->extent;
     609           0 :         if (fwrite(buffer, 1, temp.query_len + 1, qfile) != temp.query_len + 1)
     610           0 :             goto write_error;
     611           0 :         pgss->extent += temp.query_len + 1;
     612             : 
     613             :         /* make the hashtable entry (discards old entries if too many) */
     614           0 :         entry = entry_alloc(&temp.key, query_offset, temp.query_len,
     615             :                             temp.encoding,
     616             :                             false);
     617             : 
     618             :         /* copy in the actual stats */
     619           0 :         entry->counters = temp.counters;
     620             :     }
     621             : 
     622           0 :     pfree(buffer);
     623           0 :     FreeFile(file);
     624           0 :     FreeFile(qfile);
     625             : 
     626             :     /*
     627             :      * Remove the persisted stats file so it's not included in
     628             :      * backups/replication standbys, etc.  A new file will be written on next
     629             :      * shutdown.
     630             :      *
     631             :      * Note: it's okay if the PGSS_TEXT_FILE is included in a basebackup,
     632             :      * because we remove that file on startup; it acts inversely to
     633             :      * PGSS_DUMP_FILE, in that it is only supposed to be around when the
     634             :      * server is running, whereas PGSS_DUMP_FILE is only supposed to be around
     635             :      * when the server is not running.  Leaving the file creates no danger of
     636             :      * a newly restored database having a spurious record of execution costs,
     637             :      * which is what we're really concerned about here.
     638             :      */
     639           0 :     unlink(PGSS_DUMP_FILE);
     640             : 
     641           0 :     return;
     642             : 
     643             : read_error:
     644           0 :     ereport(LOG,
     645             :             (errcode_for_file_access(),
     646             :              errmsg("could not read file \"%s\": %m",
     647             :                     PGSS_DUMP_FILE)));
     648           0 :     goto fail;
     649             : data_error:
     650           0 :     ereport(LOG,
     651             :             (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
     652             :              errmsg("ignoring invalid data in file \"%s\"",
     653             :                     PGSS_DUMP_FILE)));
     654           0 :     goto fail;
     655             : write_error:
     656           0 :     ereport(LOG,
     657             :             (errcode_for_file_access(),
     658             :              errmsg("could not write file \"%s\": %m",
     659             :                     PGSS_TEXT_FILE)));
     660             : fail:
     661           0 :     if (buffer)
     662           0 :         pfree(buffer);
     663           0 :     if (file)
     664           0 :         FreeFile(file);
     665           0 :     if (qfile)
     666           0 :         FreeFile(qfile);
     667             :     /* If possible, throw away the bogus file; ignore any error */
     668           0 :     unlink(PGSS_DUMP_FILE);
     669             : 
     670             :     /*
     671             :      * Don't unlink PGSS_TEXT_FILE here; it should always be around while the
     672             :      * server is running with pg_stat_statements enabled
     673             :      */
     674             : }
     675             : 
     676             : /*
     677             :  * shmem_shutdown hook: Dump statistics into file.
     678             :  *
     679             :  * Note: we don't bother with acquiring lock, because there should be no
     680             :  * other processes running when this is called.
     681             :  */
     682             : static void
     683           2 : pgss_shmem_shutdown(int code, Datum arg)
     684             : {
     685             :     FILE       *file;
     686           2 :     char       *qbuffer = NULL;
     687           2 :     Size        qbuffer_size = 0;
     688             :     HASH_SEQ_STATUS hash_seq;
     689             :     int32       num_entries;
     690             :     pgssEntry  *entry;
     691             : 
     692             :     /* Don't try to dump during a crash. */
     693           2 :     if (code)
     694           2 :         return;
     695             : 
     696             :     /* Safety check ... shouldn't get here unless shmem is set up. */
     697           2 :     if (!pgss || !pgss_hash)
     698           0 :         return;
     699             : 
     700             :     /* Don't dump if told not to. */
     701           2 :     if (!pgss_save)
     702           0 :         return;
     703             : 
     704           2 :     file = AllocateFile(PGSS_DUMP_FILE ".tmp", PG_BINARY_W);
     705           2 :     if (file == NULL)
     706           0 :         goto error;
     707             : 
     708           2 :     if (fwrite(&PGSS_FILE_HEADER, sizeof(uint32), 1, file) != 1)
     709           0 :         goto error;
     710           2 :     if (fwrite(&PGSS_PG_MAJOR_VERSION, sizeof(uint32), 1, file) != 1)
     711           0 :         goto error;
     712           2 :     num_entries = hash_get_num_entries(pgss_hash);
     713           2 :     if (fwrite(&num_entries, sizeof(int32), 1, file) != 1)
     714           0 :         goto error;
     715             : 
     716           2 :     qbuffer = qtext_load_file(&qbuffer_size);
     717           2 :     if (qbuffer == NULL)
     718           0 :         goto error;
     719             : 
     720             :     /*
     721             :      * When serializing to disk, we store query texts immediately after their
     722             :      * entry data.  Any orphaned query texts are thereby excluded.
     723             :      */
     724           2 :     hash_seq_init(&hash_seq, pgss_hash);
     725           2 :     while ((entry = hash_seq_search(&hash_seq)) != NULL)
     726             :     {
     727          10 :         int         len = entry->query_len;
     728          10 :         char       *qstr = qtext_fetch(entry->query_offset, len,
     729             :                                        qbuffer, qbuffer_size);
     730             : 
     731          10 :         if (qstr == NULL)
     732           0 :             continue;           /* Ignore any entries with bogus texts */
     733             : 
     734          20 :         if (fwrite(entry, sizeof(pgssEntry), 1, file) != 1 ||
     735          10 :             fwrite(qstr, 1, len + 1, file) != len + 1)
     736             :         {
     737             :             /* note: we assume hash_seq_term won't change errno */
     738           0 :             hash_seq_term(&hash_seq);
     739           0 :             goto error;
     740             :         }
     741             :     }
     742             : 
     743           2 :     free(qbuffer);
     744           2 :     qbuffer = NULL;
     745             : 
     746           2 :     if (FreeFile(file))
     747             :     {
     748           0 :         file = NULL;
     749           0 :         goto error;
     750             :     }
     751             : 
     752             :     /*
     753             :      * Rename file into place, so we atomically replace any old one.
     754             :      */
     755           2 :     (void) durable_rename(PGSS_DUMP_FILE ".tmp", PGSS_DUMP_FILE, LOG);
     756             : 
     757             :     /* Unlink query-texts file; it's not needed while shutdown */
     758           2 :     unlink(PGSS_TEXT_FILE);
     759             : 
     760           2 :     return;
     761             : 
     762             : error:
     763           0 :     ereport(LOG,
     764             :             (errcode_for_file_access(),
     765             :              errmsg("could not write file \"%s\": %m",
     766             :                     PGSS_DUMP_FILE ".tmp")));
     767           0 :     if (qbuffer)
     768           0 :         free(qbuffer);
     769           0 :     if (file)
     770           0 :         FreeFile(file);
     771           0 :     unlink(PGSS_DUMP_FILE ".tmp");
     772           0 :     unlink(PGSS_TEXT_FILE);
     773             : }
     774             : 
     775             : /*
     776             :  * Post-parse-analysis hook: mark query with a queryId
     777             :  */
     778             : static void
     779         284 : pgss_post_parse_analyze(ParseState *pstate, Query *query)
     780             : {
     781             :     pgssJumbleState jstate;
     782             : 
     783         284 :     if (prev_post_parse_analyze_hook)
     784           0 :         prev_post_parse_analyze_hook(pstate, query);
     785             : 
     786             :     /* Assert we didn't do this already */
     787             :     Assert(query->queryId == UINT64CONST(0));
     788             : 
     789             :     /* Safety check... */
     790         284 :     if (!pgss || !pgss_hash)
     791         124 :         return;
     792             : 
     793             :     /*
     794             :      * Utility statements get queryId zero.  We do this even in cases where
     795             :      * the statement contains an optimizable statement for which a queryId
     796             :      * could be derived (such as EXPLAIN or DECLARE CURSOR).  For such cases,
     797             :      * runtime control will first go through ProcessUtility and then the
     798             :      * executor, and we don't want the executor hooks to do anything, since we
     799             :      * are already measuring the statement's costs at the utility level.
     800             :      */
     801         284 :     if (query->utilityStmt)
     802             :     {
     803         124 :         query->queryId = UINT64CONST(0);
     804         124 :         return;
     805             :     }
     806             : 
     807             :     /* Set up workspace for query jumbling */
     808         160 :     jstate.jumble = (unsigned char *) palloc(JUMBLE_SIZE);
     809         160 :     jstate.jumble_len = 0;
     810         160 :     jstate.clocations_buf_size = 32;
     811         160 :     jstate.clocations = (pgssLocationLen *)
     812         160 :         palloc(jstate.clocations_buf_size * sizeof(pgssLocationLen));
     813         160 :     jstate.clocations_count = 0;
     814         160 :     jstate.highest_extern_param_id = 0;
     815             : 
     816             :     /* Compute query ID and mark the Query node with it */
     817         160 :     JumbleQuery(&jstate, query);
     818         160 :     query->queryId =
     819         160 :         DatumGetUInt64(hash_any_extended(jstate.jumble, jstate.jumble_len, 0));
     820             : 
     821             :     /*
     822             :      * If we are unlucky enough to get a hash of zero, use 1 instead, to
     823             :      * prevent confusion with the utility-statement case.
     824             :      */
     825         160 :     if (query->queryId == UINT64CONST(0))
     826           0 :         query->queryId = UINT64CONST(1);
     827             : 
     828             :     /*
     829             :      * If we were able to identify any ignorable constants, we immediately
     830             :      * create a hash table entry for the query, so that we can record the
     831             :      * normalized form of the query string.  If there were no such constants,
     832             :      * the normalized string would be the same as the query text anyway, so
     833             :      * there's no need for an early entry.
     834             :      */
     835         160 :     if (jstate.clocations_count > 0)
     836         120 :         pgss_store(pstate->p_sourcetext,
     837             :                    query->queryId,
     838             :                    query->stmt_location,
     839             :                    query->stmt_len,
     840             :                    0,
     841             :                    0,
     842             :                    NULL,
     843             :                    &jstate);
     844             : }
     845             : 
     846             : /*
     847             :  * ExecutorStart hook: start up tracking if needed
     848             :  */
     849             : static void
     850         158 : pgss_ExecutorStart(QueryDesc *queryDesc, int eflags)
     851             : {
     852         158 :     if (prev_ExecutorStart)
     853           0 :         prev_ExecutorStart(queryDesc, eflags);
     854             :     else
     855         158 :         standard_ExecutorStart(queryDesc, eflags);
     856             : 
     857             :     /*
     858             :      * If query has queryId zero, don't track it.  This prevents double
     859             :      * counting of optimizable statements that are directly contained in
     860             :      * utility statements.
     861             :      */
     862         158 :     if (pgss_enabled() && queryDesc->plannedstmt->queryId != UINT64CONST(0))
     863             :     {
     864             :         /*
     865             :          * Set up to track total elapsed time in ExecutorRun.  Make sure the
     866             :          * space is allocated in the per-query context so it will go away at
     867             :          * ExecutorEnd.
     868             :          */
     869         142 :         if (queryDesc->totaltime == NULL)
     870             :         {
     871             :             MemoryContext oldcxt;
     872             : 
     873         142 :             oldcxt = MemoryContextSwitchTo(queryDesc->estate->es_query_cxt);
     874         142 :             queryDesc->totaltime = InstrAlloc(1, INSTRUMENT_ALL);
     875         142 :             MemoryContextSwitchTo(oldcxt);
     876             :         }
     877             :     }
     878         158 : }
     879             : 
     880             : /*
     881             :  * ExecutorRun hook: all we need do is track nesting depth
     882             :  */
     883             : static void
     884         158 : pgss_ExecutorRun(QueryDesc *queryDesc, ScanDirection direction, uint64 count,
     885             :                  bool execute_once)
     886             : {
     887         158 :     nested_level++;
     888         158 :     PG_TRY();
     889             :     {
     890         158 :         if (prev_ExecutorRun)
     891           0 :             prev_ExecutorRun(queryDesc, direction, count, execute_once);
     892             :         else
     893         158 :             standard_ExecutorRun(queryDesc, direction, count, execute_once);
     894         158 :         nested_level--;
     895             :     }
     896           0 :     PG_CATCH();
     897             :     {
     898           0 :         nested_level--;
     899           0 :         PG_RE_THROW();
     900             :     }
     901         158 :     PG_END_TRY();
     902         158 : }
     903             : 
     904             : /*
     905             :  * ExecutorFinish hook: all we need do is track nesting depth
     906             :  */
     907             : static void
     908         158 : pgss_ExecutorFinish(QueryDesc *queryDesc)
     909             : {
     910         158 :     nested_level++;
     911         158 :     PG_TRY();
     912             :     {
     913         158 :         if (prev_ExecutorFinish)
     914           0 :             prev_ExecutorFinish(queryDesc);
     915             :         else
     916         158 :             standard_ExecutorFinish(queryDesc);
     917         158 :         nested_level--;
     918             :     }
     919           0 :     PG_CATCH();
     920             :     {
     921           0 :         nested_level--;
     922           0 :         PG_RE_THROW();
     923             :     }
     924         158 :     PG_END_TRY();
     925         158 : }
     926             : 
     927             : /*
     928             :  * ExecutorEnd hook: store results if needed
     929             :  */
     930             : static void
     931         158 : pgss_ExecutorEnd(QueryDesc *queryDesc)
     932             : {
     933         158 :     uint64      queryId = queryDesc->plannedstmt->queryId;
     934             : 
     935         158 :     if (queryId != UINT64CONST(0) && queryDesc->totaltime && pgss_enabled())
     936             :     {
     937             :         /*
     938             :          * Make sure stats accumulation is done.  (Note: it's okay if several
     939             :          * levels of hook all do this.)
     940             :          */
     941         142 :         InstrEndLoop(queryDesc->totaltime);
     942             : 
     943         568 :         pgss_store(queryDesc->sourceText,
     944             :                    queryId,
     945         142 :                    queryDesc->plannedstmt->stmt_location,
     946         142 :                    queryDesc->plannedstmt->stmt_len,
     947         142 :                    queryDesc->totaltime->total * 1000.0,  /* convert to msec */
     948         142 :                    queryDesc->estate->es_processed,
     949         142 :                    &queryDesc->totaltime->bufusage,
     950             :                    NULL);
     951             :     }
     952             : 
     953         158 :     if (prev_ExecutorEnd)
     954           0 :         prev_ExecutorEnd(queryDesc);
     955             :     else
     956         158 :         standard_ExecutorEnd(queryDesc);
     957         158 : }
     958             : 
     959             : /*
     960             :  * ProcessUtility hook
     961             :  */
     962             : static void
     963         124 : pgss_ProcessUtility(PlannedStmt *pstmt, const char *queryString,
     964             :                     ProcessUtilityContext context,
     965             :                     ParamListInfo params, QueryEnvironment *queryEnv,
     966             :                     DestReceiver *dest, char *completionTag)
     967             : {
     968         124 :     Node       *parsetree = pstmt->utilityStmt;
     969             : 
     970             :     /*
     971             :      * If it's an EXECUTE statement, we don't track it and don't increment the
     972             :      * nesting level.  This allows the cycles to be charged to the underlying
     973             :      * PREPARE instead (by the Executor hooks), which is much more useful.
     974             :      *
     975             :      * We also don't track execution of PREPARE.  If we did, we would get one
     976             :      * hash table entry for the PREPARE (with hash calculated from the query
     977             :      * string), and then a different one with the same query string (but hash
     978             :      * calculated from the query tree) would be used to accumulate costs of
     979             :      * ensuing EXECUTEs.  This would be confusing, and inconsistent with other
     980             :      * cases where planning time is not included at all.
     981             :      *
     982             :      * Likewise, we don't track execution of DEALLOCATE.
     983             :      */
     984         176 :     if (pgss_track_utility && pgss_enabled() &&
     985         104 :         !IsA(parsetree, ExecuteStmt) &&
     986         104 :         !IsA(parsetree, PrepareStmt) &&
     987          52 :         !IsA(parsetree, DeallocateStmt))
     988          52 :     {
     989             :         instr_time  start;
     990             :         instr_time  duration;
     991             :         uint64      rows;
     992             :         BufferUsage bufusage_start,
     993             :                     bufusage;
     994             : 
     995          52 :         bufusage_start = pgBufferUsage;
     996          52 :         INSTR_TIME_SET_CURRENT(start);
     997             : 
     998          52 :         nested_level++;
     999          52 :         PG_TRY();
    1000             :         {
    1001          52 :             if (prev_ProcessUtility)
    1002           0 :                 prev_ProcessUtility(pstmt, queryString,
    1003             :                                     context, params, queryEnv,
    1004             :                                     dest, completionTag);
    1005             :             else
    1006          52 :                 standard_ProcessUtility(pstmt, queryString,
    1007             :                                         context, params, queryEnv,
    1008             :                                         dest, completionTag);
    1009          52 :             nested_level--;
    1010             :         }
    1011           0 :         PG_CATCH();
    1012             :         {
    1013           0 :             nested_level--;
    1014           0 :             PG_RE_THROW();
    1015             :         }
    1016          52 :         PG_END_TRY();
    1017             : 
    1018          52 :         INSTR_TIME_SET_CURRENT(duration);
    1019          52 :         INSTR_TIME_SUBTRACT(duration, start);
    1020             : 
    1021             :         /* parse command tag to retrieve the number of affected rows. */
    1022         104 :         if (completionTag &&
    1023          52 :             strncmp(completionTag, "COPY ", 5) == 0)
    1024           0 :             rows = pg_strtouint64(completionTag + 5, NULL, 10);
    1025             :         else
    1026          52 :             rows = 0;
    1027             : 
    1028             :         /* calc differences of buffer counters. */
    1029          52 :         bufusage.shared_blks_hit =
    1030          52 :             pgBufferUsage.shared_blks_hit - bufusage_start.shared_blks_hit;
    1031          52 :         bufusage.shared_blks_read =
    1032          52 :             pgBufferUsage.shared_blks_read - bufusage_start.shared_blks_read;
    1033          52 :         bufusage.shared_blks_dirtied =
    1034          52 :             pgBufferUsage.shared_blks_dirtied - bufusage_start.shared_blks_dirtied;
    1035          52 :         bufusage.shared_blks_written =
    1036          52 :             pgBufferUsage.shared_blks_written - bufusage_start.shared_blks_written;
    1037          52 :         bufusage.local_blks_hit =
    1038          52 :             pgBufferUsage.local_blks_hit - bufusage_start.local_blks_hit;
    1039          52 :         bufusage.local_blks_read =
    1040          52 :             pgBufferUsage.local_blks_read - bufusage_start.local_blks_read;
    1041          52 :         bufusage.local_blks_dirtied =
    1042          52 :             pgBufferUsage.local_blks_dirtied - bufusage_start.local_blks_dirtied;
    1043          52 :         bufusage.local_blks_written =
    1044          52 :             pgBufferUsage.local_blks_written - bufusage_start.local_blks_written;
    1045          52 :         bufusage.temp_blks_read =
    1046          52 :             pgBufferUsage.temp_blks_read - bufusage_start.temp_blks_read;
    1047          52 :         bufusage.temp_blks_written =
    1048          52 :             pgBufferUsage.temp_blks_written - bufusage_start.temp_blks_written;
    1049          52 :         bufusage.blk_read_time = pgBufferUsage.blk_read_time;
    1050          52 :         INSTR_TIME_SUBTRACT(bufusage.blk_read_time, bufusage_start.blk_read_time);
    1051          52 :         bufusage.blk_write_time = pgBufferUsage.blk_write_time;
    1052          52 :         INSTR_TIME_SUBTRACT(bufusage.blk_write_time, bufusage_start.blk_write_time);
    1053             : 
    1054          52 :         pgss_store(queryString,
    1055             :                    0,           /* signal that it's a utility stmt */
    1056             :                    pstmt->stmt_location,
    1057             :                    pstmt->stmt_len,
    1058          52 :                    INSTR_TIME_GET_MILLISEC(duration),
    1059             :                    rows,
    1060             :                    &bufusage,
    1061             :                    NULL);
    1062             :     }
    1063             :     else
    1064             :     {
    1065          72 :         if (prev_ProcessUtility)
    1066           0 :             prev_ProcessUtility(pstmt, queryString,
    1067             :                                 context, params, queryEnv,
    1068             :                                 dest, completionTag);
    1069             :         else
    1070          72 :             standard_ProcessUtility(pstmt, queryString,
    1071             :                                     context, params, queryEnv,
    1072             :                                     dest, completionTag);
    1073             :     }
    1074         124 : }
    1075             : 
    1076             : /*
    1077             :  * Given an arbitrarily long query string, produce a hash for the purposes of
    1078             :  * identifying the query, without normalizing constants.  Used when hashing
    1079             :  * utility statements.
    1080             :  */
    1081             : static uint64
    1082          52 : pgss_hash_string(const char *str, int len)
    1083             : {
    1084          52 :     return DatumGetUInt64(hash_any_extended((const unsigned char *) str,
    1085             :                                             len, 0));
    1086             : }
    1087             : 
    1088             : /*
    1089             :  * Store some statistics for a statement.
    1090             :  *
    1091             :  * If queryId is 0 then this is a utility statement and we should compute
    1092             :  * a suitable queryId internally.
    1093             :  *
    1094             :  * If jstate is not NULL then we're trying to create an entry for which
    1095             :  * we have no statistics as yet; we just want to record the normalized
    1096             :  * query string.  total_time, rows, bufusage are ignored in this case.
    1097             :  */
    1098             : static void
    1099         314 : pgss_store(const char *query, uint64 queryId,
    1100             :            int query_location, int query_len,
    1101             :            double total_time, uint64 rows,
    1102             :            const BufferUsage *bufusage,
    1103             :            pgssJumbleState *jstate)
    1104             : {
    1105             :     pgssHashKey key;
    1106             :     pgssEntry  *entry;
    1107         314 :     char       *norm_query = NULL;
    1108         314 :     int         encoding = GetDatabaseEncoding();
    1109             : 
    1110             :     Assert(query != NULL);
    1111             : 
    1112             :     /* Safety check... */
    1113         314 :     if (!pgss || !pgss_hash)
    1114           0 :         return;
    1115             : 
    1116             :     /*
    1117             :      * Confine our attention to the relevant part of the string, if the query
    1118             :      * is a portion of a multi-statement source string.
    1119             :      *
    1120             :      * First apply starting offset, unless it's -1 (unknown).
    1121             :      */
    1122         314 :     if (query_location >= 0)
    1123             :     {
    1124             :         Assert(query_location <= strlen(query));
    1125         314 :         query += query_location;
    1126             :         /* Length of 0 (or -1) means "rest of string" */
    1127         314 :         if (query_len <= 0)
    1128          38 :             query_len = strlen(query);
    1129             :         else
    1130             :             Assert(query_len <= strlen(query));
    1131             :     }
    1132             :     else
    1133             :     {
    1134             :         /* If query location is unknown, distrust query_len as well */
    1135           0 :         query_location = 0;
    1136           0 :         query_len = strlen(query);
    1137             :     }
    1138             : 
    1139             :     /*
    1140             :      * Discard leading and trailing whitespace, too.  Use scanner_isspace()
    1141             :      * not libc's isspace(), because we want to match the lexer's behavior.
    1142             :      */
    1143         720 :     while (query_len > 0 && scanner_isspace(query[0]))
    1144          92 :         query++, query_location++, query_len--;
    1145         716 :     while (query_len > 0 && scanner_isspace(query[query_len - 1]))
    1146          88 :         query_len--;
    1147             : 
    1148             :     /*
    1149             :      * For utility statements, we just hash the query string to get an ID.
    1150             :      */
    1151         314 :     if (queryId == UINT64CONST(0))
    1152             :     {
    1153          52 :         queryId = pgss_hash_string(query, query_len);
    1154             : 
    1155             :         /*
    1156             :          * If we are unlucky enough to get a hash of zero(invalid), use
    1157             :          * queryID as 2 instead, queryID 1 is already in use for normal
    1158             :          * statements.
    1159             :          */
    1160          52 :         if (queryId == UINT64CONST(0))
    1161           0 :             queryId = UINT64CONST(2);
    1162             :     }
    1163             : 
    1164             :     /* Set up key for hashtable search */
    1165         314 :     key.userid = GetUserId();
    1166         314 :     key.dbid = MyDatabaseId;
    1167         314 :     key.queryid = queryId;
    1168             : 
    1169             :     /* Lookup the hash table entry with shared lock. */
    1170         314 :     LWLockAcquire(pgss->lock, LW_SHARED);
    1171             : 
    1172         314 :     entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL);
    1173             : 
    1174             :     /* Create new entry, if not present */
    1175         314 :     if (!entry)
    1176             :     {
    1177             :         Size        query_offset;
    1178             :         int         gc_count;
    1179             :         bool        stored;
    1180             :         bool        do_gc;
    1181             : 
    1182             :         /*
    1183             :          * Create a new, normalized query string if caller asked.  We don't
    1184             :          * need to hold the lock while doing this work.  (Note: in any case,
    1185             :          * it's possible that someone else creates a duplicate hashtable entry
    1186             :          * in the interval where we don't hold the lock below.  That case is
    1187             :          * handled by entry_alloc.)
    1188             :          */
    1189         154 :         if (jstate)
    1190             :         {
    1191          78 :             LWLockRelease(pgss->lock);
    1192          78 :             norm_query = generate_normalized_query(jstate, query,
    1193             :                                                    query_location,
    1194             :                                                    &query_len,
    1195             :                                                    encoding);
    1196          78 :             LWLockAcquire(pgss->lock, LW_SHARED);
    1197             :         }
    1198             : 
    1199             :         /* Append new query text to file with only shared lock held */
    1200         154 :         stored = qtext_store(norm_query ? norm_query : query, query_len,
    1201             :                              &query_offset, &gc_count);
    1202             : 
    1203             :         /*
    1204             :          * Determine whether we need to garbage collect external query texts
    1205             :          * while the shared lock is still held.  This micro-optimization
    1206             :          * avoids taking the time to decide this while holding exclusive lock.
    1207             :          */
    1208         154 :         do_gc = need_gc_qtexts();
    1209             : 
    1210             :         /* Need exclusive lock to make a new hashtable entry - promote */
    1211         154 :         LWLockRelease(pgss->lock);
    1212         154 :         LWLockAcquire(pgss->lock, LW_EXCLUSIVE);
    1213             : 
    1214             :         /*
    1215             :          * A garbage collection may have occurred while we weren't holding the
    1216             :          * lock.  In the unlikely event that this happens, the query text we
    1217             :          * stored above will have been garbage collected, so write it again.
    1218             :          * This should be infrequent enough that doing it while holding
    1219             :          * exclusive lock isn't a performance problem.
    1220             :          */
    1221         154 :         if (!stored || pgss->gc_count != gc_count)
    1222           0 :             stored = qtext_store(norm_query ? norm_query : query, query_len,
    1223             :                                  &query_offset, NULL);
    1224             : 
    1225             :         /* If we failed to write to the text file, give up */
    1226         154 :         if (!stored)
    1227           0 :             goto done;
    1228             : 
    1229             :         /* OK to create a new hashtable entry */
    1230         154 :         entry = entry_alloc(&key, query_offset, query_len, encoding,
    1231             :                             jstate != NULL);
    1232             : 
    1233             :         /* If needed, perform garbage collection while exclusive lock held */
    1234         154 :         if (do_gc)
    1235           0 :             gc_qtexts();
    1236             :     }
    1237             : 
    1238             :     /* Increment the counts, except when jstate is not NULL */
    1239         314 :     if (!jstate)
    1240             :     {
    1241             :         /*
    1242             :          * Grab the spinlock while updating the counters (see comment about
    1243             :          * locking rules at the head of the file)
    1244             :          */
    1245         194 :         volatile pgssEntry *e = (volatile pgssEntry *) entry;
    1246             : 
    1247         194 :         SpinLockAcquire(&e->mutex);
    1248             : 
    1249             :         /* "Unstick" entry if it was previously sticky */
    1250         194 :         if (e->counters.calls == 0)
    1251         142 :             e->counters.usage = USAGE_INIT;
    1252             : 
    1253         194 :         e->counters.calls += 1;
    1254         194 :         e->counters.total_time += total_time;
    1255         194 :         if (e->counters.calls == 1)
    1256             :         {
    1257         142 :             e->counters.min_time = total_time;
    1258         142 :             e->counters.max_time = total_time;
    1259         142 :             e->counters.mean_time = total_time;
    1260             :         }
    1261             :         else
    1262             :         {
    1263             :             /*
    1264             :              * Welford's method for accurately computing variance. See
    1265             :              * <http://www.johndcook.com/blog/standard_deviation/>
    1266             :              */
    1267          52 :             double      old_mean = e->counters.mean_time;
    1268             : 
    1269          52 :             e->counters.mean_time +=
    1270          52 :                 (total_time - old_mean) / e->counters.calls;
    1271          52 :             e->counters.sum_var_time +=
    1272          52 :                 (total_time - old_mean) * (total_time - e->counters.mean_time);
    1273             : 
    1274             :             /* calculate min and max time */
    1275          52 :             if (e->counters.min_time > total_time)
    1276          38 :                 e->counters.min_time = total_time;
    1277          52 :             if (e->counters.max_time < total_time)
    1278           8 :                 e->counters.max_time = total_time;
    1279             :         }
    1280         194 :         e->counters.rows += rows;
    1281         194 :         e->counters.shared_blks_hit += bufusage->shared_blks_hit;
    1282         194 :         e->counters.shared_blks_read += bufusage->shared_blks_read;
    1283         194 :         e->counters.shared_blks_dirtied += bufusage->shared_blks_dirtied;
    1284         194 :         e->counters.shared_blks_written += bufusage->shared_blks_written;
    1285         194 :         e->counters.local_blks_hit += bufusage->local_blks_hit;
    1286         194 :         e->counters.local_blks_read += bufusage->local_blks_read;
    1287         194 :         e->counters.local_blks_dirtied += bufusage->local_blks_dirtied;
    1288         194 :         e->counters.local_blks_written += bufusage->local_blks_written;
    1289         194 :         e->counters.temp_blks_read += bufusage->temp_blks_read;
    1290         194 :         e->counters.temp_blks_written += bufusage->temp_blks_written;
    1291         194 :         e->counters.blk_read_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_read_time);
    1292         194 :         e->counters.blk_write_time += INSTR_TIME_GET_MILLISEC(bufusage->blk_write_time);
    1293         194 :         e->counters.usage += USAGE_EXEC(total_time);
    1294             : 
    1295         194 :         SpinLockRelease(&e->mutex);
    1296             :     }
    1297             : 
    1298             : done:
    1299         314 :     LWLockRelease(pgss->lock);
    1300             : 
    1301             :     /* We postpone this clean-up until we're out of the lock */
    1302         314 :     if (norm_query)
    1303          78 :         pfree(norm_query);
    1304             : }
    1305             : 
    1306             : /*
    1307             :  * Reset statement statistics corresponding to userid, dbid, and queryid.
    1308             :  */
    1309             : Datum
    1310          24 : pg_stat_statements_reset_1_7(PG_FUNCTION_ARGS)
    1311             : {
    1312             :     Oid         userid;
    1313             :     Oid         dbid;
    1314             :     uint64      queryid;
    1315             : 
    1316          24 :     userid = PG_GETARG_OID(0);
    1317          24 :     dbid = PG_GETARG_OID(1);
    1318          24 :     queryid = (uint64) PG_GETARG_INT64(2);
    1319             : 
    1320          24 :     entry_reset(userid, dbid, queryid);
    1321             : 
    1322          24 :     PG_RETURN_VOID();
    1323             : }
    1324             : 
    1325             : /*
    1326             :  * Reset statement statistics.
    1327             :  */
    1328             : Datum
    1329           0 : pg_stat_statements_reset(PG_FUNCTION_ARGS)
    1330             : {
    1331           0 :     entry_reset(0, 0, 0);
    1332             : 
    1333           0 :     PG_RETURN_VOID();
    1334             : }
    1335             : 
    1336             : /* Number of output arguments (columns) for various API versions */
    1337             : #define PG_STAT_STATEMENTS_COLS_V1_0    14
    1338             : #define PG_STAT_STATEMENTS_COLS_V1_1    18
    1339             : #define PG_STAT_STATEMENTS_COLS_V1_2    19
    1340             : #define PG_STAT_STATEMENTS_COLS_V1_3    23
    1341             : #define PG_STAT_STATEMENTS_COLS         23  /* maximum of above */
    1342             : 
    1343             : /*
    1344             :  * Retrieve statement statistics.
    1345             :  *
    1346             :  * The SQL API of this function has changed multiple times, and will likely
    1347             :  * do so again in future.  To support the case where a newer version of this
    1348             :  * loadable module is being used with an old SQL declaration of the function,
    1349             :  * we continue to support the older API versions.  For 1.2 and later, the
    1350             :  * expected API version is identified by embedding it in the C name of the
    1351             :  * function.  Unfortunately we weren't bright enough to do that for 1.1.
    1352             :  */
    1353             : Datum
    1354          28 : pg_stat_statements_1_3(PG_FUNCTION_ARGS)
    1355             : {
    1356          28 :     bool        showtext = PG_GETARG_BOOL(0);
    1357             : 
    1358          28 :     pg_stat_statements_internal(fcinfo, PGSS_V1_3, showtext);
    1359             : 
    1360          28 :     return (Datum) 0;
    1361             : }
    1362             : 
    1363             : Datum
    1364           0 : pg_stat_statements_1_2(PG_FUNCTION_ARGS)
    1365             : {
    1366           0 :     bool        showtext = PG_GETARG_BOOL(0);
    1367             : 
    1368           0 :     pg_stat_statements_internal(fcinfo, PGSS_V1_2, showtext);
    1369             : 
    1370           0 :     return (Datum) 0;
    1371             : }
    1372             : 
    1373             : /*
    1374             :  * Legacy entry point for pg_stat_statements() API versions 1.0 and 1.1.
    1375             :  * This can be removed someday, perhaps.
    1376             :  */
    1377             : Datum
    1378           0 : pg_stat_statements(PG_FUNCTION_ARGS)
    1379             : {
    1380             :     /* If it's really API 1.1, we'll figure that out below */
    1381           0 :     pg_stat_statements_internal(fcinfo, PGSS_V1_0, true);
    1382             : 
    1383           0 :     return (Datum) 0;
    1384             : }
    1385             : 
    1386             : /* Common code for all versions of pg_stat_statements() */
    1387             : static void
    1388          28 : pg_stat_statements_internal(FunctionCallInfo fcinfo,
    1389             :                             pgssVersion api_version,
    1390             :                             bool showtext)
    1391             : {
    1392          28 :     ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo;
    1393             :     TupleDesc   tupdesc;
    1394             :     Tuplestorestate *tupstore;
    1395             :     MemoryContext per_query_ctx;
    1396             :     MemoryContext oldcontext;
    1397          28 :     Oid         userid = GetUserId();
    1398          28 :     bool        is_allowed_role = false;
    1399          28 :     char       *qbuffer = NULL;
    1400          28 :     Size        qbuffer_size = 0;
    1401          28 :     Size        extent = 0;
    1402          28 :     int         gc_count = 0;
    1403             :     HASH_SEQ_STATUS hash_seq;
    1404             :     pgssEntry  *entry;
    1405             : 
    1406             :     /* Superusers or members of pg_read_all_stats members are allowed */
    1407          28 :     is_allowed_role = is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS);
    1408             : 
    1409             :     /* hash table must exist already */
    1410          28 :     if (!pgss || !pgss_hash)
    1411           0 :         ereport(ERROR,
    1412             :                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    1413             :                  errmsg("pg_stat_statements must be loaded via shared_preload_libraries")));
    1414             : 
    1415             :     /* check to see if caller supports us returning a tuplestore */
    1416          28 :     if (rsinfo == NULL || !IsA(rsinfo, ReturnSetInfo))
    1417           0 :         ereport(ERROR,
    1418             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    1419             :                  errmsg("set-valued function called in context that cannot accept a set")));
    1420          28 :     if (!(rsinfo->allowedModes & SFRM_Materialize))
    1421           0 :         ereport(ERROR,
    1422             :                 (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
    1423             :                  errmsg("materialize mode required, but it is not " \
    1424             :                         "allowed in this context")));
    1425             : 
    1426             :     /* Switch into long-lived context to construct returned data structures */
    1427          28 :     per_query_ctx = rsinfo->econtext->ecxt_per_query_memory;
    1428          28 :     oldcontext = MemoryContextSwitchTo(per_query_ctx);
    1429             : 
    1430             :     /* Build a tuple descriptor for our result type */
    1431          28 :     if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
    1432           0 :         elog(ERROR, "return type must be a row type");
    1433             : 
    1434             :     /*
    1435             :      * Check we have the expected number of output arguments.  Aside from
    1436             :      * being a good safety check, we need a kluge here to detect API version
    1437             :      * 1.1, which was wedged into the code in an ill-considered way.
    1438             :      */
    1439          28 :     switch (tupdesc->natts)
    1440             :     {
    1441             :         case PG_STAT_STATEMENTS_COLS_V1_0:
    1442           0 :             if (api_version != PGSS_V1_0)
    1443           0 :                 elog(ERROR, "incorrect number of output arguments");
    1444           0 :             break;
    1445             :         case PG_STAT_STATEMENTS_COLS_V1_1:
    1446             :             /* pg_stat_statements() should have told us 1.0 */
    1447           0 :             if (api_version != PGSS_V1_0)
    1448           0 :                 elog(ERROR, "incorrect number of output arguments");
    1449           0 :             api_version = PGSS_V1_1;
    1450           0 :             break;
    1451             :         case PG_STAT_STATEMENTS_COLS_V1_2:
    1452           0 :             if (api_version != PGSS_V1_2)
    1453           0 :                 elog(ERROR, "incorrect number of output arguments");
    1454           0 :             break;
    1455             :         case PG_STAT_STATEMENTS_COLS_V1_3:
    1456          28 :             if (api_version != PGSS_V1_3)
    1457           0 :                 elog(ERROR, "incorrect number of output arguments");
    1458          28 :             break;
    1459             :         default:
    1460           0 :             elog(ERROR, "incorrect number of output arguments");
    1461             :     }
    1462             : 
    1463          28 :     tupstore = tuplestore_begin_heap(true, false, work_mem);
    1464          28 :     rsinfo->returnMode = SFRM_Materialize;
    1465          28 :     rsinfo->setResult = tupstore;
    1466          28 :     rsinfo->setDesc = tupdesc;
    1467             : 
    1468          28 :     MemoryContextSwitchTo(oldcontext);
    1469             : 
    1470             :     /*
    1471             :      * We'd like to load the query text file (if needed) while not holding any
    1472             :      * lock on pgss->lock.  In the worst case we'll have to do this again
    1473             :      * after we have the lock, but it's unlikely enough to make this a win
    1474             :      * despite occasional duplicated work.  We need to reload if anybody
    1475             :      * writes to the file (either a retail qtext_store(), or a garbage
    1476             :      * collection) between this point and where we've gotten shared lock.  If
    1477             :      * a qtext_store is actually in progress when we look, we might as well
    1478             :      * skip the speculative load entirely.
    1479             :      */
    1480          28 :     if (showtext)
    1481             :     {
    1482             :         int         n_writers;
    1483             : 
    1484             :         /* Take the mutex so we can examine variables */
    1485             :         {
    1486          28 :             volatile pgssSharedState *s = (volatile pgssSharedState *) pgss;
    1487             : 
    1488          28 :             SpinLockAcquire(&s->mutex);
    1489          28 :             extent = s->extent;
    1490          28 :             n_writers = s->n_writers;
    1491          28 :             gc_count = s->gc_count;
    1492          28 :             SpinLockRelease(&s->mutex);
    1493             :         }
    1494             : 
    1495             :         /* No point in loading file now if there are active writers */
    1496          28 :         if (n_writers == 0)
    1497          28 :             qbuffer = qtext_load_file(&qbuffer_size);
    1498             :     }
    1499             : 
    1500             :     /*
    1501             :      * Get shared lock, load or reload the query text file if we must, and
    1502             :      * iterate over the hashtable entries.
    1503             :      *
    1504             :      * With a large hash table, we might be holding the lock rather longer
    1505             :      * than one could wish.  However, this only blocks creation of new hash
    1506             :      * table entries, and the larger the hash table the less likely that is to
    1507             :      * be needed.  So we can hope this is okay.  Perhaps someday we'll decide
    1508             :      * we need to partition the hash table to limit the time spent holding any
    1509             :      * one lock.
    1510             :      */
    1511          28 :     LWLockAcquire(pgss->lock, LW_SHARED);
    1512             : 
    1513          28 :     if (showtext)
    1514             :     {
    1515             :         /*
    1516             :          * Here it is safe to examine extent and gc_count without taking the
    1517             :          * mutex.  Note that although other processes might change
    1518             :          * pgss->extent just after we look at it, the strings they then write
    1519             :          * into the file cannot yet be referenced in the hashtable, so we
    1520             :          * don't care whether we see them or not.
    1521             :          *
    1522             :          * If qtext_load_file fails, we just press on; we'll return NULL for
    1523             :          * every query text.
    1524             :          */
    1525          56 :         if (qbuffer == NULL ||
    1526          56 :             pgss->extent != extent ||
    1527          28 :             pgss->gc_count != gc_count)
    1528             :         {
    1529           0 :             if (qbuffer)
    1530           0 :                 free(qbuffer);
    1531           0 :             qbuffer = qtext_load_file(&qbuffer_size);
    1532             :         }
    1533             :     }
    1534             : 
    1535          28 :     hash_seq_init(&hash_seq, pgss_hash);
    1536         302 :     while ((entry = hash_seq_search(&hash_seq)) != NULL)
    1537             :     {
    1538             :         Datum       values[PG_STAT_STATEMENTS_COLS];
    1539             :         bool        nulls[PG_STAT_STATEMENTS_COLS];
    1540         246 :         int         i = 0;
    1541             :         Counters    tmp;
    1542             :         double      stddev;
    1543         246 :         int64       queryid = entry->key.queryid;
    1544             : 
    1545         246 :         memset(values, 0, sizeof(values));
    1546         246 :         memset(nulls, 0, sizeof(nulls));
    1547             : 
    1548         246 :         values[i++] = ObjectIdGetDatum(entry->key.userid);
    1549         246 :         values[i++] = ObjectIdGetDatum(entry->key.dbid);
    1550             : 
    1551         246 :         if (is_allowed_role || entry->key.userid == userid)
    1552             :         {
    1553         246 :             if (api_version >= PGSS_V1_2)
    1554         246 :                 values[i++] = Int64GetDatumFast(queryid);
    1555             : 
    1556         492 :             if (showtext)
    1557             :             {
    1558         246 :                 char       *qstr = qtext_fetch(entry->query_offset,
    1559             :                                                entry->query_len,
    1560             :                                                qbuffer,
    1561             :                                                qbuffer_size);
    1562             : 
    1563         246 :                 if (qstr)
    1564             :                 {
    1565             :                     char       *enc;
    1566             : 
    1567         246 :                     enc = pg_any_to_server(qstr,
    1568             :                                            entry->query_len,
    1569             :                                            entry->encoding);
    1570             : 
    1571         246 :                     values[i++] = CStringGetTextDatum(enc);
    1572             : 
    1573         246 :                     if (enc != qstr)
    1574           0 :                         pfree(enc);
    1575             :                 }
    1576             :                 else
    1577             :                 {
    1578             :                     /* Just return a null if we fail to find the text */
    1579           0 :                     nulls[i++] = true;
    1580             :                 }
    1581             :             }
    1582             :             else
    1583             :             {
    1584             :                 /* Query text not requested */
    1585           0 :                 nulls[i++] = true;
    1586             :             }
    1587             :         }
    1588             :         else
    1589             :         {
    1590             :             /* Don't show queryid */
    1591           0 :             if (api_version >= PGSS_V1_2)
    1592           0 :                 nulls[i++] = true;
    1593             : 
    1594             :             /*
    1595             :              * Don't show query text, but hint as to the reason for not doing
    1596             :              * so if it was requested
    1597             :              */
    1598           0 :             if (showtext)
    1599           0 :                 values[i++] = CStringGetTextDatum("<insufficient privilege>");
    1600             :             else
    1601           0 :                 nulls[i++] = true;
    1602             :         }
    1603             : 
    1604             :         /* copy counters to a local variable to keep locking time short */
    1605             :         {
    1606         246 :             volatile pgssEntry *e = (volatile pgssEntry *) entry;
    1607             : 
    1608         246 :             SpinLockAcquire(&e->mutex);
    1609         246 :             tmp = e->counters;
    1610         246 :             SpinLockRelease(&e->mutex);
    1611             :         }
    1612             : 
    1613             :         /* Skip entry if unexecuted (ie, it's a pending "sticky" entry) */
    1614         246 :         if (tmp.calls == 0)
    1615          12 :             continue;
    1616             : 
    1617         234 :         values[i++] = Int64GetDatumFast(tmp.calls);
    1618         234 :         values[i++] = Float8GetDatumFast(tmp.total_time);
    1619         234 :         if (api_version >= PGSS_V1_3)
    1620             :         {
    1621         234 :             values[i++] = Float8GetDatumFast(tmp.min_time);
    1622         234 :             values[i++] = Float8GetDatumFast(tmp.max_time);
    1623         234 :             values[i++] = Float8GetDatumFast(tmp.mean_time);
    1624             : 
    1625             :             /*
    1626             :              * Note we are calculating the population variance here, not the
    1627             :              * sample variance, as we have data for the whole population, so
    1628             :              * Bessel's correction is not used, and we don't divide by
    1629             :              * tmp.calls - 1.
    1630             :              */
    1631         234 :             if (tmp.calls > 1)
    1632          50 :                 stddev = sqrt(tmp.sum_var_time / tmp.calls);
    1633             :             else
    1634         184 :                 stddev = 0.0;
    1635         234 :             values[i++] = Float8GetDatumFast(stddev);
    1636             :         }
    1637         234 :         values[i++] = Int64GetDatumFast(tmp.rows);
    1638         234 :         values[i++] = Int64GetDatumFast(tmp.shared_blks_hit);
    1639         234 :         values[i++] = Int64GetDatumFast(tmp.shared_blks_read);
    1640         234 :         if (api_version >= PGSS_V1_1)
    1641         234 :             values[i++] = Int64GetDatumFast(tmp.shared_blks_dirtied);
    1642         234 :         values[i++] = Int64GetDatumFast(tmp.shared_blks_written);
    1643         234 :         values[i++] = Int64GetDatumFast(tmp.local_blks_hit);
    1644         234 :         values[i++] = Int64GetDatumFast(tmp.local_blks_read);
    1645         234 :         if (api_version >= PGSS_V1_1)
    1646         234 :             values[i++] = Int64GetDatumFast(tmp.local_blks_dirtied);
    1647         234 :         values[i++] = Int64GetDatumFast(tmp.local_blks_written);
    1648         234 :         values[i++] = Int64GetDatumFast(tmp.temp_blks_read);
    1649         234 :         values[i++] = Int64GetDatumFast(tmp.temp_blks_written);
    1650         234 :         if (api_version >= PGSS_V1_1)
    1651             :         {
    1652         234 :             values[i++] = Float8GetDatumFast(tmp.blk_read_time);
    1653         234 :             values[i++] = Float8GetDatumFast(tmp.blk_write_time);
    1654             :         }
    1655             : 
    1656             :         Assert(i == (api_version == PGSS_V1_0 ? PG_STAT_STATEMENTS_COLS_V1_0 :
    1657             :                      api_version == PGSS_V1_1 ? PG_STAT_STATEMENTS_COLS_V1_1 :
    1658             :                      api_version == PGSS_V1_2 ? PG_STAT_STATEMENTS_COLS_V1_2 :
    1659             :                      api_version == PGSS_V1_3 ? PG_STAT_STATEMENTS_COLS_V1_3 :
    1660             :                      -1 /* fail if you forget to update this assert */ ));
    1661             : 
    1662         234 :         tuplestore_putvalues(tupstore, tupdesc, values, nulls);
    1663             :     }
    1664             : 
    1665             :     /* clean up and return the tuplestore */
    1666          28 :     LWLockRelease(pgss->lock);
    1667             : 
    1668          28 :     if (qbuffer)
    1669          28 :         free(qbuffer);
    1670             : 
    1671             :     tuplestore_donestoring(tupstore);
    1672          28 : }
    1673             : 
    1674             : /*
    1675             :  * Estimate shared memory space needed.
    1676             :  */
    1677             : static Size
    1678           2 : pgss_memsize(void)
    1679             : {
    1680             :     Size        size;
    1681             : 
    1682           2 :     size = MAXALIGN(sizeof(pgssSharedState));
    1683           2 :     size = add_size(size, hash_estimate_size(pgss_max, sizeof(pgssEntry)));
    1684             : 
    1685           2 :     return size;
    1686             : }
    1687             : 
    1688             : /*
    1689             :  * Allocate a new hashtable entry.
    1690             :  * caller must hold an exclusive lock on pgss->lock
    1691             :  *
    1692             :  * "query" need not be null-terminated; we rely on query_len instead
    1693             :  *
    1694             :  * If "sticky" is true, make the new entry artificially sticky so that it will
    1695             :  * probably still be there when the query finishes execution.  We do this by
    1696             :  * giving it a median usage value rather than the normal value.  (Strictly
    1697             :  * speaking, query strings are normalized on a best effort basis, though it
    1698             :  * would be difficult to demonstrate this even under artificial conditions.)
    1699             :  *
    1700             :  * Note: despite needing exclusive lock, it's not an error for the target
    1701             :  * entry to already exist.  This is because pgss_store releases and
    1702             :  * reacquires lock after failing to find a match; so someone else could
    1703             :  * have made the entry while we waited to get exclusive lock.
    1704             :  */
    1705             : static pgssEntry *
    1706         154 : entry_alloc(pgssHashKey *key, Size query_offset, int query_len, int encoding,
    1707             :             bool sticky)
    1708             : {
    1709             :     pgssEntry  *entry;
    1710             :     bool        found;
    1711             : 
    1712             :     /* Make space if needed */
    1713         308 :     while (hash_get_num_entries(pgss_hash) >= pgss_max)
    1714           0 :         entry_dealloc();
    1715             : 
    1716             :     /* Find or create an entry with desired hash code */
    1717         154 :     entry = (pgssEntry *) hash_search(pgss_hash, key, HASH_ENTER, &found);
    1718             : 
    1719         154 :     if (!found)
    1720             :     {
    1721             :         /* New entry, initialize it */
    1722             : 
    1723             :         /* reset the statistics */
    1724         154 :         memset(&entry->counters, 0, sizeof(Counters));
    1725             :         /* set the appropriate initial usage count */
    1726         154 :         entry->counters.usage = sticky ? pgss->cur_median_usage : USAGE_INIT;
    1727             :         /* re-initialize the mutex each time ... we assume no one using it */
    1728         154 :         SpinLockInit(&entry->mutex);
    1729             :         /* ... and don't forget the query text metadata */
    1730             :         Assert(query_len >= 0);
    1731         154 :         entry->query_offset = query_offset;
    1732         154 :         entry->query_len = query_len;
    1733         154 :         entry->encoding = encoding;
    1734             :     }
    1735             : 
    1736         154 :     return entry;
    1737             : }
    1738             : 
    1739             : /*
    1740             :  * qsort comparator for sorting into increasing usage order
    1741             :  */
    1742             : static int
    1743           0 : entry_cmp(const void *lhs, const void *rhs)
    1744             : {
    1745           0 :     double      l_usage = (*(pgssEntry *const *) lhs)->counters.usage;
    1746           0 :     double      r_usage = (*(pgssEntry *const *) rhs)->counters.usage;
    1747             : 
    1748           0 :     if (l_usage < r_usage)
    1749           0 :         return -1;
    1750           0 :     else if (l_usage > r_usage)
    1751           0 :         return +1;
    1752             :     else
    1753           0 :         return 0;
    1754             : }
    1755             : 
    1756             : /*
    1757             :  * Deallocate least-used entries.
    1758             :  *
    1759             :  * Caller must hold an exclusive lock on pgss->lock.
    1760             :  */
    1761             : static void
    1762           0 : entry_dealloc(void)
    1763             : {
    1764             :     HASH_SEQ_STATUS hash_seq;
    1765             :     pgssEntry **entries;
    1766             :     pgssEntry  *entry;
    1767             :     int         nvictims;
    1768             :     int         i;
    1769             :     Size        tottextlen;
    1770             :     int         nvalidtexts;
    1771             : 
    1772             :     /*
    1773             :      * Sort entries by usage and deallocate USAGE_DEALLOC_PERCENT of them.
    1774             :      * While we're scanning the table, apply the decay factor to the usage
    1775             :      * values, and update the mean query length.
    1776             :      *
    1777             :      * Note that the mean query length is almost immediately obsolete, since
    1778             :      * we compute it before not after discarding the least-used entries.
    1779             :      * Hopefully, that doesn't affect the mean too much; it doesn't seem worth
    1780             :      * making two passes to get a more current result.  Likewise, the new
    1781             :      * cur_median_usage includes the entries we're about to zap.
    1782             :      */
    1783             : 
    1784           0 :     entries = palloc(hash_get_num_entries(pgss_hash) * sizeof(pgssEntry *));
    1785             : 
    1786           0 :     i = 0;
    1787           0 :     tottextlen = 0;
    1788           0 :     nvalidtexts = 0;
    1789             : 
    1790           0 :     hash_seq_init(&hash_seq, pgss_hash);
    1791           0 :     while ((entry = hash_seq_search(&hash_seq)) != NULL)
    1792             :     {
    1793           0 :         entries[i++] = entry;
    1794             :         /* "Sticky" entries get a different usage decay rate. */
    1795           0 :         if (entry->counters.calls == 0)
    1796           0 :             entry->counters.usage *= STICKY_DECREASE_FACTOR;
    1797             :         else
    1798           0 :             entry->counters.usage *= USAGE_DECREASE_FACTOR;
    1799             :         /* In the mean length computation, ignore dropped texts. */
    1800           0 :         if (entry->query_len >= 0)
    1801             :         {
    1802           0 :             tottextlen += entry->query_len + 1;
    1803           0 :             nvalidtexts++;
    1804             :         }
    1805             :     }
    1806             : 
    1807             :     /* Sort into increasing order by usage */
    1808           0 :     qsort(entries, i, sizeof(pgssEntry *), entry_cmp);
    1809             : 
    1810             :     /* Record the (approximate) median usage */
    1811           0 :     if (i > 0)
    1812           0 :         pgss->cur_median_usage = entries[i / 2]->counters.usage;
    1813             :     /* Record the mean query length */
    1814           0 :     if (nvalidtexts > 0)
    1815           0 :         pgss->mean_query_len = tottextlen / nvalidtexts;
    1816             :     else
    1817           0 :         pgss->mean_query_len = ASSUMED_LENGTH_INIT;
    1818             : 
    1819             :     /* Now zap an appropriate fraction of lowest-usage entries */
    1820           0 :     nvictims = Max(10, i * USAGE_DEALLOC_PERCENT / 100);
    1821           0 :     nvictims = Min(nvictims, i);
    1822             : 
    1823           0 :     for (i = 0; i < nvictims; i++)
    1824             :     {
    1825           0 :         hash_search(pgss_hash, &entries[i]->key, HASH_REMOVE, NULL);
    1826             :     }
    1827             : 
    1828           0 :     pfree(entries);
    1829           0 : }
    1830             : 
    1831             : /*
    1832             :  * Given a query string (not necessarily null-terminated), allocate a new
    1833             :  * entry in the external query text file and store the string there.
    1834             :  *
    1835             :  * If successful, returns true, and stores the new entry's offset in the file
    1836             :  * into *query_offset.  Also, if gc_count isn't NULL, *gc_count is set to the
    1837             :  * number of garbage collections that have occurred so far.
    1838             :  *
    1839             :  * On failure, returns false.
    1840             :  *
    1841             :  * At least a shared lock on pgss->lock must be held by the caller, so as
    1842             :  * to prevent a concurrent garbage collection.  Share-lock-holding callers
    1843             :  * should pass a gc_count pointer to obtain the number of garbage collections,
    1844             :  * so that they can recheck the count after obtaining exclusive lock to
    1845             :  * detect whether a garbage collection occurred (and removed this entry).
    1846             :  */
    1847             : static bool
    1848         154 : qtext_store(const char *query, int query_len,
    1849             :             Size *query_offset, int *gc_count)
    1850             : {
    1851             :     Size        off;
    1852             :     int         fd;
    1853             : 
    1854             :     /*
    1855             :      * We use a spinlock to protect extent/n_writers/gc_count, so that
    1856             :      * multiple processes may execute this function concurrently.
    1857             :      */
    1858             :     {
    1859         154 :         volatile pgssSharedState *s = (volatile pgssSharedState *) pgss;
    1860             : 
    1861         154 :         SpinLockAcquire(&s->mutex);
    1862         154 :         off = s->extent;
    1863         154 :         s->extent += query_len + 1;
    1864         154 :         s->n_writers++;
    1865         154 :         if (gc_count)
    1866         154 :             *gc_count = s->gc_count;
    1867         154 :         SpinLockRelease(&s->mutex);
    1868             :     }
    1869             : 
    1870         154 :     *query_offset = off;
    1871             : 
    1872             :     /* Now write the data into the successfully-reserved part of the file */
    1873         154 :     fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDWR | O_CREAT | PG_BINARY);
    1874         154 :     if (fd < 0)
    1875           0 :         goto error;
    1876             : 
    1877         154 :     if (lseek(fd, off, SEEK_SET) != off)
    1878           0 :         goto error;
    1879             : 
    1880         154 :     if (write(fd, query, query_len) != query_len)
    1881           0 :         goto error;
    1882         154 :     if (write(fd, "\0", 1) != 1)
    1883           0 :         goto error;
    1884             : 
    1885         154 :     CloseTransientFile(fd);
    1886             : 
    1887             :     /* Mark our write complete */
    1888             :     {
    1889         154 :         volatile pgssSharedState *s = (volatile pgssSharedState *) pgss;
    1890             : 
    1891         154 :         SpinLockAcquire(&s->mutex);
    1892         154 :         s->n_writers--;
    1893         154 :         SpinLockRelease(&s->mutex);
    1894             :     }
    1895             : 
    1896         154 :     return true;
    1897             : 
    1898             : error:
    1899           0 :     ereport(LOG,
    1900             :             (errcode_for_file_access(),
    1901             :              errmsg("could not write file \"%s\": %m",
    1902             :                     PGSS_TEXT_FILE)));
    1903             : 
    1904           0 :     if (fd >= 0)
    1905           0 :         CloseTransientFile(fd);
    1906             : 
    1907             :     /* Mark our write complete */
    1908             :     {
    1909           0 :         volatile pgssSharedState *s = (volatile pgssSharedState *) pgss;
    1910             : 
    1911           0 :         SpinLockAcquire(&s->mutex);
    1912           0 :         s->n_writers--;
    1913           0 :         SpinLockRelease(&s->mutex);
    1914             :     }
    1915             : 
    1916           0 :     return false;
    1917             : }
    1918             : 
    1919             : /*
    1920             :  * Read the external query text file into a malloc'd buffer.
    1921             :  *
    1922             :  * Returns NULL (without throwing an error) if unable to read, eg
    1923             :  * file not there or insufficient memory.
    1924             :  *
    1925             :  * On success, the buffer size is also returned into *buffer_size.
    1926             :  *
    1927             :  * This can be called without any lock on pgss->lock, but in that case
    1928             :  * the caller is responsible for verifying that the result is sane.
    1929             :  */
    1930             : static char *
    1931          30 : qtext_load_file(Size *buffer_size)
    1932             : {
    1933             :     char       *buf;
    1934             :     int         fd;
    1935             :     struct stat stat;
    1936             : 
    1937          30 :     fd = OpenTransientFile(PGSS_TEXT_FILE, O_RDONLY | PG_BINARY);
    1938          30 :     if (fd < 0)
    1939             :     {
    1940           0 :         if (errno != ENOENT)
    1941           0 :             ereport(LOG,
    1942             :                     (errcode_for_file_access(),
    1943             :                      errmsg("could not read file \"%s\": %m",
    1944             :                             PGSS_TEXT_FILE)));
    1945           0 :         return NULL;
    1946             :     }
    1947             : 
    1948             :     /* Get file length */
    1949          30 :     if (fstat(fd, &stat))
    1950             :     {
    1951           0 :         ereport(LOG,
    1952             :                 (errcode_for_file_access(),
    1953             :                  errmsg("could not stat file \"%s\": %m",
    1954             :                         PGSS_TEXT_FILE)));
    1955           0 :         CloseTransientFile(fd);
    1956           0 :         return NULL;
    1957             :     }
    1958             : 
    1959             :     /* Allocate buffer; beware that off_t might be wider than size_t */
    1960          30 :     if (stat.st_size <= MaxAllocHugeSize)
    1961          30 :         buf = (char *) malloc(stat.st_size);
    1962             :     else
    1963           0 :         buf = NULL;
    1964          30 :     if (buf == NULL)
    1965             :     {
    1966           0 :         ereport(LOG,
    1967             :                 (errcode(ERRCODE_OUT_OF_MEMORY),
    1968             :                  errmsg("out of memory"),
    1969             :                  errdetail("Could not allocate enough memory to read file \"%s\".",
    1970             :                            PGSS_TEXT_FILE)));
    1971           0 :         CloseTransientFile(fd);
    1972           0 :         return NULL;
    1973             :     }
    1974             : 
    1975             :     /*
    1976             :      * OK, slurp in the file.  If we get a short read and errno doesn't get
    1977             :      * set, the reason is probably that garbage collection truncated the file
    1978             :      * since we did the fstat(), so we don't log a complaint --- but we don't
    1979             :      * return the data, either, since it's most likely corrupt due to
    1980             :      * concurrent writes from garbage collection.
    1981             :      */
    1982          30 :     errno = 0;
    1983          30 :     if (read(fd, buf, stat.st_size) != stat.st_size)
    1984             :     {
    1985           0 :         if (errno)
    1986           0 :             ereport(LOG,
    1987             :                     (errcode_for_file_access(),
    1988             :                      errmsg("could not read file \"%s\": %m",
    1989             :                             PGSS_TEXT_FILE)));
    1990           0 :         free(buf);
    1991           0 :         CloseTransientFile(fd);
    1992           0 :         return NULL;
    1993             :     }
    1994             : 
    1995          30 :     if (CloseTransientFile(fd))
    1996           0 :         ereport(LOG,
    1997             :                 (errcode_for_file_access(),
    1998             :                  errmsg("could not close file \"%s\": %m", PGSS_TEXT_FILE)));
    1999             : 
    2000          30 :     *buffer_size = stat.st_size;
    2001          30 :     return buf;
    2002             : }
    2003             : 
    2004             : /*
    2005             :  * Locate a query text in the file image previously read by qtext_load_file().
    2006             :  *
    2007             :  * We validate the given offset/length, and return NULL if bogus.  Otherwise,
    2008             :  * the result points to a null-terminated string within the buffer.
    2009             :  */
    2010             : static char *
    2011         256 : qtext_fetch(Size query_offset, int query_len,
    2012             :             char *buffer, Size buffer_size)
    2013             : {
    2014             :     /* File read failed? */
    2015         256 :     if (buffer == NULL)
    2016           0 :         return NULL;
    2017             :     /* Bogus offset/length? */
    2018         512 :     if (query_len < 0 ||
    2019         256 :         query_offset + query_len >= buffer_size)
    2020           0 :         return NULL;
    2021             :     /* As a further sanity check, make sure there's a trailing null */
    2022         256 :     if (buffer[query_offset + query_len] != '\0')
    2023           0 :         return NULL;
    2024             :     /* Looks OK */
    2025         256 :     return buffer + query_offset;
    2026             : }
    2027             : 
    2028             : /*
    2029             :  * Do we need to garbage-collect the external query text file?
    2030             :  *
    2031             :  * Caller should hold at least a shared lock on pgss->lock.
    2032             :  */
    2033             : static bool
    2034         154 : need_gc_qtexts(void)
    2035             : {
    2036             :     Size        extent;
    2037             : 
    2038             :     /* Read shared extent pointer */
    2039             :     {
    2040         154 :         volatile pgssSharedState *s = (volatile pgssSharedState *) pgss;
    2041             : 
    2042         154 :         SpinLockAcquire(&s->mutex);
    2043         154 :         extent = s->extent;
    2044         154 :         SpinLockRelease(&s->mutex);
    2045             :     }
    2046             : 
    2047             :     /* Don't proceed if file does not exceed 512 bytes per possible entry */
    2048         154 :     if (extent < 512 * pgss_max)
    2049         154 :         return false;
    2050             : 
    2051             :     /*
    2052             :      * Don't proceed if file is less than about 50% bloat.  Nothing can or
    2053             :      * should be done in the event of unusually large query texts accounting
    2054             :      * for file's large size.  We go to the trouble of maintaining the mean
    2055             :      * query length in order to prevent garbage collection from thrashing
    2056             :      * uselessly.
    2057             :      */
    2058           0 :     if (extent < pgss->mean_query_len * pgss_max * 2)
    2059           0 :         return false;
    2060             : 
    2061           0 :     return true;
    2062             : }
    2063             : 
    2064             : /*
    2065             :  * Garbage-collect orphaned query texts in external file.
    2066             :  *
    2067             :  * This won't be called often in the typical case, since it's likely that
    2068             :  * there won't be too much churn, and besides, a similar compaction process
    2069             :  * occurs when serializing to disk at shutdown or as part of resetting.
    2070             :  * Despite this, it seems prudent to plan for the edge case where the file
    2071             :  * becomes unreasonably large, with no other method of compaction likely to
    2072             :  * occur in the foreseeable future.
    2073             :  *
    2074             :  * The caller must hold an exclusive lock on pgss->lock.
    2075             :  *
    2076             :  * At the first sign of trouble we unlink the query text file to get a clean
    2077             :  * slate (although existing statistics are retained), rather than risk
    2078             :  * thrashing by allowing the same problem case to recur indefinitely.
    2079             :  */
    2080             : static void
    2081           0 : gc_qtexts(void)
    2082             : {
    2083             :     char       *qbuffer;
    2084             :     Size        qbuffer_size;
    2085           0 :     FILE       *qfile = NULL;
    2086             :     HASH_SEQ_STATUS hash_seq;
    2087             :     pgssEntry  *entry;
    2088             :     Size        extent;
    2089             :     int         nentries;
    2090             : 
    2091             :     /*
    2092             :      * When called from pgss_store, some other session might have proceeded
    2093             :      * with garbage collection in the no-lock-held interim of lock strength
    2094             :      * escalation.  Check once more that this is actually necessary.
    2095             :      */
    2096           0 :     if (!need_gc_qtexts())
    2097           0 :         return;
    2098             : 
    2099             :     /*
    2100             :      * Load the old texts file.  If we fail (out of memory, for instance),
    2101             :      * invalidate query texts.  Hopefully this is rare.  It might seem better
    2102             :      * to leave things alone on an OOM failure, but the problem is that the
    2103             :      * file is only going to get bigger; hoping for a future non-OOM result is
    2104             :      * risky and can easily lead to complete denial of service.
    2105             :      */
    2106           0 :     qbuffer = qtext_load_file(&qbuffer_size);
    2107           0 :     if (qbuffer == NULL)
    2108           0 :         goto gc_fail;
    2109             : 
    2110             :     /*
    2111             :      * We overwrite the query texts file in place, so as to reduce the risk of
    2112             :      * an out-of-disk-space failure.  Since the file is guaranteed not to get
    2113             :      * larger, this should always work on traditional filesystems; though we
    2114             :      * could still lose on copy-on-write filesystems.
    2115             :      */
    2116           0 :     qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
    2117           0 :     if (qfile == NULL)
    2118             :     {
    2119           0 :         ereport(LOG,
    2120             :                 (errcode_for_file_access(),
    2121             :                  errmsg("could not write file \"%s\": %m",
    2122             :                         PGSS_TEXT_FILE)));
    2123           0 :         goto gc_fail;
    2124             :     }
    2125             : 
    2126           0 :     extent = 0;
    2127           0 :     nentries = 0;
    2128             : 
    2129           0 :     hash_seq_init(&hash_seq, pgss_hash);
    2130           0 :     while ((entry = hash_seq_search(&hash_seq)) != NULL)
    2131             :     {
    2132           0 :         int         query_len = entry->query_len;
    2133           0 :         char       *qry = qtext_fetch(entry->query_offset,
    2134             :                                       query_len,
    2135             :                                       qbuffer,
    2136             :                                       qbuffer_size);
    2137             : 
    2138           0 :         if (qry == NULL)
    2139             :         {
    2140             :             /* Trouble ... drop the text */
    2141           0 :             entry->query_offset = 0;
    2142           0 :             entry->query_len = -1;
    2143             :             /* entry will not be counted in mean query length computation */
    2144           0 :             continue;
    2145             :         }
    2146             : 
    2147           0 :         if (fwrite(qry, 1, query_len + 1, qfile) != query_len + 1)
    2148             :         {
    2149           0 :             ereport(LOG,
    2150             :                     (errcode_for_file_access(),
    2151             :                      errmsg("could not write file \"%s\": %m",
    2152             :                             PGSS_TEXT_FILE)));
    2153           0 :             hash_seq_term(&hash_seq);
    2154           0 :             goto gc_fail;
    2155             :         }
    2156             : 
    2157           0 :         entry->query_offset = extent;
    2158           0 :         extent += query_len + 1;
    2159           0 :         nentries++;
    2160             :     }
    2161             : 
    2162             :     /*
    2163             :      * Truncate away any now-unused space.  If this fails for some odd reason,
    2164             :      * we log it, but there's no need to fail.
    2165             :      */
    2166           0 :     if (ftruncate(fileno(qfile), extent) != 0)
    2167           0 :         ereport(LOG,
    2168             :                 (errcode_for_file_access(),
    2169             :                  errmsg("could not truncate file \"%s\": %m",
    2170             :                         PGSS_TEXT_FILE)));
    2171             : 
    2172           0 :     if (FreeFile(qfile))
    2173             :     {
    2174           0 :         ereport(LOG,
    2175             :                 (errcode_for_file_access(),
    2176             :                  errmsg("could not write file \"%s\": %m",
    2177             :                         PGSS_TEXT_FILE)));
    2178           0 :         qfile = NULL;
    2179           0 :         goto gc_fail;
    2180             :     }
    2181             : 
    2182           0 :     elog(DEBUG1, "pgss gc of queries file shrunk size from %zu to %zu",
    2183             :          pgss->extent, extent);
    2184             : 
    2185             :     /* Reset the shared extent pointer */
    2186           0 :     pgss->extent = extent;
    2187             : 
    2188             :     /*
    2189             :      * Also update the mean query length, to be sure that need_gc_qtexts()
    2190             :      * won't still think we have a problem.
    2191             :      */
    2192           0 :     if (nentries > 0)
    2193           0 :         pgss->mean_query_len = extent / nentries;
    2194             :     else
    2195           0 :         pgss->mean_query_len = ASSUMED_LENGTH_INIT;
    2196             : 
    2197           0 :     free(qbuffer);
    2198             : 
    2199             :     /*
    2200             :      * OK, count a garbage collection cycle.  (Note: even though we have
    2201             :      * exclusive lock on pgss->lock, we must take pgss->mutex for this, since
    2202             :      * other processes may examine gc_count while holding only the mutex.
    2203             :      * Also, we have to advance the count *after* we've rewritten the file,
    2204             :      * else other processes might not realize they read a stale file.)
    2205             :      */
    2206           0 :     record_gc_qtexts();
    2207             : 
    2208           0 :     return;
    2209             : 
    2210             : gc_fail:
    2211             :     /* clean up resources */
    2212           0 :     if (qfile)
    2213           0 :         FreeFile(qfile);
    2214           0 :     if (qbuffer)
    2215           0 :         free(qbuffer);
    2216             : 
    2217             :     /*
    2218             :      * Since the contents of the external file are now uncertain, mark all
    2219             :      * hashtable entries as having invalid texts.
    2220             :      */
    2221           0 :     hash_seq_init(&hash_seq, pgss_hash);
    2222           0 :     while ((entry = hash_seq_search(&hash_seq)) != NULL)
    2223             :     {
    2224           0 :         entry->query_offset = 0;
    2225           0 :         entry->query_len = -1;
    2226             :     }
    2227             : 
    2228             :     /*
    2229             :      * Destroy the query text file and create a new, empty one
    2230             :      */
    2231           0 :     (void) unlink(PGSS_TEXT_FILE);
    2232           0 :     qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
    2233           0 :     if (qfile == NULL)
    2234           0 :         ereport(LOG,
    2235             :                 (errcode_for_file_access(),
    2236             :                  errmsg("could not recreate file \"%s\": %m",
    2237             :                         PGSS_TEXT_FILE)));
    2238             :     else
    2239           0 :         FreeFile(qfile);
    2240             : 
    2241             :     /* Reset the shared extent pointer */
    2242           0 :     pgss->extent = 0;
    2243             : 
    2244             :     /* Reset mean_query_len to match the new state */
    2245           0 :     pgss->mean_query_len = ASSUMED_LENGTH_INIT;
    2246             : 
    2247             :     /*
    2248             :      * Bump the GC count even though we failed.
    2249             :      *
    2250             :      * This is needed to make concurrent readers of file without any lock on
    2251             :      * pgss->lock notice existence of new version of file.  Once readers
    2252             :      * subsequently observe a change in GC count with pgss->lock held, that
    2253             :      * forces a safe reopen of file.  Writers also require that we bump here,
    2254             :      * of course.  (As required by locking protocol, readers and writers don't
    2255             :      * trust earlier file contents until gc_count is found unchanged after
    2256             :      * pgss->lock acquired in shared or exclusive mode respectively.)
    2257             :      */
    2258           0 :     record_gc_qtexts();
    2259             : }
    2260             : 
    2261             : /*
    2262             :  * Release entries corresponding to parameters passed.
    2263             :  */
    2264             : static void
    2265          24 : entry_reset(Oid userid, Oid dbid, uint64 queryid)
    2266             : {
    2267             :     HASH_SEQ_STATUS hash_seq;
    2268             :     pgssEntry  *entry;
    2269             :     FILE       *qfile;
    2270             :     long        num_entries;
    2271          24 :     long        num_remove = 0;
    2272             :     pgssHashKey key;
    2273             : 
    2274          24 :     if (!pgss || !pgss_hash)
    2275           0 :         ereport(ERROR,
    2276             :                 (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
    2277             :                  errmsg("pg_stat_statements must be loaded via shared_preload_libraries")));
    2278             : 
    2279          24 :     LWLockAcquire(pgss->lock, LW_EXCLUSIVE);
    2280          24 :     num_entries = hash_get_num_entries(pgss_hash);
    2281             : 
    2282          24 :     if (userid != 0 && dbid != 0 && queryid != UINT64CONST(0))
    2283             :     {
    2284             :         /* If all the parameters are available, use the fast path. */
    2285           2 :         key.userid = userid;
    2286           2 :         key.dbid = dbid;
    2287           2 :         key.queryid = queryid;
    2288             : 
    2289             :         /* Remove the key if exists */
    2290           2 :         entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_REMOVE, NULL);
    2291           4 :         if (entry)              /* found */
    2292           2 :             num_remove++;
    2293             :     }
    2294          28 :     else if (userid != 0 || dbid != 0 || queryid != UINT64CONST(0))
    2295             :     {
    2296             :         /* Remove entries corresponding to valid parameters. */
    2297           6 :         hash_seq_init(&hash_seq, pgss_hash);
    2298          84 :         while ((entry = hash_seq_search(&hash_seq)) != NULL)
    2299             :         {
    2300          72 :             if ((!userid || entry->key.userid == userid) &&
    2301           0 :                 (!dbid || entry->key.dbid == dbid) &&
    2302          48 :                 (!queryid || entry->key.queryid == queryid))
    2303             :             {
    2304           8 :                 hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL);
    2305           8 :                 num_remove++;
    2306             :             }
    2307             :         }
    2308             :     }
    2309             :     else
    2310             :     {
    2311             :         /* Remove all entries. */
    2312          16 :         hash_seq_init(&hash_seq, pgss_hash);
    2313         166 :         while ((entry = hash_seq_search(&hash_seq)) != NULL)
    2314             :         {
    2315         134 :             hash_search(pgss_hash, &entry->key, HASH_REMOVE, NULL);
    2316         134 :             num_remove++;
    2317             :         }
    2318             :     }
    2319             : 
    2320             :     /* All entries are removed? */
    2321          24 :     if (num_entries != num_remove)
    2322           8 :         goto release_lock;
    2323             : 
    2324             :     /*
    2325             :      * Write new empty query file, perhaps even creating a new one to recover
    2326             :      * if the file was missing.
    2327             :      */
    2328          16 :     qfile = AllocateFile(PGSS_TEXT_FILE, PG_BINARY_W);
    2329          16 :     if (qfile == NULL)
    2330             :     {
    2331           0 :         ereport(LOG,
    2332             :                 (errcode_for_file_access(),
    2333             :                  errmsg("could not create file \"%s\": %m",
    2334             :                         PGSS_TEXT_FILE)));
    2335           0 :         goto done;
    2336             :     }
    2337             : 
    2338             :     /* If ftruncate fails, log it, but it's not a fatal problem */
    2339          16 :     if (ftruncate(fileno(qfile), 0) != 0)
    2340           0 :         ereport(LOG,
    2341             :                 (errcode_for_file_access(),
    2342             :                  errmsg("could not truncate file \"%s\": %m",
    2343             :                         PGSS_TEXT_FILE)));
    2344             : 
    2345          16 :     FreeFile(qfile);
    2346             : 
    2347             : done:
    2348          16 :     pgss->extent = 0;
    2349             :     /* This counts as a query text garbage collection for our purposes */
    2350          16 :     record_gc_qtexts();
    2351             : 
    2352             : release_lock:
    2353          24 :     LWLockRelease(pgss->lock);
    2354          24 : }
    2355             : 
    2356             : /*
    2357             :  * AppendJumble: Append a value that is substantive in a given query to
    2358             :  * the current jumble.
    2359             :  */
    2360             : static void
    2361        4088 : AppendJumble(pgssJumbleState *jstate, const unsigned char *item, Size size)
    2362             : {
    2363        4088 :     unsigned char *jumble = jstate->jumble;
    2364        4088 :     Size        jumble_len = jstate->jumble_len;
    2365             : 
    2366             :     /*
    2367             :      * Whenever the jumble buffer is full, we hash the current contents and
    2368             :      * reset the buffer to contain just that hash value, thus relying on the
    2369             :      * hash to summarize everything so far.
    2370             :      */
    2371       12264 :     while (size > 0)
    2372             :     {
    2373             :         Size        part_size;
    2374             : 
    2375        4088 :         if (jumble_len >= JUMBLE_SIZE)
    2376             :         {
    2377             :             uint64      start_hash;
    2378             : 
    2379           0 :             start_hash = DatumGetUInt64(hash_any_extended(jumble,
    2380             :                                                           JUMBLE_SIZE, 0));
    2381           0 :             memcpy(jumble, &start_hash, sizeof(start_hash));
    2382           0 :             jumble_len = sizeof(start_hash);
    2383             :         }
    2384        4088 :         part_size = Min(size, JUMBLE_SIZE - jumble_len);
    2385        4088 :         memcpy(jumble + jumble_len, item, part_size);
    2386        4088 :         jumble_len += part_size;
    2387        4088 :         item += part_size;
    2388        4088 :         size -= part_size;
    2389             :     }
    2390        4088 :     jstate->jumble_len = jumble_len;
    2391        4088 : }
    2392             : 
    2393             : /*
    2394             :  * Wrappers around AppendJumble to encapsulate details of serialization
    2395             :  * of individual local variable elements.
    2396             :  */
    2397             : #define APP_JUMB(item) \
    2398             :     AppendJumble(jstate, (const unsigned char *) &(item), sizeof(item))
    2399             : #define APP_JUMB_STRING(str) \
    2400             :     AppendJumble(jstate, (const unsigned char *) (str), strlen(str) + 1)
    2401             : 
    2402             : /*
    2403             :  * JumbleQuery: Selectively serialize the query tree, appending significant
    2404             :  * data to the "query jumble" while ignoring nonsignificant data.
    2405             :  *
    2406             :  * Rule of thumb for what to include is that we should ignore anything not
    2407             :  * semantically significant (such as alias names) as well as anything that can
    2408             :  * be deduced from child nodes (else we'd just be double-hashing that piece
    2409             :  * of information).
    2410             :  */
    2411             : static void
    2412         172 : JumbleQuery(pgssJumbleState *jstate, Query *query)
    2413             : {
    2414             :     Assert(IsA(query, Query));
    2415             :     Assert(query->utilityStmt == NULL);
    2416             : 
    2417         172 :     APP_JUMB(query->commandType);
    2418             :     /* resultRelation is usually predictable from commandType */
    2419         172 :     JumbleExpr(jstate, (Node *) query->cteList);
    2420         172 :     JumbleRangeTable(jstate, query->rtable);
    2421         172 :     JumbleExpr(jstate, (Node *) query->jointree);
    2422         172 :     JumbleExpr(jstate, (Node *) query->targetList);
    2423         172 :     JumbleExpr(jstate, (Node *) query->onConflict);
    2424         172 :     JumbleExpr(jstate, (Node *) query->returningList);
    2425         172 :     JumbleExpr(jstate, (Node *) query->groupClause);
    2426         172 :     JumbleExpr(jstate, (Node *) query->groupingSets);
    2427         172 :     JumbleExpr(jstate, query->havingQual);
    2428         172 :     JumbleExpr(jstate, (Node *) query->windowClause);
    2429         172 :     JumbleExpr(jstate, (Node *) query->distinctClause);
    2430         172 :     JumbleExpr(jstate, (Node *) query->sortClause);
    2431         172 :     JumbleExpr(jstate, query->limitOffset);
    2432         172 :     JumbleExpr(jstate, query->limitCount);
    2433             :     /* we ignore rowMarks */
    2434         172 :     JumbleExpr(jstate, query->setOperations);
    2435         172 : }
    2436             : 
    2437             : /*
    2438             :  * Jumble a range table
    2439             :  */
    2440             : static void
    2441         172 : JumbleRangeTable(pgssJumbleState *jstate, List *rtable)
    2442             : {
    2443             :     ListCell   *lc;
    2444             : 
    2445         246 :     foreach(lc, rtable)
    2446             :     {
    2447          74 :         RangeTblEntry *rte = lfirst_node(RangeTblEntry, lc);
    2448             : 
    2449          74 :         APP_JUMB(rte->rtekind);
    2450          74 :         switch (rte->rtekind)
    2451             :         {
    2452             :             case RTE_RELATION:
    2453          62 :                 APP_JUMB(rte->relid);
    2454          62 :                 JumbleExpr(jstate, (Node *) rte->tablesample);
    2455          62 :                 break;
    2456             :             case RTE_SUBQUERY:
    2457           4 :                 JumbleQuery(jstate, rte->subquery);
    2458           4 :                 break;
    2459             :             case RTE_JOIN:
    2460           0 :                 APP_JUMB(rte->jointype);
    2461           0 :                 break;
    2462             :             case RTE_FUNCTION:
    2463           2 :                 JumbleExpr(jstate, (Node *) rte->functions);
    2464           2 :                 break;
    2465             :             case RTE_TABLEFUNC:
    2466           0 :                 JumbleExpr(jstate, (Node *) rte->tablefunc);
    2467           0 :                 break;
    2468             :             case RTE_VALUES:
    2469           4 :                 JumbleExpr(jstate, (Node *) rte->values_lists);
    2470           4 :                 break;
    2471             :             case RTE_CTE:
    2472             : 
    2473             :                 /*
    2474             :                  * Depending on the CTE name here isn't ideal, but it's the
    2475             :                  * only info we have to identify the referenced WITH item.
    2476             :                  */
    2477           2 :                 APP_JUMB_STRING(rte->ctename);
    2478           2 :                 APP_JUMB(rte->ctelevelsup);
    2479           2 :                 break;
    2480             :             case RTE_NAMEDTUPLESTORE:
    2481           0 :                 APP_JUMB_STRING(rte->enrname);
    2482           0 :                 break;
    2483             :             case RTE_RESULT:
    2484           0 :                 break;
    2485             :             default:
    2486           0 :                 elog(ERROR, "unrecognized RTE kind: %d", (int) rte->rtekind);
    2487             :                 break;
    2488             :         }
    2489             :     }
    2490         172 : }
    2491             : 
    2492             : /*
    2493             :  * Jumble an expression tree
    2494             :  *
    2495             :  * In general this function should handle all the same node types that
    2496             :  * expression_tree_walker() does, and therefore it's coded to be as parallel
    2497             :  * to that function as possible.  However, since we are only invoked on
    2498             :  * queries immediately post-parse-analysis, we need not handle node types
    2499             :  * that only appear in planning.
    2500             :  *
    2501             :  * Note: the reason we don't simply use expression_tree_walker() is that the
    2502             :  * point of that function is to support tree walkers that don't care about
    2503             :  * most tree node types, but here we care about all types.  We should complain
    2504             :  * about any unrecognized node type.
    2505             :  */
    2506             : static void
    2507        4118 : JumbleExpr(pgssJumbleState *jstate, Node *node)
    2508             : {
    2509             :     ListCell   *temp;
    2510             : 
    2511        4118 :     if (node == NULL)
    2512        2342 :         return;
    2513             : 
    2514             :     /* Guard against stack overflow due to overly complex expressions */
    2515        1776 :     check_stack_depth();
    2516             : 
    2517             :     /*
    2518             :      * We always emit the node's NodeTag, then any additional fields that are
    2519             :      * considered significant, and then we recurse to any child nodes.
    2520             :      */
    2521        1776 :     APP_JUMB(node->type);
    2522             : 
    2523        1776 :     switch (nodeTag(node))
    2524             :     {
    2525             :         case T_Var:
    2526             :             {
    2527         210 :                 Var        *var = (Var *) node;
    2528             : 
    2529         210 :                 APP_JUMB(var->varno);
    2530         210 :                 APP_JUMB(var->varattno);
    2531         210 :                 APP_JUMB(var->varlevelsup);
    2532             :             }
    2533         210 :             break;
    2534             :         case T_Const:
    2535             :             {
    2536         260 :                 Const      *c = (Const *) node;
    2537             : 
    2538             :                 /* We jumble only the constant's type, not its value */
    2539         260 :                 APP_JUMB(c->consttype);
    2540             :                 /* Also, record its parse location for query normalization */
    2541         260 :                 RecordConstLocation(jstate, c->location);
    2542             :             }
    2543         260 :             break;
    2544             :         case T_Param:
    2545             :             {
    2546          18 :                 Param      *p = (Param *) node;
    2547             : 
    2548          18 :                 APP_JUMB(p->paramkind);
    2549          18 :                 APP_JUMB(p->paramid);
    2550          18 :                 APP_JUMB(p->paramtype);
    2551             :                 /* Also, track the highest external Param id */
    2552          36 :                 if (p->paramkind == PARAM_EXTERN &&
    2553          18 :                     p->paramid > jstate->highest_extern_param_id)
    2554          18 :                     jstate->highest_extern_param_id = p->paramid;
    2555             :             }
    2556          18 :             break;
    2557             :         case T_Aggref:
    2558             :             {
    2559           0 :                 Aggref     *expr = (Aggref *) node;
    2560             : 
    2561           0 :                 APP_JUMB(expr->aggfnoid);
    2562           0 :                 JumbleExpr(jstate, (Node *) expr->aggdirectargs);
    2563           0 :                 JumbleExpr(jstate, (Node *) expr->args);
    2564           0 :                 JumbleExpr(jstate, (Node *) expr->aggorder);
    2565           0 :                 JumbleExpr(jstate, (Node *) expr->aggdistinct);
    2566           0 :                 JumbleExpr(jstate, (Node *) expr->aggfilter);
    2567             :             }
    2568           0 :             break;
    2569             :         case T_GroupingFunc:
    2570             :             {
    2571           0 :                 GroupingFunc *grpnode = (GroupingFunc *) node;
    2572             : 
    2573           0 :                 JumbleExpr(jstate, (Node *) grpnode->refs);
    2574             :             }
    2575           0 :             break;
    2576             :         case T_WindowFunc:
    2577             :             {
    2578           0 :                 WindowFunc *expr = (WindowFunc *) node;
    2579             : 
    2580           0 :                 APP_JUMB(expr->winfnoid);
    2581           0 :                 APP_JUMB(expr->winref);
    2582           0 :                 JumbleExpr(jstate, (Node *) expr->args);
    2583           0 :                 JumbleExpr(jstate, (Node *) expr->aggfilter);
    2584             :             }
    2585           0 :             break;
    2586             :         case T_SubscriptingRef:
    2587             :             {
    2588           0 :                 SubscriptingRef *sbsref = (SubscriptingRef *) node;
    2589             : 
    2590           0 :                 JumbleExpr(jstate, (Node *) sbsref->refupperindexpr);
    2591           0 :                 JumbleExpr(jstate, (Node *) sbsref->reflowerindexpr);
    2592           0 :                 JumbleExpr(jstate, (Node *) sbsref->refexpr);
    2593           0 :                 JumbleExpr(jstate, (Node *) sbsref->refassgnexpr);
    2594             :             }
    2595           0 :             break;
    2596             :         case T_FuncExpr:
    2597             :             {
    2598         118 :                 FuncExpr   *expr = (FuncExpr *) node;
    2599             : 
    2600         118 :                 APP_JUMB(expr->funcid);
    2601         118 :                 JumbleExpr(jstate, (Node *) expr->args);
    2602             :             }
    2603         118 :             break;
    2604             :         case T_NamedArgExpr:
    2605             :             {
    2606           0 :                 NamedArgExpr *nae = (NamedArgExpr *) node;
    2607             : 
    2608           0 :                 APP_JUMB(nae->argnumber);
    2609           0 :                 JumbleExpr(jstate, (Node *) nae->arg);
    2610             :             }
    2611           0 :             break;
    2612             :         case T_OpExpr:
    2613             :         case T_DistinctExpr:    /* struct-equivalent to OpExpr */
    2614             :         case T_NullIfExpr:      /* struct-equivalent to OpExpr */
    2615             :             {
    2616          76 :                 OpExpr     *expr = (OpExpr *) node;
    2617             : 
    2618          76 :                 APP_JUMB(expr->opno);
    2619          76 :                 JumbleExpr(jstate, (Node *) expr->args);
    2620             :             }
    2621          76 :             break;
    2622             :         case T_ScalarArrayOpExpr:
    2623             :             {
    2624           2 :                 ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
    2625             : 
    2626           2 :                 APP_JUMB(expr->opno);
    2627           2 :                 APP_JUMB(expr->useOr);
    2628           2 :                 JumbleExpr(jstate, (Node *) expr->args);
    2629             :             }
    2630           2 :             break;
    2631             :         case T_BoolExpr:
    2632             :             {
    2633           0 :                 BoolExpr   *expr = (BoolExpr *) node;
    2634             : 
    2635           0 :                 APP_JUMB(expr->boolop);
    2636           0 :                 JumbleExpr(jstate, (Node *) expr->args);
    2637             :             }
    2638           0 :             break;
    2639             :         case T_SubLink:
    2640             :             {
    2641           6 :                 SubLink    *sublink = (SubLink *) node;
    2642             : 
    2643           6 :                 APP_JUMB(sublink->subLinkType);
    2644           6 :                 APP_JUMB(sublink->subLinkId);
    2645           6 :                 JumbleExpr(jstate, (Node *) sublink->testexpr);
    2646           6 :                 JumbleQuery(jstate, castNode(Query, sublink->subselect));
    2647             :             }
    2648           6 :             break;
    2649             :         case T_FieldSelect:
    2650             :             {
    2651           0 :                 FieldSelect *fs = (FieldSelect *) node;
    2652             : 
    2653           0 :                 APP_JUMB(fs->fieldnum);
    2654           0 :                 JumbleExpr(jstate, (Node *) fs->arg);
    2655             :             }
    2656           0 :             break;
    2657             :         case T_FieldStore:
    2658             :             {
    2659           0 :                 FieldStore *fstore = (FieldStore *) node;
    2660             : 
    2661           0 :                 JumbleExpr(jstate, (Node *) fstore->arg);
    2662           0 :                 JumbleExpr(jstate, (Node *) fstore->newvals);
    2663             :             }
    2664           0 :             break;
    2665             :         case T_RelabelType:
    2666             :             {
    2667           8 :                 RelabelType *rt = (RelabelType *) node;
    2668             : 
    2669           8 :                 APP_JUMB(rt->resulttype);
    2670           8 :                 JumbleExpr(jstate, (Node *) rt->arg);
    2671             :             }
    2672           8 :             break;
    2673             :         case T_CoerceViaIO:
    2674             :             {
    2675           0 :                 CoerceViaIO *cio = (CoerceViaIO *) node;
    2676             : 
    2677           0 :                 APP_JUMB(cio->resulttype);
    2678           0 :                 JumbleExpr(jstate, (Node *) cio->arg);
    2679             :             }
    2680           0 :             break;
    2681             :         case T_ArrayCoerceExpr:
    2682             :             {
    2683           0 :                 ArrayCoerceExpr *acexpr = (ArrayCoerceExpr *) node;
    2684             : 
    2685           0 :                 APP_JUMB(acexpr->resulttype);
    2686           0 :                 JumbleExpr(jstate, (Node *) acexpr->arg);
    2687           0 :                 JumbleExpr(jstate, (Node *) acexpr->elemexpr);
    2688             :             }
    2689           0 :             break;
    2690             :         case T_ConvertRowtypeExpr:
    2691             :             {
    2692           0 :                 ConvertRowtypeExpr *crexpr = (ConvertRowtypeExpr *) node;
    2693             : 
    2694           0 :                 APP_JUMB(crexpr->resulttype);
    2695           0 :                 JumbleExpr(jstate, (Node *) crexpr->arg);
    2696             :             }
    2697           0 :             break;
    2698             :         case T_CollateExpr:
    2699             :             {
    2700          24 :                 CollateExpr *ce = (CollateExpr *) node;
    2701             : 
    2702          24 :                 APP_JUMB(ce->collOid);
    2703          24 :                 JumbleExpr(jstate, (Node *) ce->arg);
    2704             :             }
    2705          24 :             break;
    2706             :         case T_CaseExpr:
    2707             :             {
    2708           0 :                 CaseExpr   *caseexpr = (CaseExpr *) node;
    2709             : 
    2710           0 :                 JumbleExpr(jstate, (Node *) caseexpr->arg);
    2711           0 :                 foreach(temp, caseexpr->args)
    2712             :                 {
    2713           0 :                     CaseWhen   *when = lfirst_node(CaseWhen, temp);
    2714             : 
    2715           0 :                     JumbleExpr(jstate, (Node *) when->expr);
    2716           0 :                     JumbleExpr(jstate, (Node *) when->result);
    2717             :                 }
    2718           0 :                 JumbleExpr(jstate, (Node *) caseexpr->defresult);
    2719             :             }
    2720           0 :             break;
    2721             :         case T_CaseTestExpr:
    2722             :             {
    2723           0 :                 CaseTestExpr *ct = (CaseTestExpr *) node;
    2724             : 
    2725           0 :                 APP_JUMB(ct->typeId);
    2726             :             }
    2727           0 :             break;
    2728             :         case T_ArrayExpr:
    2729           2 :             JumbleExpr(jstate, (Node *) ((ArrayExpr *) node)->elements);
    2730           2 :             break;
    2731             :         case T_RowExpr:
    2732           0 :             JumbleExpr(jstate, (Node *) ((RowExpr *) node)->args);
    2733           0 :             break;
    2734             :         case T_RowCompareExpr:
    2735             :             {
    2736           0 :                 RowCompareExpr *rcexpr = (RowCompareExpr *) node;
    2737             : 
    2738           0 :                 APP_JUMB(rcexpr->rctype);
    2739           0 :                 JumbleExpr(jstate, (Node *) rcexpr->largs);
    2740           0 :                 JumbleExpr(jstate, (Node *) rcexpr->rargs);
    2741             :             }
    2742           0 :             break;
    2743             :         case T_CoalesceExpr:
    2744           0 :             JumbleExpr(jstate, (Node *) ((CoalesceExpr *) node)->args);
    2745           0 :             break;
    2746             :         case T_MinMaxExpr:
    2747             :             {
    2748           0 :                 MinMaxExpr *mmexpr = (MinMaxExpr *) node;
    2749             : 
    2750           0 :                 APP_JUMB(mmexpr->op);
    2751           0 :                 JumbleExpr(jstate, (Node *) mmexpr->args);
    2752             :             }
    2753           0 :             break;
    2754             :         case T_SQLValueFunction:
    2755             :             {
    2756           0 :                 SQLValueFunction *svf = (SQLValueFunction *) node;
    2757             : 
    2758           0 :                 APP_JUMB(svf->op);
    2759             :                 /* type is fully determined by op */
    2760           0 :                 APP_JUMB(svf->typmod);
    2761             :             }
    2762           0 :             break;
    2763             :         case T_XmlExpr:
    2764             :             {
    2765           0 :                 XmlExpr    *xexpr = (XmlExpr *) node;
    2766             : 
    2767           0 :                 APP_JUMB(xexpr->op);
    2768           0 :                 JumbleExpr(jstate, (Node *) xexpr->named_args);
    2769           0 :                 JumbleExpr(jstate, (Node *) xexpr->args);
    2770             :             }
    2771           0 :             break;
    2772             :         case T_NullTest:
    2773             :             {
    2774           0 :                 NullTest   *nt = (NullTest *) node;
    2775             : 
    2776           0 :                 APP_JUMB(nt->nulltesttype);
    2777           0 :                 JumbleExpr(jstate, (Node *) nt->arg);
    2778             :             }
    2779           0 :             break;
    2780             :         case T_BooleanTest:
    2781             :             {
    2782           0 :                 BooleanTest *bt = (BooleanTest *) node;
    2783             : 
    2784           0 :                 APP_JUMB(bt->booltesttype);
    2785           0 :                 JumbleExpr(jstate, (Node *) bt->arg);
    2786             :             }
    2787           0 :             break;
    2788             :         case T_CoerceToDomain:
    2789             :             {
    2790           0 :                 CoerceToDomain *cd = (CoerceToDomain *) node;
    2791             : 
    2792           0 :                 APP_JUMB(cd->resulttype);
    2793           0 :                 JumbleExpr(jstate, (Node *) cd->arg);
    2794             :             }
    2795           0 :             break;
    2796             :         case T_CoerceToDomainValue:
    2797             :             {
    2798           0 :                 CoerceToDomainValue *cdv = (CoerceToDomainValue *) node;
    2799             : 
    2800           0 :                 APP_JUMB(cdv->typeId);
    2801             :             }
    2802           0 :             break;
    2803             :         case T_SetToDefault:
    2804             :             {
    2805           0 :                 SetToDefault *sd = (SetToDefault *) node;
    2806             : 
    2807           0 :                 APP_JUMB(sd->typeId);
    2808             :             }
    2809           0 :             break;
    2810             :         case T_CurrentOfExpr:
    2811             :             {
    2812           0 :                 CurrentOfExpr *ce = (CurrentOfExpr *) node;
    2813             : 
    2814           0 :                 APP_JUMB(ce->cvarno);
    2815           0 :                 if (ce->cursor_name)
    2816           0 :                     APP_JUMB_STRING(ce->cursor_name);
    2817           0 :                 APP_JUMB(ce->cursor_param);
    2818             :             }
    2819           0 :             break;
    2820             :         case T_NextValueExpr:
    2821             :             {
    2822           0 :                 NextValueExpr *nve = (NextValueExpr *) node;
    2823             : 
    2824           0 :                 APP_JUMB(nve->seqid);
    2825           0 :                 APP_JUMB(nve->typeId);
    2826             :             }
    2827           0 :             break;
    2828             :         case T_InferenceElem:
    2829             :             {
    2830           0 :                 InferenceElem *ie = (InferenceElem *) node;
    2831             : 
    2832           0 :                 APP_JUMB(ie->infercollid);
    2833           0 :                 APP_JUMB(ie->inferopclass);
    2834           0 :                 JumbleExpr(jstate, ie->expr);
    2835             :             }
    2836           0 :             break;
    2837             :         case T_TargetEntry:
    2838             :             {
    2839         300 :                 TargetEntry *tle = (TargetEntry *) node;
    2840             : 
    2841         300 :                 APP_JUMB(tle->resno);
    2842         300 :                 APP_JUMB(tle->ressortgroupref);
    2843         300 :                 JumbleExpr(jstate, (Node *) tle->expr);
    2844             :             }
    2845         300 :             break;
    2846             :         case T_RangeTblRef:
    2847             :             {
    2848          70 :                 RangeTblRef *rtr = (RangeTblRef *) node;
    2849             : 
    2850          70 :                 APP_JUMB(rtr->rtindex);
    2851             :             }
    2852          70 :             break;
    2853             :         case T_JoinExpr:
    2854             :             {
    2855           0 :                 JoinExpr   *join = (JoinExpr *) node;
    2856             : 
    2857           0 :                 APP_JUMB(join->jointype);
    2858           0 :                 APP_JUMB(join->isNatural);
    2859           0 :                 APP_JUMB(join->rtindex);
    2860           0 :                 JumbleExpr(jstate, join->larg);
    2861           0 :                 JumbleExpr(jstate, join->rarg);
    2862           0 :                 JumbleExpr(jstate, join->quals);
    2863             :             }
    2864           0 :             break;
    2865             :         case T_FromExpr:
    2866             :             {
    2867         172 :                 FromExpr   *from = (FromExpr *) node;
    2868             : 
    2869         172 :                 JumbleExpr(jstate, (Node *) from->fromlist);
    2870         172 :                 JumbleExpr(jstate, from->quals);
    2871             :             }
    2872         172 :             break;
    2873             :         case T_OnConflictExpr:
    2874             :             {
    2875           0 :                 OnConflictExpr *conf = (OnConflictExpr *) node;
    2876             : 
    2877           0 :                 APP_JUMB(conf->action);
    2878           0 :                 JumbleExpr(jstate, (Node *) conf->arbiterElems);
    2879           0 :                 JumbleExpr(jstate, conf->arbiterWhere);
    2880           0 :                 JumbleExpr(jstate, (Node *) conf->onConflictSet);
    2881           0 :                 JumbleExpr(jstate, conf->onConflictWhere);
    2882           0 :                 APP_JUMB(conf->constraint);
    2883           0 :                 APP_JUMB(conf->exclRelIndex);
    2884           0 :                 JumbleExpr(jstate, (Node *) conf->exclRelTlist);
    2885             :             }
    2886           0 :             break;
    2887             :         case T_List:
    2888        1226 :             foreach(temp, (List *) node)
    2889             :             {
    2890         756 :                 JumbleExpr(jstate, (Node *) lfirst(temp));
    2891             :             }
    2892         470 :             break;
    2893             :         case T_IntList:
    2894           0 :             foreach(temp, (List *) node)
    2895             :             {
    2896           0 :                 APP_JUMB(lfirst_int(temp));
    2897             :             }
    2898           0 :             break;
    2899             :         case T_SortGroupClause:
    2900             :             {
    2901          34 :                 SortGroupClause *sgc = (SortGroupClause *) node;
    2902             : 
    2903          34 :                 APP_JUMB(sgc->tleSortGroupRef);
    2904          34 :                 APP_JUMB(sgc->eqop);
    2905          34 :                 APP_JUMB(sgc->sortop);
    2906          34 :                 APP_JUMB(sgc->nulls_first);
    2907             :             }
    2908          34 :             break;
    2909             :         case T_GroupingSet:
    2910             :             {
    2911           0 :                 GroupingSet *gsnode = (GroupingSet *) node;
    2912             : 
    2913           0 :                 JumbleExpr(jstate, (Node *) gsnode->content);
    2914             :             }
    2915           0 :             break;
    2916             :         case T_WindowClause:
    2917             :             {
    2918           0 :                 WindowClause *wc = (WindowClause *) node;
    2919             : 
    2920           0 :                 APP_JUMB(wc->winref);
    2921           0 :                 APP_JUMB(wc->frameOptions);
    2922           0 :                 JumbleExpr(jstate, (Node *) wc->partitionClause);
    2923           0 :                 JumbleExpr(jstate, (Node *) wc->orderClause);
    2924           0 :                 JumbleExpr(jstate, wc->startOffset);
    2925           0 :                 JumbleExpr(jstate, wc->endOffset);
    2926             :             }
    2927           0 :             break;
    2928             :         case T_CommonTableExpr:
    2929             :             {
    2930           2 :                 CommonTableExpr *cte = (CommonTableExpr *) node;
    2931             : 
    2932             :                 /* we store the string name because RTE_CTE RTEs need it */
    2933           2 :                 APP_JUMB_STRING(cte->ctename);
    2934           2 :                 APP_JUMB(cte->ctematerialized);
    2935           2 :                 JumbleQuery(jstate, castNode(Query, cte->ctequery));
    2936             :             }
    2937           2 :             break;
    2938             :         case T_SetOperationStmt:
    2939             :             {
    2940           2 :                 SetOperationStmt *setop = (SetOperationStmt *) node;
    2941             : 
    2942           2 :                 APP_JUMB(setop->op);
    2943           2 :                 APP_JUMB(setop->all);
    2944           2 :                 JumbleExpr(jstate, setop->larg);
    2945           2 :                 JumbleExpr(jstate, setop->rarg);
    2946             :             }
    2947           2 :             break;
    2948             :         case T_RangeTblFunction:
    2949             :             {
    2950           2 :                 RangeTblFunction *rtfunc = (RangeTblFunction *) node;
    2951             : 
    2952           2 :                 JumbleExpr(jstate, rtfunc->funcexpr);
    2953             :             }
    2954           2 :             break;
    2955             :         case T_TableFunc:
    2956             :             {
    2957           0 :                 TableFunc  *tablefunc = (TableFunc *) node;
    2958             : 
    2959           0 :                 JumbleExpr(jstate, tablefunc->docexpr);
    2960           0 :                 JumbleExpr(jstate, tablefunc->rowexpr);
    2961           0 :                 JumbleExpr(jstate, (Node *) tablefunc->colexprs);
    2962             :             }
    2963           0 :             break;
    2964             :         case T_TableSampleClause:
    2965             :             {
    2966           0 :                 TableSampleClause *tsc = (TableSampleClause *) node;
    2967             : 
    2968           0 :                 APP_JUMB(tsc->tsmhandler);
    2969           0 :                 JumbleExpr(jstate, (Node *) tsc->args);
    2970           0 :                 JumbleExpr(jstate, (Node *) tsc->repeatable);
    2971             :             }
    2972           0 :             break;
    2973             :         default:
    2974             :             /* Only a warning, since we can stumble along anyway */
    2975           0 :             elog(WARNING, "unrecognized node type: %d",
    2976             :                  (int) nodeTag(node));
    2977           0 :             break;
    2978             :     }
    2979             : }
    2980             : 
    2981             : /*
    2982             :  * Record location of constant within query string of query tree
    2983             :  * that is currently being walked.
    2984             :  */
    2985             : static void
    2986         260 : RecordConstLocation(pgssJumbleState *jstate, int location)
    2987             : {
    2988             :     /* -1 indicates unknown or undefined location */
    2989         260 :     if (location >= 0)
    2990             :     {
    2991             :         /* enlarge array if needed */
    2992         216 :         if (jstate->clocations_count >= jstate->clocations_buf_size)
    2993             :         {
    2994           0 :             jstate->clocations_buf_size *= 2;
    2995           0 :             jstate->clocations = (pgssLocationLen *)
    2996           0 :                 repalloc(jstate->clocations,
    2997           0 :                          jstate->clocations_buf_size *
    2998             :                          sizeof(pgssLocationLen));
    2999             :         }
    3000         216 :         jstate->clocations[jstate->clocations_count].location = location;
    3001             :         /* initialize lengths to -1 to simplify fill_in_constant_lengths */
    3002         216 :         jstate->clocations[jstate->clocations_count].length = -1;
    3003         216 :         jstate->clocations_count++;
    3004             :     }
    3005         260 : }
    3006             : 
    3007             : /*
    3008             :  * Generate a normalized version of the query string that will be used to
    3009             :  * represent all similar queries.
    3010             :  *
    3011             :  * Note that the normalized representation may well vary depending on
    3012             :  * just which "equivalent" query is used to create the hashtable entry.
    3013             :  * We assume this is OK.
    3014             :  *
    3015             :  * If query_loc > 0, then "query" has been advanced by that much compared to
    3016             :  * the original string start, so we need to translate the provided locations
    3017             :  * to compensate.  (This lets us avoid re-scanning statements before the one
    3018             :  * of interest, so it's worth doing.)
    3019             :  *
    3020             :  * *query_len_p contains the input string length, and is updated with
    3021             :  * the result string length on exit.  The resulting string might be longer
    3022             :  * or shorter depending on what happens with replacement of constants.
    3023             :  *
    3024             :  * Returns a palloc'd string.
    3025             :  */
    3026             : static char *
    3027          78 : generate_normalized_query(pgssJumbleState *jstate, const char *query,
    3028             :                           int query_loc, int *query_len_p, int encoding)
    3029             : {
    3030             :     char       *norm_query;
    3031          78 :     int         query_len = *query_len_p;
    3032             :     int         i,
    3033             :                 norm_query_buflen,  /* Space allowed for norm_query */
    3034             :                 len_to_wrt,     /* Length (in bytes) to write */
    3035          78 :                 quer_loc = 0,   /* Source query byte location */
    3036          78 :                 n_quer_loc = 0, /* Normalized query byte location */
    3037          78 :                 last_off = 0,   /* Offset from start for previous tok */
    3038          78 :                 last_tok_len = 0;   /* Length (in bytes) of that tok */
    3039             : 
    3040             :     /*
    3041             :      * Get constants' lengths (core system only gives us locations).  Note
    3042             :      * this also ensures the items are sorted by location.
    3043             :      */
    3044          78 :     fill_in_constant_lengths(jstate, query, query_loc);
    3045             : 
    3046             :     /*
    3047             :      * Allow for $n symbols to be longer than the constants they replace.
    3048             :      * Constants must take at least one byte in text form, while a $n symbol
    3049             :      * certainly isn't more than 11 bytes, even if n reaches INT_MAX.  We
    3050             :      * could refine that limit based on the max value of n for the current
    3051             :      * query, but it hardly seems worth any extra effort to do so.
    3052             :      */
    3053          78 :     norm_query_buflen = query_len + jstate->clocations_count * 10;
    3054             : 
    3055             :     /* Allocate result buffer */
    3056          78 :     norm_query = palloc(norm_query_buflen + 1);
    3057             : 
    3058         224 :     for (i = 0; i < jstate->clocations_count; i++)
    3059             :     {
    3060             :         int         off,        /* Offset from start for cur tok */
    3061             :                     tok_len;    /* Length (in bytes) of that tok */
    3062             : 
    3063         146 :         off = jstate->clocations[i].location;
    3064             :         /* Adjust recorded location if we're dealing with partial string */
    3065         146 :         off -= query_loc;
    3066             : 
    3067         146 :         tok_len = jstate->clocations[i].length;
    3068             : 
    3069         146 :         if (tok_len < 0)
    3070           0 :             continue;           /* ignore any duplicates */
    3071             : 
    3072             :         /* Copy next chunk (what precedes the next constant) */
    3073         146 :         len_to_wrt = off - last_off;
    3074         146 :         len_to_wrt -= last_tok_len;
    3075             : 
    3076             :         Assert(len_to_wrt >= 0);
    3077         146 :         memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt);
    3078         146 :         n_quer_loc += len_to_wrt;
    3079             : 
    3080             :         /* And insert a param symbol in place of the constant token */
    3081         146 :         n_quer_loc += sprintf(norm_query + n_quer_loc, "$%d",
    3082         146 :                               i + 1 + jstate->highest_extern_param_id);
    3083             : 
    3084         146 :         quer_loc = off + tok_len;
    3085         146 :         last_off = off;
    3086         146 :         last_tok_len = tok_len;
    3087             :     }
    3088             : 
    3089             :     /*
    3090             :      * We've copied up until the last ignorable constant.  Copy over the
    3091             :      * remaining bytes of the original query string.
    3092             :      */
    3093          78 :     len_to_wrt = query_len - quer_loc;
    3094             : 
    3095             :     Assert(len_to_wrt >= 0);
    3096          78 :     memcpy(norm_query + n_quer_loc, query + quer_loc, len_to_wrt);
    3097          78 :     n_quer_loc += len_to_wrt;
    3098             : 
    3099             :     Assert(n_quer_loc <= norm_query_buflen);
    3100          78 :     norm_query[n_quer_loc] = '\0';
    3101             : 
    3102          78 :     *query_len_p = n_quer_loc;
    3103          78 :     return norm_query;
    3104             : }
    3105             : 
    3106             : /*
    3107             :  * Given a valid SQL string and an array of constant-location records,
    3108             :  * fill in the textual lengths of those constants.
    3109             :  *
    3110             :  * The constants may use any allowed constant syntax, such as float literals,
    3111             :  * bit-strings, single-quoted strings and dollar-quoted strings.  This is
    3112             :  * accomplished by using the public API for the core scanner.
    3113             :  *
    3114             :  * It is the caller's job to ensure that the string is a valid SQL statement
    3115             :  * with constants at the indicated locations.  Since in practice the string
    3116             :  * has already been parsed, and the locations that the caller provides will
    3117             :  * have originated from within the authoritative parser, this should not be
    3118             :  * a problem.
    3119             :  *
    3120             :  * Duplicate constant pointers are possible, and will have their lengths
    3121             :  * marked as '-1', so that they are later ignored.  (Actually, we assume the
    3122             :  * lengths were initialized as -1 to start with, and don't change them here.)
    3123             :  *
    3124             :  * If query_loc > 0, then "query" has been advanced by that much compared to
    3125             :  * the original string start, so we need to translate the provided locations
    3126             :  * to compensate.  (This lets us avoid re-scanning statements before the one
    3127             :  * of interest, so it's worth doing.)
    3128             :  *
    3129             :  * N.B. There is an assumption that a '-' character at a Const location begins
    3130             :  * a negative numeric constant.  This precludes there ever being another
    3131             :  * reason for a constant to start with a '-'.
    3132             :  */
    3133             : static void
    3134          78 : fill_in_constant_lengths(pgssJumbleState *jstate, const char *query,
    3135             :                          int query_loc)
    3136             : {
    3137             :     pgssLocationLen *locs;
    3138             :     core_yyscan_t yyscanner;
    3139             :     core_yy_extra_type yyextra;
    3140             :     core_YYSTYPE yylval;
    3141             :     YYLTYPE     yylloc;
    3142          78 :     int         last_loc = -1;
    3143             :     int         i;
    3144             : 
    3145             :     /*
    3146             :      * Sort the records by location so that we can process them in order while
    3147             :      * scanning the query text.
    3148             :      */
    3149          78 :     if (jstate->clocations_count > 1)
    3150          44 :         qsort(jstate->clocations, jstate->clocations_count,
    3151             :               sizeof(pgssLocationLen), comp_location);
    3152          78 :     locs = jstate->clocations;
    3153             : 
    3154             :     /* initialize the flex scanner --- should match raw_parser() */
    3155          78 :     yyscanner = scanner_init(query,
    3156             :                              &yyextra,
    3157             :                              &ScanKeywords,
    3158             :                              ScanKeywordTokens);
    3159             : 
    3160             :     /* we don't want to re-emit any escape string warnings */
    3161          78 :     yyextra.escape_string_warning = false;
    3162             : 
    3163             :     /* Search for each constant, in sequence */
    3164         224 :     for (i = 0; i < jstate->clocations_count; i++)
    3165             :     {
    3166         146 :         int         loc = locs[i].location;
    3167             :         int         tok;
    3168             : 
    3169             :         /* Adjust recorded location if we're dealing with partial string */
    3170         146 :         loc -= query_loc;
    3171             : 
    3172             :         Assert(loc >= 0);
    3173             : 
    3174         146 :         if (loc <= last_loc)
    3175           0 :             continue;           /* Duplicate constant, ignore */
    3176             : 
    3177             :         /* Lex tokens until we find the desired constant */
    3178             :         for (;;)
    3179             :         {
    3180        1194 :             tok = core_yylex(&yylval, &yylloc, yyscanner);
    3181             : 
    3182             :             /* We should not hit end-of-string, but if we do, behave sanely */
    3183         670 :             if (tok == 0)
    3184           0 :                 break;          /* out of inner for-loop */
    3185             : 
    3186             :             /*
    3187             :              * We should find the token position exactly, but if we somehow
    3188             :              * run past it, work with that.
    3189             :              */
    3190         670 :             if (yylloc >= loc)
    3191             :             {
    3192         146 :                 if (query[loc] == '-')
    3193             :                 {
    3194             :                     /*
    3195             :                      * It's a negative value - this is the one and only case
    3196             :                      * where we replace more than a single token.
    3197             :                      *
    3198             :                      * Do not compensate for the core system's special-case
    3199             :                      * adjustment of location to that of the leading '-'
    3200             :                      * operator in the event of a negative constant.  It is
    3201             :                      * also useful for our purposes to start from the minus
    3202             :                      * symbol.  In this way, queries like "select * from foo
    3203             :                      * where bar = 1" and "select * from foo where bar = -2"
    3204             :                      * will have identical normalized query strings.
    3205             :                      */
    3206           2 :                     tok = core_yylex(&yylval, &yylloc, yyscanner);
    3207           2 :                     if (tok == 0)
    3208           0 :                         break;  /* out of inner for-loop */
    3209             :                 }
    3210             : 
    3211             :                 /*
    3212             :                  * We now rely on the assumption that flex has placed a zero
    3213             :                  * byte after the text of the current token in scanbuf.
    3214             :                  */
    3215         146 :                 locs[i].length = strlen(yyextra.scanbuf + loc);
    3216         146 :                 break;          /* out of inner for-loop */
    3217             :             }
    3218             :         }
    3219             : 
    3220             :         /* If we hit end-of-string, give up, leaving remaining lengths -1 */
    3221         146 :         if (tok == 0)
    3222           0 :             break;
    3223             : 
    3224         146 :         last_loc = loc;
    3225             :     }
    3226             : 
    3227          78 :     scanner_finish(yyscanner);
    3228          78 : }
    3229             : 
    3230             : /*
    3231             :  * comp_location: comparator for qsorting pgssLocationLen structs by location
    3232             :  */
    3233             : static int
    3234          70 : comp_location(const void *a, const void *b)
    3235             : {
    3236          70 :     int         l = ((const pgssLocationLen *) a)->location;
    3237          70 :     int         r = ((const pgssLocationLen *) b)->location;
    3238             : 
    3239          70 :     if (l < r)
    3240          62 :         return -1;
    3241           8 :     else if (l > r)
    3242           8 :         return +1;
    3243             :     else
    3244           0 :         return 0;
    3245             : }

Generated by: LCOV version 1.13