Patch file name is not great, changing it.

On 30/11/2019 12:35, Alexey Bashtanov wrote:
I've implemented it using error contexts, please could you have a look at the patch attached?

diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 4ec13f3311..b3a0d27861 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -6597,6 +6597,29 @@ log_line_prefix = '%m [%p] %q%u@%d/%a '
       </listitem>
      </varlistentry>
 
+     <varlistentry id="guc-log-parameters-on-error" xreflabel="log_parameters_on_error">
+      <term><varname>log_parameters_on_error</varname> (<type>boolean</type>)
+      <indexterm>
+       <primary><varname>log_parameters_on_error</varname> configuration parameter</primary>
+      </indexterm>
+      </term>
+      <listitem>
+       <para>
+        Controls whether bind parameters are logged when a statement is logged
+        as a result of <xref linkend="guc-log-min-error-statement"/>.
+        It adds some overhead, as postgres will compute and store textual
+        representations of parameter values in memory for all statements,
+        even if they eventually do not get logged.
+        This setting has no effect on statements logged due to
+        <xref linkend="guc-log-min-duration-statement"/> or
+        <xref linkend="guc-log-statement"/> settings, as they are always logged
+        with parameters.
+        The default is <literal>off</literal>.
+        Only superusers can change this setting.
+       </para>
+      </listitem>
+     </varlistentry>
+
      <varlistentry id="guc-log-statement" xreflabel="log_statement">
       <term><varname>log_statement</varname> (<type>enum</type>)
       <indexterm>
diff --git a/src/backend/nodes/params.c b/src/backend/nodes/params.c
index cf4387e40f..7c1f86f5c5 100644
--- a/src/backend/nodes/params.c
+++ b/src/backend/nodes/params.c
@@ -15,12 +15,16 @@
 
 #include "postgres.h"
 
+#include "lib/stringinfo.h"
 #include "nodes/bitmapset.h"
 #include "nodes/params.h"
 #include "storage/shmem.h"
 #include "utils/datum.h"
+#include "utils/guc.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 
+static void LogParams(void *arg);
 
 /*
  * Allocate and initialize a new ParamListInfo structure.
@@ -44,6 +48,7 @@ makeParamList(int numParams)
 	retval->paramCompileArg = NULL;
 	retval->parserSetup = NULL;
 	retval->parserSetupArg = NULL;
+	retval->logString = NULL;
 	retval->numParams = numParams;
 
 	return retval;
@@ -58,6 +63,8 @@ makeParamList(int numParams)
  * set of parameter values.  If dynamic parameter hooks are present, we
  * intentionally do not copy them into the result.  Rather, we forcibly
  * instantiate all available parameter values and copy the datum values.
+ *
+ * We also don't copy logString.
  */
 ParamListInfo
 copyParamList(ParamListInfo from)
@@ -221,6 +228,8 @@ SerializeParamList(ParamListInfo paramLI, char **start_address)
  * set of parameter values.  If dynamic parameter hooks are present, we
  * intentionally do not copy them into the result.  Rather, we forcibly
  * instantiate all available parameter values and copy the datum values.
+ *
+ * We also don't copy logString.
  */
 ParamListInfo
 RestoreParamList(char **start_address)
@@ -251,3 +260,152 @@ RestoreParamList(char **start_address)
 
 	return paramLI;
 }
