Skip to content

Commit 6e951f2

Browse files
committed
psql: Forbid use of COPY and \copy while in a pipeline
Running COPY within a pipeline can break protocol synchronization in multiple ways. psql is limited in terms of result processing if mixing COPY commands with normal queries while controlling a pipeline with the new meta-commands, as an effect of the following reasons: - In COPY mode, the backend ignores additional Sync messages and will not send a matching ReadyForQuery expected by the frontend. Doing a \syncpipeline just after COPY will leave the frontend waiting for a ReadyForQuery message that won't be sent, leaving psql out-of-sync. - libpq automatically sends a Sync with the Copy message which is not tracked in the command queue, creating an unexpected synchronisation point that psql cannot really know about. While it is possible to track such activity for a \copy, this cannot really be done sanely with plain COPY queries. Backend failures during a COPY would leave the pipeline in an aborted state while the backend would be in a clean state, ready to process commands. At the end, fixing those issues would require modifications in how libpq handles pipeline and COPY. So, rather than implementing workarounds in psql to shortcut the libpq internals (with command queue handling for one), and because meta-commands for pipelines in psql are a new feature with COPY in a pipeline having a limited impact compared to other queries, this commit forbids the use of COPY within a pipeline to avoid possible break of protocol synchronisation within psql. If there is a use-case for COPY support within pipelines in libpq, this could always be added in the future, if necessary. Most of the changes of this commit impacts the tests for psql pipelines, removing the tests related to COPY. Some TAP tests still exist for COPY TO/FROM and \copy to/from, to check that that connections are aborted when this operation is attempted. Reported-by: Nikita Kalinin <n.kalinin@postgrespro.ru> Author: Anthonin Bonnefoy <anthonin.bonnefoy@datadoghq.com> Discussion: https://postgr.es/m/AC468509-06E8-4E2A-A4B1-63046A4AC6AB@postgrespro.ru
1 parent 2c76c6a commit 6e951f2

File tree

5 files changed

+43
-325
lines changed

5 files changed

+43
-325
lines changed

doc/src/sgml/ref/psql-ref.sgml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3733,6 +3733,10 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
37333733
See <xref linkend="app-psql-prompting-p-uc"/> for more details
37343734
</para>
37353735

3736+
<para>
3737+
<command>COPY</command> is not supported while in pipeline mode.
3738+
</para>
3739+
37363740
<para>
37373741
Example:
37383742
<programlisting>

src/bin/psql/common.c

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1867,18 +1867,30 @@ ExecQueryAndProcessResults(const char *query,
18671867
{
18681868
FILE *copy_stream = NULL;
18691869

1870-
if (pset.piped_syncs > 1)
1870+
if (PQpipelineStatus(pset.db) != PQ_PIPELINE_OFF)
18711871
{
18721872
/*
1873-
* When reading COPY data, the backend ignores sync messages
1874-
* and will not send a matching ReadyForQuery response. Even
1875-
* if we adjust piped_syncs and requested_results, it is not
1876-
* possible to salvage this as the sync message would still be
1877-
* in libpq's command queue and we would be stuck in a busy
1878-
* pipeline state. Thus, we abort the connection to avoid
1879-
* this state.
1873+
* Running COPY within a pipeline can break the protocol
1874+
* synchronisation in multiple ways, and psql shows its limits
1875+
* when it comes to tracking this information.
1876+
*
1877+
* While in COPY mode, the backend process ignores additional
1878+
* Sync messages and will not send the matching ReadyForQuery
1879+
* expected by the frontend.
1880+
*
1881+
* Additionally, libpq automatically sends a Sync with the
1882+
* Copy message, creating an unexpected synchronisation point.
1883+
* A failure during COPY would leave the pipeline in an
1884+
* aborted state while the backend would be in a clean state,
1885+
* ready to process commands.
1886+
*
1887+
* Improving those issues would require modifications in how
1888+
* libpq handles pipelines and COPY. Hence, for the time
1889+
* being, we forbid the use of COPY within a pipeline,
1890+
* aborting the connection to avoid an inconsistent state on
1891+
* psql side if trying to use a COPY command.
18801892
*/
1881-
pg_log_info("\\syncpipeline after COPY is not supported, aborting connection");
1893+
pg_log_info("COPY in a pipeline is not supported, aborting connection");
18821894
exit(EXIT_BADCONN);
18831895
}
18841896

src/bin/psql/t/001_basic.pl

Lines changed: 17 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,8 @@ sub psql_fails_like
483483
my $c4 = slurp_file($g_file);
484484
like($c4, qr/foo.*bar/s);
485485

486-
# Tests with pipelines. These trigger FATAL failures in the backend,
487-
# so they cannot be tested via SQL.
486+
# Test COPY within pipelines. These abort the connection from
487+
# the frontend so they cannot be tested via SQL.
488488
$node->safe_psql('postgres', 'CREATE TABLE psql_pipeline()');
489489
my $log_location = -s $node->logfile;
490490
psql_fails_like(
@@ -493,53 +493,41 @@ sub psql_fails_like
493493
COPY psql_pipeline FROM STDIN;
494494
SELECT 'val1';
495495
\\syncpipeline
496-
\\getresults
497496
\\endpipeline},
498-
qr/server closed the connection unexpectedly/,
499-
'protocol sync loss in pipeline: direct COPY, SELECT, sync and getresult'
500-
);
497+
qr/COPY in a pipeline is not supported, aborting connection/,
498+
'COPY FROM in pipeline: fails');
501499
$node->wait_for_log(
502500
qr/FATAL: .*terminating connection because protocol synchronization was lost/,
503501
$log_location);
504502

