diff --git a/contrib/pg_stat_statements/pg_stat_statements.c b/contrib/pg_stat_statements/pg_stat_statements.c
new file mode 100644
index 434aa71..8bc9cc5
*** a/contrib/pg_stat_statements/pg_stat_statements.c
--- b/contrib/pg_stat_statements/pg_stat_statements.c
***************
*** 10,15 ****
--- 10,34 ----
   * an entry, one must hold the lock shared or exclusive (so the entry doesn't
   * disappear!) and also take the entry's mutex spinlock.
   *
+  * As of Postgres 9.2, this module normalizes query strings. Normalization is a
+  * process whereby similar queries, typically differing only in their constants
+  * (though the exact rules are somewhat more subtle than that) are recognized as
+  * equivalent, and are tracked as a single entry. This is particularly useful
+  * for non-prepared queries.
+  *
+  * Normalization is implemented by selectively serializing fields of each query
+  * tree's nodes, which are judged to be essential to the nature of the query.
+  * This is referred to as a query jumble. This is distinct from a straight
+  * serialization of the query tree in that constants are canonicalized, and
+  * various extraneous information is ignored as irrelevant, such as the
+  * collation of Vars. Once this jumble is acquired, a 64-bit hash is taken,
+  * which is copied back into the query tree at the post-analysis stage.
+  * Postgres then naively copies this value around, making it later available
+  * from within the corresponding plan tree. The executor can then use this value
+  * to blame query costs on a known query_id.
+  *
+  * Within the executor hook, the module stores the cost of the queries
+  * execution, based on a query_id provided by the core system.
   *
   * Copyright (c) 2008-2012, PostgreSQL Global Development Group
   *
***************
*** 22,38 ****
--- 41,67 ----
  
  #include <unistd.h>
  
+ /*
+  * XXX: include scanner.h first, to prevent code from gram.h complaining about
+  * lack of a core-parser type definition.
+  */
+ #include "parser/scanner.h"
+ 
  #include "access/hash.h"
  #include "executor/instrument.h"
  #include "funcapi.h"
  #include "mb/pg_wchar.h"
  #include "miscadmin.h"
+ #include "parser/analyze.h"
+ #include "parser/gram.h"
+ #include "parser/parsetree.h"
  #include "pgstat.h"
  #include "storage/fd.h"
  #include "storage/ipc.h"
  #include "storage/spin.h"
  #include "tcop/utility.h"
  #include "utils/builtins.h"
+ #include "utils/memutils.h"
  
  
  PG_MODULE_MAGIC;
*************** PG_MODULE_MAGIC;
*** 41,54 ****
  #define PGSS_DUMP_FILE	"global/pg_stat_statements.stat"
  
  /* This constant defines the magic number in the stats file header */
! static const uint32 PGSS_FILE_HEADER = 0x20100108;
  
  /* XXX: Should USAGE_EXEC reflect execution time and/or buffer usage? */
  #define USAGE_EXEC(duration)	(1.0)
  #define USAGE_INIT				(1.0)	/* including initial planning */
  #define USAGE_DECREASE_FACTOR	(0.99)	/* decreased every entry_dealloc */
  #define USAGE_DEALLOC_PERCENT	5		/* free this % of entries at once */
! 
  /*
   * Hashtable key that defines the identity of a hashtable entry.  The
   * hash comparators do not assume that the query string is null-terminated;
--- 70,88 ----
  #define PGSS_DUMP_FILE	"global/pg_stat_statements.stat"
  
  /* This constant defines the magic number in the stats file header */
! static const uint32 PGSS_FILE_HEADER = 0x20120103;
  
  /* XXX: Should USAGE_EXEC reflect execution time and/or buffer usage? */
  #define USAGE_EXEC(duration)	(1.0)
  #define USAGE_INIT				(1.0)	/* including initial planning */
  #define USAGE_DECREASE_FACTOR	(0.99)	/* decreased every entry_dealloc */
  #define USAGE_DEALLOC_PERCENT	5		/* free this % of entries at once */
! #define JUMBLE_SIZE				1024    /* query serialization buffer size */
! /* Magic values for jumble */
! #define MAG_HASH_BUF				0xFA	/* buffer is a hash of query tree */
! #define MAG_STR_BUF					0xEB	/* buffer is query string itself */
! #define MAG_RETURN_LIST				0xAE	/* returning list node follows */
! #define MAG_LIMIT_OFFSET			0xBA	/* limit/offset node follows */
  /*
   * Hashtable key that defines the identity of a hashtable entry.  The
   * hash comparators do not assume that the query string is null-terminated;
*************** typedef struct pgssHashKey
*** 63,70 ****
  	Oid			userid;			/* user OID */
  	Oid			dbid;			/* database OID */
  	int			encoding;		/* query encoding */
! 	int			query_len;		/* # of valid bytes in query string */
! 	const char *query_ptr;		/* query string proper */
  } pgssHashKey;
  
  /*
--- 97,103 ----
  	Oid			userid;			/* user OID */
  	Oid			dbid;			/* database OID */
  	int			encoding;		/* query encoding */
! 	uint64		query_id;		/* query identifier */
  } pgssHashKey;
  
  /*
*************** typedef struct pgssEntry
*** 95,100 ****
--- 128,134 ----
  {
  	pgssHashKey key;			/* hash key of entry - MUST BE FIRST */
  	Counters	counters;		/* the statistics for this query */
+ 	int			query_len;		/* # of valid bytes in query string */
  	slock_t		mutex;			/* protects the counters only */
  	char		query[1];		/* VARIABLE LENGTH ARRAY - MUST BE LAST */
  	/* Note: the allocated length of query[] is actually pgss->query_size */
*************** typedef struct pgssSharedState
*** 109,115 ****
--- 143,167 ----
  	int			query_size;		/* max query length in bytes */
  } pgssSharedState;
  
+ /*
+  * Last seen constant positions for a statement
+  */
+ typedef struct pgssQueryConEntry
+ {
+ 	pgssHashKey		key;			/* hash key of entry - MUST BE FIRST */
+ 	int				n_elems;		/* length of offsets array */
+ 	Size offsets[1];		/* VARIABLE LENGTH ARRAY - MUST BE LAST */
+ 	/* Note: the allocated length of offsets is actually n_elems */
+ } pgssQueryConEntry;
  /*---- Local variables ----*/
+ /* Jumble of current query tree */
+ static unsigned char *last_jumble = NULL;
+ /* Buffer that represents position of normalized characters */
+ static Size *last_offsets = NULL;
+ /* Current Length of last_offsets buffer */
+ static Size last_offset_buf_size = 10;
+ /* Current number of actual offsets stored in last_offsets */
+ static Size last_offset_num = 0;
  
  /* Current nesting depth of ExecutorRun calls */
  static int	nested_level = 0;
*************** static ExecutorRun_hook_type prev_Execut
*** 121,131 ****
--- 173,192 ----
  static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
  static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
  static ProcessUtility_hook_type prev_ProcessUtility = NULL;
+ static parse_analyze_hook_type prev_parse_analyze_hook = NULL;
+ static parse_analyze_varparams_hook_type prev_parse_analyze_varparams_hook = NULL;
  
  /* Links to shared memory state */
  static pgssSharedState *pgss = NULL;
  static HTAB *pgss_hash = NULL;
  
+ /*
+  * Maintain a stack of the rangetable of the query tree that we're currently
+  * walking, so subqueries can reference parent rangetables. The stack is pushed
+  * and popped as each Query struct is walked into or out of.
+  */
+ static List* pgss_rangetbl_stack = NIL;
+ 
  /*---- GUC variables ----*/
  
  typedef enum
*************** static int	pgss_max;			/* max # statemen
*** 147,152 ****
--- 208,214 ----
  static int	pgss_track;			/* tracking level */
  static bool pgss_track_utility; /* whether to track utility commands */
  static bool pgss_save;			/* whether to save stats across shutdown */
+ static bool pgss_string_key;	/* whether to always only hash query str */
  
  
  #define pgss_enabled() \
*************** PG_FUNCTION_INFO_V1(pg_stat_statements);
*** 166,171 ****
--- 228,250 ----
  
  static void pgss_shmem_startup(void);
  static void pgss_shmem_shutdown(int code, Datum arg);