+
+/*
+ * BuildParamLogString - create a string to represent parameters list for
+ * logging and save it to params. If caller already knows textual
+ * representations for some or all parameters, it can pass an array of exactly
+ * params->numParams values as knownTextValues. It can contain NULLs for the
+ * individual values not known, or, if no text text values known at all the
+ * caller may pass NULL pointer.
+ */
+void
+BuildParamLogString(ParamListInfo params, char **knownTextValues)
+{
+	MemoryContext tmpCxt,
+				  oldCxt;
+	int logStringLen = 0;
+	StringInfoData logString;
+	char **textValues;
+
+	if (params->logString != NULL)
+		return;
+
+	Assert(params->paramFetch == NULL);
+
+	initStringInfo(&logString);
+
+	tmpCxt = AllocSetContextCreate(CurrentMemoryContext,
+								   "BuildParamLogString",
+								   ALLOCSET_DEFAULT_SIZES);
+	oldCxt = MemoryContextSwitchTo(tmpCxt);
+
+	textValues = (char **) palloc0(params->numParams * sizeof(char *));
+
+	/* calculate unknown text representations and pre-calculate byte lengths */
+	for (int paramno = 0; paramno < params->numParams; paramno++)
+	{
+		ParamExternData param = params->params[paramno];
+
+		/* reserve the space for the number of digits in paramno + 1 */
+		for (int d = paramno + 1; d > 0; d /= 10)
+			logStringLen++;
+
+		if (param.isnull)
+		{
+			logStringLen += 4; /* for "NULL" */
+		}
+		else
+		{
+			char *textValue,
+				 *s;
+
+			/* find out textValues[paramno] */
+			if (knownTextValues != NULL && knownTextValues[paramno] != NULL)
+			{
+				textValues[paramno] = knownTextValues[paramno];
+			}
+			else
+			{
+				Oid		typoutput;
+				bool	typisvarlena;
+				getTypeOutputInfo(param.ptype, &typoutput, &typisvarlena);
+				textValues[paramno] =
+								OidOutputFunctionCall(typoutput, param.value);
+			}
+
+			/* caluclate the space needed its printed length */
+			textValue = textValues[paramno];
+			for (s = textValue; *s != '\0'; s++)
+				if (*s == '\'')
+					logStringLen++; /* for quotes doubling */
+			/* string length + 2 for quotes */
+			logStringLen += s - textValue + 2;
+		}
+	}
+
+	/*
+	 * "$1 = something, " -- 6 bytes extra to the ordinal and the value;
+	 *  ^ ^^^         ^^
+	 * for the last one we don't need the comma and the space but need 0 byte
+	 */
+	logStringLen += params->numParams * 6 - 1;
+
+	MemoryContextSwitchTo(oldCxt);
+
+	/* enlarge at once to avoid multiple reallocations */
+	enlargeStringInfo(&logString, logStringLen);
+
+	for (int paramno = 0; paramno < params->numParams; paramno++)
+	{
+		appendStringInfo(&logString,
+						 "%s$%d = ",
+						 paramno > 0 ? ", " : "",
+						 paramno + 1);
+		appendStringInfoStringQuoted(&logString, textValues[paramno]);
+	}
+
+	params->logString = logString.data;
+
+	MemoryContextDelete(tmpCxt);
+}
+
+/*
+ * PutParamsInErrorContext - if needed and possible,
+ * add an error context entry with bind parameters
+ *
+ * params is a pointer to ParamListInfo for the caller to be able to clear it
+ * if the actual ParamListInfo is gone
+ */
+void
+PutParamsInErrorContext(ErrorContextCallback *new_error_context_entry,
+						ParamListInfo *params)
+{
+	/*
+	 * Setting this anyway for the caller to be able to revert this operation
+	 * with `error_context_stack = new_error_context_entry->previous` regardless
+	 * whether we actually change the stack
+	 */
+	new_error_context_entry->previous = error_context_stack;
+
+	if (!log_parameters_on_error || *params == NULL
+								 || (*params)->logString == NULL)
+		return;
+
+	new_error_context_entry->callback = LogParams;
+	new_error_context_entry->arg = (void *)(params);
+	error_context_stack = new_error_context_entry;
+}
+
+/*
+ * LogParams - callback for printing parameters in error context
+ */
+static void
+LogParams(void *arg)
+{
+	ParamListInfo params = *((ParamListInfo *)(arg));
+
+	/*
+	 * XXX: errdetail_log copies the error message formatting, so we're
+	 * allocating a potentially large amounts of memory for the whole parameter
+	 * string again
+	 *
+	 * We can have NULL in params->logString if log_parameters_on_error was off
+	 * when we decided whether to call BuildParamLogString. In this rare case
+	 * we don't log the parameters: building the log string now wouldn't be safe
+	 * as we must't call user-defined I/O functions when in an aborted xact.
+	 */
+	if (params != NULL && params->logString != NULL
+							&& log_parameters_on_error && geterrshowstmt())
+		errdetail_log("parameters: %s", params->logString);
+}
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 3b85e48333..9b562f7f01 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -177,7 +177,7 @@ static void forbidden_in_wal_sender(char firstchar);
 static List *pg_rewrite_query(Query *query);
 static bool check_log_statement(List *stmt_list);
 static int	errdetail_execute(List *raw_parsetree_list);