503+
# Remove \syncpipeline here.
505504
psql_fails_like(
506505
$node,
507506
qq{\\startpipeline
508-
COPY psql_pipeline FROM STDIN \\bind \\sendpipeline
509-
SELECT 'val1' \\bind \\sendpipeline
510-
\\syncpipeline
511-
\\getresults
512-
\\endpipeline},
513-
qr/server closed the connection unexpectedly/,
514-
'protocol sync loss in pipeline: bind COPY, SELECT, sync and getresult');
515-
516-
# This time, test without the \getresults and \syncpipeline.
517-
psql_fails_like(
518-
$node,
519-
qq{\\startpipeline
520-
COPY psql_pipeline FROM STDIN;
507+
COPY psql_pipeline TO STDOUT;
521508
SELECT 'val1';
522509
\\endpipeline},
523-
qr/server closed the connection unexpectedly/,
524-
'protocol sync loss in pipeline: COPY, SELECT and sync');
510+
qr/COPY in a pipeline is not supported, aborting connection/,
511+
'COPY TO in pipeline: fails');
525512

526-
# Tests sending a sync after a COPY TO/FROM. These abort the connection
527-
# from the frontend.
528513
psql_fails_like(
529514
$node,
530515
qq{\\startpipeline
531-
COPY psql_pipeline FROM STDIN;
516+
\\copy psql_pipeline from stdin;
517+
SELECT 'val1';
532518
\\syncpipeline
533519
\\endpipeline},
534-
qr/\\syncpipeline after COPY is not supported, aborting connection/,
535-
'sending sync after COPY FROM');
520+
qr/COPY in a pipeline is not supported, aborting connection/,
521+
'\copy from in pipeline: fails');
522+
523+
# Sync attempt after a COPY TO/FROM.
536524
psql_fails_like(
537525
$node,
538526
qq{\\startpipeline
539-
COPY psql_pipeline TO STDOUT;
527+
\\copy psql_pipeline to stdout;
540528
\\syncpipeline
541529
\\endpipeline},
542-
qr/\\syncpipeline after COPY is not supported, aborting connection/,
543-
'sending sync after COPY TO');
530+
qr/COPY in a pipeline is not supported, aborting connection/,
531+
'\copy to in pipeline: fails');
544532

545533
done_testing();

src/test/regress/expected/psql_pipeline.out

