Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wSTG8-003Cd3-0b for pgsql-bugs@arkaria.postgresql.org; Thu, 28 May 2026 05:26:20 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wSTG6-00BAMJ-0W for pgsql-bugs@arkaria.postgresql.org; Thu, 28 May 2026 05:26:19 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wSTG5-00BAMA-1a for pgsql-bugs@lists.postgresql.org; Thu, 28 May 2026 05:26:18 +0000 Received: from fhigh-b5-smtp.messagingengine.com ([202.12.124.156]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wSTG3-00000001o8P-2t6s for pgsql-bugs@lists.postgresql.org; Thu, 28 May 2026 05:26:18 +0000 Received: from phl-compute-02.internal (phl-compute-02.internal [10.202.2.42]) by mailfhigh.stl.internal (Postfix) with ESMTP id 746E67A00D3; Thu, 28 May 2026 01:26:12 -0400 (EDT) Received: from phl-frontend-03 ([10.202.2.162]) by phl-compute-02.internal (MEProxy); Thu, 28 May 2026 01:26:12 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=paquier.xyz; h= cc:content-type:content-type:date:date:from:from:in-reply-to :in-reply-to:message-id:mime-version:references:reply-to:subject :subject:to:to; s=fm3; t=1779945972; x=1780032372; bh=IVXJ1lK9hy 50qzjO/rPy3+jpLcnOrw24ulnL3a76/Bo=; b=ZpHQ1VK5ExockjPhA3S2OO11Zj gnrLV+Lz2a8bcBLx/W2axb+0gaS0fUUbBnoijVux0EJNXYIqIVcp7VF3uTdO2PfJ h0iQQNGfK3OfE6emBLDfYFDtfw9T5rbWsgFmyZHxJTE+lPdtFeRVer3ZMp4emTmx Mc8ctHvDL/u5DyUhd99DT6/Fa3eS1nnpC51YO/TeOaPu53IBgp6zPDbnzYkEFDy4 39+9XAHRgrY0s9Li30cKTWZwRAu2QFUG5rjEW50pzYteBGdYfk/fVaXr1621Msjx Gq7vVuQdnTG49UhY2NhTrhU1w4lUf7xR49VC1z5uAONpOyMa7uAJ9yOhyerQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-type:content-type:date:date :feedback-id:feedback-id:from:from:in-reply-to:in-reply-to :message-id:mime-version:references:reply-to:subject:subject:to :to:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s=fm3; t= 1779945972; x=1780032372; bh=IVXJ1lK9hy50qzjO/rPy3+jpLcnOrw24uln L3a76/Bo=; b=RMSP9r7cIGxPFW90jARxYfJE2/MyxWQ5yn54R49Fc9EbelPZo9R MAMXE4s+dme2hZyiTQYrxS8VXq4Zk31/HrJeR98zH+KF/3cC2aH8ZDHkFf3MLSHO az8ylsEeDh7w0MEtjWJTNEBQdRrEUaXBBA6xcqegwUv1pHM+OYodLSGW+qd9lQzY k4ojmIVdH14mBhs/uwCGBLB7oBZIOl5ryVl52c4IDGEHDhrQfbDAx5Qoerjd6U13 m8Iq1joHgx+dJsUl696aYCA/2i7+gPVnjfAcMLdhKCPFpuqGDABpIshQxAsmJ3Gi AJHqHqP5fEdhA3gQjd3lOtFV5Um+I3/tWpg== X-ME-Sender: X-ME-Received: X-ME-Proxy-Cause: dmFkZTFgYHL2GB2FyvQlthcZjFt9LE4bwtWQgjrZVif89DBsGVtMzTYeDFzbEfJpMzcU3q 7a/c9bj2N5hwBh+K6SEv/7v1pQ5y1kabN9uF9z/oPUdsFt6L9tFImmpehDm5cHzmFhHC83 t3jwLeiwOpz7zULQQ6JaxX68e7yTa1uAYNf/Hm4OLoHkCP+h3Q3LoHqSEYBqXjuw47F5Kw ticlQ0/S2ePYh1aWUSTRZFS8Jgpkc2U1OerSdX26PQBoiA3Jl1epsbw2v2UVgEM6s7DEwh MObwCTMlh6O6tx6dc+1ZD54F4snbF2Uioajyfhf8N+I1yp5ppL22X8Dd9GEaYpjOHVOrNA Jnd6n6kuWSScNXrlxlsCI53CJd0unu6IsfrCcD2mP7NpPm2uYB+lI2j9Y4IsyvL2OSc93Y C+gL739QXY1rY44OBXUcGDlipaxbr/x6OusRgnaroyFi0eNOQad1fMkCS2lr/j2vNvafeA T0suPFWCF2hnunVjU7PCi1jsedMLeYq0O0B3x/jjm3FFTbSmx1hW1pf+dIDQZ6rv45wwV9 iRHyateKyISXvuchlE2m4mSdSvG8g6bYJBUO7wMlFHMfcJCKKZABxxWBUnYbHgBCH6r2nb KkmrWFXoM5ozCRJjZ08gYFpFp7huj0BvLaExC4am1tNWJDH8AU+I15yHqWzA X-ME-Proxy: Feedback-ID: i0fe9450f:Fastmail Received: by mail.messagingengine.com (Postfix) with ESMTPA; Thu, 28 May 2026 01:26:10 -0400 (EDT) Date: Thu, 28 May 2026 14:26:06 +0900 From: Michael Paquier To: exclusion@gmail.com, pgsql-bugs@lists.postgresql.org Subject: Re: BUG #19494: Error on transaction commit inside pipeline triggers psql's Assert Message-ID: References: <19494-97a86d84fee71c47@postgresql.org> MIME-Version: 1.0 Content-Type: multipart/signed; micalg=pgp-sha512; protocol="application/pgp-signature"; boundary="niSFj3EAmbtQuAvl" Content-Disposition: inline In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --niSFj3EAmbtQuAvl Content-Type: multipart/mixed; boundary="GmiEme0sUhGVvoa7" Content-Disposition: inline --GmiEme0sUhGVvoa7 Content-Type: text/plain; charset=us-ascii Content-Disposition: inline On Thu, May 28, 2026 at 12:51:38PM +0900, Michael Paquier wrote: > I am completely sure yet, but it looks like we will need to be smarter > with the handling of the number of piped commands by tracking them > across the syncs in the shape of a queue, or something like that? So > it feels like we need to think harder about the tracking of this > activity depending on the state of the pipeline we're in. Or we could > lift some of these assertions, but that would not be right to me. Hmm. Taking a step back this would be overcomplicating things. As long as we are careful to consume the synced results still in a pipeline, it looks like we should be fine. While digging into it, I have found a third assertion that was triggerable with available_results at the end of the pipeline, once I began mixing \getresults with a deferred error. This stuff is tricky enough that I may not have overseen all the patterns possible, of course, at least this is progress. Alexander, what do you think? -- Michael --GmiEme0sUhGVvoa7 Content-Type: text/plain; charset=us-ascii Content-Disposition: attachment; filename=0001-psql-Fix-failures-with-deferred-errors-in-pipelines.patch Content-Transfer-Encoding: quoted-printable =46rom f9ce93fc1e9bb47f4a50605a2b836bb9cc278989 Mon Sep 17 00:00:00 2001 =46rom: Michael Paquier Date: Thu, 28 May 2026 14:20:47 +0900 Subject: [PATCH] psql: Fix failures with deferred errors in pipelines --- src/bin/psql/common.c | 48 ++++++++--- src/test/regress/expected/psql_pipeline.out | 91 +++++++++++++++++++++ src/test/regress/sql/psql_pipeline.sql | 39 +++++++++ 3 files changed, 166 insertions(+), 12 deletions(-) diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 1a4e2ea0da82..3698f37c742c 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -1499,11 +1499,19 @@ discardAbortedPipelineResults(void) } else if (res =3D=3D NULL) { - /* A query was processed, decrement the counters */ - Assert(pset.available_results > 0); - Assert(pset.requested_results > 0); - pset.available_results--; - pset.requested_results--; + /* + * A query was processed, decrement the counters. + * + * It is possible to get here with available_results =3D=3D 0 when an + * error is generated by the Sync message processing itself. + * Such errors are not counted in available_results because they + * are not associated with a piped command. In that case, skip + * the counter decrements and continue to find the sync result. + */ + if (pset.available_results > 0) + pset.available_results--; + if (pset.requested_results > 0) + pset.requested_results--; } =20 if (pset.requested_results =3D=3D 0) @@ -2175,14 +2183,30 @@ ExecQueryAndProcessResults(const char *query, =20 if (end_pipeline) { - /* after a pipeline is processed, pipeline piped_syncs should be 0 */ - Assert(pset.piped_syncs =3D=3D 0); - /* all commands have been processed */ - Assert(pset.piped_commands =3D=3D 0); - /* all results were read */ - Assert(pset.available_results =3D=3D 0); + /* + * Reset available/requested results. Normally these are already 0, + * but an error generated by Sync processing itself can leave some of + * them behind. Consume them before exiting pipeline mode. + */ + while (pset.piped_syncs > 0) + { + PGresult *remaining =3D PQgetResult(pset.db); + + if (remaining =3D=3D NULL) + continue; + if (PQresultStatus(remaining) =3D=3D PGRES_PIPELINE_SYNC) + pset.piped_syncs--; + PQclear(remaining); + } + pset.piped_syncs =3D 0; + pset.piped_commands =3D 0; + pset.available_results =3D 0; + pset.requested_results =3D 0; + + if (PQpipelineStatus(pset.db) !=3D PQ_PIPELINE_OFF) + PQexitPipelineMode(pset.db); } - Assert(pset.requested_results =3D=3D 0); + SetPipelineVariables(); =20 /* may need this to recover from conn loss during COPY */ diff --git a/src/test/regress/expected/psql_pipeline.out b/src/test/regress= /expected/psql_pipeline.out index a0816fb10b68..66e1e4f2ef5e 100644 --- a/src/test/regress/expected/psql_pipeline.out +++ b/src/test/regress/expected/psql_pipeline.out @@ -764,5 +764,96 @@ VACUUM psql_pipeline \bind \sendpipeline 1 (1 row) =20 +-- Deferred constraint violation at commit time in a pipeline. +CREATE TABLE psql_pipeline_defer (a INTEGER PRIMARY KEY DEFERRABLE INITIAL= LY DEFERRED); +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) RETURNING * \bind 1 \sen= dpipeline +\endpipeline + a=20 +--- + 1 + 1 +(2 rows) + +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +-- Same with \syncpipeline and commands after the failing sync. +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind 'after_sync_1' \sendpipeline +\endpipeline +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. + ?column? =20 +-------------- + after_sync_1 +(1 row) + +-- More patterns with more \syncpipeline, more commands and \getresults +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind 'after_sync_1' \sendpipeline +\getresults +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +SELECT $1 \bind 'after_sync_2' \sendpipeline +\endpipeline + ?column? =20 +-------------- + after_sync_1 +(1 row) + + ?column? =20 +-------------- + after_sync_2 +(1 row) + +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +\getresults +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +SELECT $1 \bind 'after_sync_1' \sendpipeline +\getresults +SELECT $1 \bind 'after_sync_2' \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +SELECT $1 \bind 'after_sync_3' \sendpipeline +SELECT $1 \bind 'after_sync_4' \sendpipeline +SELECT $1 \bind 'after_sync_5' \sendpipeline +\endpipeline + ?column? =20 +-------------- + after_sync_1 +(1 row) + + ?column? =20 +-------------- + after_sync_2 +(1 row) + + ?column? =20 +-------------- + after_sync_3 +(1 row) + + ?column? =20 +-------------- + after_sync_4 +(1 row) + + ?column? =20 +-------------- + after_sync_5 +(1 row) + +ERROR: duplicate key value violates unique constraint "psql_pipeline_defe= r_pkey" +DETAIL: Key (a)=3D(1) already exists. +DROP TABLE psql_pipeline_defer; -- Clean up DROP TABLE psql_pipeline; diff --git a/src/test/regress/sql/psql_pipeline.sql b/src/test/regress/sql/= psql_pipeline.sql index 6788dceee2e9..ec68e48e25aa 100644 --- a/src/test/regress/sql/psql_pipeline.sql +++ b/src/test/regress/sql/psql_pipeline.sql @@ -438,5 +438,44 @@ SELECT 1 \bind \sendpipeline VACUUM psql_pipeline \bind \sendpipeline \endpipeline =20 +-- Deferred constraint violation at commit time in a pipeline. +CREATE TABLE psql_pipeline_defer (a INTEGER PRIMARY KEY DEFERRABLE INITIAL= LY DEFERRED); +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) RETURNING * \bind 1 \sen= dpipeline +\endpipeline + +-- Same with \syncpipeline and commands after the failing sync. +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind 'after_sync_1' \sendpipeline +\endpipeline + +-- More patterns with more \syncpipeline, more commands and \getresults +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +SELECT $1 \bind 'after_sync_1' \sendpipeline +\getresults +SELECT $1 \bind 'after_sync_2' \sendpipeline +\endpipeline +\startpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +\syncpipeline +\getresults +SELECT $1 \bind 'after_sync_1' \sendpipeline +\getresults +SELECT $1 \bind 'after_sync_2' \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +INSERT INTO psql_pipeline_defer VALUES ($1), ($1) \bind 1 \sendpipeline +SELECT $1 \bind 'after_sync_3' \sendpipeline +SELECT $1 \bind 'after_sync_4' \sendpipeline +SELECT $1 \bind 'after_sync_5' \sendpipeline +\endpipeline + +DROP TABLE psql_pipeline_defer; + -- Clean up DROP TABLE psql_pipeline; --=20 2.54.0 --GmiEme0sUhGVvoa7-- --niSFj3EAmbtQuAvl Content-Type: application/pgp-signature; name=signature.asc -----BEGIN PGP SIGNATURE----- iQIzBAEBCgAdFiEEG72nH6vTowiyblFKnvQgOdbyQH0FAmoX0e4ACgkQnvQgOdby QH3luQ/9EP79NiPQTrZJJ5nACZn3Oe6ikSJXA7LSdNM34c4YPi2QMi+I9LbggUIv LTVDlrYijBJErfw5Za04a3bJPDVpzvtoWMjnNLet0m5KQNgVRIFrMhvdkLyyvl+J lMKPPaDqrMswUObjLTnsyxdZ/OJtw+rCmUhsPJOF+khl+9oFbnreZJgo4XFd06TF ZPe5megTb0wWBl2Z8eTQNDJV8Q9vqQ0zWVzdXpbIPTFAGS2DOTWDdgIu/VLh5M0z Hw0UB8GlAkFdIlH4heFRKJBt9aqQFH9XDaoIh5OgBeYr1hlEwo0pWZGKhT8Cj3bo B9fsD7B/kan+NDmUog8Vz9CRGwQirNh9wdgj+XPjmcaD2leb1vs1l78lhYjk0ysG BU2Dek9S1s5Hq8AzZMSfTmHwbVf1IO19/pg682mpwX6rlYOUoh6Ma3M/lRg0b6l3 6QgHu8O2WJmCPY3ztg2qn0oSq0vRf1uQqX7q2kx27lgu0l+A5YjR4pnOlMaCToH+ XgMq1kqcgEpGtyFAqUjF5bNyW6I9iGb9IYL7HpgBl9mUrYRTnrOWzmFa1J0jKiy+ xSu5xcEuE7VQsPRjq7yEuBBloKfcKiYZZoOWPUSYgLcl6KKsEE+SCKyLJaaZdsiq sxoc7p+Zs/fP65GsAHaTnzV4NtTMOT/54URp2IxQzTbbPdou8yQ= =9ei+ -----END PGP SIGNATURE----- --niSFj3EAmbtQuAvl--