-static int	errdetail_params(ParamListInfo params);
+static int	errdetail_portal_params(Portal portal);
 static int	errdetail_abort(void);
 static int	errdetail_recovery_conflict(void);
 static void start_xact_command(void);
@@ -1596,23 +1596,24 @@ exec_parse_message(const char *query_string,	/* string to execute */
 static void
 exec_bind_message(StringInfo input_message)
 {
-	const char *portal_name;
-	const char *stmt_name;
-	int			numPFormats;
-	int16	   *pformats = NULL;
-	int			numParams;
-	int			numRFormats;
-	int16	   *rformats = NULL;
-	CachedPlanSource *psrc;
-	CachedPlan *cplan;
-	Portal		portal;
-	char	   *query_string;
-	char	   *saved_stmt_name;
-	ParamListInfo params;
-	MemoryContext oldContext;
-	bool		save_log_statement_stats = log_statement_stats;
-	bool		snapshot_set = false;
-	char		msec_str[32];
+	const char			*portal_name;
+	const char			*stmt_name;
+	int					 numPFormats;
+	int16				*pformats = NULL;
+	int					 numParams;
+	int					 numRFormats;
+	int16				*rformats = NULL;
+	CachedPlanSource	*psrc;
+	CachedPlan			*cplan;
+	Portal				 portal;
+	char				*query_string;
+	char				*saved_stmt_name;
+	ParamListInfo 		 params;
+	MemoryContext 		 oldContext;
+	bool				 save_log_statement_stats = log_statement_stats;
+	bool				 snapshot_set = false;
+	char				 msec_str[32];
+	ErrorContextCallback params_error_context;
 
 	/* Get the fixed part of the message */
 	portal_name = pq_getmsgstring(input_message);
@@ -1752,6 +1753,10 @@ exec_bind_message(StringInfo input_message)
 	 */
 	if (numParams > 0)
 	{
+		/* These are short-living, so we don't pollute the portal context */
+		char **knownTextValues = MemoryContextAllocZero(MessageContext,
+													numParams * sizeof(char *));
+
 		params = makeParamList(numParams);
 
 		for (int paramno = 0; paramno < numParams; paramno++)
@@ -1819,9 +1824,17 @@ exec_bind_message(StringInfo input_message)
 
 				pval = OidInputFunctionCall(typinput, pstring, typioparam, -1);
 
-				/* Free result of encoding conversion, if any */
-				if (pstring && pstring != pbuf.data)
-					pfree(pstring);
+				/*
+				 * If we need the parameter string, save it.
+				 * If not -- get rid of it.
+				 */
+				if (pstring)
+				{
+					if (log_parameters_on_error)
+						knownTextValues[paramno] = pstring;
+					else if (pstring != pbuf.data)
+						pfree(pstring);
+				}
 			}
 			else if (pformat == 1)	/* binary mode */
 			{
@@ -1871,6 +1884,9 @@ exec_bind_message(StringInfo input_message)
 			params->params[paramno].pflags = PARAM_FLAG_CONST;
 			params->params[paramno].ptype = ptype;
 		}
+
+		if (log_parameters_on_error)
+			BuildParamLogString(params, knownTextValues);
 	}
 	else
 		params = NULL;
@@ -1878,6 +1894,9 @@ exec_bind_message(StringInfo input_message)
 	/* Done storing stuff in portal's context */
 	MemoryContextSwitchTo(oldContext);
 
+	/* From now on, log parameters on error if enabled in GUC */
+	PutParamsInErrorContext(&params_error_context, &params);
+
 	/* Get the result format codes */
 	numRFormats = pq_getmsgint(input_message, 2);
 	if (numRFormats > 0)
@@ -1948,13 +1967,14 @@ exec_bind_message(StringInfo input_message)
 							*portal_name ? portal_name : "",
 							psrc->query_string),
 					 errhidestmt(true),
-					 errdetail_params(params)));
+					 errdetail_portal_params(portal)));
 			break;
 	}
 
 	if (save_log_statement_stats)
 		ShowUsage("BIND MESSAGE STATISTICS");
 
+	error_context_stack = params_error_context.previous;
 	debug_query_string = NULL;
 }
 
