agora inbox for pgsql-bugs@postgresql.org
help / color / mirror / Atom feedFrom: Tom Lane <tgl@sss.pgh.pa.us>
To: Zsolt Parragi <zsolt.parragi@percona.com>
Cc: pgsql-bugs@lists.postgresql.org
Subject: Re: COPY TO regression with psql -c
Date: Thu, 13 Aug 2026 16:44:35 -0400
Message-ID: <3598003.1786653875@sss.pgh.pa.us> (raw)
In-Reply-To: <CAN4CZFOY3Z3zH4r0hfWt6Tvvkc4GGz6VR2b_6uCdLfb3U4AJ9A@mail.gmail.com>
References: <CAN4CZFPqa6c+u4uX5jJ8LANHTQ4dxM3m4_8G9WmX_A4-2wuv2A@mail.gmail.com>
<2801602.1786557522@sss.pgh.pa.us>
<CAN4CZFOY3Z3zH4r0hfWt6Tvvkc4GGz6VR2b_6uCdLfb3U4AJ9A@mail.gmail.com>
After thinking some more about how to handle cases where we stop with
PSCAN_BACKSLASH or PSCAN_INCOMPLETE, I feel that the safest answer is
to set num_copy_from_stdin = 0 in those cases. This is consistent
with the fact that we know we won't get a PGRES_COPY_IN message,
even if there was a valid COPY FROM STDIN in the string before the
syntax error. This might prevent us from skipping following data
in cases where it'd be best to do that, but here are two arguments
against trying to do so:
* The ambition of the security patch extended only to handling
syntactically-valid cases, which these aren't. Trying to do more
leads into a guessing game, eg should we skip data after "COPY
mytable FRPM STDIN"?
* Not trying to skip data ensures that the behavior of such cases
is the same as it was before the security patch, which seems like
the right direction to err in.
So v3 attached does it like that. I also simplified the test
script. The two-COPY-commands case seems like it covers everything
we want to test; the other cases just add cycles and complicate
the script.
regards, tom lane
Attachments:
[text/x-diff] v3-0001-psql-count-every-COPY-FROM-STDIN-when-scanning-a-.patch (4.1K, ../3598003.1786653875@sss.pgh.pa.us/2-v3-0001-psql-count-every-COPY-FROM-STDIN-when-scanning-a-.patch)
download | inline diff:
From 45fbd29420928f6230762f99e1901b1a45ff1c4b Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Thu, 13 Aug 2026 16:26:08 -0400
Subject: [PATCH v3] psql: count every COPY FROM STDIN when scanning a query
string.
When SendQuery() is not told how many COPY FROM STDIN commands the
query string contains (as for -c, \gexec, and \watch), it scans the
string to count them itself. But it called psql_scan() only once,
which stops at the first semicolon, so any COPY FROM STDIN past the
first sub-command was not counted, causing failure of cases that used
to work. Oversight in commit 3045a25ba.
Author: Zsolt Parragi <zsolt.parragi@percona.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/CAN4CZFPqa6c+u4uX5jJ8LANHTQ4dxM3m4_8G9WmX_A4-2wuv2A@mail.gmail.com
Backpatch-through: 14
---
src/bin/psql/common.c | 25 +++++++++++++++++++++++--
src/bin/psql/t/001_basic.pl | 30 ++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+), 2 deletions(-)
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 314bf2388ac..f220344daaf 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1796,15 +1796,36 @@ ExecQueryAndProcessResults(const char *query,
PsqlScanState scan_state;
PQExpBuffer query_buf;
promptStatus_t prompt_tmp;
+ PsqlScanResult scan_result;
scan_state = psql_scan_create(&psqlscan_callbacks);
psql_scan_setup(scan_state, query, strlen(query),
pset.encoding, standard_strings());
query_buf = createPQExpBuffer();
- (void) psql_scan(scan_state, query_buf, &prompt_tmp);
+ /*
+ * A semicolon ends only one sub-command; keep scanning so that COPY
+ * FROM STDIN commands past the first semicolon are counted too. The
+ * count accumulates in scan_state across the psql_scan() calls.
+ */
+ do
+ {
+ scan_result = psql_scan(scan_state, query_buf, &prompt_tmp);
+ } while (scan_result == PSCAN_SEMICOLON);
- num_copy_from_stdin = psql_scan_count_copy_from_stdin(scan_state);
+ /*
+ * We expect the result now to be PSCAN_EOL. If it is PSCAN_BACKSLASH
+ * or PSCAN_INCOMPLETE, the server will get a parse error and refuse
+ * to execute any part of the command string, so don't expect any
+ * PGRES_COPY_IN results. (This will mean that we don't attempt to
+ * discard any following data, but this seems consistent with the
+ * general contract of psql_scan_count_copy_from_stdin, which is that
+ * it only promises to count syntactically-valid COPY commands.)
+ */
+ if (scan_result == PSCAN_EOL)
+ num_copy_from_stdin = psql_scan_count_copy_from_stdin(scan_state);
+ else
+ num_copy_from_stdin = 0;
destroyPQExpBuffer(query_buf);
psql_scan_destroy(scan_state);
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 04644f2fdfc..028df33ce8a 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -533,6 +533,36 @@ psql_fails_like(
qr/COPY in a pipeline is not supported, aborting connection/,
'\copy to in pipeline: fails');
+# Test execution of COPY FROM STDIN in -c. This case is a bit weird
+# because it will read from psql's stdin not from the command source.
+# To make it even weirder, try two such commands, to stress psql's logic
+# that counts them. Also test both \. and EOF termination.
+{
+ $node->safe_psql('postgres', 'CREATE TABLE copy_stdin_count (a int)');
+ my ($stdin, $stdout, $stderr) = ("50\n\\.\n60\n", '', '');
+ my $ret = IPC::Run::run(
+ [
+ 'psql', '--no-psqlrc',
+ '--set' => 'ON_ERROR_STOP=1',
+ '--dbname' => $node->connstr('postgres'),
+ '--command' =>
+ 'COPY copy_stdin_count FROM STDIN; COPY copy_stdin_count FROM STDIN',
+ ],
+ '<' => \$stdin,
+ '>' => \$stdout,
+ '2>' => \$stderr);
+
+ ok($ret, '-c COPY FROM STDIN: psql exits 0');
+ unlike(
+ $stderr,
+ qr/unexpected COPY_IN result/,
+ '-c COPY FROM STDIN: unexpected COPY_IN result');
+
+ my $data = $node->safe_psql('postgres', 'SELECT * FROM copy_stdin_count');
+ is($data, "50\n60", '-c COPY FROM STDIN: correct data loaded');
+}
+
+# Test \restrict and \unrestrict.
psql_fails_like(
$node,
qq{\\restrict test
--
2.52.0
view thread (7+ messages) latest in thread
Message-ID: <3598003.1786653875@sss.pgh.pa.us>
Permalink: ../3598003.1786653875@sss.pgh.pa.us/
Also on: postgresql.org/message-id/3598003.1786653875@sss.pgh.pa.us
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: pgsql-bugs@postgresql.org
Cc: tgl@sss.pgh.pa.us, zsolt.parragi@percona.com, pgsql-bugs@lists.postgresql.org
Subject: Re: COPY TO regression with psql -c
In-Reply-To: <3598003.1786653875@sss.pgh.pa.us>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox