postgres.git / summary / log / commit / refs
commit ed0a2b70709ac215f3c669d607f043e570f0cbf5
Author: Tom Lane <tgl@sss.pgh.pa.us>
Date: Fri Aug 14 16:14:24 2026 +0000
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 1a097772b32..e7efd3199d7 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1444,15 +1444,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 660c9ab4fac..ed7b4e6ff32 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -325,6 +325,36 @@ is($row_count, '10',
'client-side error commits transaction, no ON_ERROR_STOP and multiple -c switches'
);
+# 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
[parent: ef9063a8870f]