@@ -1966,19 +1986,20 @@ exec_bind_message(StringInfo input_message)
 static void
 exec_execute_message(const char *portal_name, long max_rows)
 {
-	CommandDest dest;
-	DestReceiver *receiver;
-	Portal		portal;
-	bool		completed;
-	char		completionTag[COMPLETION_TAG_BUFSIZE];
-	const char *sourceText;
-	const char *prepStmtName;
-	ParamListInfo portalParams;
-	bool		save_log_statement_stats = log_statement_stats;
-	bool		is_xact_command;
-	bool		execute_is_fetch;
-	bool		was_logged = false;
-	char		msec_str[32];
+	CommandDest			 dest;
+	DestReceiver		 *receiver;
+	Portal				 portal;
+	ParamListInfo		 params;
+	bool				 completed;
+	char				 completionTag[COMPLETION_TAG_BUFSIZE];
+	const char			*sourceText;
+	const char			*prepStmtName;
+	bool				 save_log_statement_stats = log_statement_stats;
+	bool				 is_xact_command;
+	bool				 execute_is_fetch;
+	bool				 was_logged = false;
+	char				 msec_str[32];
+	ErrorContextCallback params_error_context;
 
 	/* Adjust destination to tell printtup.c what to do */
 	dest = whereToSendOutput;
@@ -2002,6 +2023,10 @@ exec_execute_message(const char *portal_name, long max_rows)
 		return;
 	}
 
+	/* From now on, log parameters on error if enabled in GUC */
+	params = portal->portalParams;
+	PutParamsInErrorContext(&params_error_context, &params);
+
 	/* Does the portal contain a transaction command? */
 	is_xact_command = IsTransactionStmtList(portal->stmts);
 
@@ -2017,12 +2042,6 @@ exec_execute_message(const char *portal_name, long max_rows)
 			prepStmtName = pstrdup(portal->prepStmtName);
 		else
 			prepStmtName = "<unnamed>";
-
-		/*
-		 * An xact command shouldn't have any parameters, which is a good
-		 * thing because they wouldn't be around after finish_xact_command.
-		 */
-		portalParams = NULL;
 	}
 	else
 	{
@@ -2031,7 +2050,6 @@ exec_execute_message(const char *portal_name, long max_rows)
 			prepStmtName = portal->prepStmtName;
 		else
 			prepStmtName = "<unnamed>";
-		portalParams = portal->portalParams;
 	}
 
 	/*
@@ -2083,7 +2101,7 @@ exec_execute_message(const char *portal_name, long max_rows)
 						*portal_name ? portal_name : "",
 						sourceText),
 				 errhidestmt(true),
-				 errdetail_params(portalParams)));
+				 errdetail_portal_params(portal)));
 		was_logged = true;
 	}
 
@@ -2122,6 +2140,15 @@ exec_execute_message(const char *portal_name, long max_rows)
 	{
 		if (is_xact_command)
 		{
+			/*
+			 * The portal and its params may be destroyed during
+			 * finish_xact_command below. We only may need the portal for
+			 * parameters logging, but an xact command shouldn't have any
+			 * parameters.
+			 */
+			portal = NULL;
+			params = NULL;
+
 			/*
 			 * If this was a transaction control statement, commit it.  We
 			 * will start a new xact command for the next command (if any).
@@ -2175,13 +2202,14 @@ exec_execute_message(const char *portal_name, long max_rows)
 							*portal_name ? portal_name : "",
 							sourceText),
 					 errhidestmt(true),
-					 errdetail_params(portalParams)));
+					 errdetail_portal_params(portal)));
 			break;
 	}
 
 	if (save_log_statement_stats)
 		ShowUsage("EXECUTE MESSAGE STATISTICS");
 
+	error_context_stack = params_error_context.previous;
 	debug_query_string = NULL;
 }
 
@@ -2321,66 +2349,26 @@ errdetail_execute(List *raw_parsetree_list)
 }
 
 /*
- * errdetail_params
+ * errdetail_portal_params
  *
  * Add an errdetail() line showing bind-parameter data, if available.
  */
 static int
