Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kwTMY-0003Sz-4z for pgsql-hackers@arkaria.postgresql.org; Mon, 04 Jan 2021 17:09:46 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.92) (envelope-from ) id 1kwTMX-0008BG-2r for pgsql-hackers@arkaria.postgresql.org; Mon, 04 Jan 2021 17:09:45 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kwTMW-0008B8-Jr for pgsql-hackers@lists.postgresql.org; Mon, 04 Jan 2021 17:09:44 +0000 Received: from mail-il1-x133.google.com ([2607:f8b0:4864:20::133]) by makus.postgresql.org with esmtps (TLS1.3:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.92) (envelope-from ) id 1kwTMT-0003RI-Kk for pgsql-hackers@postgresql.org; Mon, 04 Jan 2021 17:09:43 +0000 Received: by mail-il1-x133.google.com with SMTP id n9so25980402ili.0 for ; Mon, 04 Jan 2021 09:09:41 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=telsasoft-com.20150623.gappssmtp.com; s=20150623; h=date:from:to:cc:subject:message-id:mime-version:content-disposition :user-agent; bh=UbgBgX8E8B6gTI7VBM9U69smwBFmNg332GMAgIPMMF0=; b=dMj64HruX0VwLnUoG9FCwTw66TDXGGFfgAVF+ZP831ObE0D1ssnKgce8yGPE/eYhoh vpbm2/ZtS1NrZyB525BdmqqDuEAb97boEJSC7Ni3Rons7p2Fg8eBhs8siwNI37u0UdcB 9hMbCTaEKUtU8VPJP2KKi9+DWdTUNVJx+LB7I9eJmdq0pqVcfdUc8isUHOAQHdUIDbQ3 kA5uFqAgHeVsZoioC4QlyBUxmUBv5OrcHm+Em5bVITYEIs3e2PWj+gtDNexR0pJtxdtp 832/ihQqtdfvk8s2I6druKiUZsKURsK4HouO7F+MlUCBNCUI/hdXrwOd/Ch68kT89F+L SUNQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:date:from:to:cc:subject:message-id:mime-version :content-disposition:user-agent; bh=UbgBgX8E8B6gTI7VBM9U69smwBFmNg332GMAgIPMMF0=; b=mQ+ZyoJR51v5EltbCVO5uHSKqnCDdoIDu95rZVvmFaXeWFDkgj1JFM/AetxFec6Kzg 1imTU2bkNkpMV7FNCgd/40NcZh7FefbM2QtwpDtMzngXprFJiebTFcrGNHQVQg2H8f38 WSrxZNfx1xK6P4RgcAaOw93FzN29PGxvuiDVea63GOu5o4k7Vh6eYWkkqxBd3v32lH0X Dlhkoq9WoHpelC9D/z4ILfF2VxKAQV7z9EmDG21K4jRRsMtoXkdkZNy6ac8zsON8d/CV XLmGgpneytGY8WBH6PMk/BLhQRuuGUuYe2ePsnpXxj6g0H8KBOH1kMbHCoHyP48TMa5s jPaw== X-Gm-Message-State: AOAM531KNLoDhV1u0SrLY+IoEAEQlxPI9+t5cYcg9FokDe80TZVc0xtb Kqfq49/RqWykCaLuj4OZHX4VXA== X-Google-Smtp-Source: ABdhPJwQCTvkqHaWqxuvc15neJGS+PaloYsZftVFLJN/bNxW6pKGtLR9lCLH0zlLw+hUiY3FamuFLw== X-Received: by 2002:a05:6e02:ecc:: with SMTP id i12mr68893919ilk.0.1609780181139; Mon, 04 Jan 2021 09:09:41 -0800 (PST) Received: from pryzbyj.telsasoft (charmander.telsasoft.com. [50.244.222.1]) by smtp.gmail.com with ESMTPSA id f20sm4136973ilj.14.2021.01.04.09.09.39 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Mon, 04 Jan 2021 09:09:40 -0800 (PST) Received: by pryzbyj.telsasoft (Postfix, from userid 1000) id 4A9C9800DC3; Mon, 4 Jan 2021 11:09:39 -0600 (CST) Date: Mon, 4 Jan 2021 11:09:39 -0600 From: Justin Pryzby To: Alvaro Herrera Cc: Alexey Bashtanov , Franck Verrot , pgsql-hackers@postgresql.org Subject: [PATCH]: Allow errors in parameter values to be reported during the BIND phase itself.. Message-ID: <20210104170939.GH9712@telsasoft.com> MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="d01dLTUuW90fS44H" Content-Disposition: inline User-Agent: Mutt/1.9.4 (2018-02-28) List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Precedence: bulk --d01dLTUuW90fS44H Content-Type: text/plain; charset=us-ascii Content-Disposition: inline For example: $ python3.5 -c "import pg; db=pg.DB(); q = db.query(\"SET log_parameter_max_length_on_error=-1;\"); db.prepare('p', 'SELECT \$1::smallint'); db.query_prepared('p',66666);" 2021-01-03 02:21:04.547 CST [20157] ERROR: value "66666" is out of range for type smallint 2021-01-03 02:21:04.547 CST [20157] CONTEXT: unnamed portal with parameters: $1 = '66666' 2021-01-03 02:21:04.547 CST [20157] STATEMENT: SELECT $1::smallint When there are many bind params, this can be useful to determine which is out of range. Think 900 int/smallint columns, or less-wide tables being inserted multiple rows at a time with VALUES(),(),()... Of course, this isn't as good as showing the column name, so I might pursue Tom's suggestion for that at some point. -- Justin --d01dLTUuW90fS44H Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="0001-Allow-errors-in-parameter-values-to-be-reported-duri.patch" From 40dd798d8518b4d113dbf70c37687c87bf734659 Mon Sep 17 00:00:00 2001 From: Justin Pryzby Date: Fri, 1 Jan 2021 19:38:57 -0600 Subject: [PATCH] Allow errors in parameter values to be reported during the BIND phase itself.. For example: $ python3.5 -c "import pg,time; db=pg.DB('dbname=postgres host=/var/run/postgresql port=5432 host=/tmp'); q = db.query(\"SET log_parameter_max_length_on_error=-1;\"); db.prepare('p', 'SELECT \$1::smallint'); db.query_prepared('p',66666);" 2021-01-01 19:40:24.777 CST [5330] ERROR: value "66666" is out of range for type smallint 2021-01-01 19:40:24.777 CST [5330] CONTEXT: unnamed portal with parameters: $1 = '66666' This can be useful to determine which is out of range when there are many bind params. See also: commit ba79cb5dc commit 0b34e7d30 https://www.postgresql.org/message-id/flat/CANfkH5k-6nNt-4cSv1vPB80nq2BZCzhFVR5O4VznYbsX0wZmow@mail.gmail.com Mention column name in error messages --- src/backend/tcop/postgres.c | 166 +++++++++++++++++++++++------------- 1 file changed, 106 insertions(+), 60 deletions(-) diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 317d1aa573..30fb86d97f 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -1765,9 +1765,109 @@ exec_bind_message(StringInfo input_message) if (numParams > 0) { char **knownTextValues = NULL; /* allocate on first use */ + int32 *plengths; + char **pstrings; + + plengths = palloc0(numParams * sizeof(*plengths)); + + /* Indexed by paramno, but not used for nulls */ + pstrings = palloc0(numParams * sizeof(*pstrings)); params = makeParamList(numParams); + /* + * Do two loops to allow error reports during BIND; + * On the first pass, just save the offset and length of each param; + * On the second pass, convert the params to the require type. + * If this fails, the params have already been saved for error callack. + */ + + for (int paramno = 0; paramno < numParams; paramno++) + { + int32 plength; + int16 pformat; + char *pstring; + MemoryContext oldcxt; + + plength = plengths[paramno] = pq_getmsgint(input_message, 4); + + /* Minimal amount needed for error callback */ + params->params[paramno].ptype = psrc->param_types[paramno];; + params->params[paramno].isnull = (plength == -1); + + if (params->params[paramno].isnull) + continue; + + pstrings[paramno] = unconstify(char *, pq_getmsgbytes(input_message, plength)); + + if (log_parameter_max_length_on_error == 0) + continue; + + if (numPFormats > 1) + pformat = pformats[paramno]; + else if (numPFormats > 0) + pformat = pformats[0]; + else + pformat = 0; /* default = text */ + + if (pformat != 0) /* text mode */ + continue; + + /* + * If we might need to log parameters later, save a copy of + * the converted string in MessageContext; + * XXX: only supported in text mode ?? + */ + pstring = pg_client_to_server(pstrings[paramno], plength); + oldcxt = MemoryContextSwitchTo(MessageContext); + + if (knownTextValues == NULL) + knownTextValues = + palloc0(numParams * sizeof(char *)); + + if (log_parameter_max_length_on_error < 0) + knownTextValues[paramno] = pstrdup(pstring); + else + { + /* + * We can trim the saved string, knowing that we + * won't print all of it. But we must copy at + * least two more full characters than + * BuildParamLogString wants to use; otherwise it + * might fail to include the trailing ellipsis. + */ + knownTextValues[paramno] = + pnstrdup(pstring, + log_parameter_max_length_on_error + + 2 * MAX_MULTIBYTE_CHAR_LEN); + } + + MemoryContextSwitchTo(oldcxt); + if (pstring != pstrings[paramno]) + pfree(pstring); + } + + /* + * 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_parameter_max_length_on_error != 0) + { + params->paramValuesStr = + BuildParamLogString(params, + knownTextValues, + log_parameter_max_length_on_error); + } + + /* 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; + for (int paramno = 0; paramno < numParams; paramno++) { Oid ptype = psrc->param_types[paramno]; @@ -1778,13 +1878,11 @@ exec_bind_message(StringInfo input_message) char csave; int16 pformat; - plength = pq_getmsgint(input_message, 4); + plength = plengths[paramno]; isNull = (plength == -1); if (!isNull) { - const char *pvalue = pq_getmsgbytes(input_message, plength); - /* * Rather than copying data around, we just set up a phony * StringInfo pointing to the correct portion of the message @@ -1793,7 +1891,7 @@ exec_bind_message(StringInfo input_message) * trailing null. This is grotty but is a big win when * dealing with very large parameter strings. */ - pbuf.data = unconstify(char *, pvalue); + pbuf.data = pstrings[paramno]; pbuf.maxlen = plength + 1; pbuf.len = plength; pbuf.cursor = 0; @@ -1833,45 +1931,9 @@ exec_bind_message(StringInfo input_message) pval = OidInputFunctionCall(typinput, pstring, typioparam, -1); - /* - * If we might need to log parameters later, save a copy of - * the converted string in MessageContext; then free the - * result of encoding conversion, if any was done. - */ - if (pstring) - { - if (log_parameter_max_length_on_error != 0) - { - MemoryContext oldcxt; - - oldcxt = MemoryContextSwitchTo(MessageContext); - - if (knownTextValues == NULL) - knownTextValues = - palloc0(numParams * sizeof(char *)); - - if (log_parameter_max_length_on_error < 0) - knownTextValues[paramno] = pstrdup(pstring); - else - { - /* - * We can trim the saved string, knowing that we - * won't print all of it. But we must copy at - * least two more full characters than - * BuildParamLogString wants to use; otherwise it - * might fail to include the trailing ellipsis. - */ - knownTextValues[paramno] = - pnstrdup(pstring, - log_parameter_max_length_on_error - + 2 * MAX_MULTIBYTE_CHAR_LEN); - } - - MemoryContextSwitchTo(oldcxt); - } - if (pstring != pbuf.data) + /* free the result of encoding conversion, if any was done. */ + if (pstring && pstring != pbuf.data) pfree(pstring); - } } else if (pformat == 1) /* binary mode */ { @@ -1922,16 +1984,8 @@ exec_bind_message(StringInfo input_message) 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_parameter_max_length_on_error != 0) - params->paramValuesStr = - BuildParamLogString(params, - knownTextValues, - log_parameter_max_length_on_error); + pfree(plengths); + pfree(pstrings); } else params = NULL; @@ -1939,14 +1993,6 @@ 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) -- 2.17.0 --d01dLTUuW90fS44H--