+ static int comp_offset(const void *a, const void *b);
+ static Query *pgss_parse_analyze(Node *parseTree, const char *sourceText,
+ 			  Oid *paramTypes, int numParams);
+ static Query *pgss_parse_analyze_varparams(Node *parseTree, const char *sourceText,
+ 						Oid **paramTypes, int *numParams);
+ static void pgss_process_post_analysis_tree(Query* post_analysis_tree,
+ 		const char* sourceText);
+ static uint32 get_constant_length(const char* query_str_const);
+ static uint64 JumbleQuery(Query *post_analysis_tree);
+ static void AppendJumb(unsigned char* item, unsigned char jumble[], Size size, Size *i);
+ static void PerformJumble(const Query *tree, Size size, Size *i);
+ static void QualsNode(const OpExpr *node, Size size, Size *i, List *rtable);
+ static void LeafNode(const Node *arg, Size size, Size *i, List *rtable);
+ static void LimitOffsetNode(const Node *node, Size size, Size *i, List *rtable);
+ static void JoinExprNode(JoinExpr *node, Size size, Size *i, List *rtable);
+ static void JoinExprNodeChild(const Node *node, Size size, Size *i, List *rtable);
+ static void RecordConstLocation(int location);
  static void pgss_ExecutorStart(QueryDesc *queryDesc, int eflags);
  static void pgss_ExecutorRun(QueryDesc *queryDesc,
  				 ScanDirection direction,
*************** static void pgss_ProcessUtility(Node *pa
*** 177,188 ****
  					DestReceiver *dest, char *completionTag);
  static uint32 pgss_hash_fn(const void *key, Size keysize);
  static int	pgss_match_fn(const void *key1, const void *key2, Size keysize);
! static void pgss_store(const char *query, double total_time, uint64 rows,
! 		   const BufferUsage *bufusage);
  static Size pgss_memsize(void);
! static pgssEntry *entry_alloc(pgssHashKey *key);
  static void entry_dealloc(void);
  static void entry_reset(void);
  
  
  /*
--- 256,272 ----
  					DestReceiver *dest, char *completionTag);
  static uint32 pgss_hash_fn(const void *key, Size keysize);
  static int	pgss_match_fn(const void *key1, const void *key2, Size keysize);
! static uint64 pgss_hash_string(const char* str);
! static void pgss_store(const char *query, uint64 query_id,
! 				double total_time, uint64 rows,
! 				const BufferUsage *bufusage, bool empty_entry, bool normalize);
  static Size pgss_memsize(void);
! static pgssEntry *entry_alloc(pgssHashKey *key, const char* query, int new_query_len);
  static void entry_dealloc(void);
  static void entry_reset(void);
+ static int n_dups(Size offs[], Size n);
+ static Size* dedup_toks(Size offs[], Size n,
+ 		Size new_size);
  
  
  /*
*************** static void entry_reset(void);
*** 191,196 ****
--- 275,281 ----
  void
  _PG_init(void)
  {
+ 	MemoryContext cur_context;
  	/*
  	 * In order to create our shared memory area, we have to be loaded via
  	 * shared_preload_libraries.  If not, fall out without hooking into any of
*************** _PG_init(void)
*** 252,257 ****
--- 337,357 ----
  							 NULL,
  							 NULL);
  
+ 	/*
+ 	 * Support legacy pg_stat_statements behavior, for compatibility with
+ 	 * versions shipped with Postgres 8.4, 9.0 and 9.1
+ 	 */
+ 	DefineCustomBoolVariable("pg_stat_statements.string_key",
+ 			   "Differentiate queries based on query string alone.",
+ 							 NULL,
+ 							 &pgss_string_key,
+ 							 false,
+ 							 PGC_POSTMASTER,
+ 							 0,
+ 							 NULL,
+ 							 NULL,
+ 							 NULL);
+ 
  	EmitWarningsOnPlaceholders("pg_stat_statements");
  
  	/*
*************** _PG_init(void)
*** 263,268 ****
--- 363,383 ----
  	RequestAddinLWLocks(1);
  
  	/*
+ 	 * Allocate a buffer to store selective serialization of the query tree
+ 	 * for the purposes of query normalization.
+ 	 *
+ 	 * State that persists for the lifetime of the backend should be allocated
+ 	 * in TopMemoryContext
+ 	 */
+ 	cur_context = MemoryContextSwitchTo(TopMemoryContext);
+ 
+ 	last_jumble = palloc(JUMBLE_SIZE);
+ 	/* Allocate space for bookkeeping information for query str normalization */
+ 	last_offsets = palloc(last_offset_buf_size * sizeof(Size));
+ 
+ 	MemoryContextSwitchTo(cur_context);
+ 
+ 	/*
  	 * Install hooks.
  	 */
  	prev_shmem_startup_hook = shmem_startup_hook;
*************** _PG_init(void)
*** 277,282 ****
--- 392,401 ----
  	ExecutorEnd_hook = pgss_ExecutorEnd;
  	prev_ProcessUtility = ProcessUtility_hook;
  	ProcessUtility_hook = pgss_ProcessUtility;
+ 	prev_parse_analyze_hook = parse_analyze_hook;
+ 	parse_analyze_hook = pgss_parse_analyze;
+ 	prev_parse_analyze_varparams_hook = parse_analyze_varparams_hook;
+ 	parse_analyze_varparams_hook = pgss_parse_analyze_varparams;
  }
  
  /*
*************** _PG_fini(void)
*** 292,297 ****
--- 411,421 ----
  	ExecutorFinish_hook = prev_ExecutorFinish;
  	ExecutorEnd_hook = prev_ExecutorEnd;
  	ProcessUtility_hook = prev_ProcessUtility;
+ 	parse_analyze_hook = prev_parse_analyze_hook;
+ 	parse_analyze_varparams_hook = prev_parse_analyze_varparams_hook;
+ 
+ 	pfree(last_jumble);
+ 	pfree(last_offsets);
  }
  
  /*
*************** pgss_shmem_startup(void)
*** 395,421 ****
  		if (!PG_VALID_BE_ENCODING(temp.key.encoding))
  			goto error;
  
  		/* Previous incarnation might have had a larger query_size */
! 		if (temp.key.query_len >= buffer_size)
  		{
! 			buffer = (char *) repalloc(buffer, temp.key.query_len + 1);
! 			buffer_size = temp.key.query_len + 1;
  		}
  
! 		if (fread(buffer, 1, temp.key.query_len, file) != temp.key.query_len)
  			goto error;
! 		buffer[temp.key.query_len] = '\0';
  
  		/* Clip to available length if needed */
! 		if (temp.key.query_len >= query_size)
! 			temp.key.query_len = pg_encoding_mbcliplen(temp.key.encoding,
  													   buffer,
! 													   temp.key.query_len,
  													   query_size - 1);
- 		temp.key.query_ptr = buffer;
  
  		/* make the hashtable entry (discards old entries if too many) */
! 		entry = entry_alloc(&temp.key);
  
  		/* copy in the actual stats */
  		entry->counters = temp.counters;
--- 519,546 ----
  		if (!PG_VALID_BE_ENCODING(temp.key.encoding))
  			goto error;
  
+ 
  		/* Previous incarnation might have had a larger query_size */
! 		if (temp.query_len >= buffer_size)
  		{
! 			buffer = (char *) repalloc(buffer, temp.query_len + 1);
! 			buffer_size = temp.query_len + 1;
  		}
  
! 		if (fread(buffer, 1, temp.query_len, file) != temp.query_len)
  			goto error;
! 		buffer[temp.query_len] = '\0';
! 
  
  		/* Clip to available length if needed */
! 		if (temp.query_len >= query_size)
! 			temp.query_len = pg_encoding_mbcliplen(temp.key.encoding,
  													   buffer,
! 													   temp.query_len,
  													   query_size - 1);
  
  		/* make the hashtable entry (discards old entries if too many) */
! 		entry = entry_alloc(&temp.key, buffer, temp.query_len);
  
  		/* copy in the actual stats */
  		entry->counters = temp.counters;