-errdetail_params(ParamListInfo params)
+errdetail_portal_params(Portal portal)
 {
-	/* We mustn't call user-defined I/O functions when in an aborted xact */
-	if (params && params->numParams > 0 && !IsAbortedTransactionBlockState())
+	if (portal != NULL &&
+		portal->portalParams &&
+		portal->portalParams->numParams > 0)
 	{
-		StringInfoData param_str;
-		MemoryContext oldcontext;
-
-		/* This code doesn't support dynamic param lists */
-		Assert(params->paramFetch == NULL);
-
-		/* Make sure any trash is generated in MessageContext */
-		oldcontext = MemoryContextSwitchTo(MessageContext);
-
-		initStringInfo(&param_str);
-
-		for (int paramno = 0; paramno < params->numParams; paramno++)
-		{
-			ParamExternData *prm = &params->params[paramno];
-			Oid			typoutput;
-			bool		typisvarlena;
-			char	   *pstring;
-			char	   *p;
-
-			appendStringInfo(&param_str, "%s$%d = ",
-							 paramno > 0 ? ", " : "",
-							 paramno + 1);
-
-			if (prm->isnull || !OidIsValid(prm->ptype))
-			{
-				appendStringInfoString(&param_str, "NULL");
-				continue;
-			}
-
-			getTypeOutputInfo(prm->ptype, &typoutput, &typisvarlena);
-
-			pstring = OidOutputFunctionCall(typoutput, prm->value);
-
-			appendStringInfoCharMacro(&param_str, '\'');
-			for (p = pstring; *p; p++)
-			{
-				if (*p == '\'') /* double single quotes */
-					appendStringInfoCharMacro(&param_str, *p);
-				appendStringInfoCharMacro(&param_str, *p);
-			}
-			appendStringInfoCharMacro(&param_str, '\'');
-
-			pfree(pstring);
-		}
-
-		errdetail("parameters: %s", param_str.data);
-
-		pfree(param_str.data);
+		/*
+		 * We switch back to portal context for the params logString to be
+		 * generated there in order to reuse it in later invocations
+		 */
+		MemoryContext old_cxt = MemoryContextSwitchTo(portal->portalContext);
+		BuildParamLogString(portal->portalParams, NULL);
+		MemoryContextSwitchTo(old_cxt);
 
-		MemoryContextSwitchTo(oldcontext);
+		errdetail("parameters: %s", portal->portalParams->logString);
 	}
 
 	return 0;
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index f46653632e..eb39c750f4 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -1363,6 +1363,22 @@ getinternalerrposition(void)
 	return edata->internalpos;
 }
 
+/*
+ * geterrshowstmt --- return whether we are going to print the statement in the log
+ *
+ * This is only intended for use in error callback subroutines, since there
+ * is no other place outside elog.c where the concept is meaningful.
+ */
+bool
+geterrshowstmt(void)
+{
+	ErrorData  *edata = &errordata[errordata_stack_depth];
+
+	return is_log_level_output(edata->elevel, log_min_error_statement) &&
+		debug_query_string != NULL &&
+		!edata->hide_stmt;
+}
+
 
 /*
  * elog_start --- startup for old-style API
@@ -2742,7 +2758,7 @@ static void
 write_csvlog(ErrorData *edata)
 {
 	StringInfoData buf;
-	bool		print_stmt = false;
+	bool print_stmt = geterrshowstmt();
 
 	/* static counter for line numbers */
 	static long log_line_number = 0;
@@ -2886,10 +2902,6 @@ write_csvlog(ErrorData *edata)
 	appendStringInfoChar(&buf, ',');
 
 	/* user query --- only reported if not disabled by the caller */
-	if (is_log_level_output(edata->elevel, log_min_error_statement) &&
-		debug_query_string != NULL &&
-		!edata->hide_stmt)
-		print_stmt = true;
 	if (print_stmt)
 		appendCSVLiteral(&buf, debug_query_string);
 	appendStringInfoChar(&buf, ',');
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 5fccc9683e..1be1f1fb29 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -486,6 +486,7 @@ extern const struct config_enum_entry dynamic_shared_memory_options[];
  * GUC option variables that are exported from this module
  */
 bool		log_duration = false;
+bool		log_parameters_on_error = false;
 bool		Debug_print_plan = false;
 bool		Debug_print_parse = false;
 bool		Debug_print_rewritten = false;
@@ -1300,6 +1301,15 @@ static struct config_bool ConfigureNamesBool[] =
 		false,
 		NULL, NULL, NULL
 	},
