I made a number of changes on the proposed patch and ended up with the attached. (I have not included the business to limit number of characters, yet).
What this does is set an error callback, which adds the parameter values in the DETAIL line. This is at odds with all existing error callbacks: they only add stuff to the CONTEXT string. The implication is that if an error site has a direct errdetail_log() and is run under this error stack, that site's detail is going to be lost and replaced by the "params" one. Currently there aren't many errdetail_log() lines, so it doesn't seem like a terrible problem. However, it's something to keep in mind. This is not final form; there are a couple of XXX comments, and a number of other small infelicities still to fix. But the overall idea should be final. -- Álvaro Herrera https://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services
>From 1c5ffb2fb828a3589564d2f8af828e1be0549956 Mon Sep 17 00:00:00 2001 From: Alvaro Herrera <alvhe...@alvh.no-ip.org> Date: Tue, 3 Dec 2019 10:08:35 -0300 Subject: [PATCH v15 1/2] Add appendStringInfoStringQuoted MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Simplifies some coding that prints parameters, as well as optimize to do it per non-quote chunks instead of per byte. Author: Alexey Bashtanov and Álvaro Herrera, after a suggestion from Andres Freund Discussion: https://postgr.es/m/20190920203905.xkv5udsd5dxfs...@alap3.anarazel.de --- src/backend/tcop/postgres.c | 10 +-------- src/common/stringinfo.c | 40 ++++++++++++++++++++++++++++++++++++ src/include/lib/stringinfo.h | 7 +++++++ src/pl/plpgsql/src/pl_exec.c | 34 ++++++++---------------------- 4 files changed, 56 insertions(+), 35 deletions(-) diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 3b85e48333..0bff20ad67 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -2348,7 +2348,6 @@ errdetail_params(ParamListInfo params) Oid typoutput; bool typisvarlena; char *pstring; - char *p; appendStringInfo(¶m_str, "%s$%d = ", paramno > 0 ? ", " : "", @@ -2364,14 +2363,7 @@ errdetail_params(ParamListInfo params) pstring = OidOutputFunctionCall(typoutput, prm->value); - appendStringInfoCharMacro(¶m_str, '\''); - for (p = pstring; *p; p++) - { - if (*p == '\'') /* double single quotes */ - appendStringInfoCharMacro(¶m_str, *p); - appendStringInfoCharMacro(¶m_str, *p); - } - appendStringInfoCharMacro(¶m_str, '\''); + appendStringInfoStringQuoted(¶m_str, pstring); pfree(pstring); } diff --git a/src/common/stringinfo.c b/src/common/stringinfo.c index a50e587da9..f4dddf8223 100644 --- a/src/common/stringinfo.c +++ b/src/common/stringinfo.c @@ -178,6 +178,46 @@ appendStringInfoString(StringInfo str, const char *s) appendBinaryStringInfo(str, s, strlen(s)); } +/* + * appendStringInfoStringQuoted + * + * Append a null-terminated string to str, adding single quotes around it + * and doubling all single quotes. + */ +void +appendStringInfoStringQuoted(StringInfo str, const char *s) +{ + const char *chunk_search_start = s, + *chunk_copy_start = s, + *chunk_end; + int len; + + 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, ensuring sufficient space for final bytes */ + len = strlen(chunk_copy_start); + enlargeStringInfo(str, len + 2); + appendBinaryStringInfoNT(str, chunk_copy_start, len); + + /* add terminators */ + appendStringInfoCharMacro(str, '\''); + str->data[str->len] = '\0'; +} + /* * appendStringInfoChar * diff --git a/src/include/lib/stringinfo.h b/src/include/lib/stringinfo.h index e27942728e..7de2773e1f 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 it + * and doubling all single quotes. + */ +extern void appendStringInfoStringQuoted(StringInfo str, const char *s); + /*------------------------ * appendStringInfoChar * Append a single byte to str. diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c index 4f0de7a811..23553a6948 100644 --- a/src/pl/plpgsql/src/pl_exec.c +++ b/src/pl/plpgsql/src/pl_exec.c @@ -8611,19 +8611,10 @@ format_expr_params(PLpgSQL_execstate *estate, if (paramisnull) appendStringInfoString(¶mstr, "NULL"); else - { - char *value = convert_value_to_string(estate, paramdatum, paramtypeid); - char *p; - - appendStringInfoCharMacro(¶mstr, '\''); - for (p = value; *p; p++) - { - if (*p == '\'') /* double single quotes */ - appendStringInfoCharMacro(¶mstr, *p); - appendStringInfoCharMacro(¶mstr, *p); - } - appendStringInfoCharMacro(¶mstr, '\''); - } + appendStringInfoStringQuoted(¶mstr, + convert_value_to_string(estate, + paramdatum, + paramtypeid)); paramno++; } @@ -8661,19 +8652,10 @@ format_preparedparamsdata(PLpgSQL_execstate *estate, if (ppd->nulls[paramno] == 'n') appendStringInfoString(¶mstr, "NULL"); else - { - char *value = convert_value_to_string(estate, ppd->values[paramno], ppd->types[paramno]); - char *p; - - appendStringInfoCharMacro(¶mstr, '\''); - for (p = value; *p; p++) - { - if (*p == '\'') /* double single quotes */ - appendStringInfoCharMacro(¶mstr, *p); - appendStringInfoCharMacro(¶mstr, *p); - } - appendStringInfoCharMacro(¶mstr, '\''); - } + appendStringInfoStringQuoted(¶mstr, + convert_value_to_string(estate, + ppd->values[paramno], + ppd->types[paramno])); } MemoryContextSwitchTo(oldcontext); -- 2.20.1
>From 850fb629ca2fa9de0868469ba9a122780462ce0f Mon Sep 17 00:00:00 2001 From: Alvaro Herrera <alvhe...@alvh.no-ip.org> Date: Mon, 2 Dec 2019 16:02:48 -0300 Subject: [PATCH v15 2/2] bashtanov v14 --- doc/src/sgml/config.sgml | 23 +++ src/backend/nodes/params.c | 125 ++++++++++++ src/backend/tcop/postgres.c | 101 ++++----- src/backend/utils/misc/guc.c | 10 + src/backend/utils/misc/postgresql.conf.sample | 1 + src/include/nodes/params.h | 3 + src/include/utils/guc.h | 1 + src/test/README | 3 +- src/test/examples/.gitignore | 1 + src/test/examples/Makefile | 2 +- src/test/examples/testlibpq5.c | 193 ++++++++++++++++++ 11 files changed, 413 insertions(+), 50 deletions(-) create mode 100644 src/test/examples/testlibpq5.c diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml index 53ac14490a..5d1c90282f 100644 --- a/doc/src/sgml/config.sgml +++ b/doc/src/sgml/config.sgml @@ -6595,6 +6595,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..dd0402a968 100644 --- a/src/backend/nodes/params.c +++ b/src/backend/nodes/params.c @@ -15,11 +15,14 @@ #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" /* @@ -44,6 +47,7 @@ makeParamList(int numParams) retval->paramCompileArg = NULL; retval->parserSetup = NULL; retval->parserSetupArg = NULL; + retval->paramValuesStr = NULL; retval->numParams = numParams; return retval; @@ -58,6 +62,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. + * + * paramValuesStr is not copied, either. */ ParamListInfo copyParamList(ParamListInfo from) @@ -221,6 +227,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. + * + * paramValuesStr is not copied. */ ParamListInfo RestoreParamList(char **start_address) @@ -251,3 +259,120 @@ 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 paramStringLen = 0; + StringInfoData buf; + char **textValues; + + /* + * nothing to do if it's is already set; also, no work if the param fetch + * hook is in use. + * + * XXX invoke the fetch hook to obtain the value? (probably useless) + */ + if (params->paramValuesStr != NULL || + params->paramFetch != NULL) + return; + + initStringInfo(&buf); + + 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]; + + /* add space for the param number of digits XXX slooow? */ + for (int d = paramno + 1; d > 0; d /= 10) + paramStringLen++; + + if (param.isnull) + { + paramStringLen += 4; /* for "NULL" */ + } + else + { + /* 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); + } + + /* Count the length of the text value itself */ + paramStringLen += strlen(textValues[paramno]); + } + } + + /*----------------------------- + * The output for each datum looks like this: + * "$1 = something, " + * ^ ^^^ ^^ + * except for the last datum where we need \0 instead of ", ". + *----------------------------- + */ + paramStringLen += params->numParams * 6 - 1; + + MemoryContextSwitchTo(oldCxt); + + /* enlarge at once to avoid multiple reallocations */ + enlargeStringInfo(&buf, paramStringLen); + + for (int paramno = 0; paramno < params->numParams; paramno++) + { + appendStringInfo(&buf, + "%s$%d = ", + paramno > 0 ? ", " : "", + paramno + 1); + appendStringInfoStringQuoted(&buf, textValues[paramno]); + } + + params->paramValuesStr = buf.data; + + MemoryContextDelete(tmpCxt); +} + +/* + * ParamsErrorCallback - callback for printing parameters in error context + */ +void +ParamsErrorCallback(void *arg) +{ + ParamListInfo params = (ParamListInfo) arg; + + if (params == NULL || params->paramValuesStr == NULL) + return; + + /* + * XXX this clobbers any other DETAIL line that the error callsite could + * have had. Do we care? + */ + errdetail_log("parameters: %s", params->paramValuesStr); +} diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 0bff20ad67..ef8eda6de8 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -1613,6 +1613,7 @@ exec_bind_message(StringInfo input_message) bool save_log_statement_stats = log_statement_stats; bool snapshot_set = false; char msec_str[32]; + ErrorContextCallback params_errcxt; /* Get the fixed part of the message */ portal_name = pq_getmsgstring(input_message); @@ -1752,6 +1753,8 @@ exec_bind_message(StringInfo input_message) */ if (numParams > 0) { + char **knownTextValues = NULL; + params = makeParamList(numParams); for (int paramno = 0; paramno < numParams; paramno++) @@ -1819,9 +1822,26 @@ 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); + /* + * Save the parameter string if we need it; otherwise free it. + */ + if (pstring) + { + if (log_parameters_on_error) + { + /* + * Allocate this on first use, making sure to put it + * in a short-lived context. + */ + if (knownTextValues == NULL) + knownTextValues = + MemoryContextAllocZero(MessageContext, + numParams * sizeof(char *)); + knownTextValues[paramno] = pstring; + } + else if (pstring != pbuf.data) + pfree(pstring); + } } else if (pformat == 1) /* binary mode */ { @@ -1871,6 +1891,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 +1901,12 @@ exec_bind_message(StringInfo input_message) /* Done storing stuff in portal's context */ MemoryContextSwitchTo(oldContext); + /* Set the error callback so that parameters are logged, as needed */ + params_errcxt.previous = error_context_stack; + params_errcxt.callback = ParamsErrorCallback; + params_errcxt.arg = (void *) params; + error_context_stack = ¶ms_errcxt; + /* Get the result format codes */ numRFormats = pq_getmsgint(input_message, 2); if (numRFormats > 0) @@ -1923,6 +1952,12 @@ exec_bind_message(StringInfo input_message) */ PortalSetResultFormat(portal, numRFormats, rformats); + /* + * Done binding; remove the parameters error callback. Entries emitted + * later determine independently whether to log the parameters or not. + */ + error_context_stack = error_context_stack->previous; + /* * Send BindComplete. */ @@ -1979,6 +2014,7 @@ exec_execute_message(const char *portal_name, long max_rows) bool execute_is_fetch; bool was_logged = false; char msec_str[32]; + ErrorContextCallback params_errcxt; /* Adjust destination to tell printtup.c what to do */ dest = whereToSendOutput; @@ -2103,8 +2139,14 @@ exec_execute_message(const char *portal_name, long max_rows) CHECK_FOR_INTERRUPTS(); /* - * Okay to run the portal. + * Okay to run the portal. Set the error callback so that parameters are + * logged, as needed. */ + params_errcxt.previous = error_context_stack; + params_errcxt.callback = ParamsErrorCallback; + params_errcxt.arg = (void *) portalParams; + error_context_stack = ¶ms_errcxt; + if (max_rows <= 0) max_rows = FETCH_ALL; @@ -2118,6 +2160,9 @@ exec_execute_message(const char *portal_name, long max_rows) receiver->rDestroy(receiver); + /* Done executing; remove the params error callback */ + error_context_stack = error_context_stack->previous; + if (completed) { if (is_xact_command) @@ -2328,51 +2373,13 @@ errdetail_execute(List *raw_parsetree_list) static int errdetail_params(ParamListInfo params) { - /* We mustn't call user-defined I/O functions when in an aborted xact */ - if (params && params->numParams > 0 && !IsAbortedTransactionBlockState()) + if (params && params->numParams > 0) { - StringInfoData param_str; - MemoryContext oldcontext; + BuildParamLogString(params, NULL); - /* This code doesn't support dynamic param lists */ - Assert(params->paramFetch == NULL); - - /* Make sure any trash is generated in MessageContext */ - oldcontext = MemoryContextSwitchTo(MessageContext); - - initStringInfo(¶m_str); - - for (int paramno = 0; paramno < params->numParams; paramno++) - { - ParamExternData *prm = ¶ms->params[paramno]; - Oid typoutput; - bool typisvarlena; - char *pstring; - - appendStringInfo(¶m_str, "%s$%d = ", - paramno > 0 ? ", " : "", - paramno + 1); - - if (prm->isnull || !OidIsValid(prm->ptype)) - { - appendStringInfoString(¶m_str, "NULL"); - continue; - } - - getTypeOutputInfo(prm->ptype, &typoutput, &typisvarlena); - - pstring = OidOutputFunctionCall(typoutput, prm->value); - - appendStringInfoStringQuoted(¶m_str, pstring); - - pfree(pstring); - } - - errdetail("parameters: %s", param_str.data); - - pfree(param_str.data); - - MemoryContextSwitchTo(oldcontext); + if (params->paramValuesStr && + params->paramValuesStr[0] != '\0') + errdetail("parameters: %s", params->paramValuesStr); } return 0; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index ba74bf9f7d..8d951ce404 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 9541879c1f..087190ce63 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/include/nodes/params.h b/src/include/nodes/params.h index fd9046619c..ba51dfe5e0 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 *paramValuesStr; /* params as a single string for errors */ int numParams; /* nominal/maximum # of Params represented */ /* @@ -156,5 +157,7 @@ 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 ParamsErrorCallback(void *arg); #endif /* PARAMS_H */ 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; +} -- 2.20.1