*************** pgss_shmem_shutdown(int code, Datum arg)
*** 477,483 ****
  	hash_seq_init(&hash_seq, pgss_hash);
  	while ((entry = hash_seq_search(&hash_seq)) != NULL)
  	{
! 		int			len = entry->key.query_len;
  
  		if (fwrite(entry, offsetof(pgssEntry, mutex), 1, file) != 1 ||
  			fwrite(entry->query, 1, len, file) != len)
--- 602,608 ----
  	hash_seq_init(&hash_seq, pgss_hash);
  	while ((entry = hash_seq_search(&hash_seq)) != NULL)
  	{
! 		int			len = entry->query_len;
  
  		if (fwrite(entry, offsetof(pgssEntry, mutex), 1, file) != 1 ||
  			fwrite(entry->query, 1, len, file) != len)
*************** error:
*** 503,508 ****
--- 628,1702 ----
  }
  
  /*
+  * comp_offset: Comparator for qsorting Size values.
+  */
+ static int
+ comp_offset(const void *a, const void *b)
+ {
+ 	Size l = *((Size*) a);
+ 	Size r = *((Size*) b);
+ 	if (l < r)
+ 		return -1;
+ 	else if (l > r)
+ 		return +1;
+ 	else
+ 		return 0;
+ }
+ 
+ static Query *
+ pgss_parse_analyze(Node *parseTree, const char *sourceText,
+ 			  Oid *paramTypes, int numParams)
+ {
+ 	Query *post_analysis_tree;
+ 
+ 	if (prev_parse_analyze_hook)
+ 		post_analysis_tree = (*prev_parse_analyze_hook) (parseTree, sourceText,
+ 			  paramTypes, numParams);
+ 	else
+ 		post_analysis_tree = standard_parse_analyze(parseTree, sourceText,
+ 			  paramTypes, numParams);
+ 
+ 	if (!post_analysis_tree->utilityStmt)
+ 		pgss_process_post_analysis_tree(post_analysis_tree, sourceText);
+ 
+ 	return post_analysis_tree;
+ }
+ 
+ static Query *
+ pgss_parse_analyze_varparams(Node *parseTree, const char *sourceText,
+ 						Oid **paramTypes, int *numParams)
+ {
+ 	Query *post_analysis_tree;
+ 
+ 	if (prev_parse_analyze_hook)
+ 		post_analysis_tree = (*prev_parse_analyze_varparams_hook) (parseTree,
+ 				sourceText, paramTypes, numParams);
+ 	else
+ 		post_analysis_tree = standard_parse_analyze_varparams(parseTree,
+ 				sourceText, paramTypes, numParams);
+ 
+ 	if (!post_analysis_tree->utilityStmt)
+ 		pgss_process_post_analysis_tree(post_analysis_tree, sourceText);
+ 
+ 	return post_analysis_tree;
+ }
+ 
+ /*
+  * pgss_process_post_analysis_tree: Record query_id, which is based on the query
+  * tree, within the tree itself, for later retrieval in the exeuctor hook. The
+  * core system will copy the value to the tree's corresponding plannedstmt.
+  */
+ static void
+ pgss_process_post_analysis_tree(Query* post_analysis_tree,
+ 		const char* sourceText)
+ {
+ 	BufferUsage bufusage;
+ 
+ 	post_analysis_tree->query_id = JumbleQuery(post_analysis_tree);
+ 
+ 	memset(&bufusage, 0, sizeof(bufusage));
+ 	pgss_store(sourceText, post_analysis_tree->query_id, 0, 0, &bufusage,
+ 			true, true);
+ 
+ 	/* Trim last_offsets */
+ 	if (last_offset_buf_size > 10)
+ 	{
+ 		last_offset_buf_size = 10;
+ 		last_offsets = repalloc(last_offsets,
+ 							last_offset_buf_size *
+ 							sizeof(Size));
+ 	}
+ }
+ 
+ /*
+  * Given query_str_const, which points to the first character of a constant
+  * within a null-terminated SQL query string, determine the total length of the
+  * constant.
+  *
+  * The constant may use any available constant syntax, including but not limited
+  * to float literals, bit-strings, single quoted strings and dollar-quoted
+  * strings. This is accomplished by using the public API for the core scanner,
+  * with a few workarounds for quirks of their representation, such as the fact
+  * that constants preceded by a minus symbol have a position at the minus
+  * symbol, and yet are separately tokenized. This is effectively the inverse of
+  * what the later parsing step would have done to the Const node's position -
+  * compensate for the inclusion of the minus symbol.
+  *
+  * It is the caller's job to ensure that the string points to the first
+  * character of a valid constant, and that it includes the constant in its
+  * entirety. Since in practice the string has already been validated, and the
+  * initial position that the caller provides will have originated from within
+  * the authoritative parser, this should not be a problem.
+  */
+ static uint32
+ get_constant_length(const char* query_str_const)
+ {
+ 	core_yyscan_t  init_scan;
+ 	core_yy_extra_type ext_type;
+ 	core_YYSTYPE type;
+ 	uint32 len;
+ 	YYLTYPE pos;
+ 	int token;
+ 	int orig_tok_len;
+ 
+ 	if (query_str_const[0] == '-')
+ 		/* Negative constant */
+ 		return 1 + get_constant_length(&query_str_const[1]);
+ 
+ 	init_scan = scanner_init(query_str_const,
+ 							 &ext_type,
+ 							 ScanKeywords,
+ 							 NumScanKeywords);
+ 
+ 	token = core_yylex(&type, &pos,
+ 			   init_scan);
+ 
+ 	orig_tok_len = strlen(ext_type.scanbuf);
+ 	switch(token)
+ 	{
+ 		case NULL_P:
+ 		case SCONST:
+ 		case BCONST:
+ 		case XCONST:
+ 		case TRUE_P:
+ 		case FALSE_P:
+ 		case FCONST:
+ 		case ICONST:
+ 		case TYPECAST:
+ 		default:
+ 			len = orig_tok_len;
+ 			break;
+ 		/* XXX: "select integer '1'" must normalize to "select ?"
+ 		 * This is due to the position given within Const nodes.
+ 		 *
+ 		 * This is rather fragile, as I must enumerate all such types that there
+ 		 * may be leading tokens for here:
+ 		 */
+ 		case DECIMAL_P:
+ 		case BOOLEAN_P:
+ 		case NAME_P:
+ 		case TEXT_P:
+ 		case XML_P:
+ 		case TIMESTAMP:
+ 		case TIME:
+ 		case INTERVAL:
+ 		case INTEGER:
+ 		case BIGINT:
+ 		case NUMERIC:
+ 		case IDENT:
+ 			for(;;)
+ 			{
+ 				int lat_tok = core_yylex(&type, &pos,
+ 						   init_scan);
+ 				if (token == IDENT && lat_tok != SCONST)
+ 				{
+ 					len = orig_tok_len;
+ 					break;
+ 				}
+ 				/* String to follow */
+ 				if (lat_tok == SCONST)
+ 				{
+ 					len = strlen(ext_type.scanbuf);
+ 					break;
+ 				}
+ 			}
+ 			break;
+ 	}
+ 	scanner_finish(init_scan);
+ 	Assert(len > 0);
+ 	return len;
+ }
+ 
+ /*
+  * JumbleQuery: Selectively serialize query tree, and return a hash representing
+  * that serialization - it's query_id.
+  *
+  * Note that this doesn't necessarily uniquely identify the query across
+  * different databases and encodings.
+  */
+ static uint64
+ JumbleQuery(Query *post_analysis_tree)
+ {
+ 	/* State for this run of PerformJumble */
+ 	Size i = 0;
+ 	last_offset_num = 0;
+ 	memset(last_jumble, 0, JUMBLE_SIZE);
+ 	last_jumble[++i] = MAG_HASH_BUF;
+ 	PerformJumble(post_analysis_tree, JUMBLE_SIZE, &i);
+ 	/* Reset rangetbl state */
+ 	list_free(pgss_rangetbl_stack);
+ 	pgss_rangetbl_stack = NIL;
+ 
+ 	/* Sort offsets for later query string canonicalization */
+ 	qsort(last_offsets, last_offset_num, sizeof(Size), comp_offset);
+ 	return hash_any64((const unsigned char* ) last_jumble, i);
+ }
+ 
+ /*
+  * AppendJumb: Append a value that is substantive to a given query to jumble,
+  * while incrementing the iterator, i.
+  */
+ static void
+ AppendJumb(unsigned char* item, unsigned char jumble[], Size size, Size *i)
+ {
+ 	Assert(item != NULL);
+ 	Assert(jumble != NULL);
+ 	Assert(i != NULL);
+ 
+ 	/*
+ 	 * Copy the entire item to the buffer, or as much of it as possible to fill
+ 	 * the buffer to capacity.
+ 	 */
+ 	memcpy(jumble + *i, item, Min(*i > JUMBLE_SIZE? 0:JUMBLE_SIZE - *i, size));
+ 
+ 	/*
+ 	 * Continually hash the query tree's jumble.
+ 	 *
+ 	 * Was JUMBLE_SIZE exceeded? If so, hash the jumble and append that to the
+ 	 * start of the jumble buffer, and then continue to append the fraction of
+ 	 * "item" that we might not have been able to fit at the end of the buffer
+ 	 * in the last iteration. Since the value of i has been set to 0, there is
+ 	 * no need to memset the buffer in advance of this new iteration, but
+ 	 * effectively we are completely discarding the prior iteration's jumble
+ 	 * except for this hashed value.
+ 	 */
+ 	if (*i > JUMBLE_SIZE)
+ 	{
+ 		uint64 start_hash = hash_any64((const unsigned char* ) last_jumble, JUMBLE_SIZE);
+ 		int hash_l = sizeof(start_hash);
+ 		int part_left_l = Max(0, ((int) size - ((int) *i - JUMBLE_SIZE)));
+ 
+ 		Assert(part_left_l >= 0 && part_left_l <= size);
+ 
+ 		memcpy(jumble, &start_hash, hash_l);
+ 		memcpy(jumble + hash_l, item + (size - part_left_l), part_left_l);
+ 		*i = hash_l + part_left_l;
+ 	}
+ 	else
+ 	{
+ 		*i += size;
+ 	}
+ }
+ 
+ /*
+  * Wrapper around AppendJumb to encapsulate details of serialization
+  * of individual local variable elements.
+  */
+ #define APP_JUMB(item) \
+ AppendJumb((unsigned char*)&item, last_jumble, sizeof(item), i)
+ 
+ /*
+  * Space in the jumble buffer is limited - we can compact enum representations
+  * that will obviously never really need more than a single byte to store all
+  * possible enumerations.
+  *
+  * It would be pretty questionable to attempt this with an enum that has
+  * explicit integer values corresponding to constants, such as the huge enum
+  * "Node" that we use to dynamically identify nodes, and it would be downright
+  * incorrect to do so with one with negative values explicitly assigned to
+  * constants. This is intended to be used with enums with perhaps less than a
+  * dozen possible values, that are never likely to far exceed that.
+  */
+ #define COMPACT_ENUM(val) \
+ 	(unsigned char) val;
+ /*
+  * PerformJumble: Serialize the query tree "parse" and canonicalize
+  * constants, while simply skipping over others that are not essential to the
+  * query, such that it is usefully normalized, excluding things from the tree
+  * that are not essential to the query itself.
+  *
+  * The last_jumble buffer, which this function writes to, can be hashed to
+  * uniquely identify a query that may use different constants in successive
+  * calls.
+  */
+ static void
+ PerformJumble(const Query *tree, Size size, Size *i)
+ {
+ 	ListCell *l;
+ 	/* table join tree (FROM and WHERE clauses) */
+ 	FromExpr *jt = (FromExpr *) tree->jointree;
+ 	/* # of result tuples to skip (int8 expr) */
+ 	FuncExpr *off = (FuncExpr *) tree->limitOffset;
+ 	/* # of result tuples to skip (int8 expr) */
+ 	FuncExpr *limcount = (FuncExpr *) tree->limitCount;
+ 
+ 	if (pgss_rangetbl_stack &&
+ 			!IsA(pgss_rangetbl_stack, List))
+ 		pgss_rangetbl_stack = NIL;
+ 
+ 	if (tree->rtable != NIL)
+ 	{
+ 		pgss_rangetbl_stack = lappend(pgss_rangetbl_stack, tree->rtable);
+ 	}
+ 	else
+ 	{
+ 		/* Add dummy Range table entry to maintain stack */
+ 		RangeTblEntry *rte = makeNode(RangeTblEntry);
+ 		List *dummy = lappend(NIL, rte);
+ 		pgss_rangetbl_stack = lappend(pgss_rangetbl_stack, dummy);
+ 	}
+ 
+ 
+ 	APP_JUMB(tree->resultRelation);
+ 
+ 	if (tree->intoClause)
+ 	{
+ 		unsigned char OnCommit;
+ 		unsigned char skipData;
+ 		IntoClause *ic = tree->intoClause;
+ 		RangeVar   *rel = ic->rel;
+ 
+ 		OnCommit = COMPACT_ENUM(ic->onCommit);
+ 		skipData = COMPACT_ENUM(ic->skipData);
+ 		APP_JUMB(OnCommit);
+ 		APP_JUMB(skipData);
+ 		if (rel)
+ 		{
+ 			APP_JUMB(rel->relpersistence);
+ 			/* Bypass macro abstraction to supply size directly.
+ 			 *
+ 			 * Serialize schemaname, relname themselves - this makes us
+ 			 * somewhat consistent with the behavior of utility statements like "create
+ 			 * table", which seems appropriate.
+ 			 */
+ 			if (rel->schemaname)
+ 				AppendJumb((unsigned char *)rel->schemaname, last_jumble,
+ 								strlen(rel->schemaname), i);
+ 			if (rel->relname)
+ 				AppendJumb((unsigned char *)rel->relname, last_jumble,
+ 								strlen(rel->relname), i);
+ 		}
+ 	}
+ 
+ 	/* WITH list (of CommonTableExpr's) */
+ 	foreach(l, tree->cteList)
+ 	{
+ 		CommonTableExpr	*cte = (CommonTableExpr *) lfirst(l);
+ 		Query			*cteq = (Query*) cte->ctequery;
+ 		if (cteq)
+ 			PerformJumble(cteq, size, i);
+ 	}
+ 	if (jt)
+ 	{
+ 		if (jt->quals)
+ 		{
+ 			if (IsA(jt->quals, OpExpr))
+ 			{
+ 				QualsNode((OpExpr*) jt->quals, size, i, tree->rtable);
+ 			}
+ 			else
+ 			{
+ 				LeafNode((Node*) jt->quals, size, i, tree->rtable);
+ 			}
+ 		}
+ 		/* table join tree */
+ 		foreach(l, jt->fromlist)
+ 		{
+ 			Node* fr = lfirst(l);
+ 			if (IsA(fr, JoinExpr))
+ 			{
+ 				JoinExprNode((JoinExpr*) fr, size, i, tree->rtable);
+ 			}
+ 			else if (IsA(fr, RangeTblRef))
+ 			{
+ 				unsigned char rtekind;
+ 				RangeTblRef   *rtf = (RangeTblRef *) fr;
+ 				RangeTblEntry *rte = rt_fetch(rtf->rtindex, tree->rtable);
+ 				APP_JUMB(rte->relid);
+ 				rtekind = COMPACT_ENUM(rte->rtekind);
+ 				APP_JUMB(rtekind);
+ 				/* Subselection in where clause */
+ 				if (rte->subquery)
+ 					PerformJumble(rte->subquery, size, i);
+ 
+ 				/* Function call in where clause */
+ 				if (rte->funcexpr)
+ 					LeafNode((Node*) rte->funcexpr, size, i, tree->rtable);
+ 			}
+ 			else
+ 			{
+ 				ereport(WARNING,
+ 						(errcode(ERRCODE_INTERNAL_ERROR),
+ 						 errmsg("unexpected, unrecognised fromlist node type: %d",
+ 							 (int) nodeTag(fr))));
+ 			}
+ 		}
+ 	}
+ 	/*
+ 	 * target list (of TargetEntry)
+ 	 * columns returned by query
+ 	 */
+ 	foreach(l, tree->targetList)
+ 	{
+ 		TargetEntry *tg = (TargetEntry *) lfirst(l);
+ 		Node        *e  = (Node*) tg->expr;
+ 		if (tg->ressortgroupref)
+ 			/* nonzero if referenced by a sort/group - for ORDER BY */
+ 			APP_JUMB(tg->ressortgroupref);
+ 		APP_JUMB(tg->resno); /* column number for select */
+ 		/*
+ 		 * Handle the various types of nodes in
+ 		 * the select list of this query
+ 		 */
+ 		LeafNode(e, size, i, tree->rtable);
+ 	}
+ 	/* return-values list (of TargetEntry) */
+ 	foreach(l, tree->returningList)
+ 	{
+ 		TargetEntry *rt = (TargetEntry *) lfirst(l);
+ 		Expr        *e  = (Expr*) rt->expr;
+ 		unsigned char magic = MAG_RETURN_LIST;
+ 		APP_JUMB(magic);
+ 		/*
+ 		 * Handle the various types of nodes in
+ 		 * the select list of this query
+ 		 */
+ 		LeafNode((Node*) e, size, i, tree->rtable);
+ 	}
+ 	/* a list of SortGroupClause's */
+ 	foreach(l, tree->groupClause)
+ 	{
+ 		SortGroupClause *gc = (SortGroupClause *) lfirst(l);
+ 		APP_JUMB(gc->tleSortGroupRef);
+ 		APP_JUMB(gc->nulls_first);
+ 	}
+ 
+ 	if (tree->havingQual)
+ 	{
+ 		if (IsA(tree->havingQual, OpExpr))
+ 		{
+ 			OpExpr *na = (OpExpr *) tree->havingQual;
+ 			QualsNode(na, size, i, tree->rtable);
+ 		}
+ 		else
+ 		{
+ 			Node *n = (Node*) tree->havingQual;
+ 			LeafNode(n, size, i, tree->rtable);
+ 		}
+ 	}
+ 
+ 	foreach(l, tree->windowClause)
+ 	{
+ 		WindowClause *wc = (WindowClause *) lfirst(l);
+ 		ListCell     *il;
+ 		APP_JUMB(wc->frameOptions);
+ 		foreach(il, wc->partitionClause)	/* PARTITION BY list */
+ 		{
+ 			Node *n = (Node *) lfirst(il);
+ 			LeafNode(n, size, i, tree->rtable);
+ 		}
+ 		foreach(il, wc->orderClause)		/* ORDER BY list */
+ 		{
+ 			Node *n = (Node *) lfirst(il);
+ 			LeafNode(n, size, i, tree->rtable);
+ 		}
+ 	}
+ 
+ 	foreach(l, tree->distinctClause)
+ 	{
+ 		SortGroupClause *dc = (SortGroupClause *) lfirst(l);
+ 		APP_JUMB(dc->tleSortGroupRef);
+ 		APP_JUMB(dc->nulls_first);
+ 	}
+ 
+ 	/* Don't look at tree->sortClause,
+ 	 * because the value ressortgroupref is already
+ 	 * serialized when we iterate through targetList
+ 	 */
+ 
+ 	if (off)
+ 		LimitOffsetNode((Node*) off, size, i, tree->rtable);
+ 
+ 	if (limcount)
+ 		LimitOffsetNode((Node*) limcount, size, i, tree->rtable);
+ 
+ 	if (tree->setOperations)
+ 	{
+ 		/*
+ 		 * set-operation tree if this is top
+ 		 * level of a UNION/INTERSECT/EXCEPT query
+ 		 */
+ 		unsigned char op;
+ 		SetOperationStmt *topop = (SetOperationStmt *) tree->setOperations;
+ 		op = COMPACT_ENUM(topop->op);
+ 		APP_JUMB(op);
+ 		APP_JUMB(topop->all);
+ 
+ 		/* leaf selects are RTE subselections */
+ 		foreach(l, tree->rtable)
+ 		{
+ 			RangeTblEntry *r = (RangeTblEntry *) lfirst(l);
+ 			if (r->subquery)
+ 				PerformJumble(r->subquery, size, i);
+ 		}
+ 	}
+ 	pgss_rangetbl_stack = list_delete_ptr(pgss_rangetbl_stack,
+ 			list_nth(pgss_rangetbl_stack, pgss_rangetbl_stack->length - 1));
+ }
+ 
+ /*
+  * Perform selective serialization of "Quals" nodes when
+  * they're IsA(*, OpExpr)
+  */
+ static void
+ QualsNode(const OpExpr *node, Size size, Size *i, List *rtable)
+ {
+ 	ListCell *l;
+ 	APP_JUMB(node->xpr);
+ 	APP_JUMB(node->opno);
+ 	foreach(l, node->args)
+ 	{
+ 		Node *arg = (Node *) lfirst(l);
+ 		LeafNode(arg, size, i, rtable);
+ 	}
+ }
+ 
+ /*
+  * LeafNode: Selectively serialize a selection of parser/prim nodes that are
+  * frequently, though certainly not necesssarily leaf nodes, such as Vars
+  * (columns), constants and function calls
+  */
+ static void
+ LeafNode(const Node *arg, Size size, Size *i, List *rtable)
+ {
+ 	ListCell *l;
+ 	/* Use the node's NodeTag as a magic number */
+ 	APP_JUMB(arg->type);
+ 
+ 	if (IsA(arg, Const))
+ 	{
+ 		/*
+ 		 * If implicit casts are used, such as when inserting an integer into a
+ 		 * text column, then that will be a distinct query from directly
+ 		 * inserting a string literal, so that literal value will be a FuncExpr
+ 		 * to cast, and control won't reach here for that node. This behavior is
+ 		 * considered correct.
+ 		 */
+ 		Const *c = (Const *) arg;
+ 
+ 		/*
+ 		 * Datatype of the constant is a
+ 		 * differentiator
+ 		 */
+ 		APP_JUMB(c->consttype);
+ 		RecordConstLocation(c->location);
+ 	}
+ 	else if(IsA(arg, CoerceToDomain))
+ 	{
+ 		CoerceToDomain *cd = (CoerceToDomain*) arg;
+ 		/*
+ 		 * Datatype of the constant is a
+ 		 * differentiator
+ 		 */
+ 		APP_JUMB(cd->resulttype);
+ 		LeafNode((Node*) cd->arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, Var))
+ 	{
+ 		Var			  *v = (Var *) arg;
+ 		RangeTblEntry *rte;
+ 		ListCell *lc;
+ 
+ 		/*
+ 		 * We need to get the details of the rangetable, but rtable may not
+ 		 * refer to the relevant one if we're in a subselection.
+ 		 */
+ 		if (v->varlevelsup == 0)
+ 		{
+ 			rte = rt_fetch(v->varno, rtable);
+ 		}
+ 		else
+ 		{
+ 			List *rtable_upper = list_nth(pgss_rangetbl_stack,
+ 					(list_length(pgss_rangetbl_stack) - 1) - v->varlevelsup);
+ 			rte = rt_fetch(v->varno, rtable_upper);
+ 		}
+ 		APP_JUMB(rte->relid);
+ 
+ 		foreach(lc, rte->values_lists)
+ 		{
+ 			List	   *sublist = (List *) lfirst(lc);
+ 			ListCell   *lc2;
+ 
+ 			foreach(lc2, sublist)
+ 			{
+ 				Node	   *col = (Node *) lfirst(lc2);
+ 				LeafNode(col, size, i, rtable);
+ 			}
+ 		}
+ 		APP_JUMB(v->varattno);
+ 	}
+ 	else if (IsA(arg, CurrentOfExpr))
+ 	{
+ 		CurrentOfExpr *CoE = (CurrentOfExpr*) arg;
+ 		APP_JUMB(CoE->cvarno);
+ 		APP_JUMB(CoE->cursor_param);
+ 	}
+ 	else if (IsA(arg, CollateExpr))
+ 	{
+ 		CollateExpr *Ce = (CollateExpr*) arg;
+ 		APP_JUMB(Ce->collOid);
+ 	}
+ 	else if (IsA(arg, FieldSelect))
+ 	{
+ 		FieldSelect *Fs = (FieldSelect*) arg;
+ 		APP_JUMB(Fs->resulttype);
+ 		LeafNode((Node*) Fs->arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, NamedArgExpr))
+ 	{
+ 		NamedArgExpr *Nae = (NamedArgExpr*) arg;
+ 		APP_JUMB(Nae->argnumber);
+ 		LeafNode((Node*) Nae->arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, Param))
+ 	{
+ 		Param *p = ((Param *) arg);
+ 		APP_JUMB(p->paramkind);
+ 		APP_JUMB(p->paramid);
+ 	}
+ 	else if (IsA(arg, RelabelType))
+ 	{
+ 		RelabelType *rt = (RelabelType*) arg;
+ 		APP_JUMB(rt->resulttype);
+ 		LeafNode((Node*) rt->arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, WindowFunc))
+ 	{
+ 		WindowFunc *wf = (WindowFunc *) arg;
+ 		APP_JUMB(wf->winfnoid);
+ 		foreach(l, wf->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, FuncExpr))
+ 	{
+ 		FuncExpr *f = (FuncExpr *) arg;
+ 		APP_JUMB(f->funcid);
+ 		foreach(l, f->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, OpExpr) || IsA(arg, DistinctExpr))
+ 	{
+ 		QualsNode((OpExpr*) arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, CoerceViaIO))
+ 	{
+ 		CoerceViaIO *Cio = (CoerceViaIO*) arg;
+ 		APP_JUMB(Cio->coerceformat);
+ 		APP_JUMB(Cio->resulttype);
+ 		LeafNode((Node*) Cio->arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, Aggref))
+ 	{
+ 		Aggref *a =  (Aggref *) arg;
+ 		APP_JUMB(a->aggfnoid);
+ 		foreach(l, a->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, SubLink))
+ 	{
+ 		SubLink *s = (SubLink*) arg;
+ 		APP_JUMB(s->subLinkType);
+ 		/* Serialize select-list subselect recursively */
+ 		if (s->subselect)
+ 			PerformJumble((Query*) s->subselect, size, i);
+ 
+ 		if (s->testexpr)
+ 			LeafNode((Node*) s->testexpr, size, i, rtable);
+ 		foreach(l, s->operName)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, TargetEntry))
+ 	{
+ 		TargetEntry *rt = (TargetEntry *) arg;
+ 		Node *e = (Node*) rt->expr;
+ 		APP_JUMB(rt->resorigtbl);
+ 		APP_JUMB(rt->ressortgroupref);
+ 		LeafNode(e, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, BoolExpr))
+ 	{
+ 		BoolExpr *be = (BoolExpr *) arg;
+ 		APP_JUMB(be->boolop);
+ 		foreach(l, be->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, NullTest))
+ 	{
+ 		NullTest *nt = (NullTest *) arg;
+ 		Node     *arg = (Node *) nt->arg;
+ 		unsigned char nulltesttype = COMPACT_ENUM(nt->nulltesttype);
+ 		APP_JUMB(nulltesttype);	/* IS NULL, IS NOT NULL */
+ 		APP_JUMB(nt->argisrow);	/* is input a composite type ? */
+ 		LeafNode(arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, ArrayExpr))
+ 	{
+ 		ArrayExpr *ae = (ArrayExpr *) arg;
+ 		APP_JUMB(ae->array_typeid);		/* type of expression result */
+ 		APP_JUMB(ae->element_typeid);	/* common type of array elements */
+ 		foreach(l, ae->elements)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, CaseExpr))
+ 	{
+ 		CaseExpr *ce = (CaseExpr*) arg;
+ 		Assert(ce->casetype != InvalidOid);
+ 		APP_JUMB(ce->casetype);
+ 		foreach(l, ce->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 		if (ce->arg)
+ 			LeafNode((Node*) ce->arg, size, i, rtable);
+ 
+ 		if (ce->defresult)
+ 		{
+ 			/* Default result (ELSE clause).
+ 			 *
+ 			 * May be NULL, because no else clause
+ 			 * was actually specified, and thus the value is
+ 			 * equivalent to SQL ELSE NULL
+ 			 */
+ 			LeafNode((Node*) ce->defresult, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, CaseTestExpr))
+ 	{
+ 		CaseTestExpr *ct = (CaseTestExpr*) arg;
+ 		APP_JUMB(ct->typeId);
+ 	}
+ 	else if (IsA(arg, CaseWhen))
+ 	{
+ 		CaseWhen *cw = (CaseWhen*) arg;
+ 		Node     *res = (Node*) cw->result;
+ 		Node     *exp = (Node*) cw->expr;
+ 		if (res)
+ 			LeafNode(res, size, i, rtable);
+ 		if (exp)
+ 			LeafNode(exp, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, MinMaxExpr))
+ 	{
+ 		MinMaxExpr *cw = (MinMaxExpr*) arg;
+ 		APP_JUMB(cw->minmaxtype);
+ 		APP_JUMB(cw->op);
+ 		foreach(l, cw->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, ScalarArrayOpExpr))
+ 	{
+ 		ScalarArrayOpExpr *sa = (ScalarArrayOpExpr*) arg;
+ 		APP_JUMB(sa->opfuncid);
+ 		APP_JUMB(sa->useOr);
+ 		foreach(l, sa->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, CoalesceExpr))
+ 	{
+ 		CoalesceExpr *ca = (CoalesceExpr*) arg;
+ 		foreach(l, ca->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, ArrayCoerceExpr))
+ 	{
+ 		ArrayCoerceExpr *ac = (ArrayCoerceExpr *) arg;
+ 		LeafNode((Node*) ac->arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, WindowClause))
+ 	{
+ 		WindowClause *wc = (WindowClause*) arg;
+ 		foreach(l, wc->partitionClause)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 		foreach(l, wc->orderClause)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, SortGroupClause))
+ 	{
+ 		SortGroupClause *sgc = (SortGroupClause*) arg;
+ 		APP_JUMB(sgc->tleSortGroupRef);
+ 		APP_JUMB(sgc->nulls_first);
+ 	}
+ 	else if (IsA(arg, Integer) ||
+ 		  IsA(arg, Float) ||
+ 		  IsA(arg, String) ||
+ 		  IsA(arg, BitString) ||
+ 		  IsA(arg, Null)
+ 		)
+ 	{
+ 		/* It is not necessary to serialize Value nodes - they are seen when
+ 		 * aliases are used, which are ignored.
+ 		 */
+ 		return;
+ 	}
+ 	else if (IsA(arg, BooleanTest))
+ 	{
+ 		BooleanTest *bt = (BooleanTest *) arg;
+ 		APP_JUMB(bt->booltesttype);
+ 		LeafNode((Node*) bt->arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, ArrayRef))
+ 	{
+ 		ArrayRef *ar = (ArrayRef*) arg;
+ 		APP_JUMB(ar->refarraytype);
+ 		foreach(l, ar->refupperindexpr)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 		foreach(l, ar->reflowerindexpr)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 		if (ar->refexpr)
+ 			LeafNode((Node*) ar->refexpr, size, i, rtable);
+ 		if (ar->refassgnexpr)
+ 			LeafNode((Node*) ar->refassgnexpr, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, NullIfExpr))
+ 	{
+ 		/* NullIfExpr is just a typedef for OpExpr */
+ 		QualsNode((OpExpr*) arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, RowExpr))
+ 	{
+ 		RowExpr *re = (RowExpr*) arg;
+ 		APP_JUMB(re->row_format);
+ 		foreach(l, re->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 
+ 	}
+ 	else if (IsA(arg, XmlExpr))
+ 	{
+ 		XmlExpr *xml = (XmlExpr*) arg;
+ 		APP_JUMB(xml->op);
+ 		foreach(l, xml->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 		/* non-XML expressions for xml_attributes */
+ 		foreach(l, xml->named_args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 		/* parallel list of Value strings */
+ 		foreach(l, xml->arg_names)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, RowCompareExpr))
+ 	{
+ 		RowCompareExpr *rc = (RowCompareExpr*) arg;
+ 		foreach(l, rc->largs)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 		foreach(l, rc->rargs)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(arg, SetToDefault))
+ 	{
+ 		SetToDefault *sd = (SetToDefault*) arg;
+ 		APP_JUMB(sd->typeId);
+ 		APP_JUMB(sd->typeMod);
+ 	}
+ 	else if (IsA(arg, ConvertRowtypeExpr))
+ 	{
+ 		ConvertRowtypeExpr* Cr = (ConvertRowtypeExpr*) arg;
+ 		APP_JUMB(Cr->convertformat);
+ 		APP_JUMB(Cr->resulttype);
+ 		LeafNode((Node*) Cr->arg, size, i, rtable);
+ 	}
+ 	else if (IsA(arg, FieldStore))
+ 	{
+ 		FieldStore* Fs = (FieldStore*) arg;
+ 		LeafNode((Node*) Fs->arg, size, i, rtable);
+ 		foreach(l, Fs->newvals)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		ereport(WARNING,
+ 				(errcode(ERRCODE_INTERNAL_ERROR),
+ 				 errmsg("unexpected, unrecognised LeafNode node type: %d",
+ 					 (int) nodeTag(arg))));
+ 	}
+ }
+ 
+ /*
+  * Perform selective serialization of limit or offset nodes
+  */
+ static void
+ LimitOffsetNode(const Node *node, Size size, Size *i, List *rtable)
+ {
+ 	ListCell *l;
+ 	unsigned char magic = MAG_LIMIT_OFFSET;
+ 	APP_JUMB(magic);
+ 
+ 	if (IsA(node, FuncExpr))
+ 	{
+ 
+ 		foreach(l, ((FuncExpr*) node)->args)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else
+ 	{
+ 		/* Fall back on leaf node representation */
+ 		LeafNode(node, size, i, rtable);
+ 	}
+ }
+ 
+ /*
+  * JoinExprNode: Perform selective serialization of JoinExpr nodes
+  */
+ static void
+ JoinExprNode(JoinExpr *node, Size size, Size *i, List *rtable)
+ {
+ 	Node	 *larg = node->larg;	/* left subtree */
+ 	Node	 *rarg = node->rarg;	/* right subtree */
+ 	ListCell *l;
+ 
+ 	Assert( IsA(node, JoinExpr));
+ 
+ 	APP_JUMB(node->jointype);
+ 	APP_JUMB(node->isNatural);
+ 
+ 	if (node->quals)
+ 	{
+ 		if ( IsA(node, OpExpr))
+ 		{
+ 			QualsNode((OpExpr*) node->quals, size, i, rtable);
+ 		}
+ 		else
+ 		{
+ 			LeafNode((Node*) node->quals, size, i, rtable);
+ 		}
+ 
+ 	}
+ 	foreach(l, node->usingClause) /* USING clause, if any (list of String) */
+ 	{
+ 		Node *arg = (Node *) lfirst(l);
+ 		LeafNode(arg, size, i, rtable);
+ 	}
+ 	if (larg)
+ 		JoinExprNodeChild(larg, size, i, rtable);
+ 	if (rarg)
+ 		JoinExprNodeChild(rarg, size, i, rtable);
+ }
+ 
+ /*
+  * JoinExprNodeChild: Serialize children of the JoinExpr node
+  */
+ static void
+ JoinExprNodeChild(const Node *node, Size size, Size *i, List *rtable)
+ {
+ 	if (IsA(node, RangeTblRef))
+ 	{
+ 		RangeTblRef   *rt = (RangeTblRef*) node;
+ 		RangeTblEntry *rte = rt_fetch(rt->rtindex, rtable);
+ 		ListCell      *l;
+ 
+ 		APP_JUMB(rte->relid);
+ 		APP_JUMB(rte->jointype);
+ 
+ 		if (rte->subquery)
+ 			PerformJumble((Query*) rte->subquery, size, i);
+ 
+ 		foreach(l, rte->joinaliasvars)
+ 		{
+ 			Node *arg = (Node *) lfirst(l);
+ 			LeafNode(arg, size, i, rtable);
+ 		}
+ 	}
+ 	else if (IsA(node, JoinExpr))
+ 	{
+ 		JoinExprNode((JoinExpr*) node, size, i, rtable);
+ 	}
+ 	else
+ 	{
+ 		LeafNode(node, size, i, rtable);
+ 	}
+ }
+ 
+ /*
+  * Record location of constant within query string of query tree that is
+  * currently being walked.
+  */
+ static void
+ RecordConstLocation(int location)
+ {
+ 	/* -1 indicates unknown or undefined location */
+ 	if (location > -1)
+ 	{
+ 		if (last_offset_num < pgss->query_size / 2)
+ 		{
+ 			if (last_offset_num >= last_offset_buf_size)
+ 			{
+ 				last_offset_buf_size *= 2;
+ 				last_offsets = repalloc(last_offsets,
+ 								last_offset_buf_size *
+ 								sizeof(Size));
+ 
+ 			}
+ 			last_offsets[last_offset_num] = location;
+ 			last_offset_num++;
+ 		}
+ 	}
+ }
+ 
+ /*
   * ExecutorStart hook: start up tracking if needed
   */
  static void
*************** pgss_ExecutorEnd(QueryDesc *queryDesc)
*** 585,590 ****
--- 1779,1789 ----
  {
  	if (queryDesc->totaltime && pgss_enabled())
  	{
+ 		uint64 queryId;
+ 		if (pgss_string_key)
+ 			queryId = pgss_hash_string(queryDesc->sourceText);
+ 		else
+ 			queryId = queryDesc->plannedstmt->queryId;
  		/*
  		 * Make sure stats accumulation is done.  (Note: it's okay if several
  		 * levels of hook all do this.)
*************** pgss_ExecutorEnd(QueryDesc *queryDesc)
*** 592,600 ****
  		InstrEndLoop(queryDesc->totaltime);
  
  		pgss_store(queryDesc->sourceText,
! 				   queryDesc->totaltime->total,
! 				   queryDesc->estate->es_processed,
! 				   &queryDesc->totaltime->bufusage);
  	}
  
  	if (prev_ExecutorEnd)
--- 1791,1803 ----
  		InstrEndLoop(queryDesc->totaltime);
  
  		pgss_store(queryDesc->sourceText,
! 		   queryId,
! 		   queryDesc->totaltime->total,
! 		   queryDesc->estate->es_processed,
! 		   &queryDesc->totaltime->bufusage,
! 		   false,
! 		   false);
! 
  	}
  
  	if (prev_ExecutorEnd)
*************** pgss_ProcessUtility(Node *parsetree, con
*** 616,621 ****
--- 1819,1825 ----
  		instr_time	start;
  		instr_time	duration;
  		uint64		rows = 0;
+ 		uint64		query_id;
  		BufferUsage bufusage;
  
  		bufusage = pgBufferUsage;
*************** pgss_ProcessUtility(Node *parsetree, con
*** 665,672 ****
  		bufusage.temp_blks_written =
  			pgBufferUsage.temp_blks_written - bufusage.temp_blks_written;
  
! 		pgss_store(queryString, INSTR_TIME_GET_DOUBLE(duration), rows,
! 				   &bufusage);
  	}
  	else
  	{
--- 1869,1879 ----
  		bufusage.temp_blks_written =
  			pgBufferUsage.temp_blks_written - bufusage.temp_blks_written;
  
! 		query_id = pgss_hash_string(queryString);
! 
! 		/* In the case of utility statements, hash the query string directly */
! 		pgss_store(queryString, query_id,
! 				INSTR_TIME_GET_DOUBLE(duration), rows, &bufusage, false, false);
  	}
  	else
  	{
*************** pgss_hash_fn(const void *key, Size keysi
*** 690,697 ****
  	/* we don't bother to include encoding in the hash */
  	return hash_uint32((uint32) k->userid) ^
  		hash_uint32((uint32) k->dbid) ^
! 		DatumGetUInt32(hash_any((const unsigned char *) k->query_ptr,
! 								k->query_len));
  }
  
  /*
--- 1897,1904 ----
  	/* we don't bother to include encoding in the hash */
  	return hash_uint32((uint32) k->userid) ^
  		hash_uint32((uint32) k->dbid) ^
! 		DatumGetUInt32(hash_any((const unsigned char* ) &k->query_id,
! 					sizeof(k->query_id)) );
  }
  
  /*
*************** pgss_match_fn(const void *key1, const vo
*** 706,727 ****
  	if (k1->userid == k2->userid &&
  		k1->dbid == k2->dbid &&
  		k1->encoding == k2->encoding &&
! 		k1->query_len == k2->query_len &&
! 		memcmp(k1->query_ptr, k2->query_ptr, k1->query_len) == 0)
  		return 0;
  	else
  		return 1;
  }
  
  /*
   * Store some statistics for a statement.
   */
  static void
! pgss_store(const char *query, double total_time, uint64 rows,
! 		   const BufferUsage *bufusage)
  {
  	pgssHashKey key;
  	double		usage;
  	pgssEntry  *entry;
  
  	Assert(query != NULL);
--- 1913,1958 ----
  	if (k1->userid == k2->userid &&
  		k1->dbid == k2->dbid &&
  		k1->encoding == k2->encoding &&
! 		k1->query_id == k2->query_id)
  		return 0;
  	else
  		return 1;
  }
  
  /*
+  * Given an arbitrarily long query string, produce a hash for the purposes of
+  * identifying the query, without canonicalizing constants. Used when hashing
+  * utility statements, or for legacy compatibility mode.
+  */
+ static uint64
+ pgss_hash_string(const char* str)
+ {
+ 	/* For additional protection against collisions, including magic value */
+ 	uint64 Magic = MAG_STR_BUF;
+ 	uint64 result;
+ 	Size size = sizeof(Magic) + strlen(str);
+ 	unsigned char* p = palloc(size);
+ 	memcpy(p, &Magic, sizeof(Magic));
+ 	memcpy(p + sizeof(Magic), str, strlen(str));
+ 	result = hash_any64((const unsigned char *) p, size);
+ 	pfree(p);
+ 	return result;
+ }
+ 
+ /*
   * Store some statistics for a statement.
   */
  static void
! pgss_store(const char *query, uint64 query_id,
! 				double total_time, uint64 rows,
! 				const BufferUsage *bufusage,
! 				bool empty_entry,
! 				bool normalize)
  {
  	pgssHashKey key;
  	double		usage;
+ 	int		    new_query_len = strlen(query);
+ 	char	   *norm_query = NULL;
  	pgssEntry  *entry;
  
  	Assert(query != NULL);
*************** pgss_store(const char *query, double tot
*** 734,747 ****
  	key.userid = GetUserId();
  	key.dbid = MyDatabaseId;
  	key.encoding = GetDatabaseEncoding();
! 	key.query_len = strlen(query);
! 	if (key.query_len >= pgss->query_size)
! 		key.query_len = pg_encoding_mbcliplen(key.encoding,
  											  query,
! 											  key.query_len,
  											  pgss->query_size - 1);
- 	key.query_ptr = query;
- 
  	usage = USAGE_EXEC(duration);
  
  	/* Lookup the hash table entry with shared lock. */
--- 1965,1980 ----
  	key.userid = GetUserId();
  	key.dbid = MyDatabaseId;
  	key.encoding = GetDatabaseEncoding();
! 	key.query_id = query_id;
! 
! 	if (new_query_len >= pgss->query_size)
! 		/* We don't have to worry about this later, because canonicalization
! 		 * cannot possibly result in a longer query string
! 		 */
! 		new_query_len = pg_encoding_mbcliplen(key.encoding,
  											  query,
! 											  new_query_len,
  											  pgss->query_size - 1);
  	usage = USAGE_EXEC(duration);
  
  	/* Lookup the hash table entry with shared lock. */
*************** pgss_store(const char *query, double tot
*** 750,759 ****
  	entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL);
  	if (!entry)
  	{
! 		/* Must acquire exclusive lock to add a new entry. */
! 		LWLockRelease(pgss->lock);
! 		LWLockAcquire(pgss->lock, LW_EXCLUSIVE);
! 		entry = entry_alloc(&key);
  	}
  
  	/* Grab the spinlock while updating the counters. */
--- 1983,2103 ----
  	entry = (pgssEntry *) hash_search(pgss_hash, &key, HASH_FIND, NULL);
  	if (!entry)
  	{
! 		Size *offs = NULL;
! 		int qry_n_dups;
! 		int off_n = 0;
! 
! 		offs = last_offsets;
! 		off_n = last_offset_num;
! 
! 		if ((qry_n_dups = n_dups(offs, off_n)) > 0)
! 		{
! 			/* Assuming that we cannot have walked some part of the tree twice
! 			 * seems rather fragile, and besides, there isn't much we can do to
! 			 * ensure that this does not happen with targetLists that contain
! 			 * duplicate entries, as with queries like "values(1,2), (3,4)".
! 			 */
! 			Size *offs_new;
! 			offs_new = dedup_toks(offs, off_n, off_n - qry_n_dups);
! 			offs = offs_new;
! 			off_n = off_n - qry_n_dups;
! 		}
! 
! 		/*
! 		 * It is necessary to generate a normalized version of the query
! 		 * string that will be used to represent it. It's important that
! 		 * the user be presented with a stable representation of the query.
! 		 *
! 		 * Note that the representation seen by the user will only have
! 		 * non-differentiating Const tokens swapped with '?' characters, and
! 		 * this does not for example take account of the fact that alias names
! 		 * could vary between successive calls of what is regarded as the same
! 		 * query.
! 		 */
! 		if (off_n > 0 && normalize)
! 		{
! 			int i,
! 			  off = 0,			/* Offset from start for cur tok */
! 			  tok_len = 0,		/* length (in bytes) of that tok */
! 			  quer_it = 0,		/* Original query iterator */
! 			  n_quer_it = 0,	/* Normalized query iterator */
! 			  len_to_wrt = 0,	/* Length (in bytes) to write */
! 			  last_off = 0,		/* Offset from start for last iter tok */
! 			  last_tok_len = 0,	/* length (in bytes) of that tok */
! 			  length_delta = 0; /* Finished str is n bytes shorter so far */
! 
! 			norm_query = palloc0(new_query_len + 1);
! 			for(i = 0; i < off_n; i++)
! 			{
! 				off = offs[i];
! 				tok_len = get_constant_length(&query[off]);
! 				len_to_wrt = off - last_off;
! 				len_to_wrt -= last_tok_len;
! 				length_delta += tok_len - 1;
! 				Assert(tok_len > 0);
! 				Assert(len_to_wrt >= 0);
! 				/*
! 				 * Each iteration copies everything prior to the current
! 				 * offset/token to be replaced, except bytes copied in
! 				 * previous iterations
! 				 */
! 				if (off - length_delta + tok_len > new_query_len)
! 				{
! 					/* We could just be oversized due to a large constant
! 					 * literal. Try and copy bytes prior to the literal that may
! 					 * have been missed.
! 					 */
! 					if (off - length_delta < new_query_len)
! 					{
! 						memcpy(norm_query + n_quer_it, query + quer_it,
! 								Min(len_to_wrt, new_query_len - n_quer_it));
! 						n_quer_it += len_to_wrt;
! 						quer_it += len_to_wrt + tok_len;
! 
! 						/*
! 						 * See if there is room left for a '?', and copy one
! 						 * over if there is.
! 						 */
! 						if (n_quer_it < new_query_len)
! 							norm_query[n_quer_it++] = '?';
! 					}
! 					/*
! 					 * Even though we'd have exceeded buffer size with last
! 					 * constant if constants weren't canonicalized, they are, so
! 					 * there could be additional constants that will fit in that
! 					 * buffer, once they're actually represented as '?'
! 					 */
! 					continue;
! 				}
! 				memcpy(norm_query + n_quer_it, query + quer_it, len_to_wrt);
! 
! 				n_quer_it += len_to_wrt;
! 				norm_query[n_quer_it++] = '?';
! 				quer_it += len_to_wrt + tok_len;
! 				last_off = off;
! 				last_tok_len = tok_len;
! 			}
! 			/* Copy end of query string (piece past last constant) if there's room */
! 			memcpy(norm_query + n_quer_it, query + (off + tok_len),
! 				Min( strlen(query) - (off + tok_len),
! 					new_query_len - n_quer_it ) );
! 			/*
! 			 * Must acquire exclusive lock to add a new entry.
! 			 * Leave that until as late as possible.
! 			 */
! 			LWLockRelease(pgss->lock);
! 			LWLockAcquire(pgss->lock, LW_EXCLUSIVE);
! 
! 			entry = entry_alloc(&key, norm_query, strlen(norm_query));
! 		}
! 		else
! 		{
! 			/* Acquire exclusive lock as required by entry_alloc() */
! 			LWLockRelease(pgss->lock);
! 			LWLockAcquire(pgss->lock, LW_EXCLUSIVE);
! 
! 			entry = entry_alloc(&key, query, new_query_len);
! 		}
  	}
  
  	/* Grab the spinlock while updating the counters. */
*************** pgss_store(const char *query, double tot
*** 761,767 ****
  		volatile pgssEntry *e = (volatile pgssEntry *) entry;
  
  		SpinLockAcquire(&e->mutex);
! 		e->counters.calls += 1;
  		e->counters.total_time += total_time;
  		e->counters.rows += rows;
  		e->counters.shared_blks_hit += bufusage->shared_blks_hit;
--- 2105,2113 ----
  		volatile pgssEntry *e = (volatile pgssEntry *) entry;
  
  		SpinLockAcquire(&e->mutex);
! 		if (!empty_entry)
! 			e->counters.calls += 1;
! 
  		e->counters.total_time += total_time;
  		e->counters.rows += rows;
  		e->counters.shared_blks_hit += bufusage->shared_blks_hit;
*************** pgss_store(const char *query, double tot
*** 775,782 ****
  		e->counters.usage += usage;
  		SpinLockRelease(&e->mutex);
  	}
- 
  	LWLockRelease(pgss->lock);
  }
  
  /*
--- 2121,2129 ----
  		e->counters.usage += usage;
  		SpinLockRelease(&e->mutex);
  	}
  	LWLockRelease(pgss->lock);
+ 	if (norm_query)
+ 		pfree(norm_query);
  }
  
  /*
*************** pg_stat_statements(PG_FUNCTION_ARGS)
*** 863,869 ****
  
  			qstr = (char *)
  				pg_do_encoding_conversion((unsigned char *) entry->query,
! 										  entry->key.query_len,
  										  entry->key.encoding,
  										  GetDatabaseEncoding());
  			values[i++] = CStringGetTextDatum(qstr);
--- 2210,2216 ----
  
  			qstr = (char *)
  				pg_do_encoding_conversion((unsigned char *) entry->query,
! 										  entry->query_len,
  										  entry->key.encoding,
  										  GetDatabaseEncoding());
  			values[i++] = CStringGetTextDatum(qstr);
*************** pg_stat_statements(PG_FUNCTION_ARGS)
*** 881,886 ****
--- 2228,2236 ----
  			tmp = e->counters;
  			SpinLockRelease(&e->mutex);
  		}
+ 		/* Skip record of unexecuted query */
+ 		if (tmp.calls == 0)
+ 			continue;
  
  		values[i++] = Int64GetDatumFast(tmp.calls);
  		values[i++] = Float8GetDatumFast(tmp.total_time);
*************** pgss_memsize(void)
*** 933,946 ****
   * have made the entry while we waited to get exclusive lock.
   */
  static pgssEntry *
! entry_alloc(pgssHashKey *key)
  {
  	pgssEntry  *entry;
  	bool		found;
  
- 	/* Caller must have clipped query properly */
- 	Assert(key->query_len < pgss->query_size);
- 
  	/* Make space if needed */
  	while (hash_get_num_entries(pgss_hash) >= pgss_max)
  		entry_dealloc();
--- 2283,2293 ----
   * have made the entry while we waited to get exclusive lock.
   */
  static pgssEntry *
! entry_alloc(pgssHashKey *key, const char* query, int new_query_len)
  {
  	pgssEntry  *entry;
  	bool		found;
  
  	/* Make space if needed */
  	while (hash_get_num_entries(pgss_hash) >= pgss_max)
  		entry_dealloc();
*************** entry_alloc(pgssHashKey *key)
*** 952,968 ****
  	{
  		/* New entry, initialize it */
  
! 		/* dynahash tried to copy the key for us, but must fix query_ptr */
! 		entry->key.query_ptr = entry->query;
  		/* reset the statistics */
  		memset(&entry->counters, 0, sizeof(Counters));
  		entry->counters.usage = USAGE_INIT;
  		/* re-initialize the mutex each time ... we assume no one using it */
  		SpinLockInit(&entry->mutex);
  		/* ... and don't forget the query text */
! 		memcpy(entry->query, key->query_ptr, key->query_len);
! 		entry->query[key->query_len] = '\0';
  	}
  
  	return entry;
  }
--- 2299,2318 ----
  	{
  		/* New entry, initialize it */
  
! 		entry->query_len = new_query_len;
! 		Assert(entry->query_len > 0);
  		/* reset the statistics */
  		memset(&entry->counters, 0, sizeof(Counters));
  		entry->counters.usage = USAGE_INIT;
  		/* re-initialize the mutex each time ... we assume no one using it */
  		SpinLockInit(&entry->mutex);
  		/* ... and don't forget the query text */
! 		memcpy(entry->query, query, entry->query_len);
! 		Assert(new_query_len <= pgss->query_size);
! 		entry->query[entry->query_len] = '\0';
  	}
+ 	/* Caller must have clipped query properly */
+ 	Assert(entry->query_len < pgss->query_size);
  
  	return entry;
  }
*************** entry_reset(void)
*** 1040,1042 ****
--- 2390,2437 ----
  
  	LWLockRelease(pgss->lock);
  }
+ 
+ /*
+  * Returns a value indicating the number of duplicates.
+  *
+  * Assumes that the array has already been sorted.
+  */
+ static int
+ n_dups(Size offs[], Size n)
+ {
+ 	int i, n_fdups = 0;
+ 
+ 	for(i = 1; i < n; i++)
+ 		if (offs[i - 1] == offs[i])
+ 			n_fdups++;
+ 
+ 	return n_fdups;
+ }
+ 
+ /*
+  * Function removes duplicate values, returning new array.
+  *
+  * new_size specifies the size of the new, duplicate-free array, which must be
+  * known ahead of time.
+  *
+  * Assumes that the array has already been sorted.
+  */
+ static Size*
+ dedup_toks(Size offs[], Size n, Size new_size)
+ {
+ 	int i, j = 0;
+ 	Size *new_offsets;
+ 
+ 	new_offsets = (Size*) palloc(sizeof(Size) * new_size);
+ 	new_offsets[j++] = offs[0];
+ 
+ 	for(i = 1; i < n; i++)
+ 	{
+ 		if (offs[i - 1] == offs[i])
+ 			continue;
+ 		new_offsets[j++] = offs[i];
+ 	}
+ 	Assert(j == new_size);
+ 
+ 	return new_offsets;
+ }
diff --git a/src/backend/access/hash/hashfunc.c b/src/backend/access/hash/hashfunc.c
new file mode 100644
index 0e4cf8e..5497deb
*** a/src/backend/access/hash/hashfunc.c
--- b/src/backend/access/hash/hashfunc.c
*************** hashvarlena(PG_FUNCTION_ARGS)
*** 291,308 ****
   *		k		: the key (the unaligned variable-length array of bytes)
   *		len		: the length of the key, counting by bytes
   *
!  * Returns a uint32 value.	Every bit of the key affects every bit of
!  * the return value.  Every 1-bit and 2-bit delta achieves avalanche.
!  * About 6*len+35 instructions. The best hash table sizes are powers
!  * of 2.  There is no need to do mod a prime (mod is sooo slow!).
!  * If you need less than 32 bits, use a bitmask.
   *
!  * Note: we could easily change this function to return a 64-bit hash value
!  * by using the final values of both b and c.  b is perhaps a little less
!  * well mixed than c, however.
   */
  Datum
! hash_any(register const unsigned char *k, register int keylen)
  {
  	register uint32 a,
  				b,
--- 291,305 ----
   *		k		: the key (the unaligned variable-length array of bytes)
   *		len		: the length of the key, counting by bytes
   *
!  * Returns a uint32 or a uint64 value, depending on the width_32 argument.
   *
!  * Every bit of the key affects every bit of the return value.  Every 1-bit and
!  * 2-bit delta achieves avalanche.  About 6*len+35 instructions. The best hash
!  * table sizes are powers of 2.  There is no need to do mod a prime (mod is sooo
!  * slow!).  If you need less than 32 bits, use a bitmask.
   */
  Datum
! hash_any_var_width(register const unsigned char *k, register int keylen, bool width_32)
  {
  	register uint32 a,
  				b,
*************** hash_any(register const unsigned char *k
*** 496,502 ****
  	final(a, b, c);
  
  	/* report the result */
! 	return UInt32GetDatum(c);
  }
  
  /*
--- 493,503 ----
  	final(a, b, c);
  
  	/* report the result */
! 	if (width_32)
! 		return UInt32GetDatum(c);
! 	else
! 		return (uint64) b |
! 			(((uint64) c) << (sizeof(uint64) / 2) * BITS_PER_BYTE);
  }
  
  /*
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
new file mode 100644
index cc3168d..385c13a
*** a/src/backend/nodes/copyfuncs.c
--- b/src/backend/nodes/copyfuncs.c
***************
*** 62,67 ****
--- 62,70 ----
  #define COPY_LOCATION_FIELD(fldname) \
  	(newnode->fldname = from->fldname)
  
+ /* Copy a query_id field (for Copy, this is same as scalar case) */
+ #define COPY_QUERYID_FIELD(fldname) \
+ 	(newnode->fldname = from->fldname)
  
  /* ****************************************************************
   *					 plannodes.h copy functions
*************** _copyPlannedStmt(const PlannedStmt *from
*** 92,97 ****
--- 95,101 ----
  	COPY_NODE_FIELD(relationOids);
  	COPY_NODE_FIELD(invalItems);
  	COPY_SCALAR_FIELD(nParamExec);
+ 	COPY_QUERYID_FIELD(queryId);
  
  	return newnode;
  }
*************** _copyQuery(const Query *from)
*** 2415,2420 ****
--- 2419,2425 ----
  
  	COPY_SCALAR_FIELD(commandType);
  	COPY_SCALAR_FIELD(querySource);
+ 	COPY_SCALAR_FIELD(query_id);
  	COPY_SCALAR_FIELD(canSetTag);
  	COPY_NODE_FIELD(utilityStmt);
  	COPY_SCALAR_FIELD(resultRelation);
diff --git a/src/backend/nodes/equalfuncs.c b/src/backend/nodes/equalfuncs.c
new file mode 100644
index 2295195..ce75da3
*** a/src/backend/nodes/equalfuncs.c
--- b/src/backend/nodes/equalfuncs.c
***************
*** 83,88 ****
--- 83,91 ----
  #define COMPARE_LOCATION_FIELD(fldname) \
  	((void) 0)
  
+ /* Compare a query_id field (this is a no-op, per note above) */
+ #define COMPARE_QUERYID_FIELD(fldname) \
+ 	((void) 0)
  
  /*
   *	Stuff from primnodes.h
*************** _equalQuery(const Query *a, const Query
*** 897,902 ****
--- 900,906 ----
  {
  	COMPARE_SCALAR_FIELD(commandType);
  	COMPARE_SCALAR_FIELD(querySource);
+ 	COMPARE_QUERYID_FIELD(query_id);
  	COMPARE_SCALAR_FIELD(canSetTag);
  	COMPARE_NODE_FIELD(utilityStmt);
  	COMPARE_SCALAR_FIELD(resultRelation);
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
new file mode 100644
index 829f6d4..1b5cfaf
*** a/src/backend/nodes/outfuncs.c
--- b/src/backend/nodes/outfuncs.c
***************
*** 46,51 ****
--- 46,55 ----
  #define WRITE_UINT_FIELD(fldname) \
  	appendStringInfo(str, " :" CppAsString(fldname) " %u", node->fldname)
  
+ /* Write a location/query id field (anything written as ":fldname %lu") */
+ #define WRITE_ULINT_FIELD(fldname) \
+ 	appendStringInfo(str, " :" CppAsString(fldname) " %lu", node->fldname)
+ 
  /* Write an OID field (don't hard-wire assumption that OID is same as uint) */
  #define WRITE_OID_FIELD(fldname) \
  	appendStringInfo(str, " :" CppAsString(fldname) " %u", node->fldname)
***************
*** 81,86 ****
--- 85,94 ----
  #define WRITE_LOCATION_FIELD(fldname) \
  	appendStringInfo(str, " :" CppAsString(fldname) " %d", node->fldname)
  
+ /* Write a query id field */
+ #define WRITE_QUERYID_FIELD(fldname) \
+ 	((void) 0)
+ 
  /* Write a Node field */
  #define WRITE_NODE_FIELD(fldname) \
  	(appendStringInfo(str, " :" CppAsString(fldname) " "), \
*************** _outPlannedStmt(StringInfo str, const Pl
*** 255,260 ****
--- 263,269 ----
  	WRITE_NODE_FIELD(relationOids);
  	WRITE_NODE_FIELD(invalItems);
  	WRITE_INT_FIELD(nParamExec);
+ 	WRITE_QUERYID_FIELD(queryId);
  }
  
  /*
*************** _outQuery(StringInfo str, const Query *n
*** 2159,2164 ****
--- 2168,2174 ----
  
  	WRITE_ENUM_FIELD(commandType, CmdType);
  	WRITE_ENUM_FIELD(querySource, QuerySource);
+ 	WRITE_QUERYID_FIELD(query_id);
  	WRITE_BOOL_FIELD(canSetTag);
  
  	/*
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
new file mode 100644
index b9258ad..d2e65ef
*** a/src/backend/nodes/readfuncs.c
--- b/src/backend/nodes/readfuncs.c
***************
*** 68,73 ****
--- 68,79 ----
  	token = pg_strtok(&length);		/* get field value */ \
  	local_node->fldname = atoui(token)
  
+ /* Read an unsigned integer field (anything written as ":fldname %lu") */
+ #define READ_ULINT_FIELD(fldname) \
+ 	token = pg_strtok(&length);		/* skip :fldname */ \
+ 	token = pg_strtok(&length);		/* get field value */ \
+ 	local_node->fldname = atoul(token)
+ 
  /* Read an OID field (don't hard-wire assumption that OID is same as uint) */
  #define READ_OID_FIELD(fldname) \
  	token = pg_strtok(&length);		/* skip :fldname */ \
***************
*** 110,115 ****
--- 116,124 ----
  	token = pg_strtok(&length);		/* get field value */ \
  	local_node->fldname = -1	/* set field to "unknown" */
  
+ /* NOOP */
+ #define READ_QUERYID_FIELD(fldname) \
+ 	((void) 0)
  /* Read a Node field */
  #define READ_NODE_FIELD(fldname) \
  	token = pg_strtok(&length);		/* skip :fldname */ \
***************
*** 133,138 ****
--- 142,149 ----
   */
  #define atoui(x)  ((unsigned int) strtoul((x), NULL, 10))
  
+ #define atoul(x)  ((unsigned long) strtoul((x), NULL, 10))
+ 
  #define atooid(x)  ((Oid) strtoul((x), NULL, 10))
  
  #define strtobool(x)  ((*(x) == 't') ? true : false)
*************** _readQuery(void)
*** 195,200 ****
--- 206,212 ----
  
  	READ_ENUM_FIELD(commandType, CmdType);
  	READ_ENUM_FIELD(querySource, QuerySource);
+ 	READ_QUERYID_FIELD(query_id);
  	READ_BOOL_FIELD(canSetTag);
  	READ_NODE_FIELD(utilityStmt);
  	READ_INT_FIELD(resultRelation);
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
new file mode 100644
index 2e8ea5a..ae011ae
*** a/src/backend/optimizer/plan/planner.c
--- b/src/backend/optimizer/plan/planner.c
*************** standard_planner(Query *parse, int curso
*** 240,245 ****
--- 240,246 ----
  	result->relationOids = glob->relationOids;
  	result->invalItems = glob->invalItems;
  	result->nParamExec = list_length(glob->paramlist);
+ 	result->queryId = parse->query_id;
  
  	return result;
  }
diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c
new file mode 100644
index be6e93e..fc43bed
*** a/src/backend/parser/analyze.c
--- b/src/backend/parser/analyze.c
*************** static Query *transformExplainStmt(Parse
*** 65,73 ****
  static void transformLockingClause(ParseState *pstate, Query *qry,
  					   LockingClause *lc, bool pushedDown);
  
  
  /*
!  * parse_analyze
   *		Analyze a raw parse tree and transform it to Query form.
   *
   * Optionally, information about $n parameter types can be supplied.
--- 65,89 ----
  static void transformLockingClause(ParseState *pstate, Query *qry,
  					   LockingClause *lc, bool pushedDown);
  
+ /* Hooks for plugins to get control of parse analysis */
+ parse_analyze_hook_type				parse_analyze_hook = NULL;
+ parse_analyze_varparams_hook_type	parse_analyze_varparams_hook = NULL;
+ 
+ 
+ Query *
+ parse_analyze(Node *parseTree, const char *sourceText,
+ 			  Oid *paramTypes, int numParams)
+ {
+ 	if (parse_analyze_hook)
+ 		return (*parse_analyze_hook) (parseTree, sourceText,
+ 			  paramTypes, numParams);
+ 	else
+ 		return standard_parse_analyze(parseTree, sourceText,
+ 			  paramTypes, numParams);
+ }
  
  /*
!  * standard_parse_analyze
   *		Analyze a raw parse tree and transform it to Query form.
   *
   * Optionally, information about $n parameter types can be supplied.
*************** static void transformLockingClause(Parse
*** 78,84 ****
   * a dummy CMD_UTILITY Query node.
   */
  Query *
! parse_analyze(Node *parseTree, const char *sourceText,
  			  Oid *paramTypes, int numParams)
  {
  	ParseState *pstate = make_parsestate(NULL);
--- 94,100 ----
   * a dummy CMD_UTILITY Query node.
   */
  Query *
! standard_parse_analyze(Node *parseTree, const char *sourceText,
  			  Oid *paramTypes, int numParams)
  {
  	ParseState *pstate = make_parsestate(NULL);
*************** parse_analyze(Node *parseTree, const cha
*** 98,112 ****
  	return query;
  }
  
  /*
!  * parse_analyze_varparams
   *
   * This variant is used when it's okay to deduce information about $n
   * symbol datatypes from context.  The passed-in paramTypes[] array can
   * be modified or enlarged (via repalloc).
   */
  Query *
! parse_analyze_varparams(Node *parseTree, const char *sourceText,
  						Oid **paramTypes, int *numParams)
  {
  	ParseState *pstate = make_parsestate(NULL);
--- 114,140 ----
  	return query;
  }
  
+ Query *
+ parse_analyze_varparams(Node *parseTree, const char *sourceText,
+ 						Oid **paramTypes, int *numParams)
+ {
+ 	if (parse_analyze_varparams_hook)
+ 		return (*parse_analyze_varparams_hook) (parseTree, sourceText,
+ 						paramTypes, numParams);
+ 	else
+ 		return standard_parse_analyze_varparams(parseTree, sourceText,
+ 			  paramTypes, numParams);
+ }
+ 
  /*
!  * standard_parse_analyze_varparams
   *
   * This variant is used when it's okay to deduce information about $n
   * symbol datatypes from context.  The passed-in paramTypes[] array can
   * be modified or enlarged (via repalloc).
   */
  Query *
! standard_parse_analyze_varparams(Node *parseTree, const char *sourceText,
  						Oid **paramTypes, int *numParams)
  {
  	ParseState *pstate = make_parsestate(NULL);
*************** transformSelectStmt(ParseState *pstate,
*** 877,882 ****
--- 905,911 ----
  	ListCell   *l;
  
  	qry->commandType = CMD_SELECT;
+ 	qry->query_id = 0;
  
  	/* process the WITH clause independently of all else */
  	if (stmt->withClause)
diff --git a/src/include/access/hash.h b/src/include/access/hash.h
new file mode 100644
index a3d0f98..464d772
*** a/src/include/access/hash.h
--- b/src/include/access/hash.h
*************** typedef HashMetaPageData *HashMetaPage;
*** 239,244 ****
--- 239,246 ----
   */
  #define HASHPROC		1
  
+ #define hash_any(k, keylen) (hash_any_var_width(k, keylen, true))
+ #define hash_any64(k, keylen) (hash_any_var_width(k, keylen, false))
  
  /* public routines */
  
*************** extern Datum hashint2vector(PG_FUNCTION_
*** 278,284 ****
  extern Datum hashname(PG_FUNCTION_ARGS);
  extern Datum hashtext(PG_FUNCTION_ARGS);
  extern Datum hashvarlena(PG_FUNCTION_ARGS);
! extern Datum hash_any(register const unsigned char *k, register int keylen);
  extern Datum hash_uint32(uint32 k);
  
  /* private routines */
--- 280,286 ----
  extern Datum hashname(PG_FUNCTION_ARGS);
  extern Datum hashtext(PG_FUNCTION_ARGS);
  extern Datum hashvarlena(PG_FUNCTION_ARGS);
! extern Datum hash_any_var_width(register const unsigned char *k, register int keylen, bool width_32);
  extern Datum hash_uint32(uint32 k);
  
  /* private routines */
diff --git a/src/include/nodes/parsenodes.h b/src/include/nodes/parsenodes.h
new file mode 100644
index 1d33ceb..d4133f2
*** a/src/include/nodes/parsenodes.h
--- b/src/include/nodes/parsenodes.h
*************** typedef struct Query
*** 103,108 ****
--- 103,111 ----
  
  	QuerySource querySource;	/* where did I come from? */
  
+ 	uint64		query_id;		/* query identifier that can be set by plugins.
+ 								 * Will be copied to resulting PlannedStmt. */
+ 
  	bool		canSetTag;		/* do I set the command result tag? */
  
  	Node	   *utilityStmt;	/* non-null if this is DECLARE CURSOR or a
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
new file mode 100644
index 7d90b91..f520085
*** a/src/include/nodes/plannodes.h
--- b/src/include/nodes/plannodes.h
*************** typedef struct PlannedStmt
*** 67,72 ****
--- 67,74 ----
  	List	   *invalItems;		/* other dependencies, as PlanInvalItems */
  
  	int			nParamExec;		/* number of PARAM_EXEC Params used */
+ 
+ 	uint64		queryId;		/* query identifier carried from query tree */
  } PlannedStmt;
  
  /* macro for fetching the Plan associated with a SubPlan node */
diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h
new file mode 100644
index b8987db..2bad10f
*** a/src/include/parser/analyze.h
--- b/src/include/parser/analyze.h
***************
*** 16,26 ****
--- 16,38 ----
  
  #include "parser/parse_node.h"
  
+ /* Hook for plugins to get control in parse_analyze() */
+ typedef Query* (*parse_analyze_hook_type) (Node *parseTree, const char *sourceText,
+ 			  Oid *paramTypes, int numParams);
+ extern PGDLLIMPORT parse_analyze_hook_type parse_analyze_hook;
+ /* Hook for plugins to get control in parse_analyze_varparams() */
+ typedef Query* (*parse_analyze_varparams_hook_type) (Node *parseTree, const char *sourceText,
+ 						Oid **paramTypes, int *numParams);
+ extern PGDLLIMPORT parse_analyze_varparams_hook_type parse_analyze_varparams_hook;
  
  extern Query *parse_analyze(Node *parseTree, const char *sourceText,
  			  Oid *paramTypes, int numParams);
+ extern Query *standard_parse_analyze(Node *parseTree, const char *sourceText,
+ 			  Oid *paramTypes, int numParams);
  extern Query *parse_analyze_varparams(Node *parseTree, const char *sourceText,
  						Oid **paramTypes, int *numParams);
+ extern Query *standard_parse_analyze_varparams(Node *parseTree, const char *sourceText,
+ 						Oid **paramTypes, int *numParams);
  
  extern Query *parse_sub_analyze(Node *parseTree, ParseState *parentParseState,
  				  CommonTableExpr *parentCTE,