+	{
+		{"log_parameters_on_error", PGC_SUSET, LOGGING_WHAT,
+			gettext_noop("Logs bind parameters of the logged statements where possible."),
+			NULL
+		},
+		&log_parameters_on_error,
+		false,
+		NULL, NULL, NULL
+	},
 	{
 		{"debug_print_parse", PGC_USERSET, LOGGING_WHAT,
 			gettext_noop("Logs each query's parse tree."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 46a06ffacd..91cdc06fc4 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -544,6 +544,7 @@
 					# e.g. '<%u%%%d> '
 #log_lock_waits = off			# log lock waits >= deadlock_timeout
 #log_statement = 'none'			# none, ddl, mod, all
+#log_parameters_on_error = off	# on error log statements with bind parameters
 #log_replication_commands = off
 #log_temp_files = -1			# log temporary files equal or larger
 					# than the specified size in kilobytes;
diff --git a/src/common/stringinfo.c b/src/common/stringinfo.c
index a50e587da9..300fa4527b 100644
--- a/src/common/stringinfo.c
+++ b/src/common/stringinfo.c
@@ -178,6 +178,42 @@ appendStringInfoString(StringInfo str, const char *s)
 	appendBinaryStringInfo(str, s, strlen(s));
 }
 
+/*
+ * appendStringInfoStringQuoted
+ *
+ * Append a null-terminated string to str, adding single quotes around
+ * and doubling all single quotes.
+ */
+void
+appendStringInfoStringQuoted(StringInfo str, const char *s)
+{
+	const char *chunk_search_start = s,
+			   *chunk_copy_start = s,
+			   *chunk_end;
+
+	Assert(str != NULL);
+
+	appendStringInfoCharMacro(str, '\'');
+
+	while ((chunk_end = strchr(chunk_search_start, '\'')) != NULL)
+	{
+		/* copy including the found delimiting ' */
+		appendBinaryStringInfoNT(str,
+								 chunk_copy_start,
+								 chunk_end - chunk_copy_start + 1);
+
+		/* in order to double it, include this ' into the next chunk as well*/
+		chunk_copy_start = chunk_end;
+		chunk_search_start = chunk_end + 1;
+	}
+
+	/* copy the last chunk */
+	appendBinaryStringInfoNT(str, chunk_copy_start, strlen(chunk_copy_start));
+
+	appendStringInfoCharMacro(str, '\'');
+	str->data[str->len] = '\0';
+}
+
 /*
  * appendStringInfoChar
  *
diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h
index e27942728e..4978ac9aa3 100644
--- a/src/include/lib/stringinfo.h
+++ b/src/include/lib/stringinfo.h
@@ -113,6 +113,13 @@ extern int	appendStringInfoVA(StringInfo str, const char *fmt, va_list args) pg_
  */
 extern void appendStringInfoString(StringInfo str, const char *s);
 
+/*
+ * appendStringInfoStringQuoted
+ * Append a null-terminated string to str, adding single quotes around
+ * and doubling all single quotes.
+ */
+extern void appendStringInfoStringQuoted(StringInfo str, const char *s);
+
 /*------------------------
  * appendStringInfoChar
  * Append a single byte to str.
diff --git a/src/include/nodes/params.h b/src/include/nodes/params.h
index fd9046619c..49a450b6d0 100644
--- a/src/include/nodes/params.h
+++ b/src/include/nodes/params.h
@@ -115,6 +115,7 @@ typedef struct ParamListInfoData
 	void	   *paramCompileArg;
 	ParserSetupHook parserSetup;	/* parser setup hook */
 	void	   *parserSetupArg;
+	char	   *logString;		/* string to log parameters */
 	int			numParams;		/* nominal/maximum # of Params represented */
 
 	/*
@@ -156,5 +157,9 @@ extern ParamListInfo copyParamList(ParamListInfo from);
 extern Size EstimateParamListSpace(ParamListInfo paramLI);
 extern void SerializeParamList(ParamListInfo paramLI, char **start_address);
 extern ParamListInfo RestoreParamList(char **start_address);
+extern void BuildParamLogString(ParamListInfo params, char **paramTextValues);
+extern void PutParamsInErrorContext(
+				ErrorContextCallback *new_error_context_entry,
+				ParamListInfo *params);
 
 #endif							/* PARAMS_H */
diff --git a/src/include/utils/elog.h b/src/include/utils/elog.h
index 9ab4b5470b..ecc78d2ae6 100644
--- a/src/include/utils/elog.h
+++ b/src/include/utils/elog.h
@@ -202,6 +202,7 @@ extern int	err_generic_string(int field, const char *str);
 extern int	geterrcode(void);
 extern int	geterrposition(void);
 extern int	getinternalerrposition(void);
+extern bool geterrshowstmt(void);
 
 
 /*----------
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 50098e63fe..41d5e1d14a 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -234,6 +234,7 @@ typedef enum
 
 /* GUC vars that are actually declared in guc.c, rather than elsewhere */
 extern bool log_duration;
+extern bool log_parameters_on_error;
 extern bool Debug_print_plan;
 extern bool Debug_print_parse;
 extern bool Debug_print_rewritten;
diff --git a/src/test/README b/src/test/README
index b5ccfc0cf6..22e624d8ba 100644
--- a/src/test/README
+++ b/src/test/README
@@ -12,8 +12,7 @@ authentication/
   Tests for authentication
 
 examples/
-  Demonstration programs for libpq that double as regression tests via
-  "make check"
+  Demonstration programs for libpq that double as regression tests
 
 isolation/
   Tests for concurrent behavior at the SQL level
diff --git a/src/test/examples/.gitignore b/src/test/examples/.gitignore
index 1957ec198f..007aaee590 100644
--- a/src/test/examples/.gitignore
+++ b/src/test/examples/.gitignore
@@ -2,5 +2,6 @@
 /testlibpq2
 /testlibpq3
 /testlibpq4
+/testlibpq5
 /testlo
 /testlo64
diff --git a/src/test/examples/Makefile b/src/test/examples/Makefile
index a67f456904..0407ca60bc 100644
--- a/src/test/examples/Makefile
+++ b/src/test/examples/Makefile
@@ -14,7 +14,7 @@ override CPPFLAGS := -I$(libpq_srcdir) $(CPPFLAGS)
 LDFLAGS_INTERNAL += $(libpq_pgport)
 
 
-PROGS = testlibpq testlibpq2 testlibpq3 testlibpq4 testlo testlo64
+PROGS = testlibpq testlibpq2 testlibpq3 testlibpq4 testlibpq5 testlo testlo64
 
 all: $(PROGS)
 
diff --git a/src/test/examples/testlibpq5.c b/src/test/examples/testlibpq5.c
new file mode 100644
index 0000000000..47a5e31335
--- /dev/null
+++ b/src/test/examples/testlibpq5.c
@@ -0,0 +1,193 @@
+/*
+ * src/test/examples/testlibpq5.c
+ *
+ * testlibpq5.c
+ *		Test logging of statement parameters in case of errors.
+ */
+
+#ifdef WIN32
+#include <windows.h>
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/types.h>
+#include "libpq-fe.h"
+
+static void
+exit_nicely(PGconn *conn)
+{
+	PQfinish(conn);
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	const char *conninfo;
+	PGconn	   *conn;
+	PGresult   *res;
+	const char *paramValues[3];
+	int			paramLengths[3];
+	int			paramFormats[3];
+	uint32_t	binaryIntVal;
+
+	/*
+	 * If the user supplies a parameter on the command line, use it as the
+	 * conninfo string; otherwise default to setting dbname=postgres and using
+	 * environment variables or defaults for all other connection parameters.
+	 */
+	if (argc > 1)
+		conninfo = argv[1];
+	else
+		conninfo = "dbname = postgres";
+
+	/* Make a connection to the database */
+	conn = PQconnectdb(conninfo);
+
+	/* Check to see that the backend connection was successfully made */
+	if (PQstatus(conn) != CONNECTION_OK)
+	{
+		fprintf(stderr, "Connection to database failed: %s",
+				PQerrorMessage(conn));
+		exit_nicely(conn);
+	}
+
+	/* Set always-secure search path, so malicious users can't take control. */
+	res = PQexec(conn,
+				 "SELECT pg_catalog.set_config('search_path', '', false)");
+	if (PQresultStatus(res) != PGRES_TUPLES_OK)
+	{
+		fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+		PQclear(res);
+		exit_nicely(conn);
+	}
+	PQclear(res);
+
+	/*
+	 * Transmit parameters in different forms and make a statement fail.  User
+	 * can then verify the server log.
+	 */
+	res = PQexec(conn, "SET log_parameters_on_error = on");
+	if (PQresultStatus(res) != PGRES_COMMAND_OK)
+	{
+		fprintf(stderr, "SET failed: %s", PQerrorMessage(conn));
+		PQclear(res);
+		exit_nicely(conn);
+	}
+	PQclear(res);
+
+	/* emit error on parse stage */
+	paramValues[0] = (char *) "non-number -- parse";
+	paramLengths[0] = strlen(paramValues[2]) + 1;
+	paramFormats[0] = 0;		/* text */
+	res = PQexecParams(conn,
+					   "SELECT $1::int",
+					   1,		/* # params */
+					   NULL,	/* let the backend deduce param type */
+					   paramValues,
+					   paramLengths,
+					   paramFormats,
+					   1);		/* ask for binary results */
+
+	if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+	{
+		fprintf(stderr, "SELECT succeeded but was supposed to fail\n");
+		PQclear(res);
+		exit_nicely(conn);
+	}
+	PQclear(res);
+	printf("text->int conversion error expected on parse stage, "
+		   "got an error message from server: %s\n",
+		   PQerrorMessage(conn));
+
+	/* emit error on bind stage */
+	paramValues[0] = (char *) "{malformed json -- bind :-{";
+	paramLengths[0] = strlen(paramValues[2]) + 1;
+	paramFormats[0] = 0;		/* text */
+	res = PQexecParams(conn,
+					   "SELECT (' ' || $1::text || ' ')::json",
+					   1,		/* # params */
+					   NULL,	/* let the backend deduce param type */
+					   paramValues,
+					   paramLengths,
+					   paramFormats,
+					   1);		/* ask for binary results */
+
+	if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+	{
+		fprintf(stderr, "SELECT succeeded but was supposed to fail\n");
+		PQclear(res);
+		exit_nicely(conn);
+	}
+	PQclear(res);
+	printf("Json parsing error expected on bind stage, "
+		   "got an error message from server: %s\n"
+		   "Please make sure it has been logged "
+		   "with bind parameters in server log\n",
+		   PQerrorMessage(conn));
+
+	/* divide by zero -- but server won't realize until execution; 0 params */
+	res = PQexecParams(conn,
+					   "SELECT 1 / (random() / 2)::int /*exec*/",
+					   0,		/* # params */
+					   NULL,	/* let the backend deduce param type */
+					   NULL,
+					   NULL,
+					   NULL,
+					   1);		/* ask for binary results */
+
+	if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+	{
+		fprintf(stderr, "SELECT succeeded but was supposed to fail\n");
+		PQclear(res);
+		exit_nicely(conn);
+	}
+	PQclear(res);
+	printf("Division by zero on execution expected, "
+		   "got an error message from server: %s\n"
+		   "Please make sure it has been logged "
+		   "with bind parameters in server log\n",
+		   PQerrorMessage(conn));
+
+	/* divide by zero -- but server won't realize until execution; 3 params */
+	paramValues[0] = (char *) &binaryIntVal;
+	paramLengths[0] = sizeof(binaryIntVal);
+	paramFormats[0] = 1;		/* binary */
+	paramValues[1] = "2";
+	paramLengths[1] = strlen(paramValues[1]) + 1;
+	paramFormats[1] = 0;		/* text */
+	paramValues[2] = (char *) "everyone's little $$ in \"dollar\"";
+	paramLengths[2] = strlen(paramValues[2]) + 1;
+	paramFormats[2] = 0;		/* text */
+	res = PQexecParams(
+		conn,
+		"SELECT 1 / (random() / 2)::int + $1::int + $2::int, $3::text /*exec*/",
+		3,		/* # params */
+		NULL,	/* let the backend deduce param type */
+		paramValues,
+		paramLengths,
+		paramFormats,
+		1		/* ask for binary results */
+	);
+
+	if (PQresultStatus(res) != PGRES_FATAL_ERROR)
+	{
+		fprintf(stderr, "SELECT succeeded but was supposed to fail\n");
+		PQclear(res);
+		exit_nicely(conn);
+	}
+	PQclear(res);
+	printf("Division by zero on execution expected, "
+		   "got an error message from server: %s\n"
+		   "Please make sure it has been logged "
+		   "with bind parameters in server log\n",
+		   PQerrorMessage(conn));
+
+	/* close the connection to the database and cleanup */
+	PQfinish(conn);
+
+	return 0;
+}

Reply via email to