Lines changed: 1 addition & 187 deletions
Original file line numberDiff line numberDiff line change
@@ -228,192 +228,6 @@ BEGIN \bind \sendpipeline
228228
INSERT INTO psql_pipeline VALUES ($1) \bind 1 \sendpipeline
229229
COMMIT \bind \sendpipeline
230230
\endpipeline
231-
-- COPY FROM STDIN
232-
-- with \sendpipeline and \bind
233-
\startpipeline
234-
SELECT $1 \bind 'val1' \sendpipeline
235-
COPY psql_pipeline FROM STDIN \bind \sendpipeline
236-
\endpipeline
237-
?column?
238-
----------
239-
val1
240-
(1 row)
241-
242-
-- with semicolon
243-
\startpipeline
244-
SELECT 'val1';
245-
COPY psql_pipeline FROM STDIN;
246-
\endpipeline
247-
?column?
248-
----------
249-
val1
250-
(1 row)
251-
252-
-- COPY FROM STDIN with \flushrequest + \getresults
253-
-- with \sendpipeline and \bind
254-
\startpipeline
255-
SELECT $1 \bind 'val1' \sendpipeline
256-
COPY psql_pipeline FROM STDIN \bind \sendpipeline
257-
\flushrequest
258-
\getresults
259-
?column?
260-
----------
261-
val1
262-
(1 row)
263-
264-
message type 0x5a arrived from server while idle
265-
\endpipeline
266-
-- with semicolon
267-
\startpipeline
268-
SELECT 'val1';
269-
COPY psql_pipeline FROM STDIN;
270-
\flushrequest
271-
\getresults
272-
?column?
273-
----------
274-
val1
275-
(1 row)
276-
277-
message type 0x5a arrived from server while idle
278-
\endpipeline
279-
-- COPY FROM STDIN with \syncpipeline + \getresults
280-
-- with \bind and \sendpipeline
281-
\startpipeline
282-
SELECT $1 \bind 'val1' \sendpipeline
283-
COPY psql_pipeline FROM STDIN \bind \sendpipeline
284-
\syncpipeline
285-
\getresults
286-
?column?
287-
----------
288-
val1
289-
(1 row)
290-
291-
\endpipeline
292-
-- with semicolon
293-
\startpipeline
294-
SELECT 'val1';
295-
COPY psql_pipeline FROM STDIN;
296-
\syncpipeline
297-
\getresults
298-
?column?
299-
----------
300-
val1
301-
(1 row)
302-
303-
\endpipeline
304-
-- COPY TO STDOUT
305-
-- with \bind and \sendpipeline
306-
\startpipeline
307-
SELECT $1 \bind 'val1' \sendpipeline
308-
copy psql_pipeline TO STDOUT \bind \sendpipeline
309-
\endpipeline
310-
?column?
311-
----------
312-
val1
313-
(1 row)
314-
315-
1 \N
316-
2 test2
317-
20 test2
318-
3 test3
319-
30 test3
320-
4 test4
321-
40 test4
322-
-- with semicolon
323-
\startpipeline
324-
SELECT 'val1';
325-
copy psql_pipeline TO STDOUT;
326-
\endpipeline
327-
?column?
328-
----------
329-
val1
330-
(1 row)
331-
332-
1 \N
333-
2 test2
334-
20 test2
335-
3 test3
336-
30 test3
337-
4 test4
338-
40 test4
339-
-- COPY TO STDOUT with \flushrequest + \getresults
340-
-- with \bind and \sendpipeline
341-
\startpipeline
342-
SELECT $1 \bind 'val1' \sendpipeline
343-
copy psql_pipeline TO STDOUT \bind \sendpipeline
344-
\flushrequest
345-
\getresults
346-
?column?
347-
----------
348-
val1
349-
(1 row)
350-
351-
1 \N
352-
2 test2
353-
20 test2
354-
3 test3
355-
30 test3
356-
4 test4
357-
40 test4
358-
\endpipeline
359-
-- with semicolon
360-
\startpipeline
361-
SELECT 'val1';
362-
copy psql_pipeline TO STDOUT;
363-
\flushrequest
364-
\getresults
365-
?column?
366-
----------
367-
val1
368-
(1 row)
369-
370-
1 \N
371-
2 test2
372-
20 test2
373-
3 test3
374-
30 test3
375-
4 test4
376-
40 test4
377-
\endpipeline
378-
-- COPY TO STDOUT with \syncpipeline + \getresults
379-
-- with \bind and \sendpipeline
380-
\startpipeline
381-
SELECT $1 \bind 'val1' \sendpipeline
382-
copy psql_pipeline TO STDOUT \bind \sendpipeline
383-
\syncpipeline
384-
\getresults
385-
?column?
386-
----------
387-
val1
388-
(1 row)
389-
390-
1 \N
391-
2 test2
392-
20 test2
393-
3 test3
394-
30 test3
395-
4 test4
396-
40 test4
397-
\endpipeline
398-
-- with semicolon
399-
\startpipeline
400-
SELECT 'val1';
401-
copy psql_pipeline TO STDOUT;
402-
\syncpipeline
403-
\getresults
404-
?column?
405-
----------
406-
val1
407-
(1 row)
408-
409-
1 \N
410-
2 test2
411-
20 test2
412-
3 test3
413-
30 test3
414-
4 test4
415-
40 test4
416-
\endpipeline
417231
-- Use \parse and \bind_named
418232
\startpipeline
419233
SELECT $1 \parse ''
@@ -740,7 +554,7 @@ SELECT COUNT(*) FROM psql_pipeline \bind \sendpipeline
740554

741555
count
742556
-------
743-
7
557+
1
744558
(1 row)
745559

746560
-- After an error, pipeline is aborted and requires \syncpipeline to be

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy