public inbox for [email protected]  
help / color / mirror / Atom feed
[PATCH v1 05/12] jit: explain: remove backend lifetime module count from function name.
25+ messages / 8 participants
[nested] [flat]

* [PATCH v1 05/12] jit: explain: remove backend lifetime module count from function name.
@ 2019-09-26 21:05  Andres Freund <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Andres Freund @ 2019-09-26 21:05 UTC (permalink / raw)

Also expand function name to include in which module the function is -
without that it's harder to analyze which functions were emitted
separately (a performance concern).

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/commands/explain.c | 65 +++++++++++++++++++++++++++++-----
 src/backend/jit/llvm/llvmjit.c | 18 +++++++---
 src/include/jit/llvmjit.h      |  5 ++-
 3 files changed, 75 insertions(+), 13 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 3ccb76bdfd1..02455865d9f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2228,6 +2228,43 @@ show_expression(Node *node, ExprState *expr, const char *qlabel,
 	}
 }
 
+/*
+ * To make JIT explain output reproducible, remove the module generation from
+ * function names. That makes it a bit harder to correlate with profiles etc,
+ * but reproducability is more important.
+ */
+static char *
+jit_funcname_for_display(const char *funcname)
+{
+	int			func_counter; /* nth function in query */
+	size_t		mod_num; /* nth module in query */
+	size_t		mod_generation; /* nth module in backend */
+	int			basename_end;
+	int			matchcount = 0;
+
+	/*
+	 * The pattern we need to match, see llvm_expand_funcname, is
+	 * "%s_%zu_%d_mod_%zu". Find the fourth _ from the end, so a _ in the name
+	 * is OK.
+	 */
+	for (basename_end = strlen(funcname); basename_end >= 0; basename_end--)
+	{
+		if (funcname[basename_end] == '_' && ++matchcount == 4)
+			break;
+	}
+
+	/* couldn't parse, bail out */
+	if (matchcount != 4)
+		return pstrdup(funcname);
+
+	/* couldn't parse, bail out */
+	if (sscanf(funcname + basename_end, "_%zu_%d_mod_%zu",
+			   &mod_num, &func_counter, &mod_generation) != 3)
+		return pstrdup(funcname);
+
+	return psprintf("%s_%zu_%d", pnstrdup(funcname, basename_end), mod_num, func_counter);
+}
+
 static void
 show_jit_expr_details(ExprState *expr, ExplainState *es)
 {
@@ -2239,7 +2276,8 @@ show_jit_expr_details(ExprState *expr, ExplainState *es)
 	if (es->format == EXPLAIN_FORMAT_TEXT)
 	{
 		if (expr->flags & EEO_FLAG_JIT_EXPR)
-			appendStringInfo(es->str, "JIT-Expr: %s", expr->expr_funcname);
+			appendStringInfo(es->str, "JIT-Expr: %s",
+							 jit_funcname_for_display(expr->expr_funcname));
 		else
 			appendStringInfoString(es->str, "JIT-Expr: false");
 
@@ -2250,19 +2288,22 @@ show_jit_expr_details(ExprState *expr, ExplainState *es)
 		 */
 
 		if (expr->scan_funcname)
-			appendStringInfo(es->str, ", JIT-Deform-Scan: %s", expr->scan_funcname);
+			appendStringInfo(es->str, ", JIT-Deform-Scan: %s",
+							 jit_funcname_for_display(expr->scan_funcname));
 		else if (expr->flags & EEO_FLAG_JIT_EXPR &&
 				 expr->flags & EEO_FLAG_DEFORM_SCAN)
 			appendStringInfo(es->str, ", JIT-Deform-Scan: false");
 
 		if (expr->outer_funcname)
-			appendStringInfo(es->str, ", JIT-Deform-Outer: %s", expr->outer_funcname);
+			appendStringInfo(es->str, ", JIT-Deform-Outer: %s",
+							 jit_funcname_for_display(expr->outer_funcname));
 		else if (expr->flags & EEO_FLAG_JIT_EXPR &&
 				 expr->flags & EEO_FLAG_DEFORM_OUTER)
 			appendStringInfo(es->str, ", JIT-Deform-Outer: false");
 
 		if (expr->inner_funcname)
-			appendStringInfo(es->str, ", JIT-Deform-Inner: %s", expr->inner_funcname);
+			appendStringInfo(es->str, ", JIT-Deform-Inner: %s",
+							 jit_funcname_for_display(expr->inner_funcname));
 		else if (expr->flags & EEO_FLAG_JIT_EXPR &&
 				 expr->flags & (EEO_FLAG_DEFORM_INNER))
 			appendStringInfo(es->str, ", JIT-Deform-Inner: false");
@@ -2270,26 +2311,34 @@ show_jit_expr_details(ExprState *expr, ExplainState *es)
 	else
 	{
 		if (expr->flags & EEO_FLAG_JIT_EXPR)
-			ExplainPropertyText("JIT-Expr", expr->expr_funcname, es);
+			ExplainPropertyText("JIT-Expr",
+								jit_funcname_for_display(expr->expr_funcname),
+								es);
 		else
 			ExplainPropertyBool("JIT-Expr", false, es);
 
 		if (expr->scan_funcname)
-			ExplainProperty("JIT-Deform-Scan", NULL, expr->scan_funcname, false, es);
+			ExplainProperty("JIT-Deform-Scan", NULL,
+							jit_funcname_for_display(expr->scan_funcname),
+							false, es);
 		else if (expr->flags & EEO_FLAG_DEFORM_SCAN)
 			ExplainProperty("JIT-Deform-Scan", NULL, "false", true, es);
 		else
 			ExplainProperty("JIT-Deform-Scan", NULL, "null", true, es);
 
 		if (expr->outer_funcname)
-			ExplainProperty("JIT-Deform-Outer", NULL, expr->outer_funcname, false, es);
+			ExplainProperty("JIT-Deform-Outer", NULL,
+							jit_funcname_for_display(expr->outer_funcname),
+							false, es);
 		else if (expr->flags & EEO_FLAG_DEFORM_OUTER)
 			ExplainProperty("JIT-Deform-Outer", NULL, "false", true, es);
 		else
 			ExplainProperty("JIT-Deform-Outer", NULL, "null", true, es);
 
 		if (expr->inner_funcname)
-			ExplainProperty("JIT-Deform-Inner", NULL, expr->inner_funcname, false, es);
+			ExplainProperty("JIT-Deform-Inner", NULL,
+							jit_funcname_for_display(expr->inner_funcname),
+							false, es);
 		else if (expr->flags & EEO_FLAG_DEFORM_INNER)
 			ExplainProperty("JIT-Deform-Inner", NULL, "false", true, es);
 		else
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 5489e118041..177a00f3826 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -227,6 +227,8 @@ llvm_mutable_module(LLVMJitContext *context)
 char *
 llvm_expand_funcname(struct LLVMJitContext *context, const char *basename)
 {
+	char *funcname;
+
 	Assert(context->module != NULL);
 
 	context->base.instr.created_functions++;
@@ -234,11 +236,19 @@ llvm_expand_funcname(struct LLVMJitContext *context, const char *basename)
 	/*
 	 * Previously we used dots to separate, but turns out some tools, e.g.
 	 * GDB, don't like that and truncate name.
+	 *
+	 * Append the backend-lifetime module count to the end, so it's easier for
+	 * humans and machines to compare the generated function names across
+	 * queries, the prefix will be the same from query execution to query
+	 * execution.
 	 */
-	return psprintf("%s_%zu_%d",
-					basename,
-					context->module_generation,
-					context->counter++);
+	funcname = psprintf("%s_%zu_%d_mod_%zu",
+					  basename,
+					  context->base.instr.created_modules - 1,
+					  context->counter++,
+					  context->module_generation);
+
+	return funcname;
 }
 
 /*
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 6178864b2e6..e45ff99194f 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -41,7 +41,10 @@ typedef struct LLVMJitContext
 {
 	JitContext	base;
 
-	/* number of modules created */
+	/*
+	 * llvm_generation when ->module was created, monotonically increasing
+	 * within the lifetime of a backend.
+	 */
 	size_t		module_generation;
 
 	/* current, "open for write", module */
-- 
2.23.0.162.gf1d4a28250


--kgppz6muakthis74
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v1-0006-WIP-explain-Show-per-phase-information-about-aggr.patch"



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* [PATCH v2 5/8] jit: explain: remove backend lifetime module count from function name.
@ 2019-09-26 21:05  Andres Freund <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Andres Freund @ 2019-09-26 21:05 UTC (permalink / raw)

Also expand function name to include in which module the function is -
without that it's harder to analyze which functions were emitted
separately (a performance concern).

Author:
Reviewed-By:
Discussion: https://postgr.es/m/
Backpatch:
---
 src/backend/commands/explain.c | 65 +++++++++++++++++++++++++++++-----
 src/backend/jit/llvm/llvmjit.c | 18 +++++++---
 src/include/jit/llvmjit.h      |  5 ++-
 3 files changed, 75 insertions(+), 13 deletions(-)

diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index 3ccb76bdfd1..02455865d9f 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -2228,6 +2228,43 @@ show_expression(Node *node, ExprState *expr, const char *qlabel,
 	}
 }
 
+/*
+ * To make JIT explain output reproducible, remove the module generation from
+ * function names. That makes it a bit harder to correlate with profiles etc,
+ * but reproducability is more important.
+ */
+static char *
+jit_funcname_for_display(const char *funcname)
+{
+	int			func_counter; /* nth function in query */
+	size_t		mod_num; /* nth module in query */
+	size_t		mod_generation; /* nth module in backend */
+	int			basename_end;
+	int			matchcount = 0;
+
+	/*
+	 * The pattern we need to match, see llvm_expand_funcname, is
+	 * "%s_%zu_%d_mod_%zu". Find the fourth _ from the end, so a _ in the name
+	 * is OK.
+	 */
+	for (basename_end = strlen(funcname); basename_end >= 0; basename_end--)
+	{
+		if (funcname[basename_end] == '_' && ++matchcount == 4)
+			break;
+	}
+
+	/* couldn't parse, bail out */
+	if (matchcount != 4)
+		return pstrdup(funcname);
+
+	/* couldn't parse, bail out */
+	if (sscanf(funcname + basename_end, "_%zu_%d_mod_%zu",
+			   &mod_num, &func_counter, &mod_generation) != 3)
+		return pstrdup(funcname);
+
+	return psprintf("%s_%zu_%d", pnstrdup(funcname, basename_end), mod_num, func_counter);
+}
+
 static void
 show_jit_expr_details(ExprState *expr, ExplainState *es)
 {
@@ -2239,7 +2276,8 @@ show_jit_expr_details(ExprState *expr, ExplainState *es)
 	if (es->format == EXPLAIN_FORMAT_TEXT)
 	{
 		if (expr->flags & EEO_FLAG_JIT_EXPR)
-			appendStringInfo(es->str, "JIT-Expr: %s", expr->expr_funcname);
+			appendStringInfo(es->str, "JIT-Expr: %s",
+							 jit_funcname_for_display(expr->expr_funcname));
 		else
 			appendStringInfoString(es->str, "JIT-Expr: false");
 
@@ -2250,19 +2288,22 @@ show_jit_expr_details(ExprState *expr, ExplainState *es)
 		 */
 
 		if (expr->scan_funcname)
-			appendStringInfo(es->str, ", JIT-Deform-Scan: %s", expr->scan_funcname);
+			appendStringInfo(es->str, ", JIT-Deform-Scan: %s",
+							 jit_funcname_for_display(expr->scan_funcname));
 		else if (expr->flags & EEO_FLAG_JIT_EXPR &&
 				 expr->flags & EEO_FLAG_DEFORM_SCAN)
 			appendStringInfo(es->str, ", JIT-Deform-Scan: false");
 
 		if (expr->outer_funcname)
-			appendStringInfo(es->str, ", JIT-Deform-Outer: %s", expr->outer_funcname);
+			appendStringInfo(es->str, ", JIT-Deform-Outer: %s",
+							 jit_funcname_for_display(expr->outer_funcname));
 		else if (expr->flags & EEO_FLAG_JIT_EXPR &&
 				 expr->flags & EEO_FLAG_DEFORM_OUTER)
 			appendStringInfo(es->str, ", JIT-Deform-Outer: false");
 
 		if (expr->inner_funcname)
-			appendStringInfo(es->str, ", JIT-Deform-Inner: %s", expr->inner_funcname);
+			appendStringInfo(es->str, ", JIT-Deform-Inner: %s",
+							 jit_funcname_for_display(expr->inner_funcname));
 		else if (expr->flags & EEO_FLAG_JIT_EXPR &&
 				 expr->flags & (EEO_FLAG_DEFORM_INNER))
 			appendStringInfo(es->str, ", JIT-Deform-Inner: false");
@@ -2270,26 +2311,34 @@ show_jit_expr_details(ExprState *expr, ExplainState *es)
 	else
 	{
 		if (expr->flags & EEO_FLAG_JIT_EXPR)
-			ExplainPropertyText("JIT-Expr", expr->expr_funcname, es);
+			ExplainPropertyText("JIT-Expr",
+								jit_funcname_for_display(expr->expr_funcname),
+								es);
 		else
 			ExplainPropertyBool("JIT-Expr", false, es);
 
 		if (expr->scan_funcname)
-			ExplainProperty("JIT-Deform-Scan", NULL, expr->scan_funcname, false, es);
+			ExplainProperty("JIT-Deform-Scan", NULL,
+							jit_funcname_for_display(expr->scan_funcname),
+							false, es);
 		else if (expr->flags & EEO_FLAG_DEFORM_SCAN)
 			ExplainProperty("JIT-Deform-Scan", NULL, "false", true, es);
 		else
 			ExplainProperty("JIT-Deform-Scan", NULL, "null", true, es);
 
 		if (expr->outer_funcname)
-			ExplainProperty("JIT-Deform-Outer", NULL, expr->outer_funcname, false, es);
+			ExplainProperty("JIT-Deform-Outer", NULL,
+							jit_funcname_for_display(expr->outer_funcname),
+							false, es);
 		else if (expr->flags & EEO_FLAG_DEFORM_OUTER)
 			ExplainProperty("JIT-Deform-Outer", NULL, "false", true, es);
 		else
 			ExplainProperty("JIT-Deform-Outer", NULL, "null", true, es);
 
 		if (expr->inner_funcname)
-			ExplainProperty("JIT-Deform-Inner", NULL, expr->inner_funcname, false, es);
+			ExplainProperty("JIT-Deform-Inner", NULL,
+							jit_funcname_for_display(expr->inner_funcname),
+							false, es);
 		else if (expr->flags & EEO_FLAG_DEFORM_INNER)
 			ExplainProperty("JIT-Deform-Inner", NULL, "false", true, es);
 		else
diff --git a/src/backend/jit/llvm/llvmjit.c b/src/backend/jit/llvm/llvmjit.c
index 5489e118041..177a00f3826 100644
--- a/src/backend/jit/llvm/llvmjit.c
+++ b/src/backend/jit/llvm/llvmjit.c
@@ -227,6 +227,8 @@ llvm_mutable_module(LLVMJitContext *context)
 char *
 llvm_expand_funcname(struct LLVMJitContext *context, const char *basename)
 {
+	char *funcname;
+
 	Assert(context->module != NULL);
 
 	context->base.instr.created_functions++;
@@ -234,11 +236,19 @@ llvm_expand_funcname(struct LLVMJitContext *context, const char *basename)
 	/*
 	 * Previously we used dots to separate, but turns out some tools, e.g.
 	 * GDB, don't like that and truncate name.
+	 *
+	 * Append the backend-lifetime module count to the end, so it's easier for
+	 * humans and machines to compare the generated function names across
+	 * queries, the prefix will be the same from query execution to query
+	 * execution.
 	 */
-	return psprintf("%s_%zu_%d",
-					basename,
-					context->module_generation,
-					context->counter++);
+	funcname = psprintf("%s_%zu_%d_mod_%zu",
+					  basename,
+					  context->base.instr.created_modules - 1,
+					  context->counter++,
+					  context->module_generation);
+
+	return funcname;
 }
 
 /*
diff --git a/src/include/jit/llvmjit.h b/src/include/jit/llvmjit.h
index 6178864b2e6..e45ff99194f 100644
--- a/src/include/jit/llvmjit.h
+++ b/src/include/jit/llvmjit.h
@@ -41,7 +41,10 @@ typedef struct LLVMJitContext
 {
 	JitContext	base;
 
-	/* number of modules created */
+	/*
+	 * llvm_generation when ->module was created, monotonically increasing
+	 * within the lifetime of a backend.
+	 */
 	size_t		module_generation;
 
 	/* current, "open for write", module */
-- 
2.23.0.385.gbc12974a89


--ga6shgqrocqphdjc
Content-Type: text/x-diff; charset=us-ascii
Content-Disposition: attachment;
	filename="v2-0006-WIP-explain-Show-per-phase-information-about-aggr.patch"



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: de-catalog one error message
@ 2023-05-16 09:48  Alvaro Herrera <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Alvaro Herrera @ 2023-05-16 09:48 UTC (permalink / raw)
  To: Daniel Gustafsson <[email protected]>; +Cc: Pg Hackers <[email protected]>

On 2023-May-11, Daniel Gustafsson wrote:

> > On 10 May 2023, at 19:54, Alvaro Herrera <[email protected]> wrote:

> > Therefore, I propose to turn these messages into errmsg_internal(), so
> > that we do not translate them.
> 
> AFAICT from following the code that seems correct, and I agree with removing
> them from translation to lessen the burden on our translators.

Thanks for looking!  Pushed now.

-- 
Álvaro Herrera               48°01'N 7°57'E  —  https://www.EnterpriseDB.com/
"Pido que me den el Nobel por razones humanitarias" (Nicanor Parra)






^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-07 00:05  Jelte Fennema-Nio <[email protected]>
  0 siblings, 2 replies; 25+ messages in thread

From: Jelte Fennema-Nio @ 2025-03-07 00:05 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Anthonin Bonnefoy <[email protected]>; pgsql-hackers

On Tue, 25 Feb 2025 at 02:11, Michael Paquier <[email protected]> wrote:
> Initial digestion has gone well.

One thing I've noticed is that \startpipeline throws warnings when
copy pasting multiple lines. It seems to still execute everything as
expected though. As an example you can copy paste this tiny script:

\startpipeline
select pg_sleep(5) \bind \g
\endpipeline

And then it will show these "extra argument ... ignored" warnings

\startpipeline: extra argument "select" ignored
\startpipeline: extra argument "pg_sleep(5)" ignored





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-07 08:08  Anthonin Bonnefoy <[email protected]>
  parent: Jelte Fennema-Nio <[email protected]>
  1 sibling, 0 replies; 25+ messages in thread

From: Anthonin Bonnefoy @ 2025-03-07 08:08 UTC (permalink / raw)
  To: Jelte Fennema-Nio <[email protected]>; +Cc: Michael Paquier <[email protected]>; pgsql-hackers

On Thu, Mar 6, 2025 at 5:20 AM Michael Paquier <[email protected]> wrote:
> That was not a test case we had in mind originally here, but if it is
> possible to keep the implementation simple while supporting your
> demand, well, let's do it.  If it's not that straight-forward, let's
> use the new meta-command, forbidding \g and \gx based on your
> arguments from upthread.

I think the new meta-command is a separate issue from allowing ';' to
push in a pipeline. Any time there's a change or an additional format
option added to \g, it will need to be forbidden for pipelining. The
\sendpipeline meta-command will help keep those exceptions low since
the whole \g will be forbidden.

Another possible option would be to allow both \g and \gx, but send a
warning like "printing options within a pipeline will be ignored" if
those options are used, similar to "SET LOCAL" warning when done
outside of a transaction block. That would have the benefit of making
existing scripts using \g and \gx compatible.

For using ';' to push commands in a pipeline, I think it should be
fairly straightforward. I can try to work on that next week (I'm
currently chasing a weird memory context bug that I need to finish
first).

On Fri, Mar 7, 2025 at 1:05 AM Jelte Fennema-Nio <[email protected]> wrote:
> One thing I've noticed is that \startpipeline throws warnings when
> copy pasting multiple lines. It seems to still execute everything as
> expected though. As an example you can copy paste this tiny script:
>
> \startpipeline
> select pg_sleep(5) \bind \g
> \endpipeline
>
> And then it will show these "extra argument ... ignored" warnings
>
> \startpipeline: extra argument "select" ignored
> \startpipeline: extra argument "pg_sleep(5)" ignored

It looks like an issue with libreadline. At least, I've been able to
reproduce the warnings and 'readline(prompt);' returns everything as a
single line, with the \n inside the string. This explains why what is
after \startpipeline is processed as arguments. This can also be done
with:
select 1 \bind \g
select 2 \bind \g
And somehow, I couldn't reproduce the issue anymore once I've compiled
and installed libreadline with debug symbols.





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-07 22:31  Daniel Verite <[email protected]>
  parent: Jelte Fennema-Nio <[email protected]>
  1 sibling, 1 reply; 25+ messages in thread

From: Daniel Verite @ 2025-03-07 22:31 UTC (permalink / raw)
  To: Jelte Fennema-Nio <[email protected]>; +Cc: Michael Paquier <[email protected]>; Anthonin Bonnefoy <[email protected]>; pgsql-hackers

	Jelte Fennema-Nio wrote:

> As an example you can copy paste this tiny script:
> 
> \startpipeline
> select pg_sleep(5) \bind \g
> \endpipeline
> 
> And then it will show these "extra argument ... ignored" warnings
> 
> \startpipeline: extra argument "select" ignored
> \startpipeline: extra argument "pg_sleep(5)" ignored

It happens with other metacommands as well, and appears
to depend on a readline option that is "on" by default since
readline-8.1 [1]

 enable-bracketed-paste

    When set to ‘On’, Readline configures the terminal to insert each
    paste into the editing buffer as a single string of characters,
    instead of treating each character as if it had been read from the
    keyboard. This is called putting the terminal into bracketed paste
    mode; it prevents Readline from executing any editing commands
    bound to key sequences appearing in the pasted text. The default
    is ‘On’.


This behavior of the metacommand complaining about arguments 
on the next line also happens if using \e and typing this sequence
of commands  in the editor. In that case readline is not involved.

There might be something to improve here, because
a metacommand cannot take its argument from the next line,
and yet that's what the error messages somewhat imply.
But that issue is not related to the new pipeline metacommands.


[1]
https://tiswww.case.edu/php/chet/readline/readline.html#index-enable_002dbracketed_002dpaste


Best regards,
-- 
Daniel Vérité 
https://postgresql.verite.pro/





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-17 09:50  Anthonin Bonnefoy <[email protected]>
  parent: Daniel Verite <[email protected]>
  0 siblings, 3 replies; 25+ messages in thread

From: Anthonin Bonnefoy @ 2025-03-17 09:50 UTC (permalink / raw)
  To: Daniel Verite <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Michael Paquier <[email protected]>; pgsql-hackers

Here is a new patch set:

0001: This introduces the \sendpipeline meta-command and forbid \g in
a pipeline. This is to fix the formatting options of \g that are not
supported in a pipeline.

0002: Allows ';' to send a query using extended protocol when within a
pipeline by using PQsendQueryParams with 0 parameters. It is not
possible to send parameters with extended protocol this way and
everything will be propagated through the query string, similar to a
simple query.


Attachments:

  [application/octet-stream] v02-0001-psql-Create-new-sendpipeline-meta-command.patch (38.8K, ../../CAO6_Xqq4JEHRaGyC=r+dpi0Ejbp2P3aUo-KfR2NuipCvE-o_ZA@mail.gmail.com/2-v02-0001-psql-Create-new-sendpipeline-meta-command.patch)
  download | inline diff:
From 097084856575dbcd53baad01a1b5420b4201036d Mon Sep 17 00:00:00 2001
From: Anthonin Bonnefoy <[email protected]>
Date: Tue, 4 Mar 2025 09:47:45 +0100
Subject: psql: Create new \sendpipeline meta-command

In the initial pipelining support for psql, \g was reused as a way to
push extended query into an ongoing pipeline. However, all related
meta-command options are not applicable in pipeline mode. Pipeline
output only happens during \getresults or \endpipeline and any change to
output options would have been reset.

To avoid possible confusion, a new \sendpipeline meta-command is
introduced, replacing \g when within a pipeline.
---
 doc/src/sgml/ref/psql-ref.sgml              |  11 +-
 src/bin/psql/command.c                      |  45 +++-
 src/bin/psql/help.c                         |   1 +
 src/bin/psql/tab-complete.in.c              |   2 +-
 src/test/regress/expected/psql.out          |   1 +
 src/test/regress/expected/psql_pipeline.out | 245 +++++++++++---------
 src/test/regress/sql/psql.sql               |   1 +
 src/test/regress/sql/psql_pipeline.sql      | 230 +++++++++---------
 8 files changed, 310 insertions(+), 226 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index cedccc14129..cddf6e07531 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3677,6 +3677,7 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
 
      <varlistentry id="app-psql-meta-command-pipeline">
       <term><literal>\startpipeline</literal></term>
+      <term><literal>\sendpipeline</literal></term>
       <term><literal>\syncpipeline</literal></term>
       <term><literal>\endpipeline</literal></term>
       <term><literal>\flushrequest</literal></term>
@@ -3701,10 +3702,10 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
         queries need to be sent using the meta-commands
         <literal>\bind</literal>, <literal>\bind_named</literal>,
         <literal>\close</literal> or <literal>\parse</literal>. While a
-        pipeline is ongoing, <literal>\g</literal> will append the current
-        query buffer to the pipeline. Other meta-commands like
-        <literal>\gx</literal> or <literal>\gdesc</literal> are not allowed
-        in pipeline mode.
+        pipeline is ongoing, <literal>\sendpipeline</literal> will append the
+        current query buffer to the pipeline. Other meta-commands like
+        <literal>\g</literal>, <literal>\gx</literal> or <literal>\gdesc</literal>
+        are not allowed in pipeline mode.
        </para>
 
        <para>
@@ -3738,7 +3739,7 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
         Example:
 <programlisting>
 \startpipeline
-SELECT 1 \bind \g
+SELECT 1 \bind \sendpipeline
 \flushrequest
 \getresults
 \endpipeline
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index fb0b27568c5..7670c20b29e 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -137,6 +137,7 @@ static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active
 static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_branch,
 										  const char *cmd, bool is_func);
 static backslashResult exec_command_startpipeline(PsqlScanState scan_state, bool active_branch);
+static backslashResult exec_command_sendpipeline(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_syncpipeline(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch);
 static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch);
@@ -427,6 +428,8 @@ exec_command(const char *cmd,
 		status = exec_command_sf_sv(scan_state, active_branch, cmd, false);
 	else if (strcmp(cmd, "startpipeline") == 0)
 		status = exec_command_startpipeline(scan_state, active_branch);
+	else if (strcmp(cmd, "sendpipeline") == 0)
+		status = exec_command_sendpipeline(scan_state, active_branch);
 	else if (strcmp(cmd, "syncpipeline") == 0)
 		status = exec_command_syncpipeline(scan_state, active_branch);
 	else if (strcmp(cmd, "t") == 0)
@@ -1734,10 +1737,9 @@ exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
 
 	if (status == PSQL_CMD_SKIP_LINE && active_branch)
 	{
-		if (strcmp(cmd, "gx") == 0 &&
-			PQpipelineStatus(pset.db) != PQ_PIPELINE_OFF)
+		if (PQpipelineStatus(pset.db) != PQ_PIPELINE_OFF)
 		{
-			pg_log_error("\\gx not allowed in pipeline mode");
+			pg_log_error("\\%s not allowed in pipeline mode", cmd);
 			clean_extended_state();
 			free(fname);
 			return PSQL_CMD_ERROR;
@@ -2979,6 +2981,43 @@ exec_command_startpipeline(PsqlScanState scan_state, bool active_branch)
 	return status;
 }
 
+/*
+ * \sendpipeline -- send an extended query to an ongoing pipeline
+ */
+static backslashResult
+exec_command_sendpipeline(PsqlScanState scan_state, bool active_branch)
+{
+	backslashResult status = PSQL_CMD_SKIP_LINE;
+
+	if (active_branch)
+	{
+		if (PQpipelineStatus(pset.db) != PQ_PIPELINE_OFF)
+		{
+			if (pset.send_mode == PSQL_SEND_EXTENDED_QUERY_PREPARED ||
+				pset.send_mode == PSQL_SEND_EXTENDED_QUERY_PARAMS)
+			{
+				status = PSQL_CMD_SEND;
+			}
+			else
+			{
+				pg_log_error("\\sendpipeline must be used after \\bind or \\bind_named");
+				clean_extended_state();
+				return PSQL_CMD_ERROR;
+			}
+		}
+		else
+		{
+			pg_log_error("\\sendpipeline not allowed outside of pipeline mode");
+			clean_extended_state();
+			return PSQL_CMD_ERROR;
+		}
+	}
+	else
+		ignore_slash_options(scan_state);
+
+	return status;
+}
+
 /*
  * \syncpipeline -- send a sync message to an active pipeline
  */
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 714b8619233..edfc794b76f 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -182,6 +182,7 @@ slashUsage(unsigned short int pager)
 	HELP0("  \\parse STMT_NAME       create a prepared statement\n");
 	HELP0("  \\q                     quit psql\n");
 	HELP0("  \\startpipeline         enter pipeline mode\n");
+	HELP0("  \\sendpipeline          send an extended query to an ongoing pipeline\n");
 	HELP0("  \\syncpipeline          add a synchronisation point to an ongoing pipeline\n");
 	HELP0("  \\watch [[i=]SEC] [c=N] [m=MIN]\n"
 		  "                         execute query every SEC seconds, up to N times,\n"
diff --git a/src/bin/psql/tab-complete.in.c b/src/bin/psql/tab-complete.in.c
index 8432be641ac..b2de63a5b74 100644
--- a/src/bin/psql/tab-complete.in.c
+++ b/src/bin/psql/tab-complete.in.c
@@ -1895,7 +1895,7 @@ psql_completion(const char *text, int start, int end)
 		"\\parse", "\\password", "\\print", "\\prompt", "\\pset",
 		"\\qecho", "\\quit",
 		"\\reset",
-		"\\s", "\\set", "\\setenv", "\\sf", "\\startpipeline", "\\sv", "\\syncpipeline",
+		"\\s", "\\set", "\\setenv", "\\sf", "\\startpipeline", "\\sendpipeline", "\\sv", "\\syncpipeline",
 		"\\t", "\\T", "\\timing",
 		"\\unset",
 		"\\x",
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 6543e90de75..ee6f8671bea 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4711,6 +4711,7 @@ invalid command \lo
 	\sf whole_line
 	\sv whole_line
 	\startpipeline
+	\sendpipeline
 	\syncpipeline
 	\t arg1
 	\T arg1
diff --git a/src/test/regress/expected/psql_pipeline.out b/src/test/regress/expected/psql_pipeline.out
index 3df2415a840..53f2edfd986 100644
--- a/src/test/regress/expected/psql_pipeline.out
+++ b/src/test/regress/expected/psql_pipeline.out
@@ -4,7 +4,7 @@
 CREATE TABLE psql_pipeline(a INTEGER PRIMARY KEY, s TEXT);
 -- Single query
 \startpipeline
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -13,9 +13,9 @@ SELECT $1 \bind 'val1' \g
 
 -- Multiple queries
 \startpipeline
-SELECT $1 \bind 'val1' \g
-SELECT $1, $2 \bind 'val2' 'val3' \g
-SELECT $1, $2 \bind 'val2' 'val3' \g
+SELECT $1 \bind 'val1' \sendpipeline
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -35,10 +35,10 @@ SELECT $1, $2 \bind 'val2' 'val3' \g
 -- Test \flush
 \startpipeline
 \flush
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \flush
-SELECT $1, $2 \bind 'val2' 'val3' \g
-SELECT $1, $2 \bind 'val2' 'val3' \g
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -63,12 +63,12 @@ SELECT $1, $2 \bind 'val2' 'val3' \g
 0
 \echo :PIPELINE_RESULT_COUNT
 0
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \syncpipeline
 \syncpipeline
-SELECT $1, $2 \bind 'val2' 'val3' \g
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
 \syncpipeline
-SELECT $1, $2 \bind 'val4' 'val5' \g
+SELECT $1, $2 \bind 'val4' 'val5' \sendpipeline
 \echo :PIPELINE_COMMAND_COUNT
 1
 \echo :PIPELINE_SYNC_COUNT
@@ -94,7 +94,7 @@ SELECT $1, $2 \bind 'val4' 'val5' \g
 -- \startpipeline should not have any effect if already in a pipeline.
 \startpipeline
 \startpipeline
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -103,24 +103,24 @@ SELECT $1 \bind 'val1' \g
 
 -- Convert an implicit transaction block to an explicit transaction block.
 \startpipeline
-INSERT INTO psql_pipeline VALUES ($1) \bind 1 \g
-BEGIN \bind \g
-INSERT INTO psql_pipeline VALUES ($1) \bind 2 \g
-ROLLBACK \bind \g
+INSERT INTO psql_pipeline VALUES ($1) \bind 1 \sendpipeline
+BEGIN \bind \sendpipeline
+INSERT INTO psql_pipeline VALUES ($1) \bind 2 \sendpipeline
+ROLLBACK \bind \sendpipeline
 \endpipeline
 -- Multiple explicit transactions
 \startpipeline
-BEGIN \bind \g
-INSERT INTO psql_pipeline VALUES ($1) \bind 1 \g
-ROLLBACK \bind \g
-BEGIN \bind \g
-INSERT INTO psql_pipeline VALUES ($1) \bind 1 \g
-COMMIT \bind \g
+BEGIN \bind \sendpipeline
+INSERT INTO psql_pipeline VALUES ($1) \bind 1 \sendpipeline
+ROLLBACK \bind \sendpipeline
+BEGIN \bind \sendpipeline
+INSERT INTO psql_pipeline VALUES ($1) \bind 1 \sendpipeline
+COMMIT \bind \sendpipeline
 \endpipeline
 -- COPY FROM STDIN
 \startpipeline
-SELECT $1 \bind 'val1' \g
-COPY psql_pipeline FROM STDIN \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+COPY psql_pipeline FROM STDIN \bind \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -129,8 +129,8 @@ COPY psql_pipeline FROM STDIN \bind \g
 
 -- COPY FROM STDIN with \flushrequest + \getresults
 \startpipeline
-SELECT $1 \bind 'val1' \g
-COPY psql_pipeline FROM STDIN \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+COPY psql_pipeline FROM STDIN \bind \sendpipeline
 \flushrequest
 \getresults
  ?column? 
@@ -142,8 +142,8 @@ message type 0x5a arrived from server while idle
 \endpipeline
 -- COPY FROM STDIN with \syncpipeline + \getresults
 \startpipeline
-SELECT $1 \bind 'val1' \g
-COPY psql_pipeline FROM STDIN \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+COPY psql_pipeline FROM STDIN \bind \sendpipeline
 \syncpipeline
 \getresults
  ?column? 
@@ -154,8 +154,8 @@ COPY psql_pipeline FROM STDIN \bind \g
 \endpipeline
 -- COPY TO STDOUT
 \startpipeline
-SELECT $1 \bind 'val1' \g
-copy psql_pipeline TO STDOUT \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+copy psql_pipeline TO STDOUT \bind \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -168,8 +168,8 @@ copy psql_pipeline TO STDOUT \bind \g
 4	test4
 -- COPY TO STDOUT with \flushrequest + \getresults
 \startpipeline
-SELECT $1 \bind 'val1' \g
-copy psql_pipeline TO STDOUT \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+copy psql_pipeline TO STDOUT \bind \sendpipeline
 \flushrequest
 \getresults
  ?column? 
@@ -184,8 +184,8 @@ copy psql_pipeline TO STDOUT \bind \g
 \endpipeline
 -- COPY TO STDOUT with \syncpipeline + \getresults
 \startpipeline
-SELECT $1 \bind 'val1' \g
-copy psql_pipeline TO STDOUT \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+copy psql_pipeline TO STDOUT \bind \sendpipeline
 \syncpipeline
 \getresults
  ?column? 
@@ -203,14 +203,14 @@ copy psql_pipeline TO STDOUT \bind \g
 SELECT $1 \parse ''
 SELECT $1, $2 \parse ''
 SELECT $2 \parse pipeline_1
-\bind_named '' 1 2 \g
-\bind_named pipeline_1 2 \g
+\bind_named '' 1 2 \sendpipeline
+\bind_named pipeline_1 2 \sendpipeline
 \endpipeline
 ERROR:  could not determine data type of parameter $1
 -- \getresults displays all results preceding a \flushrequest.
 \startpipeline
-SELECT $1 \bind 1 \g
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 1 \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \flushrequest
 \getresults
  ?column? 
@@ -226,8 +226,8 @@ SELECT $1 \bind 2 \g
 \endpipeline
 -- \getresults displays all results preceding a \syncpipeline.
 \startpipeline
-SELECT $1 \bind 1 \g
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 1 \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \syncpipeline
 \getresults
  ?column? 
@@ -245,7 +245,7 @@ SELECT $1 \bind 2 \g
 \startpipeline
 \getresults
 No pending results to get
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \getresults
 No pending results to get
 \flushrequest
@@ -259,9 +259,9 @@ No pending results to get
 No pending results to get
 -- \getresults only fetches results preceding a \flushrequest.
 \startpipeline
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \flushrequest
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \getresults
  ?column? 
 ----------
@@ -276,9 +276,9 @@ SELECT $1 \bind 2 \g
 
 -- \getresults only fetches results preceding a \syncpipeline.
 \startpipeline
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \syncpipeline
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \getresults
  ?column? 
 ----------
@@ -294,7 +294,7 @@ SELECT $1 \bind 2 \g
 -- Use pipeline with chunked results for both \getresults and \endpipeline.
 \startpipeline
 \set FETCH_COUNT 10
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \flushrequest
 \getresults
  ?column? 
@@ -302,7 +302,7 @@ SELECT $1 \bind 2 \g
  2
 (1 row)
 
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -312,9 +312,9 @@ SELECT $1 \bind 2 \g
 \unset FETCH_COUNT
 -- \getresults with specific number of requested results.
 \startpipeline
-SELECT $1 \bind 1 \g
-SELECT $1 \bind 2 \g
-SELECT $1 \bind 3 \g
+SELECT $1 \bind 1 \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
+SELECT $1 \bind 3 \sendpipeline
 \echo :PIPELINE_SYNC_COUNT
 0
 \syncpipeline
@@ -330,7 +330,7 @@ SELECT $1 \bind 3 \g
 
 \echo :PIPELINE_RESULT_COUNT
 2
-SELECT $1 \bind 4 \g
+SELECT $1 \bind 4 \sendpipeline
 \getresults 3
  ?column? 
 ----------
@@ -354,7 +354,7 @@ SELECT $1 \bind 4 \g
 \startpipeline
 \syncpipeline
 \syncpipeline
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 \flushrequest
 \getresults 2
 \getresults 1
@@ -366,9 +366,9 @@ SELECT $1 \bind 1 \g
 \endpipeline
 -- \getresults 0 should get all the results.
 \startpipeline
-SELECT $1 \bind 1 \g
-SELECT $1 \bind 2 \g
-SELECT $1 \bind 3 \g
+SELECT $1 \bind 1 \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
+SELECT $1 \bind 3 \sendpipeline
 \syncpipeline
 \getresults 0
  ?column? 
@@ -398,7 +398,7 @@ cannot send pipeline when not in pipeline mode
 \startpipeline
 SELECT 1;
 PQsendQuery not allowed in pipeline mode
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -408,9 +408,9 @@ SELECT $1 \bind 'val1' \g
 -- After an aborted pipeline, commands after a \syncpipeline should be
 -- displayed.
 \startpipeline
-SELECT $1 \bind \g
+SELECT $1 \bind \sendpipeline
 \syncpipeline
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 \endpipeline
 ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1
  ?column? 
@@ -421,23 +421,23 @@ ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1
 -- For an incorrect number of parameters, the pipeline is aborted and
 -- the following queries will not be executed.
 \startpipeline
-SELECT \bind 'val1' \g
-SELECT $1 \bind 'val1' \g
+SELECT \bind 'val1' \sendpipeline
+SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
 ERROR:  bind message supplies 1 parameters, but prepared statement "" requires 0
 -- An explicit transaction with an error needs to be rollbacked after
 -- the pipeline.
 \startpipeline
-BEGIN \bind \g
-INSERT INTO psql_pipeline VALUES ($1) \bind 1 \g
-ROLLBACK \bind \g
+BEGIN \bind \sendpipeline
+INSERT INTO psql_pipeline VALUES ($1) \bind 1 \sendpipeline
+ROLLBACK \bind \sendpipeline
 \endpipeline
 ERROR:  duplicate key value violates unique constraint "psql_pipeline_pkey"
 DETAIL:  Key (a)=(1) already exists.
 ROLLBACK;
 -- \watch sends a simple query, something not allowed within a pipeline.
 \startpipeline
-SELECT \bind \g
+SELECT \bind \sendpipeline
 \watch 1
 PQsendQuery not allowed in pipeline mode
 
@@ -450,7 +450,7 @@ PQsendQuery not allowed in pipeline mode
 \startpipeline
 SELECT $1 \bind 1 \gdesc
 synchronous command execution functions are not allowed in pipeline mode
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -465,19 +465,33 @@ SELECT $1 as k, $2 as l \parse 'second'
 \gset not allowed in pipeline mode
 \bind_named second 1 2 \gset pref02_ \echo :pref02_i :pref02_j
 \gset not allowed in pipeline mode
-\bind_named '' 1 2 \g
+\bind_named '' 1 2 \sendpipeline
 \endpipeline
  i | j 
 ---+---
  1 | 2
 (1 row)
 
--- \gx is not allowed, pipeline should still be usable.
+-- \g and \gx are not allowed, pipeline should still be usable.
 \startpipeline
+SELECT $1 \bind 1 \g
+\g not allowed in pipeline mode
+SELECT $1 \bind 1 \g filename
+\g not allowed in pipeline mode
+SELECT $1 \bind 1 \g |cat
+\g not allowed in pipeline mode
+SELECT $1 \bind 1 \g (format=unaligned tuples_only=on)
+\g not allowed in pipeline mode
 SELECT $1 \bind 1 \gx
 \gx not allowed in pipeline mode
+SELECT $1 \bind 1 \gx filename
+\gx not allowed in pipeline mode
+SELECT $1 \bind 1 \gx |cat
+\gx not allowed in pipeline mode
+SELECT $1 \bind 1 \gx (format=unaligned tuples_only=on)
+\gx not allowed in pipeline mode
 \reset
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -487,54 +501,63 @@ SELECT $1 \bind 1 \g
 -- \gx warning should be emitted in an aborted pipeline, with
 -- pipeline still usable.
 \startpipeline
-SELECT $1 \bind \g
+SELECT $1 \bind \sendpipeline
 \flushrequest
 \getresults
 ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1
 SELECT $1 \bind 1 \gx
 \gx not allowed in pipeline mode
 \endpipeline
+-- \sendpipeline is not allowed outside of a pipeline
+\sendpipeline
+\sendpipeline not allowed outside of pipeline mode
+SELECT $1 \bind 1 \sendpipeline
+\sendpipeline not allowed outside of pipeline mode
+\reset
+-- \sendpipeline is not allowed if not preceded by \bind or \bind_named
+\startpipeline
+\sendpipeline
+\sendpipeline must be used after \bind or \bind_named
+SELECT 1 \sendpipeline
+\sendpipeline must be used after \bind or \bind_named
+\endpipeline
 -- \gexec is not allowed, pipeline should still be usable.
 \startpipeline
 SELECT 'INSERT INTO psql_pipeline(a) SELECT generate_series(1, 10)' \parse 'insert_stmt'
 \bind_named insert_stmt \gexec
 \gexec not allowed in pipeline mode
-\bind_named insert_stmt \g
+\bind_named insert_stmt \sendpipeline
 SELECT COUNT(*) FROM psql_pipeline \bind \g
+\g not allowed in pipeline mode
 \endpipeline
                           ?column?                          
 ------------------------------------------------------------
  INSERT INTO psql_pipeline(a) SELECT generate_series(1, 10)
 (1 row)
 
- count 
--------
-     4
-(1 row)
-
 -- After an error, pipeline is aborted and requires \syncpipeline to be
 -- reusable.
 \startpipeline
-SELECT $1 \bind \g
-SELECT $1 \bind 1 \g
+SELECT $1 \bind \sendpipeline
+SELECT $1 \bind 1 \sendpipeline
 SELECT $1 \parse a
-\bind_named a 1 \g
+\bind_named a 1 \sendpipeline
 \close a
 \flushrequest
 \getresults
 ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1
 -- Pipeline is aborted.
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 SELECT $1 \parse a
-\bind_named a 1 \g
+\bind_named a 1 \sendpipeline
 \close a
 -- Sync allows pipeline to recover.
 \syncpipeline
 \getresults
 Pipeline aborted, command did not run
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 SELECT $1 \parse a
-\bind_named a 1 \g
+\bind_named a 1 \sendpipeline
 \close a
 \flushrequest
 \getresults
@@ -551,10 +574,10 @@ SELECT $1 \parse a
 \endpipeline
 -- In an aborted pipeline, \getresults 1 aborts commands one at a time.
 \startpipeline
-SELECT $1 \bind \g
-SELECT $1 \bind 1 \g
+SELECT $1 \bind \sendpipeline
+SELECT $1 \bind 1 \sendpipeline
 SELECT $1 \parse a
-\bind_named a 1 \g
+\bind_named a 1 \sendpipeline
 \syncpipeline
 \getresults 1
 ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1
@@ -569,11 +592,11 @@ Pipeline aborted, command did not run
 -- Test chunked results with an aborted pipeline.
 \startpipeline
 \set FETCH_COUNT 10
-SELECT $1 \bind \g
+SELECT $1 \bind \sendpipeline
 \flushrequest
 \getresults
 ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1
-SELECT $1 \bind \g
+SELECT $1 \bind \sendpipeline
 \endpipeline
 fetching results in chunked mode failed
 Pipeline aborted, command did not run
@@ -595,7 +618,7 @@ select 1;
 
 -- Error messages accumulate and are repeated.
 \startpipeline
-SELECT 1 \bind \g
+SELECT 1 \bind \sendpipeline
 SELECT 1;
 PQsendQuery not allowed in pipeline mode
 SELECT 1;
@@ -616,12 +639,12 @@ PQsendQuery not allowed in pipeline mode
 -- commit the implicit transaction block. The first command after a
 -- sync will not be seen as belonging to a pipeline.
 \startpipeline
-SET LOCAL statement_timeout='1h' \bind \g
-SHOW statement_timeout \bind \g
+SET LOCAL statement_timeout='1h' \bind \sendpipeline
+SHOW statement_timeout \bind \sendpipeline
 \syncpipeline
-SHOW statement_timeout \bind \g
-SET LOCAL statement_timeout='2h' \bind \g
-SHOW statement_timeout \bind \g
+SHOW statement_timeout \bind \sendpipeline
+SET LOCAL statement_timeout='2h' \bind \sendpipeline
+SHOW statement_timeout \bind \sendpipeline
 \endpipeline
 WARNING:  SET LOCAL can only be used in transaction blocks
  statement_timeout 
@@ -641,9 +664,9 @@ WARNING:  SET LOCAL can only be used in transaction blocks
 
 -- REINDEX CONCURRENTLY fails if not the first command in a pipeline.
 \startpipeline
-SELECT $1 \bind 1 \g
-REINDEX TABLE CONCURRENTLY psql_pipeline \bind \g
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 1 \sendpipeline
+REINDEX TABLE CONCURRENTLY psql_pipeline \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -653,8 +676,8 @@ SELECT $1 \bind 2 \g
 ERROR:  REINDEX CONCURRENTLY cannot run inside a transaction block
 -- REINDEX CONCURRENTLY works if it is the first command in a pipeline.
 \startpipeline
-REINDEX TABLE CONCURRENTLY psql_pipeline \bind \g
-SELECT $1 \bind 2 \g
+REINDEX TABLE CONCURRENTLY psql_pipeline \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -663,25 +686,25 @@ SELECT $1 \bind 2 \g
 
 -- Subtransactions are not allowed in a pipeline.
 \startpipeline
-SAVEPOINT a \bind \g
-SELECT $1 \bind 1 \g
-ROLLBACK TO SAVEPOINT a \bind \g
-SELECT $1 \bind 2 \g
+SAVEPOINT a \bind \sendpipeline
+SELECT $1 \bind 1 \sendpipeline
+ROLLBACK TO SAVEPOINT a \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
 ERROR:  SAVEPOINT can only be used in transaction blocks
 -- LOCK fails as the first command in a pipeline, as not seen in an
 -- implicit transaction block.
 \startpipeline
-LOCK psql_pipeline \bind \g
-SELECT $1 \bind 2 \g
+LOCK psql_pipeline \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
 ERROR:  LOCK TABLE can only be used in transaction blocks
 -- LOCK succeeds as it is not the first command in a pipeline,
 -- seen in an implicit transaction block.
 \startpipeline
-SELECT $1 \bind 1 \g
-LOCK psql_pipeline \bind \g
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 1 \sendpipeline
+LOCK psql_pipeline \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -695,12 +718,12 @@ SELECT $1 \bind 2 \g
 
 -- VACUUM works as the first command in a pipeline.
 \startpipeline
-VACUUM psql_pipeline \bind \g
+VACUUM psql_pipeline \bind \sendpipeline
 \endpipeline
 -- VACUUM fails when not the first command in a pipeline.
 \startpipeline
-SELECT 1 \bind \g
-VACUUM psql_pipeline \bind \g
+SELECT 1 \bind \sendpipeline
+VACUUM psql_pipeline \bind \sendpipeline
 \endpipeline
  ?column? 
 ----------
@@ -710,9 +733,9 @@ VACUUM psql_pipeline \bind \g
 ERROR:  VACUUM cannot run inside a transaction block
 -- VACUUM works after a \syncpipeline.
 \startpipeline
-SELECT 1 \bind \g
+SELECT 1 \bind \sendpipeline
 \syncpipeline
-VACUUM psql_pipeline \bind \g
+VACUUM psql_pipeline \bind \sendpipeline
 \endpipeline
  ?column? 
 ----------
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 97d1be3aac3..b6ba947b812 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1079,6 +1079,7 @@ select \if false \\ (bogus \else \\ 42 \endif \\ forty_two;
 	\sf whole_line
 	\sv whole_line
 	\startpipeline
+	\sendpipeline
 	\syncpipeline
 	\t arg1
 	\T arg1
diff --git a/src/test/regress/sql/psql_pipeline.sql b/src/test/regress/sql/psql_pipeline.sql
index 6517ebb71f8..256081dfd5f 100644
--- a/src/test/regress/sql/psql_pipeline.sql
+++ b/src/test/regress/sql/psql_pipeline.sql
@@ -6,23 +6,23 @@ CREATE TABLE psql_pipeline(a INTEGER PRIMARY KEY, s TEXT);
 
 -- Single query
 \startpipeline
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
 
 -- Multiple queries
 \startpipeline
-SELECT $1 \bind 'val1' \g
-SELECT $1, $2 \bind 'val2' 'val3' \g
-SELECT $1, $2 \bind 'val2' 'val3' \g
+SELECT $1 \bind 'val1' \sendpipeline
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
 \endpipeline
 
 -- Test \flush
 \startpipeline
 \flush
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \flush
-SELECT $1, $2 \bind 'val2' 'val3' \g
-SELECT $1, $2 \bind 'val2' 'val3' \g
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
 \endpipeline
 
 -- Send multiple syncs
@@ -30,12 +30,12 @@ SELECT $1, $2 \bind 'val2' 'val3' \g
 \echo :PIPELINE_COMMAND_COUNT
 \echo :PIPELINE_SYNC_COUNT
 \echo :PIPELINE_RESULT_COUNT
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \syncpipeline
 \syncpipeline
-SELECT $1, $2 \bind 'val2' 'val3' \g
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
 \syncpipeline
-SELECT $1, $2 \bind 'val4' 'val5' \g
+SELECT $1, $2 \bind 'val4' 'val5' \sendpipeline
 \echo :PIPELINE_COMMAND_COUNT
 \echo :PIPELINE_SYNC_COUNT
 \echo :PIPELINE_RESULT_COUNT
@@ -44,39 +44,39 @@ SELECT $1, $2 \bind 'val4' 'val5' \g
 -- \startpipeline should not have any effect if already in a pipeline.
 \startpipeline
 \startpipeline
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
 
 -- Convert an implicit transaction block to an explicit transaction block.
 \startpipeline
-INSERT INTO psql_pipeline VALUES ($1) \bind 1 \g
-BEGIN \bind \g
-INSERT INTO psql_pipeline VALUES ($1) \bind 2 \g
-ROLLBACK \bind \g
+INSERT INTO psql_pipeline VALUES ($1) \bind 1 \sendpipeline
+BEGIN \bind \sendpipeline
+INSERT INTO psql_pipeline VALUES ($1) \bind 2 \sendpipeline
+ROLLBACK \bind \sendpipeline
 \endpipeline
 
 -- Multiple explicit transactions
 \startpipeline
-BEGIN \bind \g
-INSERT INTO psql_pipeline VALUES ($1) \bind 1 \g
-ROLLBACK \bind \g
-BEGIN \bind \g
-INSERT INTO psql_pipeline VALUES ($1) \bind 1 \g
-COMMIT \bind \g
+BEGIN \bind \sendpipeline
+INSERT INTO psql_pipeline VALUES ($1) \bind 1 \sendpipeline
+ROLLBACK \bind \sendpipeline
+BEGIN \bind \sendpipeline
+INSERT INTO psql_pipeline VALUES ($1) \bind 1 \sendpipeline
+COMMIT \bind \sendpipeline
 \endpipeline
 
 -- COPY FROM STDIN
 \startpipeline
-SELECT $1 \bind 'val1' \g
-COPY psql_pipeline FROM STDIN \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+COPY psql_pipeline FROM STDIN \bind \sendpipeline
 \endpipeline
 2	test2
 \.
 
 -- COPY FROM STDIN with \flushrequest + \getresults
 \startpipeline
-SELECT $1 \bind 'val1' \g
-COPY psql_pipeline FROM STDIN \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+COPY psql_pipeline FROM STDIN \bind \sendpipeline
 \flushrequest
 \getresults
 3	test3
@@ -85,8 +85,8 @@ COPY psql_pipeline FROM STDIN \bind \g
 
 -- COPY FROM STDIN with \syncpipeline + \getresults
 \startpipeline
-SELECT $1 \bind 'val1' \g
-COPY psql_pipeline FROM STDIN \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+COPY psql_pipeline FROM STDIN \bind \sendpipeline
 \syncpipeline
 \getresults
 4	test4
@@ -95,22 +95,22 @@ COPY psql_pipeline FROM STDIN \bind \g
 
 -- COPY TO STDOUT
 \startpipeline
-SELECT $1 \bind 'val1' \g
-copy psql_pipeline TO STDOUT \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+copy psql_pipeline TO STDOUT \bind \sendpipeline
 \endpipeline
 
 -- COPY TO STDOUT with \flushrequest + \getresults
 \startpipeline
-SELECT $1 \bind 'val1' \g
-copy psql_pipeline TO STDOUT \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+copy psql_pipeline TO STDOUT \bind \sendpipeline
 \flushrequest
 \getresults
 \endpipeline
 
 -- COPY TO STDOUT with \syncpipeline + \getresults
 \startpipeline
-SELECT $1 \bind 'val1' \g
-copy psql_pipeline TO STDOUT \bind \g
+SELECT $1 \bind 'val1' \sendpipeline
+copy psql_pipeline TO STDOUT \bind \sendpipeline
 \syncpipeline
 \getresults
 \endpipeline
@@ -120,22 +120,22 @@ copy psql_pipeline TO STDOUT \bind \g
 SELECT $1 \parse ''
 SELECT $1, $2 \parse ''
 SELECT $2 \parse pipeline_1
-\bind_named '' 1 2 \g
-\bind_named pipeline_1 2 \g
+\bind_named '' 1 2 \sendpipeline
+\bind_named pipeline_1 2 \sendpipeline
 \endpipeline
 
 -- \getresults displays all results preceding a \flushrequest.
 \startpipeline
-SELECT $1 \bind 1 \g
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 1 \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \flushrequest
 \getresults
 \endpipeline
 
 -- \getresults displays all results preceding a \syncpipeline.
 \startpipeline
-SELECT $1 \bind 1 \g
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 1 \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \syncpipeline
 \getresults
 \endpipeline
@@ -143,7 +143,7 @@ SELECT $1 \bind 2 \g
 -- \getresults immediately returns if there is no result to fetch.
 \startpipeline
 \getresults
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \getresults
 \flushrequest
 \endpipeline
@@ -151,42 +151,42 @@ SELECT $1 \bind 2 \g
 
 -- \getresults only fetches results preceding a \flushrequest.
 \startpipeline
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \flushrequest
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \getresults
 \endpipeline
 
 -- \getresults only fetches results preceding a \syncpipeline.
 \startpipeline
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \syncpipeline
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \getresults
 \endpipeline
 
 -- Use pipeline with chunked results for both \getresults and \endpipeline.
 \startpipeline
 \set FETCH_COUNT 10
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \flushrequest
 \getresults
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
 \unset FETCH_COUNT
 
 -- \getresults with specific number of requested results.
 \startpipeline
-SELECT $1 \bind 1 \g
-SELECT $1 \bind 2 \g
-SELECT $1 \bind 3 \g
+SELECT $1 \bind 1 \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
+SELECT $1 \bind 3 \sendpipeline
 \echo :PIPELINE_SYNC_COUNT
 \syncpipeline
 \echo :PIPELINE_SYNC_COUNT
 \echo :PIPELINE_RESULT_COUNT
 \getresults 1
 \echo :PIPELINE_RESULT_COUNT
-SELECT $1 \bind 4 \g
+SELECT $1 \bind 4 \sendpipeline
 \getresults 3
 \echo :PIPELINE_RESULT_COUNT
 \endpipeline
@@ -195,7 +195,7 @@ SELECT $1 \bind 4 \g
 \startpipeline
 \syncpipeline
 \syncpipeline
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 \flushrequest
 \getresults 2
 \getresults 1
@@ -203,9 +203,9 @@ SELECT $1 \bind 1 \g
 
 -- \getresults 0 should get all the results.
 \startpipeline
-SELECT $1 \bind 1 \g
-SELECT $1 \bind 2 \g
-SELECT $1 \bind 3 \g
+SELECT $1 \bind 1 \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
+SELECT $1 \bind 3 \sendpipeline
 \syncpipeline
 \getresults 0
 \endpipeline
@@ -221,36 +221,36 @@ SELECT $1 \bind 3 \g
 -- pipeline usable.
 \startpipeline
 SELECT 1;
-SELECT $1 \bind 'val1' \g
+SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
 
 -- After an aborted pipeline, commands after a \syncpipeline should be
 -- displayed.
 \startpipeline
-SELECT $1 \bind \g
+SELECT $1 \bind \sendpipeline
 \syncpipeline
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 \endpipeline
 
 -- For an incorrect number of parameters, the pipeline is aborted and
 -- the following queries will not be executed.
 \startpipeline
-SELECT \bind 'val1' \g
-SELECT $1 \bind 'val1' \g
+SELECT \bind 'val1' \sendpipeline
+SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
 
 -- An explicit transaction with an error needs to be rollbacked after
 -- the pipeline.
 \startpipeline
-BEGIN \bind \g
-INSERT INTO psql_pipeline VALUES ($1) \bind 1 \g
-ROLLBACK \bind \g
+BEGIN \bind \sendpipeline
+INSERT INTO psql_pipeline VALUES ($1) \bind 1 \sendpipeline
+ROLLBACK \bind \sendpipeline
 \endpipeline
 ROLLBACK;
 
 -- \watch sends a simple query, something not allowed within a pipeline.
 \startpipeline
-SELECT \bind \g
+SELECT \bind \sendpipeline
 \watch 1
 \endpipeline
 
@@ -258,7 +258,7 @@ SELECT \bind \g
 -- and the pipeline should still be usable.
 \startpipeline
 SELECT $1 \bind 1 \gdesc
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 \endpipeline
 
 -- \gset is not allowed in a pipeline, pipeline should still be usable.
@@ -267,54 +267,72 @@ SELECT $1 as i, $2 as j \parse ''
 SELECT $1 as k, $2 as l \parse 'second'
 \bind_named '' 1 2 \gset
 \bind_named second 1 2 \gset pref02_ \echo :pref02_i :pref02_j
-\bind_named '' 1 2 \g
+\bind_named '' 1 2 \sendpipeline
 \endpipeline
 
--- \gx is not allowed, pipeline should still be usable.
+-- \g and \gx are not allowed, pipeline should still be usable.
 \startpipeline
+SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \g filename
+SELECT $1 \bind 1 \g |cat
+SELECT $1 \bind 1 \g (format=unaligned tuples_only=on)
 SELECT $1 \bind 1 \gx
+SELECT $1 \bind 1 \gx filename
+SELECT $1 \bind 1 \gx |cat
+SELECT $1 \bind 1 \gx (format=unaligned tuples_only=on)
 \reset
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 \endpipeline
 
 -- \gx warning should be emitted in an aborted pipeline, with
 -- pipeline still usable.
 \startpipeline
-SELECT $1 \bind \g
+SELECT $1 \bind \sendpipeline
 \flushrequest
 \getresults
 SELECT $1 \bind 1 \gx
 \endpipeline
 
+-- \sendpipeline is not allowed outside of a pipeline
+\sendpipeline
+SELECT $1 \bind 1 \sendpipeline
+\reset
+
+-- \sendpipeline is not allowed if not preceded by \bind or \bind_named
+\startpipeline
+\sendpipeline
+SELECT 1 \sendpipeline
+\endpipeline
+
 -- \gexec is not allowed, pipeline should still be usable.
 \startpipeline
 SELECT 'INSERT INTO psql_pipeline(a) SELECT generate_series(1, 10)' \parse 'insert_stmt'
 \bind_named insert_stmt \gexec
-\bind_named insert_stmt \g
+\bind_named insert_stmt \sendpipeline
 SELECT COUNT(*) FROM psql_pipeline \bind \g
 \endpipeline
 
 -- After an error, pipeline is aborted and requires \syncpipeline to be
 -- reusable.
 \startpipeline
-SELECT $1 \bind \g
-SELECT $1 \bind 1 \g
+SELECT $1 \bind \sendpipeline
+SELECT $1 \bind 1 \sendpipeline
 SELECT $1 \parse a
-\bind_named a 1 \g
+\bind_named a 1 \sendpipeline
 \close a
 \flushrequest
 \getresults
 -- Pipeline is aborted.
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 SELECT $1 \parse a
-\bind_named a 1 \g
+\bind_named a 1 \sendpipeline
 \close a
 -- Sync allows pipeline to recover.
 \syncpipeline
 \getresults
-SELECT $1 \bind 1 \g
+SELECT $1 \bind 1 \sendpipeline
 SELECT $1 \parse a
-\bind_named a 1 \g
+\bind_named a 1 \sendpipeline
 \close a
 \flushrequest
 \getresults
@@ -322,10 +340,10 @@ SELECT $1 \parse a
 
 -- In an aborted pipeline, \getresults 1 aborts commands one at a time.
 \startpipeline
-SELECT $1 \bind \g
-SELECT $1 \bind 1 \g
+SELECT $1 \bind \sendpipeline
+SELECT $1 \bind 1 \sendpipeline
 SELECT $1 \parse a
-\bind_named a 1 \g
+\bind_named a 1 \sendpipeline
 \syncpipeline
 \getresults 1
 \getresults 1
@@ -337,10 +355,10 @@ SELECT $1 \parse a
 -- Test chunked results with an aborted pipeline.
 \startpipeline
 \set FETCH_COUNT 10
-SELECT $1 \bind \g
+SELECT $1 \bind \sendpipeline
 \flushrequest
 \getresults
-SELECT $1 \bind \g
+SELECT $1 \bind \sendpipeline
 \endpipeline
 \unset FETCH_COUNT
 
@@ -356,7 +374,7 @@ select 1;
 
 -- Error messages accumulate and are repeated.
 \startpipeline
-SELECT 1 \bind \g
+SELECT 1 \bind \sendpipeline
 SELECT 1;
 SELECT 1;
 \endpipeline
@@ -371,66 +389,66 @@ SELECT 1;
 -- commit the implicit transaction block. The first command after a
 -- sync will not be seen as belonging to a pipeline.
 \startpipeline
-SET LOCAL statement_timeout='1h' \bind \g
-SHOW statement_timeout \bind \g
+SET LOCAL statement_timeout='1h' \bind \sendpipeline
+SHOW statement_timeout \bind \sendpipeline
 \syncpipeline
-SHOW statement_timeout \bind \g
-SET LOCAL statement_timeout='2h' \bind \g
-SHOW statement_timeout \bind \g
+SHOW statement_timeout \bind \sendpipeline
+SET LOCAL statement_timeout='2h' \bind \sendpipeline
+SHOW statement_timeout \bind \sendpipeline
 \endpipeline
 
 -- REINDEX CONCURRENTLY fails if not the first command in a pipeline.
 \startpipeline
-SELECT $1 \bind 1 \g
-REINDEX TABLE CONCURRENTLY psql_pipeline \bind \g
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 1 \sendpipeline
+REINDEX TABLE CONCURRENTLY psql_pipeline \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
 
 -- REINDEX CONCURRENTLY works if it is the first command in a pipeline.
 \startpipeline
-REINDEX TABLE CONCURRENTLY psql_pipeline \bind \g
-SELECT $1 \bind 2 \g
+REINDEX TABLE CONCURRENTLY psql_pipeline \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
 
 -- Subtransactions are not allowed in a pipeline.
 \startpipeline
-SAVEPOINT a \bind \g
-SELECT $1 \bind 1 \g
-ROLLBACK TO SAVEPOINT a \bind \g
-SELECT $1 \bind 2 \g
+SAVEPOINT a \bind \sendpipeline
+SELECT $1 \bind 1 \sendpipeline
+ROLLBACK TO SAVEPOINT a \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
 
 -- LOCK fails as the first command in a pipeline, as not seen in an
 -- implicit transaction block.
 \startpipeline
-LOCK psql_pipeline \bind \g
-SELECT $1 \bind 2 \g
+LOCK psql_pipeline \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
 
 -- LOCK succeeds as it is not the first command in a pipeline,
 -- seen in an implicit transaction block.
 \startpipeline
-SELECT $1 \bind 1 \g
-LOCK psql_pipeline \bind \g
-SELECT $1 \bind 2 \g
+SELECT $1 \bind 1 \sendpipeline
+LOCK psql_pipeline \bind \sendpipeline
+SELECT $1 \bind 2 \sendpipeline
 \endpipeline
 
 -- VACUUM works as the first command in a pipeline.
 \startpipeline
-VACUUM psql_pipeline \bind \g
+VACUUM psql_pipeline \bind \sendpipeline
 \endpipeline
 
 -- VACUUM fails when not the first command in a pipeline.
 \startpipeline
-SELECT 1 \bind \g
-VACUUM psql_pipeline \bind \g
+SELECT 1 \bind \sendpipeline
+VACUUM psql_pipeline \bind \sendpipeline
 \endpipeline
 
 -- VACUUM works after a \syncpipeline.
 \startpipeline
-SELECT 1 \bind \g
+SELECT 1 \bind \sendpipeline
 \syncpipeline
-VACUUM psql_pipeline \bind \g
+VACUUM psql_pipeline \bind \sendpipeline
 \endpipeline
 
 -- Clean up
-- 
2.39.5 (Apple Git-154)



  [application/octet-stream] v02-0002-psql-Allow-to-add-queries-in-an-ongoing-pipeline.patch (7.5K, ../../CAO6_Xqq4JEHRaGyC=r+dpi0Ejbp2P3aUo-KfR2NuipCvE-o_ZA@mail.gmail.com/3-v02-0002-psql-Allow-to-add-queries-in-an-ongoing-pipeline.patch)
  download | inline diff:
From 2ebe091c9fcc5cd35bbbc02ece90645539330ebd Mon Sep 17 00:00:00 2001
From: Anthonin Bonnefoy <[email protected]>
Date: Wed, 5 Mar 2025 14:55:33 +0100
Subject: psql: Allow ';' to add queries in an ongoing pipeline

Currently, the only way to pipe queries in an ongoing pipeline is to
leverage psql meta-commands to create extended queries such as \bind,
\parse or \bind_named. This prevents using psql's pipeline on existing
scripts as it would require to convert all queries to use those
meta-commands.

This patch modifies ';' behaviour within an active pipeline and send all
queries as extended queries, allowing them to be piped in a pipeline.
---
 doc/src/sgml/ref/psql-ref.sgml              | 21 +++++----
 src/bin/psql/command.c                      |  7 +++
 src/bin/psql/common.c                       | 10 +++-
 src/test/regress/expected/psql_pipeline.out | 52 +++++++++++++--------
 src/test/regress/sql/psql_pipeline.sql      | 24 ++++++----
 5 files changed, 77 insertions(+), 37 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index cddf6e07531..2763486e268 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3698,14 +3698,19 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
        </para>
 
        <para>
-        Pipeline mode requires the use of the extended query protocol. All
-        queries need to be sent using the meta-commands
-        <literal>\bind</literal>, <literal>\bind_named</literal>,
-        <literal>\close</literal> or <literal>\parse</literal>. While a
-        pipeline is ongoing, <literal>\sendpipeline</literal> will append the
-        current query buffer to the pipeline. Other meta-commands like
-        <literal>\g</literal>, <literal>\gx</literal> or <literal>\gdesc</literal>
-        are not allowed in pipeline mode.
+        Pipeline mode requires the use of the extended query protocol. Queries
+        can be sent using the meta-commands <literal>\bind</literal>,
+        <literal>\bind_named</literal>, <literal>\close</literal> or
+        <literal>\parse</literal>. While a pipeline is ongoing,
+        <literal>\sendpipeline</literal> will append the current query
+        buffer to the pipeline. Other meta-commands like <literal>\g</literal>,
+        <literal>\gx</literal> or <literal>\gdesc</literal> are not allowed
+        in pipeline mode.
+       </para>
+
+       <para>
+        Queries can also be sent using <literal>;</literal>. While a pipeline is
+        ongoing, they will automatically be sent using extended query protocol.
        </para>
 
        <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 7670c20b29e..3e7eb086367 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3282,6 +3282,13 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch,
 		int			iter = 0;
 		int			min_rows = 0;
 
+		if (PQpipelineStatus(pset.db) != PQ_PIPELINE_OFF)
+		{
+			pg_log_error("\\watch not allowed in pipeline mode");
+			clean_extended_state();
+			success = false;
+		}
+
 		/*
 		 * Parse arguments.  We allow either an unlabeled interval or
 		 * "name=value", where name is from the set ('i', 'interval', 'c',
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index ed340a466f9..5249336bcf2 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1668,7 +1668,15 @@ ExecQueryAndProcessResults(const char *query,
 			}
 			break;
 		case PSQL_SEND_QUERY:
-			success = PQsendQuery(pset.db, query);
+			if (PQpipelineStatus(pset.db) != PQ_PIPELINE_OFF)
+			{
+				success = PQsendQueryParams(pset.db, query,
+											0, NULL, NULL, NULL, NULL, 0);
+				if (success)
+					pset.piped_commands++;
+			}
+			else
+				success = PQsendQuery(pset.db, query);
 			break;
 	}
 
diff --git a/src/test/regress/expected/psql_pipeline.out b/src/test/regress/expected/psql_pipeline.out
index 53f2edfd986..f05429b36ac 100644
--- a/src/test/regress/expected/psql_pipeline.out
+++ b/src/test/regress/expected/psql_pipeline.out
@@ -387,24 +387,33 @@ SELECT $1 \bind 3 \sendpipeline
 (1 row)
 
 \endpipeline
+-- Queries can be pipelined ';'. They will be sent using extended protocol.
+\startpipeline
+SELECT 1;
+SELECT $1 \bind 'val1' \sendpipeline
+SELECT 2;
+\endpipeline
+ ?column? 
+----------
+        1
+(1 row)
+
+ ?column? 
+----------
+ val1
+(1 row)
+
+ ?column? 
+----------
+        2
+(1 row)
+
 --
 -- Pipeline errors
 --
 -- \endpipeline outside of pipeline should fail
 \endpipeline
 cannot send pipeline when not in pipeline mode
--- Query using simple protocol should not be sent and should leave the
--- pipeline usable.
-\startpipeline
-SELECT 1;
-PQsendQuery not allowed in pipeline mode
-SELECT $1 \bind 'val1' \sendpipeline
-\endpipeline
- ?column? 
-----------
- val1
-(1 row)
-
 -- After an aborted pipeline, commands after a \syncpipeline should be
 -- displayed.
 \startpipeline
@@ -425,6 +434,12 @@ SELECT \bind 'val1' \sendpipeline
 SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
 ERROR:  bind message supplies 1 parameters, but prepared statement "" requires 0
+-- Using ';' with a parameter will trigger an incorrect parameter errors.
+\startpipeline
+SELECT $1;
+SELECT 1;
+\endpipeline
+ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1
 -- An explicit transaction with an error needs to be rollbacked after
 -- the pipeline.
 \startpipeline
@@ -439,8 +454,7 @@ ROLLBACK;
 \startpipeline
 SELECT \bind \sendpipeline
 \watch 1
-PQsendQuery not allowed in pipeline mode
-
+\watch not allowed in pipeline mode
 \endpipeline
 --
 (1 row)
@@ -619,11 +633,11 @@ select 1;
 -- Error messages accumulate and are repeated.
 \startpipeline
 SELECT 1 \bind \sendpipeline
-SELECT 1;
-PQsendQuery not allowed in pipeline mode
-SELECT 1;
-PQsendQuery not allowed in pipeline mode
-PQsendQuery not allowed in pipeline mode
+\gdesc
+synchronous command execution functions are not allowed in pipeline mode
+\gdesc
+synchronous command execution functions are not allowed in pipeline mode
+synchronous command execution functions are not allowed in pipeline mode
 \endpipeline
  ?column? 
 ----------
diff --git a/src/test/regress/sql/psql_pipeline.sql b/src/test/regress/sql/psql_pipeline.sql
index 256081dfd5f..76c0ece01af 100644
--- a/src/test/regress/sql/psql_pipeline.sql
+++ b/src/test/regress/sql/psql_pipeline.sql
@@ -210,6 +210,13 @@ SELECT $1 \bind 3 \sendpipeline
 \getresults 0
 \endpipeline
 
+-- Queries can be pipelined ';'. They will be sent using extended protocol.
+\startpipeline
+SELECT 1;
+SELECT $1 \bind 'val1' \sendpipeline
+SELECT 2;
+\endpipeline
+
 --
 -- Pipeline errors
 --
@@ -217,13 +224,6 @@ SELECT $1 \bind 3 \sendpipeline
 -- \endpipeline outside of pipeline should fail
 \endpipeline
 
--- Query using simple protocol should not be sent and should leave the
--- pipeline usable.
-\startpipeline
-SELECT 1;
-SELECT $1 \bind 'val1' \sendpipeline
-\endpipeline
-
 -- After an aborted pipeline, commands after a \syncpipeline should be
 -- displayed.
 \startpipeline
@@ -239,6 +239,12 @@ SELECT \bind 'val1' \sendpipeline
 SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
 
+-- Using ';' with a parameter will trigger an incorrect parameter errors.
+\startpipeline
+SELECT $1;
+SELECT 1;
+\endpipeline
+
 -- An explicit transaction with an error needs to be rollbacked after
 -- the pipeline.
 \startpipeline
@@ -375,8 +381,8 @@ select 1;
 -- Error messages accumulate and are repeated.
 \startpipeline
 SELECT 1 \bind \sendpipeline
-SELECT 1;
-SELECT 1;
+\gdesc
+\gdesc
 \endpipeline
 
 --
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-17 10:19  Michael Paquier <[email protected]>
  parent: Anthonin Bonnefoy <[email protected]>
  2 siblings, 0 replies; 25+ messages in thread

From: Michael Paquier @ 2025-03-17 10:19 UTC (permalink / raw)
  To: Anthonin Bonnefoy <[email protected]>; +Cc: Daniel Verite <[email protected]>; Jelte Fennema-Nio <[email protected]>; pgsql-hackers

On Mon, Mar 17, 2025 at 10:50:50AM +0100, Anthonin Bonnefoy wrote:
> 0001: This introduces the \sendpipeline meta-command and forbid \g in
> a pipeline. This is to fix the formatting options of \g that are not
> supported in a pipeline.
> 
> 0002: Allows ';' to send a query using extended protocol when within a
> pipeline by using PQsendQueryParams with 0 parameters. It is not
> possible to send parameters with extended protocol this way and
> everything will be propagated through the query string, similar to a
> simple query.

Thanks for sending a new patch set.  I was planning to look at the
situation tomorrow, and you have beaten me to it.

The split makes sense, and I'm OK with 0001.  0002 is going to require
a much closer lookup.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-18 00:50  Michael Paquier <[email protected]>
  parent: Anthonin Bonnefoy <[email protected]>
  2 siblings, 1 reply; 25+ messages in thread

From: Michael Paquier @ 2025-03-18 00:50 UTC (permalink / raw)
  To: Anthonin Bonnefoy <[email protected]>; +Cc: Daniel Verite <[email protected]>; Jelte Fennema-Nio <[email protected]>; pgsql-hackers

On Mon, Mar 17, 2025 at 10:50:50AM +0100, Anthonin Bonnefoy wrote:
> 0001: This introduces the \sendpipeline meta-command and forbid \g in
> a pipeline. This is to fix the formatting options of \g that are not
> supported in a pipeline.

- count 
--------
-     4
-(1 row)

This removal done in the regression tests was not intentional.

I have done some reordering of the code around the new meta-command so
as things are ordered alphabetically, and applied the result.

> 0002: Allows ';' to send a query using extended protocol when within a
> pipeline by using PQsendQueryParams with 0 parameters. It is not
> possible to send parameters with extended protocol this way and
> everything will be propagated through the query string, similar to a
> simple query.

I like the simplicity of what you are doing here, relying on
PSQL_SEND_QUERY being the default so as we use PQsendQueryParams()
with no parameters rather than PQsendQuery() when the pipeline mode is
not off.

How about adding a check on PIPELINE_COMMAND_COUNT when sending a
query through this path?  Should we check for more scenarios with
syncs and flushes as well when sending these queries?
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-18 08:55  Anthonin Bonnefoy <[email protected]>
  parent: Michael Paquier <[email protected]>
  0 siblings, 2 replies; 25+ messages in thread

From: Anthonin Bonnefoy @ 2025-03-18 08:55 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Daniel Verite <[email protected]>; Jelte Fennema-Nio <[email protected]>; pgsql-hackers

On Tue, Mar 18, 2025 at 1:50 AM Michael Paquier <[email protected]> wrote:
> - count
> --------
> -     4
> -(1 row)
>
> This removal done in the regression tests was not intentional.

Yes, thanks for fixing that.

> How about adding a check on PIPELINE_COMMAND_COUNT when sending a
> query through this path?  Should we check for more scenarios with
> syncs and flushes as well when sending these queries?

I've added additional tests when piping queries with ';':
- I've reused the same scenario with \sendpipeline: single query,
multiple queries, flushes, syncs, using COPY...
- Using ';' will replace the unnamed prepared statement. It's a bit
different from expected as a simple query will delete the unnamed
prepared statement.
- Sending an extended query prepared with \bind using a ';' on a
newline, though this is not specific to pipelining. The scanned
semicolon triggers the call to SendQuery, processing the buffered
extended query. It's a bit unusual but that's the current behaviour.


Attachments:

  [application/octet-stream] v03-0001-psql-Allow-to-add-queries-in-an-ongoing-pipeline.patch (13.5K, ../../CAO6_Xqo=fNh0KCRZG7T18OZfjiabeL-t4C6mkXsz-3dAZ1YwTA@mail.gmail.com/2-v03-0001-psql-Allow-to-add-queries-in-an-ongoing-pipeline.patch)
  download | inline diff:
From ae9ce7e44c1e2502429920c5d305e1ffcd30b1a5 Mon Sep 17 00:00:00 2001
From: Anthonin Bonnefoy <[email protected]>
Date: Wed, 5 Mar 2025 14:55:33 +0100
Subject: psql: Allow ';' to add queries in an ongoing pipeline

Currently, the only way to pipe queries in an ongoing pipeline is to
leverage psql meta-commands to create extended queries such as \bind,
\parse or \bind_named. This prevents using psql's pipeline on existing
scripts as it would require to convert all queries to use those
meta-commands.

This patch modifies ';' behaviour within an active pipeline and send all
queries as extended queries, allowing them to be piped in a pipeline.
---
 doc/src/sgml/ref/psql-ref.sgml              |  21 +-
 src/bin/psql/command.c                      |   7 +
 src/bin/psql/common.c                       |  10 +-
 src/test/regress/expected/psql_pipeline.out | 292 ++++++++++++++++++--
 src/test/regress/sql/psql_pipeline.sql      | 139 +++++++++-
 5 files changed, 429 insertions(+), 40 deletions(-)

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index cddf6e07531..2763486e268 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3698,14 +3698,19 @@ testdb=&gt; <userinput>\setenv LESS -imx4F</userinput>
        </para>
 
        <para>
-        Pipeline mode requires the use of the extended query protocol. All
-        queries need to be sent using the meta-commands
-        <literal>\bind</literal>, <literal>\bind_named</literal>,
-        <literal>\close</literal> or <literal>\parse</literal>. While a
-        pipeline is ongoing, <literal>\sendpipeline</literal> will append the
-        current query buffer to the pipeline. Other meta-commands like
-        <literal>\g</literal>, <literal>\gx</literal> or <literal>\gdesc</literal>
-        are not allowed in pipeline mode.
+        Pipeline mode requires the use of the extended query protocol. Queries
+        can be sent using the meta-commands <literal>\bind</literal>,
+        <literal>\bind_named</literal>, <literal>\close</literal> or
+        <literal>\parse</literal>. While a pipeline is ongoing,
+        <literal>\sendpipeline</literal> will append the current query
+        buffer to the pipeline. Other meta-commands like <literal>\g</literal>,
+        <literal>\gx</literal> or <literal>\gdesc</literal> are not allowed
+        in pipeline mode.
+       </para>
+
+       <para>
+        Queries can also be sent using <literal>;</literal>. While a pipeline is
+        ongoing, they will automatically be sent using extended query protocol.
        </para>
 
        <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index a87ff7e4597..bbe337780ff 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3282,6 +3282,13 @@ exec_command_watch(PsqlScanState scan_state, bool active_branch,
 		int			iter = 0;
 		int			min_rows = 0;
 
+		if (PQpipelineStatus(pset.db) != PQ_PIPELINE_OFF)
+		{
+			pg_log_error("\\watch not allowed in pipeline mode");
+			clean_extended_state();
+			success = false;
+		}
+
 		/*
 		 * Parse arguments.  We allow either an unlabeled interval or
 		 * "name=value", where name is from the set ('i', 'interval', 'c',
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index ed340a466f9..5249336bcf2 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1668,7 +1668,15 @@ ExecQueryAndProcessResults(const char *query,
 			}
 			break;
 		case PSQL_SEND_QUERY:
-			success = PQsendQuery(pset.db, query);
+			if (PQpipelineStatus(pset.db) != PQ_PIPELINE_OFF)
+			{
+				success = PQsendQueryParams(pset.db, query,
+											0, NULL, NULL, NULL, NULL, 0);
+				if (success)
+					pset.piped_commands++;
+			}
+			else
+				success = PQsendQuery(pset.db, query);
 			break;
 	}
 
diff --git a/src/test/regress/expected/psql_pipeline.out b/src/test/regress/expected/psql_pipeline.out
index 68e3c19ea05..7dddf26a9fd 100644
--- a/src/test/regress/expected/psql_pipeline.out
+++ b/src/test/regress/expected/psql_pipeline.out
@@ -386,6 +386,262 @@ SELECT $1 \bind 3 \sendpipeline
  3
 (1 row)
 
+\endpipeline
+--
+-- Tests pipelining queries with ';'
+--
+-- Single query sent with ';'
+\startpipeline
+SELECT 1;
+\endpipeline
+ ?column? 
+----------
+        1
+(1 row)
+
+-- Multiple queries sent with ';'
+\startpipeline
+SELECT 1;
+SELECT 2;
+SELECT 3;
+\endpipeline
+ ?column? 
+----------
+        1
+(1 row)
+
+ ?column? 
+----------
+        2
+(1 row)
+
+ ?column? 
+----------
+        3
+(1 row)
+
+-- Multiple queries on the same line can be piped with ';'
+\startpipeline
+SELECT 1; SELECT 2; SELECT 3
+;
+\endpipeline
+ ?column? 
+----------
+        1
+(1 row)
+
+ ?column? 
+----------
+        2
+(1 row)
+
+ ?column? 
+----------
+        3
+(1 row)
+
+-- Test \flush with queries piped with ';'
+\startpipeline
+\flush
+SELECT 1;
+\flush
+SELECT 2;
+SELECT 3;
+\endpipeline
+ ?column? 
+----------
+        1
+(1 row)
+
+ ?column? 
+----------
+        2
+(1 row)
+
+ ?column? 
+----------
+        3
+(1 row)
+
+-- Send multiple syncs with queries piped with ';'
+\startpipeline
+\echo :PIPELINE_COMMAND_COUNT
+0
+\echo :PIPELINE_SYNC_COUNT
+0
+\echo :PIPELINE_RESULT_COUNT
+0
+SELECT 1;
+\syncpipeline
+\syncpipeline
+SELECT 2;
+\syncpipeline
+SELECT 3;
+\echo :PIPELINE_COMMAND_COUNT
+1
+\echo :PIPELINE_SYNC_COUNT
+3
+\echo :PIPELINE_RESULT_COUNT
+2
+\endpipeline
+ ?column? 
+----------
+        1
+(1 row)
+
+ ?column? 
+----------
+        2
+(1 row)
+
+ ?column? 
+----------
+        3
+(1 row)
+
+-- Mix queries piped with ';' and \sendpipeline
+\startpipeline
+SELECT 1;
+SELECT $1 \bind 'val1' \sendpipeline
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
+SELECT 2;
+\endpipeline
+ ?column? 
+----------
+        1
+(1 row)
+
+ ?column? 
+----------
+ val1
+(1 row)
+
+ ?column? | ?column? 
+----------+----------
+ val2     | val3
+(1 row)
+
+ ?column? 
+----------
+        2
+(1 row)
+
+-- Piping a query with ';' will replace the unnamed prepared statement
+\startpipeline
+SELECT $1 \parse ''
+SELECT 1;
+\bind_named ''
+\endpipeline
+ ?column? 
+----------
+        1
+(1 row)
+
+-- An extended query can be piped by a ';' after a newline
+\startpipeline
+SELECT $1 \bind 1
+;
+SELECT 2;
+\endpipeline
+ ?column? 
+----------
+ 1
+(1 row)
+
+ ?column? 
+----------
+        2
+(1 row)
+
+-- COPY FROM STDIN, using ';'
+\startpipeline
+SELECT 'val1';
+COPY psql_pipeline FROM STDIN;
+\endpipeline
+ ?column? 
+----------
+ val1
+(1 row)
+
+-- COPY FROM STDIN with \flushrequest + \getresults, using ';'
+\startpipeline
+SELECT 'val1';
+COPY psql_pipeline FROM STDIN;
+\flushrequest
+\getresults
+ ?column? 
+----------
+ val1
+(1 row)
+
+message type 0x5a arrived from server while idle
+\endpipeline
+-- COPY FROM STDIN with \syncpipeline + \getresults, using ';'
+\startpipeline
+SELECT 'val1';
+COPY psql_pipeline FROM STDIN;
+\syncpipeline
+\getresults
+ ?column? 
+----------
+ val1
+(1 row)
+
+\endpipeline
+-- COPY TO STDOUT, using ';'
+\startpipeline
+SELECT 'val1';
+copy psql_pipeline TO STDOUT;
+\endpipeline
+ ?column? 
+----------
+ val1
+(1 row)
+
+1	\N
+2	test2
+3	test3
+4	test4
+20	test2
+30	test3
+40	test4
+-- COPY TO STDOUT with \flushrequest + \getresults, using ';'
+\startpipeline
+SELECT 'val1';
+copy psql_pipeline TO STDOUT;
+\flushrequest
+\getresults
+ ?column? 
+----------
+ val1
+(1 row)
+
+1	\N
+2	test2
+3	test3
+4	test4
+20	test2
+30	test3
+40	test4
+\endpipeline
+-- COPY TO STDOUT with \syncpipeline + \getresults, using ';'
+\startpipeline
+SELECT 'val1';
+copy psql_pipeline TO STDOUT;
+\syncpipeline
+\getresults
+ ?column? 
+----------
+ val1
+(1 row)
+
+1	\N
+2	test2
+3	test3
+4	test4
+20	test2
+30	test3
+40	test4
 \endpipeline
 --
 -- Pipeline errors
@@ -393,18 +649,6 @@ SELECT $1 \bind 3 \sendpipeline
 -- \endpipeline outside of pipeline should fail
 \endpipeline
 cannot send pipeline when not in pipeline mode
--- Query using simple protocol should not be sent and should leave the
--- pipeline usable.
-\startpipeline
-SELECT 1;
-PQsendQuery not allowed in pipeline mode
-SELECT $1 \bind 'val1' \sendpipeline
-\endpipeline
- ?column? 
-----------
- val1
-(1 row)
-
 -- After an aborted pipeline, commands after a \syncpipeline should be
 -- displayed.
 \startpipeline
@@ -425,6 +669,13 @@ SELECT \bind 'val1' \sendpipeline
 SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
 ERROR:  bind message supplies 1 parameters, but prepared statement "" requires 0
+-- Using ';' with a parameter will trigger an incorrect parameter error and
+-- abort the pipeline
+\startpipeline
+SELECT $1;
+SELECT 1;
+\endpipeline
+ERROR:  bind message supplies 0 parameters, but prepared statement "" requires 1
 -- An explicit transaction with an error needs to be rollbacked after
 -- the pipeline.
 \startpipeline
@@ -435,12 +686,11 @@ ROLLBACK \bind \sendpipeline
 ERROR:  duplicate key value violates unique constraint "psql_pipeline_pkey"
 DETAIL:  Key (a)=(1) already exists.
 ROLLBACK;
--- \watch sends a simple query, something not allowed within a pipeline.
+-- \watch is not allowed within a pipeline.
 \startpipeline
 SELECT \bind \sendpipeline
 \watch 1
-PQsendQuery not allowed in pipeline mode
-
+\watch not allowed in pipeline mode
 \endpipeline
 --
 (1 row)
@@ -530,7 +780,7 @@ SELECT COUNT(*) FROM psql_pipeline \bind \sendpipeline
 
  count 
 -------
-     4
+     7
 (1 row)
 
 -- After an error, pipeline is aborted and requires \syncpipeline to be
@@ -617,11 +867,11 @@ select 1;
 -- Error messages accumulate and are repeated.
 \startpipeline
 SELECT 1 \bind \sendpipeline
-SELECT 1;
-PQsendQuery not allowed in pipeline mode
-SELECT 1;
-PQsendQuery not allowed in pipeline mode
-PQsendQuery not allowed in pipeline mode
+\gdesc
+synchronous command execution functions are not allowed in pipeline mode
+\gdesc
+synchronous command execution functions are not allowed in pipeline mode
+synchronous command execution functions are not allowed in pipeline mode
 \endpipeline
  ?column? 
 ----------
diff --git a/src/test/regress/sql/psql_pipeline.sql b/src/test/regress/sql/psql_pipeline.sql
index e4d7e614af3..6f76adcba82 100644
--- a/src/test/regress/sql/psql_pipeline.sql
+++ b/src/test/regress/sql/psql_pipeline.sql
@@ -210,6 +210,125 @@ SELECT $1 \bind 3 \sendpipeline
 \getresults 0
 \endpipeline
 
+--
+-- Tests pipelining queries with ';'
+--
+
+-- Single query sent with ';'
+\startpipeline
+SELECT 1;
+\endpipeline
+
+-- Multiple queries sent with ';'
+\startpipeline
+SELECT 1;
+SELECT 2;
+SELECT 3;
+\endpipeline
+
+-- Multiple queries on the same line can be piped with ';'
+\startpipeline
+SELECT 1; SELECT 2; SELECT 3
+;
+\endpipeline
+
+-- Test \flush with queries piped with ';'
+\startpipeline
+\flush
+SELECT 1;
+\flush
+SELECT 2;
+SELECT 3;
+\endpipeline
+
+-- Send multiple syncs with queries piped with ';'
+\startpipeline
+\echo :PIPELINE_COMMAND_COUNT
+\echo :PIPELINE_SYNC_COUNT
+\echo :PIPELINE_RESULT_COUNT
+SELECT 1;
+\syncpipeline
+\syncpipeline
+SELECT 2;
+\syncpipeline
+SELECT 3;
+\echo :PIPELINE_COMMAND_COUNT
+\echo :PIPELINE_SYNC_COUNT
+\echo :PIPELINE_RESULT_COUNT
+\endpipeline
+
+-- Mix queries piped with ';' and \sendpipeline
+\startpipeline
+SELECT 1;
+SELECT $1 \bind 'val1' \sendpipeline
+SELECT $1, $2 \bind 'val2' 'val3' \sendpipeline
+SELECT 2;
+\endpipeline
+
+-- Piping a query with ';' will replace the unnamed prepared statement
+\startpipeline
+SELECT $1 \parse ''
+SELECT 1;
+\bind_named ''
+\endpipeline
+
+-- An extended query can be piped by a ';' after a newline
+\startpipeline
+SELECT $1 \bind 1
+;
+SELECT 2;
+\endpipeline
+
+-- COPY FROM STDIN, using ';'
+\startpipeline
+SELECT 'val1';
+COPY psql_pipeline FROM STDIN;
+\endpipeline
+20	test2
+\.
+
+-- COPY FROM STDIN with \flushrequest + \getresults, using ';'
+\startpipeline
+SELECT 'val1';
+COPY psql_pipeline FROM STDIN;
+\flushrequest
+\getresults
+30	test3
+\.
+\endpipeline
+
+-- COPY FROM STDIN with \syncpipeline + \getresults, using ';'
+\startpipeline
+SELECT 'val1';
+COPY psql_pipeline FROM STDIN;
+\syncpipeline
+\getresults
+40	test4
+\.
+\endpipeline
+
+-- COPY TO STDOUT, using ';'
+\startpipeline
+SELECT 'val1';
+copy psql_pipeline TO STDOUT;
+\endpipeline
+
+-- COPY TO STDOUT with \flushrequest + \getresults, using ';'
+\startpipeline
+SELECT 'val1';
+copy psql_pipeline TO STDOUT;
+\flushrequest
+\getresults
+\endpipeline
+
+-- COPY TO STDOUT with \syncpipeline + \getresults, using ';'
+\startpipeline
+SELECT 'val1';
+copy psql_pipeline TO STDOUT;
+\syncpipeline
+\getresults
+\endpipeline
+
 --
 -- Pipeline errors
 --
@@ -217,13 +336,6 @@ SELECT $1 \bind 3 \sendpipeline
 -- \endpipeline outside of pipeline should fail
 \endpipeline
 
--- Query using simple protocol should not be sent and should leave the
--- pipeline usable.
-\startpipeline
-SELECT 1;
-SELECT $1 \bind 'val1' \sendpipeline
-\endpipeline
-
 -- After an aborted pipeline, commands after a \syncpipeline should be
 -- displayed.
 \startpipeline
@@ -239,6 +351,13 @@ SELECT \bind 'val1' \sendpipeline
 SELECT $1 \bind 'val1' \sendpipeline
 \endpipeline
 
+-- Using ';' with a parameter will trigger an incorrect parameter error and
+-- abort the pipeline
+\startpipeline
+SELECT $1;
+SELECT 1;
+\endpipeline
+
 -- An explicit transaction with an error needs to be rollbacked after
 -- the pipeline.
 \startpipeline
@@ -248,7 +367,7 @@ ROLLBACK \bind \sendpipeline
 \endpipeline
 ROLLBACK;
 
--- \watch sends a simple query, something not allowed within a pipeline.
+-- \watch is not allowed within a pipeline.
 \startpipeline
 SELECT \bind \sendpipeline
 \watch 1
@@ -372,8 +491,8 @@ select 1;
 -- Error messages accumulate and are repeated.
 \startpipeline
 SELECT 1 \bind \sendpipeline
-SELECT 1;
-SELECT 1;
+\gdesc
+\gdesc
 \endpipeline
 
 --
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-18 09:27  Jelte Fennema-Nio <[email protected]>
  parent: Anthonin Bonnefoy <[email protected]>
  1 sibling, 1 reply; 25+ messages in thread

From: Jelte Fennema-Nio @ 2025-03-18 09:27 UTC (permalink / raw)
  To: Anthonin Bonnefoy <[email protected]>; +Cc: Michael Paquier <[email protected]>; Daniel Verite <[email protected]>; pgsql-hackers

On Tue, 18 Mar 2025 at 09:55, Anthonin Bonnefoy
<[email protected]> wrote:
> I've added additional tests when piping queries with ';':
> - I've reused the same scenario with \sendpipeline: single query,
> multiple queries, flushes, syncs, using COPY...
> - Using ';' will replace the unnamed prepared statement. It's a bit
> different from expected as a simple query will delete the unnamed
> prepared statement.
> - Sending an extended query prepared with \bind using a ';' on a
> newline, though this is not specific to pipelining. The scanned
> semicolon triggers the call to SendQuery, processing the buffered
> extended query. It's a bit unusual but that's the current behaviour.

One thing that comes to mind that I think would be quite useful and
pretty easy to implement if we have this functionality within a
pipeline: An \extended command. That puts psql in "extended protocol
mode" (without enabling pipelining). In "extended protocol mode" all
queries would automatically be sent using PQsendQueryParams. That
would remove the need to use \bind anymore outside of a pipeline
either.





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-18 09:36  Daniel Verite <[email protected]>
  parent: Anthonin Bonnefoy <[email protected]>
  2 siblings, 1 reply; 25+ messages in thread

From: Daniel Verite @ 2025-03-18 09:36 UTC (permalink / raw)
  To: Anthonin Bonnefoy <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Michael Paquier <[email protected]>; pgsql-hackers

	Anthonin Bonnefoy wrote:

> 0002: Allows ';' to send a query using extended protocol when within a
> pipeline by using PQsendQueryParams

It's a nice improvement!

> with 0 parameters. It is not
> possible to send parameters with extended protocol this way and
> everything will be propagated through the query string, similar to a
> simple query.

It's actually possible to use parameters

\startpipeline 
\bind 'foo'
select $1;
\endpipeline

 ?column? 
----------
 foo
(1 row)

I suspect there's a misunderstanding that \bind can only be placed
after the query, because it's always written like that in the regression
tests, and in the documentation.
But that's just a notational preference.


Best regards,
-- 
Daniel Vérité 
https://postgresql.verite.pro/





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-18 23:56  Michael Paquier <[email protected]>
  parent: Daniel Verite <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Michael Paquier @ 2025-03-18 23:56 UTC (permalink / raw)
  To: Daniel Verite <[email protected]>; +Cc: Anthonin Bonnefoy <[email protected]>; Jelte Fennema-Nio <[email protected]>; pgsql-hackers

On Tue, Mar 18, 2025 at 10:36:28AM +0100, Daniel Verite wrote:
> It's actually possible to use parameters
> 
> \startpipeline 
> \bind 'foo'
> select $1;
> \endpipeline
> 
>  ?column? 
> ----------
>  foo
> (1 row)
> 
> I suspect there's a misunderstanding that \bind can only be placed
> after the query, because it's always written like that in the regression
> tests, and in the documentation.
> But that's just a notational preference.

Nice trick, unrelated to pipelines.  I don't think that we have
anything testing this specific pattern for \bind.  At quick glance all
our test use \bind at the end of a query string.  Perhaps we should?
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-19 02:28  Michael Paquier <[email protected]>
  parent: Jelte Fennema-Nio <[email protected]>
  0 siblings, 1 reply; 25+ messages in thread

From: Michael Paquier @ 2025-03-19 02:28 UTC (permalink / raw)
  To: Jelte Fennema-Nio <[email protected]>; +Cc: Anthonin Bonnefoy <[email protected]>; Daniel Verite <[email protected]>; pgsql-hackers

On Tue, Mar 18, 2025 at 10:27:38AM +0100, Jelte Fennema-Nio wrote:
> One thing that comes to mind that I think would be quite useful and
> pretty easy to implement if we have this functionality within a
> pipeline: An \extended command. That puts psql in "extended protocol
> mode" (without enabling pipelining). In "extended protocol mode" all
> queries would automatically be sent using PQsendQueryParams. That
> would remove the need to use \bind anymore outside of a pipeline
> either.

How does that help when passing parameter values?  \bind is here to be
able to pass down parameter values to queries that are prepared, so we
cannot bypass it as the parameter values need to be passed to the
\bind meta-command itself.

Perhaps an \extended command that behaves outside a pipeline makes
sense to force the use of queries without parameters to use the
extended mode, but I cannot get much excited about the concept knowing
all the meta-commands we have now (not talking about the pipeline
part, which is different, as we can treat queries in batches).
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-19 04:49  Michael Paquier <[email protected]>
  parent: Anthonin Bonnefoy <[email protected]>
  1 sibling, 0 replies; 25+ messages in thread

From: Michael Paquier @ 2025-03-19 04:49 UTC (permalink / raw)
  To: Anthonin Bonnefoy <[email protected]>; +Cc: Daniel Verite <[email protected]>; Jelte Fennema-Nio <[email protected]>; pgsql-hackers

On Tue, Mar 18, 2025 at 09:55:21AM +0100, Anthonin Bonnefoy wrote:
> I've added additional tests when piping queries with ';':
> - I've reused the same scenario with \sendpipeline: single query,
> multiple queries, flushes, syncs, using COPY...
> - Using ';' will replace the unnamed prepared statement. It's a bit
> different from expected as a simple query will delete the unnamed
> prepared statement.
> - Sending an extended query prepared with \bind using a ';' on a
> newline, though this is not specific to pipelining. The scanned
> semicolon triggers the call to SendQuery, processing the buffered
> extended query. It's a bit unusual but that's the current behaviour.

The tests could be much more organized, particularly for the "sinple"
and "multiple" and COPY cases, rather than being treated as two
different groups at different locations of psql_pipeline.sql.  I've
spent some time reorganizing all that.

A second thing that was a bit itchy is the use of ";" for what's a
semicolon, and we use this term in the psql docs to refer to queries
terminated by that.  The whole paragraph could be simplified a bit
more, mentioning that everything in a pipeline uses the extended
protocol, while \bind & co are more like options.  The description of
PIPELINE_COMMAND_COUNT could be simpler, and the part about the
pending results can be more general now so I've removed it.

With all that set, I've applied the patch.  If you have more
suggestions, please feel free to mention them.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-03-19 13:05  Daniel Verite <[email protected]>
  parent: Michael Paquier <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Daniel Verite @ 2025-03-19 13:05 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: Jelte Fennema-Nio <[email protected]>; Anthonin Bonnefoy <[email protected]>; pgsql-hackers

	Michael Paquier wrote:

> Perhaps an \extended command that behaves outside a pipeline makes
> sense to force the use of queries without parameters to use the
> extended mode, but I cannot get much excited about the concept knowing
> all the meta-commands we have now (not talking about the pipeline
> part, which is different, as we can treat queries in batches).

When psql started supporting the extended query protocol, the question
of enabling it more globally rather than query-by-query was discussed
a bit [1]. The idea was to switch to it with a setting or a variable
rather than a metacommand.
Some pros and cons were mentioned in the thread, but on the whole
it was not convincing enough to get implemented.

[1]
https://www.postgresql.org/message-id/e8dd1cd5-0e04-3598-0518-a605159fe314%40enterprisedb.com


Best regards,
-- 
Daniel Vérité 
https://postgresql.verite.pro/





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-04-16 14:31  a.kozhemyakin <[email protected]>
  0 siblings, 1 reply; 25+ messages in thread

From: a.kozhemyakin @ 2025-04-16 14:31 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; Daniel Verite <[email protected]>; +Cc: Anthonin Bonnefoy <[email protected]>; Jelte Fennema-Nio <[email protected]>; pgsql-hackers

Hello,

After commit 2cce0fe on master

When executing query:
psql postgres <<EOF
CREATE TABLE psql_pipeline();
\startpipeline
COPY psql_pipeline FROM STDIN;
SELECT 'val1';
\syncpipeline
\getresults
EOF


ERROR:  unexpected message type 0x50 during COPY from stdin
CONTEXT:  COPY psql_pipeline, line 1
Pipeline aborted, command did not run
psql: common.c:1510: discardAbortedPipelineResults: Assertion `res == 
((void *)0) || result_status == PGRES_PIPELINE_ABORTED' failed.
Aborted (core dumped)


The psql crashes with the stack trace:
(gdb) bt
#0  __pthread_kill_implementation (no_tid=0, signo=6, 
threadid=<optimized out>) at ./nptl/pthread_kill.c:44
#1  __pthread_kill_internal (signo=6, threadid=<optimized out>) at 
./nptl/pthread_kill.c:78
#2  __GI___pthread_kill (threadid=<optimized out>, signo=signo@entry=6) 
at ./nptl/pthread_kill.c:89
#3  0x0000760edd24527e in __GI_raise (sig=sig@entry=6) at 
../sysdeps/posix/raise.c:26
#4  0x0000760edd2288ff in __GI_abort () at ./stdlib/abort.c:79
#5  0x0000760edd22881b in __assert_fail_base (fmt=0x760edd3d01e8 
"%s%s%s:%u: %s%sAssertion `%s' failed.\n%n",
     assertion=assertion@entry=0x5ba46ab79850 "res == ((void *)0) || 
result_status == PGRES_PIPELINE_ABORTED", file=file@entry=0x5ba46ab6fcad 
"common.c",
     line=line@entry=1510, function=function@entry=0x5ba46ab9c780 
<__PRETTY_FUNCTION__.3> "discardAbortedPipelineResults") at 
./assert/assert.c:96
#6  0x0000760edd23b517 in __assert_fail 
(assertion=assertion@entry=0x5ba46ab79850 "res == ((void *)0) || 
result_status == PGRES_PIPELINE_ABORTED",
     file=file@entry=0x5ba46ab6fcad "common.c", line=line@entry=1510,
     function=function@entry=0x5ba46ab9c780 <__PRETTY_FUNCTION__.3> 
"discardAbortedPipelineResults") at ./assert/assert.c:105
#7  0x00005ba46ab2bd40 in discardAbortedPipelineResults () at common.c:1510
#8  ExecQueryAndProcessResults (query=query@entry=0x5ba4a2ec1e10 "SELECT 
'val1';", elapsed_msec=elapsed_msec@entry=0x7ffeb07262a8,
     svpt_gone_p=svpt_gone_p@entry=0x7ffeb07262a7, 
is_watch=is_watch@entry=false, min_rows=min_rows@entry=0, 
opt=opt@entry=0x0, printQueryFout=0x0)
     at common.c:1811
#9  0x00005ba46ab2983f in SendQuery (query=0x5ba4a2ec1e10 "SELECT 
'val1';") at common.c:1212
#10 0x00005ba46ab3f66a in MainLoop (source=source@entry=0x760edd4038e0 
<_IO_2_1_stdin_>) at mainloop.c:515
#11 0x00005ba46ab23f2a in process_file (filename=0x0, 
use_relative_path=use_relative_path@entry=false) at command.c:4870
#12 0x00005ba46ab1e9d9 in main (argc=<optimized out>, 
argv=0x7ffeb07269d8) at startup.c:420




06.03.2025 11:20, Michael Paquier пишет:
> On Wed, Mar 05, 2025 at 03:25:12PM +0100, Daniel Verite wrote:
>> 	Anthonin Bonnefoy wrote:
>>> I do see the idea to make it easier to convert existing scripts into
>>> using pipelining. The main focus of the initial implementation was
>>> more on protocol regression tests with psql, so that's not necessarily
>>> something I had in mind.
>> Understood. Yet pipelining can accelerate considerably certain scripts
>> when client-server latency is an issue. We should expect end users to
>> benefit from it too.
> That was not a test case we had in mind originally here, but if it is
> possible to keep the implementation simple while supporting your
> demand, well, let's do it.  If it's not that straight-forward, let's
> use the new meta-command, forbidding \g and \gx based on your
> arguments from upthread.
> --
> Michael





^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-04-16 16:18  Michael Paquier <[email protected]>
  parent: a.kozhemyakin <[email protected]>
  0 siblings, 1 reply; 25+ messages in thread

From: Michael Paquier @ 2025-04-16 16:18 UTC (permalink / raw)
  To: a.kozhemyakin <[email protected]>; +Cc: Daniel Verite <[email protected]>; Anthonin Bonnefoy <[email protected]>; Jelte Fennema-Nio <[email protected]>; pgsql-hackers

On Wed, Apr 16, 2025 at 09:31:59PM +0700, a.kozhemyakin wrote:
> After commit 2cce0fe on master
> 
> ERROR:  unexpected message type 0x50 during COPY from stdin
> CONTEXT:  COPY psql_pipeline, line 1
> Pipeline aborted, command did not run
> psql: common.c:1510: discardAbortedPipelineResults: Assertion `res == ((void
> *)0) || result_status == PGRES_PIPELINE_ABORTED' failed.
> Aborted (core dumped)

Reproduced here, thanks for the report.  I'll look at that at the
beginning of next week, adding an open item for now.
--
Michael


Attachments:

  [application/pgp-signature] signature.asc (833B, ../../[email protected]/2-signature.asc)
  download

^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* Re: Add Pipelining support in psql
@ 2025-04-22 12:37  Anthonin Bonnefoy <[email protected]>
  parent: Michael Paquier <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Anthonin Bonnefoy @ 2025-04-22 12:37 UTC (permalink / raw)
  To: Michael Paquier <[email protected]>; +Cc: a.kozhemyakin <[email protected]>; Daniel Verite <[email protected]>; Jelte Fennema-Nio <[email protected]>; pgsql-hackers

On Tue, Apr 22, 2025 at 2:06 AM Michael Paquier <[email protected]> wrote:
> I am wondering if we could not be smarter with the handling of
> the counters, but I really doubt that there is much more we can do
> under a PGRES_FATAL_ERROR.

One thing that bothers me is that the reported error is silently
discarded within discardAbortedPipelineResults.

psql -f bug_assertion.sql
psql:bug_assertion.sql:7: ERROR:  unexpected message type 0x50 during
COPY from stdin
CONTEXT:  COPY psql_pipeline, line 1
psql:bug_assertion.sql:7: Pipeline aborted, command did not run

This should ideally report the "FATAL:  terminating connection because
protocol synchronization was lost" sent by the backend process.

Also, we still have a triggered assertion failure with the following:
CREATE TABLE psql_pipeline(a text);
\startpipeline
COPY psql_pipeline FROM STDIN;
SELECT 'val1';
\syncpipeline
\endpipeline
...
Assertion failed: (pset.piped_syncs == 0), function
ExecQueryAndProcessResults, file common.c, line 2153.

A possible alternative could be to abort discardAbortedPipelineResults
when we encounter a res != NULL + FATAL error and let the outer loop
handle it. As you said, the pipeline flow is borked so there's not
much to salvage. The outer loop would read and print all error
messages until the closed connection is detected. Then,
CheckConnection will reset the connection which will reset the
pipeline state.

While testing this change, I was initially looking for the "FATAL:
terminating connection because protocol synchronization was lost"
message in the tests. However, this was failing on Windows[1] as the
FATAL message wasn't reported on stderr. I'm not sure why yet.

[1]: https://cirrus-ci.com/task/5051031505076224?logs=check_world#L240-L246


Attachments:

  [application/octet-stream] v02-0001-PATCH-psql-Fix-assertion-failure-with-pipeline-m.patch (3.7K, ../../CAO6_XqoQaZVHEw1dFgvOxwectqsNo4QE1tQ6rLD-YUzFpT_+3g@mail.gmail.com/2-v02-0001-PATCH-psql-Fix-assertion-failure-with-pipeline-m.patch)
  download | inline diff:
From 5d37f2616c82c6525d656149d383ef01a6d7518c Mon Sep 17 00:00:00 2001
From: Michael Paquier <[email protected]>
Date: Mon, 21 Apr 2025 15:17:43 +0900
Subject: [PATCH] psql: Fix assertion failure with pipeline mode

---
 src/bin/psql/common.c       | 17 ++++++++++++
 src/bin/psql/t/001_basic.pl | 55 ++++++++++++++++++++++++++++++++++---
 2 files changed, 68 insertions(+), 4 deletions(-)

diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 21d660a8961..0aab02ee32e 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1478,6 +1478,23 @@ discardAbortedPipelineResults(void)
 			 */
 			return res;
 		}
+		else if (res != NULL && result_status == PGRES_FATAL_ERROR)
+		{
+			/*
+			 * We have a fatal error sent by the backend and we can't recover
+			 * from this state. Instead, return the last fatal error and let
+			 * the outer loop handle it.
+			 */
+			PGresult   *fatal_res PG_USED_FOR_ASSERTS_ONLY;
+
+			/*
+			 * Fetch result to consume the end of the current query being
+			 * processed.
+			 */
+			fatal_res = PQgetResult(pset.db);
+			Assert(fatal_res == NULL);
+			return res;
+		}
 		else if (res == NULL)
 		{
 			/* A query was processed, decrement the counters */
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 739cb439708..8d258c00c5e 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -34,11 +34,13 @@ sub psql_fails_like
 {
 	local $Test::Builder::Level = $Test::Builder::Level + 1;
 
-	my ($node, $sql, $expected_stderr, $test_name) = @_;
+	my ($node, $sql, $expected_stderr, $test_name, $replication) = @_;
+
+	# Use the context of a WAL sender, if requested by the caller.
+	$replication = '' unless defined($replication);
 
-	# Use the context of a WAL sender, some of the tests rely on that.
 	my ($ret, $stdout, $stderr) =
-	  $node->psql('postgres', $sql, replication => 'database');
+	  $node->psql('postgres', $sql, replication => $replication);
 
 	isnt($ret, 0, "$test_name: exit code not 0");
 	like($stderr, $expected_stderr, "$test_name: matches");
@@ -79,7 +81,8 @@ psql_fails_like(
 	$node,
 	'START_REPLICATION 0/0',
 	qr/unexpected PQresultStatus: 8$/,
-	'handling of unexpected PQresultStatus');
+	'handling of unexpected PQresultStatus',
+	'database');
 
 # test \timing
 psql_like(
@@ -481,4 +484,48 @@ psql_like($node, "copy (values ('foo'),('bar')) to stdout \\g | $pipe_cmd",
 my $c4 = slurp_file($g_file);
 like($c4, qr/foo.*bar/s);
 
+# Tests with pipelines.  These trigger FATAL failures in the backend,
+# so they cannot be tested through the SQL regression tests.
+$node->safe_psql('postgres', 'CREATE TABLE psql_pipeline()');
+psql_fails_like(
+	$node,
+	qq{\\startpipeline
+COPY psql_pipeline FROM STDIN;
+SELECT 'val1';
+\\syncpipeline
+\\getresults
+\\endpipeline},
+	qr/server closed the connection unexpectedly/,
+	'handling of protocol synchronization loss with pipelines');
+psql_fails_like(
+	$node,
+	qq{\\startpipeline
+COPY psql_pipeline FROM STDIN \\bind \\sendpipeline
+SELECT 'val1' \\bind \\sendpipeline
+\\syncpipeline
+\\getresults
+\\endpipeline},
+	qr/server closed the connection unexpectedly/,
+	'handling of protocol synchronization loss with pipelines');
+
+# This time, test without the \getresults
+psql_fails_like(
+	$node,
+	qq{\\startpipeline
+COPY psql_pipeline FROM STDIN;
+SELECT 'val1';
+\\syncpipeline
+\\endpipeline},
+	qr/server closed the connection unexpectedly/,
+	'handling of protocol synchronization loss with pipelines');
+psql_fails_like(
+	$node,
+	qq{\\startpipeline
+COPY psql_pipeline FROM STDIN \\bind \\sendpipeline
+SELECT 'val1' \\bind \\sendpipeline
+\\syncpipeline
+\\endpipeline},
+	qr/server closed the connection unexpectedly/,
+	'handling of protocol synchronization loss with pipelines');
+
 done_testing();
-- 
2.39.5 (Apple Git-154)



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* [PATCH v51 07/10] Check for transaction block early in ExecRepack
@ 2026-04-03 15:23  Srinath Reddy Sadipiralla <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Srinath Reddy Sadipiralla @ 2026-04-03 15:23 UTC (permalink / raw)

Currently, executing REPACK (CONCURRENTLY) without a table name inside a
transaction block throws the error "REPACK CONCURRENTLY requires explicit
table name" instead of the expected transaction block error. This occurs
because ExecRepack() validates the parsed options and missing relation
before verifying the transaction state.

This behavior is inconsistent with other utility commands like VACUUM
,REINDEX, etc; which invoke PreventInTransactionBlock() at the very start
of their execution to properly reject execution inside user transactions
before validating targets.

Add PreventInTransactionBlock to the top of ExecRepack() to enforce the
transaction block restriction early. This prevents the user from fixing
a missing table error only to immediately hit a transaction block error,
and also ensures consistency with rest of the commands.

Author: Srinath Reddy Sadipiralla <[email protected]>
---
 src/backend/commands/repack.c | 26 ++++++++++++--------------
 1 file changed, 12 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index d6e446d582d..d4c1f0e7652 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -292,6 +292,18 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 		MyProc->statusFlags |= PROC_IN_CONCURRENT_REPACK;
 		ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
 		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Make sure we're not in a transaction block.
+		 *
+		 * The reason is that repack_setup_logical_decoding() could wait
+		 * indefinitely for our XID to complete. (The deadlock detector would
+		 * not recognize it because we'd be waiting for ourselves, i.e. no
+		 * real lock conflict.) It would be possible to run in a transaction
+		 * block if we had no XID, but this restriction is simpler for users
+		 * to understand and we don't lose anything.
+		 */
+		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 	}
 
 	/*
@@ -523,21 +535,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * replica index OID.
 	 */
 	if (concurrent)
-	{
-		/*
-		 * Make sure we're not in a transaction block.
-		 *
-		 * The reason is that repack_setup_logical_decoding() could wait
-		 * indefinitely for our XID to complete. (The deadlock detector would
-		 * not recognize it because we'd be waiting for ourselves, i.e. no
-		 * real lock conflict.) It would be possible to run in a transaction
-		 * block if we had no XID, but this restriction is simpler for users
-		 * to understand and we don't lose anything.
-		 */
-		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
-
 		check_repack_concurrently_requirements(OldHeap, &ident_idx);
-	}
 
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
-- 
2.47.3


--r2slln3zpmilwu22
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v51-0008-Introduce-an-option-to-make-logical-replication-.patch"



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* [PATCH v51 07/10] Check for transaction block early in ExecRepack
@ 2026-04-03 15:23  Srinath Reddy Sadipiralla <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Srinath Reddy Sadipiralla @ 2026-04-03 15:23 UTC (permalink / raw)

Currently, executing REPACK (CONCURRENTLY) without a table name inside a
transaction block throws the error "REPACK CONCURRENTLY requires explicit
table name" instead of the expected transaction block error. This occurs
because ExecRepack() validates the parsed options and missing relation
before verifying the transaction state.

This behavior is inconsistent with other utility commands like VACUUM
,REINDEX, etc; which invoke PreventInTransactionBlock() at the very start
of their execution to properly reject execution inside user transactions
before validating targets.

Add PreventInTransactionBlock to the top of ExecRepack() to enforce the
transaction block restriction early. This prevents the user from fixing
a missing table error only to immediately hit a transaction block error,
and also ensures consistency with rest of the commands.

Author: Srinath Reddy Sadipiralla <[email protected]>
---
 src/backend/commands/repack.c | 26 ++++++++++++--------------
 1 file changed, 12 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index d6e446d582d..d4c1f0e7652 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -292,6 +292,18 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 		MyProc->statusFlags |= PROC_IN_CONCURRENT_REPACK;
 		ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
 		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Make sure we're not in a transaction block.
+		 *
+		 * The reason is that repack_setup_logical_decoding() could wait
+		 * indefinitely for our XID to complete. (The deadlock detector would
+		 * not recognize it because we'd be waiting for ourselves, i.e. no
+		 * real lock conflict.) It would be possible to run in a transaction
+		 * block if we had no XID, but this restriction is simpler for users
+		 * to understand and we don't lose anything.
+		 */
+		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 	}
 
 	/*
@@ -523,21 +535,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * replica index OID.
 	 */
 	if (concurrent)
-	{
-		/*
-		 * Make sure we're not in a transaction block.
-		 *
-		 * The reason is that repack_setup_logical_decoding() could wait
-		 * indefinitely for our XID to complete. (The deadlock detector would
-		 * not recognize it because we'd be waiting for ourselves, i.e. no
-		 * real lock conflict.) It would be possible to run in a transaction
-		 * block if we had no XID, but this restriction is simpler for users
-		 * to understand and we don't lose anything.
-		 */
-		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
-
 		check_repack_concurrently_requirements(OldHeap, &ident_idx);
-	}
 
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
-- 
2.47.3


--r2slln3zpmilwu22
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v51-0008-Introduce-an-option-to-make-logical-replication-.patch"



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* [PATCH v53 5/7] Check for transaction block early in ExecRepack
@ 2026-04-03 15:23  Srinath Reddy Sadipiralla <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Srinath Reddy Sadipiralla @ 2026-04-03 15:23 UTC (permalink / raw)

Currently, executing REPACK (CONCURRENTLY) without a table name inside a
transaction block throws the error "REPACK CONCURRENTLY requires explicit
table name" instead of the expected transaction block error. This occurs
because ExecRepack() validates the parsed options and missing relation
before verifying the transaction state.

This behavior is inconsistent with other utility commands like VACUUM
,REINDEX, etc; which invoke PreventInTransactionBlock() at the very start
of their execution to properly reject execution inside user transactions
before validating targets.

Add PreventInTransactionBlock to the top of ExecRepack() to enforce the
transaction block restriction early. This prevents the user from fixing
a missing table error only to immediately hit a transaction block error,
and also ensures consistency with rest of the commands.

Author: Srinath Reddy Sadipiralla <[email protected]>
---
 src/backend/commands/repack.c | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index f2759cdbef1..8d9b2b2e370 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -279,6 +279,21 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	/* Determine the lock mode to use. */
 	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
+	if ((params.options & CLUOPT_CONCURRENT) != 0)
+	{
+		/*
+		 * Make sure we're not in a transaction block.
+		 *
+		 * The reason is that repack_setup_logical_decoding() could wait
+		 * indefinitely for our XID to complete. (The deadlock detector would
+		 * not recognize it because we'd be waiting for ourselves, i.e. no
+		 * real lock conflict.) It would be possible to run in a transaction
+		 * block if we had no XID, but this restriction is simpler for users
+		 * to understand and we don't lose any functionality.
+		 */
+		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
+	}
+
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
 	 * the relation is a partitioned table, in which case we fall through.
@@ -511,19 +526,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * replica index OID.
 	 */
 	if (concurrent)
-	{
-		/*
-		 * Make sure we're not in a transaction block.
-		 *
-		 * The reason is that repack_setup_logical_decoding() could deadlock
-		 * if there's an XID already assigned.  It would be possible to run in
-		 * a transaction block if we had no XID, but this restriction is
-		 * simpler for users to understand and we don't lose anything.
-		 */
-		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
-
 		check_repack_concurrently_requirements(OldHeap, &ident_idx);
-	}
 
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
-- 
2.47.3


--qr3jlalmmcpkiodg
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v53-0006-Error-out-any-process-that-would-block-at-REPACK.patch"



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* [PATCH v53 5/7] Check for transaction block early in ExecRepack
@ 2026-04-03 15:23  Srinath Reddy Sadipiralla <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Srinath Reddy Sadipiralla @ 2026-04-03 15:23 UTC (permalink / raw)

Currently, executing REPACK (CONCURRENTLY) without a table name inside a
transaction block throws the error "REPACK CONCURRENTLY requires explicit
table name" instead of the expected transaction block error. This occurs
because ExecRepack() validates the parsed options and missing relation
before verifying the transaction state.

This behavior is inconsistent with other utility commands like VACUUM
,REINDEX, etc; which invoke PreventInTransactionBlock() at the very start
of their execution to properly reject execution inside user transactions
before validating targets.

Add PreventInTransactionBlock to the top of ExecRepack() to enforce the
transaction block restriction early. This prevents the user from fixing
a missing table error only to immediately hit a transaction block error,
and also ensures consistency with rest of the commands.

Author: Srinath Reddy Sadipiralla <[email protected]>
---
 src/backend/commands/repack.c | 27 +++++++++++++++------------
 1 file changed, 15 insertions(+), 12 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index f2759cdbef1..8d9b2b2e370 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -279,6 +279,21 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 	/* Determine the lock mode to use. */
 	lockmode = RepackLockLevel((params.options & CLUOPT_CONCURRENT) != 0);
 
+	if ((params.options & CLUOPT_CONCURRENT) != 0)
+	{
+		/*
+		 * Make sure we're not in a transaction block.
+		 *
+		 * The reason is that repack_setup_logical_decoding() could wait
+		 * indefinitely for our XID to complete. (The deadlock detector would
+		 * not recognize it because we'd be waiting for ourselves, i.e. no
+		 * real lock conflict.) It would be possible to run in a transaction
+		 * block if we had no XID, but this restriction is simpler for users
+		 * to understand and we don't lose any functionality.
+		 */
+		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
+	}
+
 	/*
 	 * If a single relation is specified, process it and we're done ... unless
 	 * the relation is a partitioned table, in which case we fall through.
@@ -511,19 +526,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * replica index OID.
 	 */
 	if (concurrent)
-	{
-		/*
-		 * Make sure we're not in a transaction block.
-		 *
-		 * The reason is that repack_setup_logical_decoding() could deadlock
-		 * if there's an XID already assigned.  It would be possible to run in
-		 * a transaction block if we had no XID, but this restriction is
-		 * simpler for users to understand and we don't lose anything.
-		 */
-		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
-
 		check_repack_concurrently_requirements(OldHeap, &ident_idx);
-	}
 
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
-- 
2.47.3


--qr3jlalmmcpkiodg
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v53-0006-Error-out-any-process-that-would-block-at-REPACK.patch"



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* [PATCH v52 07/10] Check for transaction block early in ExecRepack
@ 2026-04-03 15:23  Srinath Reddy Sadipiralla <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Srinath Reddy Sadipiralla @ 2026-04-03 15:23 UTC (permalink / raw)

Currently, executing REPACK (CONCURRENTLY) without a table name inside a
transaction block throws the error "REPACK CONCURRENTLY requires explicit
table name" instead of the expected transaction block error. This occurs
because ExecRepack() validates the parsed options and missing relation
before verifying the transaction state.

This behavior is inconsistent with other utility commands like VACUUM
,REINDEX, etc; which invoke PreventInTransactionBlock() at the very start
of their execution to properly reject execution inside user transactions
before validating targets.

Add PreventInTransactionBlock to the top of ExecRepack() to enforce the
transaction block restriction early. This prevents the user from fixing
a missing table error only to immediately hit a transaction block error,
and also ensures consistency with rest of the commands.

Author: Srinath Reddy Sadipiralla <[email protected]>
---
 src/backend/commands/repack.c | 26 ++++++++++++--------------
 1 file changed, 12 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index d6e446d582d..d4c1f0e7652 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -292,6 +292,18 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 		MyProc->statusFlags |= PROC_IN_CONCURRENT_REPACK;
 		ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
 		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Make sure we're not in a transaction block.
+		 *
+		 * The reason is that repack_setup_logical_decoding() could wait
+		 * indefinitely for our XID to complete. (The deadlock detector would
+		 * not recognize it because we'd be waiting for ourselves, i.e. no
+		 * real lock conflict.) It would be possible to run in a transaction
+		 * block if we had no XID, but this restriction is simpler for users
+		 * to understand and we don't lose anything.
+		 */
+		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 	}
 
 	/*
@@ -523,21 +535,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * replica index OID.
 	 */
 	if (concurrent)
-	{
-		/*
-		 * Make sure we're not in a transaction block.
-		 *
-		 * The reason is that repack_setup_logical_decoding() could wait
-		 * indefinitely for our XID to complete. (The deadlock detector would
-		 * not recognize it because we'd be waiting for ourselves, i.e. no
-		 * real lock conflict.) It would be possible to run in a transaction
-		 * block if we had no XID, but this restriction is simpler for users
-		 * to understand and we don't lose anything.
-		 */
-		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
-
 		check_repack_concurrently_requirements(OldHeap, &ident_idx);
-	}
 
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
-- 
2.47.3


--gp2pyozrd5pweboh
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v52-0008-Introduce-an-option-to-make-logical-replication-.patch"



^ permalink  raw  reply  [nested|flat] 25+ messages in thread

* [PATCH v52 07/10] Check for transaction block early in ExecRepack
@ 2026-04-03 15:23  Srinath Reddy Sadipiralla <[email protected]>
  0 siblings, 0 replies; 25+ messages in thread

From: Srinath Reddy Sadipiralla @ 2026-04-03 15:23 UTC (permalink / raw)

Currently, executing REPACK (CONCURRENTLY) without a table name inside a
transaction block throws the error "REPACK CONCURRENTLY requires explicit
table name" instead of the expected transaction block error. This occurs
because ExecRepack() validates the parsed options and missing relation
before verifying the transaction state.

This behavior is inconsistent with other utility commands like VACUUM
,REINDEX, etc; which invoke PreventInTransactionBlock() at the very start
of their execution to properly reject execution inside user transactions
before validating targets.

Add PreventInTransactionBlock to the top of ExecRepack() to enforce the
transaction block restriction early. This prevents the user from fixing
a missing table error only to immediately hit a transaction block error,
and also ensures consistency with rest of the commands.

Author: Srinath Reddy Sadipiralla <[email protected]>
---
 src/backend/commands/repack.c | 26 ++++++++++++--------------
 1 file changed, 12 insertions(+), 14 deletions(-)

diff --git a/src/backend/commands/repack.c b/src/backend/commands/repack.c
index d6e446d582d..d4c1f0e7652 100644
--- a/src/backend/commands/repack.c
+++ b/src/backend/commands/repack.c
@@ -292,6 +292,18 @@ ExecRepack(ParseState *pstate, RepackStmt *stmt, bool isTopLevel)
 		MyProc->statusFlags |= PROC_IN_CONCURRENT_REPACK;
 		ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
 		LWLockRelease(ProcArrayLock);
+
+		/*
+		 * Make sure we're not in a transaction block.
+		 *
+		 * The reason is that repack_setup_logical_decoding() could wait
+		 * indefinitely for our XID to complete. (The deadlock detector would
+		 * not recognize it because we'd be waiting for ourselves, i.e. no
+		 * real lock conflict.) It would be possible to run in a transaction
+		 * block if we had no XID, but this restriction is simpler for users
+		 * to understand and we don't lose anything.
+		 */
+		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
 	}
 
 	/*
@@ -523,21 +535,7 @@ cluster_rel(RepackCommand cmd, Relation OldHeap, Oid indexOid,
 	 * replica index OID.
 	 */
 	if (concurrent)
-	{
-		/*
-		 * Make sure we're not in a transaction block.
-		 *
-		 * The reason is that repack_setup_logical_decoding() could wait
-		 * indefinitely for our XID to complete. (The deadlock detector would
-		 * not recognize it because we'd be waiting for ourselves, i.e. no
-		 * real lock conflict.) It would be possible to run in a transaction
-		 * block if we had no XID, but this restriction is simpler for users
-		 * to understand and we don't lose anything.
-		 */
-		PreventInTransactionBlock(isTopLevel, "REPACK (CONCURRENTLY)");
-
 		check_repack_concurrently_requirements(OldHeap, &ident_idx);
-	}
 
 	/* Check for user-requested abort. */
 	CHECK_FOR_INTERRUPTS();
-- 
2.47.3


--gp2pyozrd5pweboh
Content-Type: text/x-diff; charset=utf-8
Content-Disposition: attachment;
	filename="v52-0008-Introduce-an-option-to-make-logical-replication-.patch"



^ permalink  raw  reply  [nested|flat] 25+ messages in thread


end of thread, other threads:[~2026-04-03 15:23 UTC | newest]

Thread overview: 25+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2019-09-26 21:05 [PATCH v2 5/8] jit: explain: remove backend lifetime module count from function name. Andres Freund <[email protected]>
2019-09-26 21:05 [PATCH v1 05/12] jit: explain: remove backend lifetime module count from function name. Andres Freund <[email protected]>
2023-05-16 09:48 Re: de-catalog one error message Alvaro Herrera <[email protected]>
2025-03-07 00:05 Re: Add Pipelining support in psql Jelte Fennema-Nio <[email protected]>
2025-03-07 08:08 ` Re: Add Pipelining support in psql Anthonin Bonnefoy <[email protected]>
2025-03-07 22:31 ` Re: Add Pipelining support in psql Daniel Verite <[email protected]>
2025-03-17 09:50   ` Re: Add Pipelining support in psql Anthonin Bonnefoy <[email protected]>
2025-03-17 10:19     ` Re: Add Pipelining support in psql Michael Paquier <[email protected]>
2025-03-18 00:50     ` Re: Add Pipelining support in psql Michael Paquier <[email protected]>
2025-03-18 08:55       ` Re: Add Pipelining support in psql Anthonin Bonnefoy <[email protected]>
2025-03-18 09:27         ` Re: Add Pipelining support in psql Jelte Fennema-Nio <[email protected]>
2025-03-19 02:28           ` Re: Add Pipelining support in psql Michael Paquier <[email protected]>
2025-03-19 13:05             ` Re: Add Pipelining support in psql Daniel Verite <[email protected]>
2025-03-19 04:49         ` Re: Add Pipelining support in psql Michael Paquier <[email protected]>
2025-03-18 09:36     ` Re: Add Pipelining support in psql Daniel Verite <[email protected]>
2025-03-18 23:56       ` Re: Add Pipelining support in psql Michael Paquier <[email protected]>
2025-04-16 14:31 Re: Add Pipelining support in psql a.kozhemyakin <[email protected]>
2025-04-16 16:18 ` Re: Add Pipelining support in psql Michael Paquier <[email protected]>
2025-04-22 12:37   ` Re: Add Pipelining support in psql Anthonin Bonnefoy <[email protected]>
2026-04-03 15:23 [PATCH v53 5/7] Check for transaction block early in ExecRepack Srinath Reddy Sadipiralla <[email protected]>
2026-04-03 15:23 [PATCH v51 07/10] Check for transaction block early in ExecRepack Srinath Reddy Sadipiralla <[email protected]>
2026-04-03 15:23 [PATCH v53 5/7] Check for transaction block early in ExecRepack Srinath Reddy Sadipiralla <[email protected]>
2026-04-03 15:23 [PATCH v51 07/10] Check for transaction block early in ExecRepack Srinath Reddy Sadipiralla <[email protected]>
2026-04-03 15:23 [PATCH v52 07/10] Check for transaction block early in ExecRepack Srinath Reddy Sadipiralla <[email protected]>
2026-04-03 15:23 [PATCH v52 07/10] Check for transaction block early in ExecRepack Srinath Reddy Sadipiralla <[email protected]>

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox