diff options
Diffstat (limited to 'src/backend')
-rw-r--r-- | src/backend/nodes/params.c | 110 | ||||
-rw-r--r-- | src/backend/tcop/postgres.c | 119 | ||||
-rw-r--r-- | src/backend/utils/misc/guc.c | 10 | ||||
-rw-r--r-- | src/backend/utils/misc/postgresql.conf.sample | 1 |
4 files changed, 193 insertions, 47 deletions
diff --git a/src/backend/nodes/params.c b/src/backend/nodes/params.c index cf4387e40f0..a9a7aca5334 100644 --- a/src/backend/nodes/params.c +++ b/src/backend/nodes/params.c @@ -15,11 +15,14 @@ #include "postgres.h" +#include "access/xact.h" +#include "mb/stringinfo_mb.h" #include "nodes/bitmapset.h" #include "nodes/params.h" #include "storage/shmem.h" #include "utils/datum.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) @@ -158,6 +164,8 @@ EstimateParamListSpace(ParamListInfo paramLI) * RestoreParamList can be used to recreate a ParamListInfo based on the * serialized representation; this will be a static, self-contained copy * just as copyParamList would create. + * + * paramValuesStr is not included. */ void SerializeParamList(ParamListInfo paramLI, char **start_address) @@ -251,3 +259,105 @@ RestoreParamList(char **start_address) return paramLI; } + +/* + * BuildParamLogString + * Return a string that represent the parameter list, for logging. + * + * If caller already knows textual representations for some parameters, it can + * pass an array of exactly params->numParams values as knownTextValues, which + * can contain NULLs for any unknown individual values. NULL can be given if + * no parameters are known. + * + * If maxlen is not zero, that's the maximum number of characters of the + * input string printed; an ellipsis is added if more characters exist. + * (Added quotes are not considered.) + */ +char * +BuildParamLogString(ParamListInfo params, char **knownTextValues, int maxlen) +{ + MemoryContext tmpCxt, + oldCxt; + StringInfoData buf; + + /* + * NB: think not of returning params->paramValuesStr! It may have been + * generated with a different maxlen, and so unsuitable. + */ + + /* + * No work if the param fetch hook is in use. Also, it's not possible to + * do this in an aborted transaction. (It might be possible to improve on + * this last point when some knownTextValues exist, but it seems tricky.) + */ + if (params->paramFetch != NULL || + IsAbortedTransactionBlockState()) + return NULL; + + /* Initialize the output stringinfo, in caller's memory context */ + initStringInfo(&buf); + + /* Use a temporary context to call output functions, just in case */ + tmpCxt = AllocSetContextCreate(CurrentMemoryContext, + "BuildParamLogString", + ALLOCSET_DEFAULT_SIZES); + oldCxt = MemoryContextSwitchTo(tmpCxt); + + for (int paramno = 0; paramno < params->numParams; paramno++) + { + ParamExternData *param = ¶ms->params[paramno]; + + appendStringInfo(&buf, + "%s$%d = ", + paramno > 0 ? ", " : "", + paramno + 1); + + if (param->isnull || !OidIsValid(param->ptype)) + appendStringInfoString(&buf, "NULL"); + else + { + if (knownTextValues != NULL && knownTextValues[paramno] != NULL) + appendStringInfoStringQuoted(&buf, knownTextValues[paramno], + maxlen); + else + { + Oid typoutput; + bool typisvarlena; + char *pstring; + + getTypeOutputInfo(param->ptype, &typoutput, &typisvarlena); + pstring = OidOutputFunctionCall(typoutput, param->value); + appendStringInfoStringQuoted(&buf, pstring, maxlen); + } + } + } + + MemoryContextSwitchTo(oldCxt); + MemoryContextDelete(tmpCxt); + + return buf.data; +} + +/* + * ParamsErrorCallback - callback for printing parameters in error context + * + * Note that this is a no-op unless BuildParamLogString has been called + * beforehand. + */ +void +ParamsErrorCallback(void *arg) +{ + ParamsErrorCbData *data = (ParamsErrorCbData *) arg; + + if (data == NULL || + data->params == NULL || + data->params->paramValuesStr == NULL) + return; + + if (data->portalName && data->portalName[0] != '\0') + errcontext("extended query \"%s\" with parameters: %s", + data->portalName, data->params->paramValuesStr); + else + errcontext("extended query with parameters: %s", + data->params->paramValuesStr); +} diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 512209a38c4..0b7bc1fd030 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -1614,6 +1614,8 @@ exec_bind_message(StringInfo input_message) bool save_log_statement_stats = log_statement_stats; bool snapshot_set = false; char msec_str[32]; + ParamsErrorCbData params_data; + ErrorContextCallback params_errcxt; /* Get the fixed part of the message */ portal_name = pq_getmsgstring(input_message); @@ -1753,6 +1755,8 @@ exec_bind_message(StringInfo input_message) */ if (numParams > 0) { + char **knownTextValues = NULL; /* allocate on first use */ + params = makeParamList(numParams); for (int paramno = 0; paramno < numParams; paramno++) @@ -1820,9 +1824,32 @@ 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); + /* + * Free result of encoding conversion, if any, and save a copy + * for later when logging parameters. + */ + if (pstring) + { + if (log_parameters_on_error) + { + MemoryContext oldcxt; + + oldcxt = MemoryContextSwitchTo(MessageContext); + if (knownTextValues == NULL) + knownTextValues = + palloc0(numParams * sizeof(char *)); + /* + * Note: must copy at least two more full characters + * than BuildParamLogString wants to see; otherwise it + * might fail to include the ellipsis. + */ + knownTextValues[paramno] = + pnstrdup(pstring, 64 + 2 * MAX_MULTIBYTE_CHAR_LEN); + MemoryContextSwitchTo(oldcxt); + } + if (pstring != pbuf.data) + pfree(pstring); + } } else if (pformat == 1) /* binary mode */ { @@ -1872,6 +1899,15 @@ exec_bind_message(StringInfo input_message) params->params[paramno].pflags = PARAM_FLAG_CONST; params->params[paramno].ptype = ptype; } + + /* + * Once all parameters have been received, prepare for printing them in + * errors, if configured to do so. (This is saved in the portal, so + * that they'll appear when the query is executed later.) + */ + if (log_parameters_on_error) + params->paramValuesStr = + BuildParamLogString(params, knownTextValues, 64); } else params = NULL; @@ -1879,6 +1915,14 @@ 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_data.portalName = portal->name; + params_data.params = params; + params_errcxt.previous = error_context_stack; + params_errcxt.callback = ParamsErrorCallback; + params_errcxt.arg = (void *) ¶ms_data; + error_context_stack = ¶ms_errcxt; + /* Get the result format codes */ numRFormats = pq_getmsgint(input_message, 2); if (numRFormats > 0) @@ -1925,6 +1969,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. */ if (whereToSendOutput == DestRemote) @@ -1980,6 +2030,8 @@ exec_execute_message(const char *portal_name, long max_rows) bool execute_is_fetch; bool was_logged = false; char msec_str[32]; + ParamsErrorCbData params_data; + ErrorContextCallback params_errcxt; /* Adjust destination to tell printtup.c what to do */ dest = whereToSendOutput; @@ -2104,8 +2156,16 @@ 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. The parameters must have been saved during the bind phase. */ + params_data.portalName = portal->name; + params_data.params = portalParams; + params_errcxt.previous = error_context_stack; + params_errcxt.callback = ParamsErrorCallback; + params_errcxt.arg = (void *) ¶ms_data; + error_context_stack = ¶ms_errcxt; + if (max_rows <= 0) max_rows = FETCH_ALL; @@ -2119,6 +2179,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) @@ -2329,51 +2392,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; + char *str; - /* 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, 0); - - pfree(pstring); - } - - errdetail("parameters: %s", param_str.data); - - pfree(param_str.data); - - MemoryContextSwitchTo(oldcontext); + str = BuildParamLogString(params, NULL, 0); + if (str && str[0] != '\0') + errdetail("parameters: %s", str); } return 0; diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index ba74bf9f7dc..8d951ce404c 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; @@ -1301,6 +1302,15 @@ static struct config_bool ConfigureNamesBool[] = 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."), NULL diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample index 9541879c1fa..087190ce638 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; |