Subject: psql - add SHOW_ALL_RESULTS option

From: Fabien COELHO <[email-masked]>
Date: Sat, 13 Apr 2019 22:37:37 +0200 (CEST)

Hello devs,

The attached patch implements a new SHOW_ALL_RESULTS option for psql, 
which shows all results of a combined query (\;) instead of only the last 
one.

This solves a frustration that intermediate results were hidden from view 
for no good reason that I could think of.

For that, call PQsendQuery instead of (mostly not documented) PQexec, and 
rework how results are processed afterwards.

Timing is moved to ProcessQueryResults to keep the last result handling 
out of the measured time. I think it would not be a big deal to include 
it, but this is the previous behavior.

In passing, refactor a little and add comments. Make function names about 
results plural or singular consistently with the fact the it processes one 
or several results. Change "PrintQueryResult" to "HandleQueryResult" 
because it was not always printing something. Also add a HandleCopyResult 
function, which makes the patch a little bigger by moving things around 
but clarifies the code.

Code in "common.c" is actually a little shorter than the previous version.

---

From: "Iwata, Aya" <[email-masked]>
Date: Wed, 24 Apr 2019 02:34:39 +0000

Hi Fabien, 

I review your patch. 

> Add a few tests for the new feature.
+++ b/src/test/regress/expected/psql.out
@@ -4729,3 +4729,46 @@ drop schema testpart;
 set search_path to default;
 set role to default;
 drop role testrole_partitioning;
+-- 
There is space (+--' '). Please delete it. It is cause of regression test failed.

> IMHO this new setting should be on by default: few people know about \; so
> it would not change anything for most, and I do not see why those who use
> it would not be interested by the results of all the queries they asked for.
I agree with your opinion.

I test some query combination case. And I found when warning happen, the message is printed in head of results. I think it is not clear in which query the warning occurred.
How about print warning message before the query that warning occurred?

For example,
-- devide by ';'
postgres=# BEGIN; BEGIN; SELECT 1 AS one; COMMIT; BEGIN; BEGIN; SELECT 1 AS one; COMMIT;
BEGIN
psql: WARNING:  there is already a transaction in progress
BEGIN
 one 
-----
   1
(1 row)

COMMIT
BEGIN
psql: WARNING:  there is already a transaction in progress
BEGIN
 one 
-----
   1
(1 row)

COMMIT

-- devide by '\;' and set SHOW_RESULT_ALL on
postgres=# \set SHOW_ALL_RESULTS on
postgres=# BEGIN\; BEGIN\; SELECT 1 AS one\; COMMIT\; BEGIN\; BEGIN\; SELECT 1 AS one\; COMMIT;
psql: WARNING:  there is already a transaction in progress
BEGIN
BEGIN
 one 
-----
   1
(1 row)

psql: WARNING:  there is already a transaction in progress
COMMIT
BEGIN
BEGIN
 one 
-----
   1
(1 row)

COMMIT

I will check the code soon.

Regards, 
Aya Iwata

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 26 Apr 2019 18:38:47 +0200 (CEST)

Hello Aya-san,

Thanks for this review.

> There is space (+--' '). Please delete it. It is cause of regression test failed.

Indeed, unsure how I could do that. Fixed.

> I agree with your opinion.

Ok. I did not yet change the default in the attached version, though.

> I test some query combination case. And I found when warning happen, the 
> message is printed in head of results. I think it is not clear in which 
> query the warning occurred.

Indeed.

> How about print warning message before the query that warning occurred?

Sure. It happened to be trickier than I thought to achieve this, because 
there is a callback hook to send notifications.

This attached version does:
  - ensure that warnings appear just before its
  - add the entry in psql's help
  - redefine the function boundary so that timing is cleaner
  - include somehow improved tests

-- 
Fabien.

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index b86764003d..de2b56cf88 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3918,6 +3918,17 @@ bar
         </listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><varname>SHOW_ALL_RESULTS</varname></term>
+        <listitem>
+        <para>
+        When this variable is set to <literal>on</literal>, all results of a combined
+        (<literal>\;</literal>) query are shown instead of just the last one.
+        Default is <literal>off</literal>.
+        </para>
+        </listitem>
+      </varlistentry>
+
       <varlistentry>
         <term><varname>SHOW_CONTEXT</varname></term>
         <listitem>
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index bd284446f8..19bfce9610 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -472,6 +472,16 @@ ResetCancelConn(void)
 #endif
 }
 
+static void
+ShowErrorMessage(const PGresult *result)
+{
+	const char *error = PQerrorMessage(pset.db);
+
+	if (strlen(error))
+		pg_log_info("%s", error);
+
+	CheckConnection();
+}
 
 /*
  * AcceptResult
@@ -482,7 +492,7 @@ ResetCancelConn(void)
  * Returns true for valid result, false for error state.
  */
 static bool
-AcceptResult(const PGresult *result)
+AcceptResult(const PGresult *result, bool show_error)
 {
 	bool		OK;
 
@@ -513,15 +523,8 @@ AcceptResult(const PGresult *result)
 				break;
 		}
 
-	if (!OK)
-	{
-		const char *error = PQerrorMessage(pset.db);
-
-		if (strlen(error))
-			pg_log_info("%s", error);
-
-		CheckConnection();
-	}
+	if (!OK && show_error)
+		ShowErrorMessage(result);
 
 	return OK;
 }
@@ -701,7 +704,7 @@ PSQLexec(const char *query)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		res = NULL;
@@ -743,7 +746,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		return 0;
@@ -999,199 +1002,113 @@ loop_exit:
 	return success;
 }
 
-
 /*
- * ProcessResult: utility function for use by SendQuery() only
- *
- * When our command string contained a COPY FROM STDIN or COPY TO STDOUT,
- * PQexec() has stopped at the PGresult associated with the first such
- * command.  In that event, we'll marshal data for the COPY and then cycle
- * through any subsequent PGresult objects.
- *
- * When the command string contained no such COPY command, this function
- * degenerates to an AcceptResult() call.
- *
- * Changes its argument to point to the last PGresult of the command string,
- * or NULL if that result was for a COPY TO STDOUT.  (Returning NULL prevents
- * the command status from being printed, which we want in that case so that
- * the status line doesn't get taken as part of the COPY data.)
- *
- * Returns true on complete success, false otherwise.  Possible failure modes
- * include purely client-side problems; check the transaction status for the
- * server-side opinion.
+ * Marshal the COPY data.  Either subroutine will get the
+ * connection out of its COPY state, then call PQresultStatus()
+ * once and report any error.
+ *
+ * For COPY OUT, direct the output to pset.copyStream if it's set,
+ * otherwise to pset.gfname if it's set, otherwise to queryFout.
+ * For COPY IN, use pset.copyStream as data source if it's set,
+ * otherwise cur_cmd_source.
+ *
+ * Update result if further processing is necessary.  (Returning NULL
+ * prevents the command status from being printed, which we want in that
+ * case so that the status line doesn't get taken as part of the COPY data.)
  */
 static bool
-ProcessResult(PGresult **results)
+HandleCopyResult(PGresult **result)
 {
 	bool		success = true;
-	bool		first_cycle = true;
+	FILE	   *copystream;
+	PGresult   *copy_result;
+	ExecStatusType result_status = PQresultStatus(*result);
 
-	for (;;)
+	Assert(result_status == PGRES_COPY_OUT ||
+		   result_status == PGRES_COPY_IN);
+
+	SetCancelConn();
+	if (result_status == PGRES_COPY_OUT)
 	{
-		ExecStatusType result_status;
-		bool		is_copy;
-		PGresult   *next_result;
+		bool		need_close = false;
+		bool		is_pipe = false;
 
-		if (!AcceptResult(*results))
+		if (pset.copyStream)
 		{
-			/*
-			 * Failure at this point is always a server-side failure or a
-			 * failure to submit the command string.  Either way, we're
-			 * finished with this command string.
-			 */
-			success = false;
-			break;
+			/* invoked by \copy */
+			copystream = pset.copyStream;
 		}
-
-		result_status = PQresultStatus(*results);
-		switch (result_status)
+		else if (pset.gfname)
 		{
-			case PGRES_EMPTY_QUERY:
-			case PGRES_COMMAND_OK:
-			case PGRES_TUPLES_OK:
-				is_copy = false;
-				break;
-
-			case PGRES_COPY_OUT:
-			case PGRES_COPY_IN:
-				is_copy = true;
-				break;
-
-			default:
-				/* AcceptResult() should have caught anything else. */
-				is_copy = false;
-				pg_log_error("unexpected PQresultStatus: %d", result_status);
-				break;
-		}
-
-		if (is_copy)
-		{
-			/*
-			 * Marshal the COPY data.  Either subroutine will get the
-			 * connection out of its COPY state, then call PQresultStatus()
-			 * once and report any error.
-			 *
-			 * For COPY OUT, direct the output to pset.copyStream if it's set,
-			 * otherwise to pset.gfname if it's set, otherwise to queryFout.
-			 * For COPY IN, use pset.copyStream as data source if it's set,
-			 * otherwise cur_cmd_source.
-			 */
-			FILE	   *copystream;
-			PGresult   *copy_result;
-
-			SetCancelConn();
-			if (result_status == PGRES_COPY_OUT)
+			/* invoked by \g */
+			if (openQueryOutputFile(pset.gfname,
+									&copystream, &is_pipe))
 			{
-				bool		need_close = false;
-				bool		is_pipe = false;
-
-				if (pset.copyStream)
-				{
-					/* invoked by \copy */
-					copystream = pset.copyStream;
-				}
-				else if (pset.gfname)
-				{
-					/* invoked by \g */
-					if (openQueryOutputFile(pset.gfname,
-											&copystream, &is_pipe))
-					{
-						need_close = true;
-						if (is_pipe)
-							disable_sigpipe_trap();
-					}
-					else
-						copystream = NULL;	/* discard COPY data entirely */
-				}
-				else
-				{
-					/* fall back to the generic query output stream */
-					copystream = pset.queryFout;
-				}
-
-				success = handleCopyOut(pset.db,
-										copystream,
-										&copy_result)
-					&& success
-					&& (copystream != NULL);
-
-				/*
-				 * Suppress status printing if the report would go to the same
-				 * place as the COPY data just went.  Note this doesn't
-				 * prevent error reporting, since handleCopyOut did that.
-				 */
-				if (copystream == pset.queryFout)
-				{
-					PQclear(copy_result);
-					copy_result = NULL;
-				}
-
-				if (need_close)
-				{
-					/* close \g argument file/pipe */
-					if (is_pipe)
-					{
-						pclose(copystream);
-						restore_sigpipe_trap();
-					}
-					else
-					{
-						fclose(copystream);
-					}
-				}
+				need_close = true;
+				if (is_pipe)
+					disable_sigpipe_trap();
 			}
 			else
-			{
-				/* COPY IN */
-				copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
-				success = handleCopyIn(pset.db,
-									   copystream,
-									   PQbinaryTuples(*results),
-									   &copy_result) && success;
-			}
-			ResetCancelConn();
-
-			/*
-			 * Replace the PGRES_COPY_OUT/IN result with COPY command's exit
-			 * status, or with NULL if we want to suppress printing anything.
-			 */
-			PQclear(*results);
-			*results = copy_result;
+				copystream = NULL;	/* discard COPY data entirely */
 		}
-		else if (first_cycle)
+		else
 		{
-			/* fast path: no COPY commands; PQexec visited all results */
-			break;
+			/* fall back to the generic query output stream */
+			copystream = pset.queryFout;
 		}
 
+		success = handleCopyOut(pset.db,
+								copystream,
+								&copy_result)
+			&& success
+			&& (copystream != NULL);
+
 		/*
-		 * Check PQgetResult() again.  In the typical case of a single-command
-		 * string, it will return NULL.  Otherwise, we'll have other results
-		 * to process that may include other COPYs.  We keep the last result.
+		 * Suppress status printing if the report would go to the same
+		 * place as the COPY data just went.  Note this doesn't
+		 * prevent error reporting, since handleCopyOut did that.
 		 */
-		next_result = PQgetResult(pset.db);
-		if (!next_result)
-			break;
+		if (copystream == pset.queryFout)
+		{
+			PQclear(copy_result);
+			copy_result = NULL;
+		}
 
-		PQclear(*results);
-		*results = next_result;
-		first_cycle = false;
+		if (need_close)
+		{
+			/* close \g argument file/pipe */
+			if (is_pipe)
+			{
+				pclose(copystream);
+				restore_sigpipe_trap();
+			}
+			else
+			{
+				fclose(copystream);
+			}
+		}
 	}
+	else
+	{
+		/* COPY IN */
+		copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
+		success = handleCopyIn(pset.db,
+							   copystream,
+							   PQbinaryTuples(*result),
+							   &copy_result) && success;
+	}
+	ResetCancelConn();
 
-	SetResultVariables(*results, success);
-
-	/* may need this to recover from conn loss during COPY */
-	if (!first_cycle && !CheckConnection())
-		return false;
+	PQclear(*result);
+	*result = copy_result;
 
 	return success;
 }
 
-
 /*
  * PrintQueryStatus: report command status as required
  *
- * Note: Utility function for use by PrintQueryResults() only.
+ * Note: Utility function for use by HandleQueryResult() only.
  */
 static void
 PrintQueryStatus(PGresult *results)
@@ -1219,43 +1136,49 @@ PrintQueryStatus(PGresult *results)

 /*
- * PrintQueryResults: print out (or store or execute) query results as required
- *
- * Note: Utility function for use by SendQuery() only.
+ * HandleQueryResult: print out, store or execute one query result
+ * as required.
  *
  * Returns true if the query executed successfully, false otherwise.
  */
 static bool
-PrintQueryResults(PGresult *results)
+HandleQueryResult(PGresult *result, bool last)
 {
 	bool		success;
 	const char *cmdstatus;
 
-	if (!results)
+	if (result == NULL)
 		return false;
 
-	switch (PQresultStatus(results))
+	switch (PQresultStatus(result))
 	{
 		case PGRES_TUPLES_OK:
 			/* store or execute or print the data ... */
-			if (pset.gset_prefix)
-				success = StoreQueryTuple(results);
-			else if (pset.gexec_flag)
-				success = ExecQueryTuples(results);
-			else if (pset.crosstab_flag)
-				success = PrintResultsInCrosstab(results);
+			if (last && pset.gset_prefix)
+				success = StoreQueryTuple(result);
+			else if (last && pset.gexec_flag)
+				success = ExecQueryTuples(result);
+			else if (last && pset.crosstab_flag)
+				success = PrintResultsInCrosstab(result);
+			else if (last || pset.show_all_results)
+				success = PrintQueryTuples(result);
+				/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
 			else
-				success = PrintQueryTuples(results);
-			/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
-			cmdstatus = PQcmdStatus(results);
-			if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
-				strncmp(cmdstatus, "UPDATE", 6) == 0 ||
-				strncmp(cmdstatus, "DELETE", 6) == 0)
-				PrintQueryStatus(results);
+				success = true;
+
+			if (last || pset.show_all_results)
+			{
+				cmdstatus = PQcmdStatus(result);
+				if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
+					strncmp(cmdstatus, "UPDATE", 6) == 0 ||
+					strncmp(cmdstatus, "DELETE", 6) == 0)
+					PrintQueryStatus(result);
+			}
 			break;
 
 		case PGRES_COMMAND_OK:
-			PrintQueryStatus(results);
+			if (last || pset.show_all_results)
+				PrintQueryStatus(result);
 			success = true;
 			break;
 
@@ -1265,7 +1188,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
-			/* nothing to do here */
+			/* nothing to do here: already processed */
 			success = true;
 			break;
 
@@ -1278,7 +1201,7 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-					   PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}
 
@@ -1287,6 +1210,153 @@ PrintQueryResults(PGresult *results)
 	return success;
 }
 
+/*
+ * Data structure and functions to record notices while they are
+ * emitted, so that they can be shown later.
+ *
+ * We need to know which result is last, which requires to extract
+ * one result in advance, hence two buffers are needed.
+ */
+typedef struct {
+	bool			in_flip;
+	PQExpBufferData	flip;
+	PQExpBufferData	flop;
+} t_notice_messages;
+
+static void
+AppendNoticeMessage(void *arg, const char *msg)
+{
+	t_notice_messages *notes = (t_notice_messages*) arg;
+	appendPQExpBufferStr(notes->in_flip ? &notes->flip : &notes->flop, msg);
+}
+
+static void
+ShowNoticeMessage(t_notice_messages *notes)
+{
+	PQExpBufferData	*current = notes->in_flip ? &notes->flip : &notes->flop;
+	if (current->data != NULL && *current->data != '\0')
+		pg_log_info("%s", current->data);
+	resetPQExpBuffer(current);
+}
+
+/*
+ * SendQueryAndProcessResults: utility function for use by SendQuery() only
+ *
+ * Sends query and cycles through PGresult objects.
+ *
+ * When our command string contained a COPY FROM STDIN or COPY TO STDOUT,
+ * the PGresult associated with these commands must be processed.  In that
+ * event, we'll marshal data for the COPY.
+ *
+ * Returns true on complete success, false otherwise.  Possible failure modes
+ * include purely client-side problems; check the transaction status for the
+ * server-side opinion.
+ *
+ * Note that on a combined query, failure does not mean that nothing was
+ * committed.
+ */
+static bool
+SendQueryAndProcessResults(const char *query, double *pelapsed_msec)
+{
+	bool				success;
+	instr_time			before;
+	PGresult		   *result;
+	t_notice_messages	notes;
+
+	if (pset.timing)
+		INSTR_TIME_SET_CURRENT(before);
+
+	success = PQsendQuery(pset.db, query);
+	ResetCancelConn();
+
+	if (!success)
+		return false;
+
+	/* intercept notices */
+	notes.in_flip = true;
+	initPQExpBuffer(&notes.flip);
+	initPQExpBuffer(&notes.flop);
+	PQsetNoticeProcessor(pset.db, AppendNoticeMessage, &notes);
+
+	/* first result */
+	result = PQgetResult(pset.db);
+
+	while (result != NULL)
+	{
+		ExecStatusType result_status;
+		PGresult   *next_result;
+		bool		last;
+
+		if (!AcceptResult(result, false))
+		{
+			/* some error occured, record that */
+			ShowNoticeMessage(&notes);
+			ShowErrorMessage(result);
+			SetResultVariables(result, false);
+			PQclear(result);
+			success = false;
+
+			/* and switch to next result */
+			result = PQgetResult(pset.db);
+			continue;
+		}
+
+		/* must handle COPY before changing the current result */
+		result_status = PQresultStatus(result);
+		if (result_status == PGRES_COPY_IN ||
+			result_status == PGRES_COPY_OUT)
+		{
+			ShowNoticeMessage(&notes);
+			HandleCopyResult(&result);
+		}
+
+		/*
+		 * Check PQgetResult() again.  In the typical case of a single-command
+		 * string, it will return NULL.  Otherwise, we'll have other results
+		 * to process that may include other COPYs.
+		 */
+		notes.in_flip = !notes.in_flip;
+		next_result = PQgetResult(pset.db);
+		notes.in_flip = !notes.in_flip;
+		last = (next_result == NULL);
+
+		/* timing measure before printing the last result */
+		if (last && pset.timing)
+		{
+			instr_time	now;
+			INSTR_TIME_SET_CURRENT(now);
+			INSTR_TIME_SUBTRACT(now, before);
+			*pelapsed_msec = INSTR_TIME_GET_MILLISEC(now);
+		}
+
+		/* notices already show above for copy */
+		ShowNoticeMessage(&notes);
+
+		/* this may or may not print something depending on settings */
+		if (result != NULL)
+			success &= HandleQueryResult(result, last);
+
+		/* set variables on last result if all went well */
+		if (last && success)
+			SetResultVariables(result, true);
+
+		PQclear(result);
+		notes.in_flip = !notes.in_flip;
+		result = next_result;
+	}
+
+	/* reset notice hook */
+	PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
+	termPQExpBuffer(&notes.flip);
+	termPQExpBuffer(&notes.flop);
+
+	/* may need this to recover from conn loss during COPY */
+	if (!CheckConnection())
+		return false;
+
+	return success;
+}
+
 
 /*
  * SendQuery: send the query string to the backend
@@ -1303,7 +1373,7 @@ PrintQueryResults(PGresult *results)
 bool
 SendQuery(const char *query)
 {
-	PGresult   *results;
+	PGresult   *results = NULL;
 	PGTransactionStatusType transaction_status;
 	double		elapsed_msec = 0;
 	bool		OK = false;
@@ -1408,28 +1478,7 @@ SendQuery(const char *query)
 			 pset.crosstab_flag || !is_select_command(query))
 	{
 		/* Default fetch-it-all-and-print mode */
-		instr_time	before,
-					after;
-
-		if (pset.timing)
-			INSTR_TIME_SET_CURRENT(before);
-
-		results = PQexec(pset.db, query);
-
-		/* these operations are included in the timing result: */
-		ResetCancelConn();
-		OK = ProcessResult(&results);
-
-		if (pset.timing)
-		{
-			INSTR_TIME_SET_CURRENT(after);
-			INSTR_TIME_SUBTRACT(after, before);
-			elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
-		}
-
-		/* but printing results isn't: */
-		if (OK && results)
-			OK = PrintQueryResults(results);
+		OK = SendQueryAndProcessResults(query, &elapsed_msec);
 	}
 	else
 	{
@@ -1606,7 +1655,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
 	results = PQdescribePrepared(pset.db, "");
-	OK = AcceptResult(results) &&
+	OK = AcceptResult(results, true) &&
 		(PQresultStatus(results) == PGRES_COMMAND_OK);
 	if (OK && results)
 	{
@@ -1654,7 +1703,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);
 
 			results = PQexec(pset.db, buf.data);
-			OK = AcceptResult(results);
+			OK = AcceptResult(results, true);
 
 			if (pset.timing)
 			{
@@ -1664,7 +1713,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
-				OK = PrintQueryResults(results);
+				OK = HandleQueryResult(results, true);
 
 			termPQExpBuffer(&buf);
 		}
@@ -1723,7 +1772,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1737,7 +1786,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
 	results = PQexec(pset.db, buf.data);
-	OK = AcceptResult(results) &&
+	OK = AcceptResult(results, true) &&
 		(PQresultStatus(results) == PGRES_COMMAND_OK);
 	if (!OK)
 		SetResultVariables(results, OK);
@@ -1814,7 +1863,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
-			OK = AcceptResult(results);
+			OK = AcceptResult(results, true);
 			Assert(!OK);
 			SetResultVariables(results, OK);
 			ClearOrSaveResult(results);
@@ -1923,7 +1972,7 @@ cleanup:
 	results = PQexec(pset.db, "CLOSE _psql_cursor");
 	if (OK)
 	{
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 	}
@@ -1933,7 +1982,7 @@ cleanup:
 	if (started_txn)
 	{
 		results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
-		OK &= AcceptResult(results) &&
+		OK &= AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 	}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index d6d41b51d5..3cc71731b3 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -404,6 +404,8 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  SERVER_VERSION_NAME\n"
 					  "  SERVER_VERSION_NUM\n"
 					  "    server's version (in short string or numeric format)\n"));
+	fprintf(output, _("  SHOW_ALL_RESULTS\n"
+					  "    show all results of a combined query (\\;) instead of only the last\n"));
 	fprintf(output, _("  SHOW_CONTEXT\n"
 					  "    controls display of message context fields [never, errors, always]\n"));
 	fprintf(output, _("  SINGLELINE\n"
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 5be5091f0e..f26bf7fc6b 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -127,6 +127,7 @@ typedef struct _psqlSettings
 	bool		quiet;
 	bool		singleline;
 	bool		singlestep;
+	bool		show_all_results;
 	bool		hide_tableam;
 	int			fetch_count;
 	int			histsize;
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 855133bbcb..24b2fc8c28 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -882,6 +882,12 @@ singlestep_hook(const char *newval)
 	return ParseVariableBool(newval, "SINGLESTEP", &pset.singlestep);
 }
 
+static bool
+show_all_results_hook(const char *newval)
+{
+	return ParseVariableBool(newval, "SHOW_ALL_RESULTS", &pset.show_all_results);
+}
+
 static char *
 fetch_count_substitute_hook(char *newval)
 {
@@ -1182,6 +1188,9 @@ EstablishVariableSpace(void)
 	SetVariableHooks(pset.vars, "SINGLESTEP",
 					 bool_substitute_hook,
 					 singlestep_hook);
+	SetVariableHooks(pset.vars, "SHOW_ALL_RESULTS",
+					 bool_substitute_hook,
+					 show_all_results_hook);
 	SetVariableHooks(pset.vars, "FETCH_COUNT",
 					 fetch_count_substitute_hook,
 					 fetch_count_hook);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index bcddc7601e..b66e5db9e2 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3662,7 +3662,7 @@ psql_completion(const char *text, int start, int end)
 	else if (TailMatchesCS("\\set", MatchAny))
 	{
 		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
-						  "SINGLELINE|SINGLESTEP"))
+						  "SINGLELINE|SINGLESTEP|SHOW_ALL_RESULTS"))
 			COMPLETE_WITH_CS("on", "off");
 		else if (TailMatchesCS("COMP_KEYWORD_CASE"))
 			COMPLETE_WITH_CS("lower", "upper",
diff --git a/src/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index 75d4119eaa..4fa6343795 100644
--- a/src/test/regress/expected/copy2.out
+++ b/src/test/regress/expected/copy2.out
@@ -475,10 +475,10 @@ copy check_con_tbl from stdin;
 NOTICE:  input = {"f1":1}
 NOTICE:  input = {"f1":null}
 copy check_con_tbl from stdin;
-NOTICE:  input = {"f1":0}
 ERROR:  new row for relation "check_con_tbl" violates check constraint "check_con_tbl_check"
 DETAIL:  Failing row contains (0).
 CONTEXT:  COPY check_con_tbl, line 1: "0"
+NOTICE:  input = {"f1":0}
 select * from check_con_tbl;
  f1 
 ----
diff --git a/src/test/regress/expected/copydml.out b/src/test/regress/expected/copydml.out
index 1b533962c6..b5a225628f 100644
--- a/src/test/regress/expected/copydml.out
+++ b/src/test/regress/expected/copydml.out
@@ -84,10 +84,10 @@ drop rule qqq on copydml_test;
 create function qqq_trig() returns trigger as $$
 begin
 if tg_op in ('INSERT', 'UPDATE') then
-    raise notice '% %', tg_op, new.id;
+    raise notice '% % %', tg_when, tg_op, new.id;
     return new;
 else
-    raise notice '% %', tg_op, old.id;
+    raise notice '% % %', tg_when, tg_op, old.id;
     return old;
 end if;
 end
@@ -97,16 +97,16 @@ create trigger qqqbef before insert or update or delete on copydml_test
 create trigger qqqaf after insert or update or delete on copydml_test
     for each row execute procedure qqq_trig();
 copy (insert into copydml_test (t) values ('f') returning id) to stdout;
-NOTICE:  INSERT 8
+NOTICE:  BEFORE INSERT 8
 8
-NOTICE:  INSERT 8
+NOTICE:  AFTER INSERT 8
 copy (update copydml_test set t = 'g' where t = 'f' returning id) to stdout;
-NOTICE:  UPDATE 8
+NOTICE:  BEFORE UPDATE 8
 8
-NOTICE:  UPDATE 8
+NOTICE:  AFTER UPDATE 8
 copy (delete from copydml_test where t = 'g' returning id) to stdout;
-NOTICE:  DELETE 8
+NOTICE:  BEFORE DELETE 8
 8
-NOTICE:  DELETE 8
+NOTICE:  AFTER DELETE 8
 drop table copydml_test;
 drop function qqq_trig();
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 35856bffdd..bca5bf2fe7 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4729,3 +4729,89 @@ drop schema testpart;
 set search_path to default;
 set role to default;
 drop role testrole_partitioning;
+--
+-- combined queries & SHOW_ALL_RESULTS option
+--
+\echo '# SHOW_ALL_RESULTS tests'
+# SHOW_ALL_RESULTS tests
+CREATE FUNCTION warn(msg TEXT) RETURNS BOOLEAN AS $$
+  BEGIN RAISE NOTICE 'warn %', msg ; RETURN TRUE ; END
+$$ LANGUAGE plpgsql;
+\set SHOW_ALL_RESULTS on
+-- show both
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+ one 
+-----
+   1
+(1 row)
+
+NOTICE:  warn 1.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ warn 
+------
+ t
+(1 row)
+
+ two 
+-----
+   2
+(1 row)
+
+-- \gset applies to last query only
+SELECT 3 AS three \; SELECT warn('3.5') \; SELECT 4 AS four \gset
+ three 
+-------
+     3
+(1 row)
+
+NOTICE:  warn 3.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ warn 
+------
+ t
+(1 row)
+
+\echo :three :four
+:three 4
+-- syntax error stops all processing
+SELECT 5 \; SELECT 6 + \; SELECT warn('6.5') \; SELECT 7 ;
+ERROR:  syntax error at or near ";"
+LINE 1: SELECT 5 ; SELECT 6 + ; SELECT warn('6.5') ; SELECT 7 ;
+                              ^
+-- with aborted transaction, stop on first error
+BEGIN \; SELECT 8 AS eight \; SELECT 9/0 AS nine \; ROLLBACK \; SELECT 10 AS ten ;
+ eight 
+-------
+     8
+(1 row)
+
+ERROR:  division by zero
+-- close previously aborted transaction
+ROLLBACK;
+-- misc SQL commands
+-- (non SELECT output is sent to stderr, thus is not shown in expected results)
+SELECT 'ok' AS "begin" \;
+CREATE TABLE psql_comics(s TEXT) \;
+INSERT INTO psql_comics VALUES ('Calvin'), ('hobbes') \;
+COPY psql_comics FROM STDIN \;
+UPDATE psql_comics SET s = 'Hobbes' WHERE s = 'hobbes' \;
+DELETE FROM psql_comics WHERE s = 'Moe' \;
+COPY psql_comics TO STDOUT \;
+TRUNCATE psql_comics \;
+DROP TABLE psql_comics \;
+SELECT 'ok' AS "done" ;
+ begin 
+-------
+ ok
+(1 row)
+
+Calvin
+Susie
+Hobbes
+ done 
+------
+ ok
+(1 row)
+
+\set SHOW_ALL_RESULTS off
+DROP FUNCTION warn(TEXT);
diff --git a/src/test/regress/sql/copydml.sql b/src/test/regress/sql/copydml.sql
index 9a29f9c9ac..4578342253 100644
--- a/src/test/regress/sql/copydml.sql
+++ b/src/test/regress/sql/copydml.sql
@@ -70,10 +70,10 @@ drop rule qqq on copydml_test;
 create function qqq_trig() returns trigger as $$
 begin
 if tg_op in ('INSERT', 'UPDATE') then
-    raise notice '% %', tg_op, new.id;
+    raise notice '% % %', tg_when, tg_op, new.id;
     return new;
 else
-    raise notice '% %', tg_op, old.id;
+    raise notice '% % %', tg_when, tg_op, old.id;
     return old;
 end if;
 end
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 78f4b5d7d5..03a378de3d 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1115,3 +1115,40 @@ set search_path to default;
 
 set role to default;
 drop role testrole_partitioning;
+
+--
+-- combined queries & SHOW_ALL_RESULTS option
+--
+\echo '# SHOW_ALL_RESULTS tests'
+CREATE FUNCTION warn(msg TEXT) RETURNS BOOLEAN AS $$
+  BEGIN RAISE NOTICE 'warn %', msg ; RETURN TRUE ; END
+$$ LANGUAGE plpgsql;
+\set SHOW_ALL_RESULTS on
+-- show both
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+-- \gset applies to last query only
+SELECT 3 AS three \; SELECT warn('3.5') \; SELECT 4 AS four \gset
+\echo :three :four
+-- syntax error stops all processing
+SELECT 5 \; SELECT 6 + \; SELECT warn('6.5') \; SELECT 7 ;
+-- with aborted transaction, stop on first error
+BEGIN \; SELECT 8 AS eight \; SELECT 9/0 AS nine \; ROLLBACK \; SELECT 10 AS ten ;
+-- close previously aborted transaction
+ROLLBACK;
+-- misc SQL commands
+-- (non SELECT output is sent to stderr, thus is not shown in expected results)
+SELECT 'ok' AS "begin" \;
+CREATE TABLE psql_comics(s TEXT) \;
+INSERT INTO psql_comics VALUES ('Calvin'), ('hobbes') \;
+COPY psql_comics FROM STDIN \;
+UPDATE psql_comics SET s = 'Hobbes' WHERE s = 'hobbes' \;
+DELETE FROM psql_comics WHERE s = 'Moe' \;
+COPY psql_comics TO STDOUT \;
+TRUNCATE psql_comics \;
+DROP TABLE psql_comics \;
+SELECT 'ok' AS "done" ;
+Moe
+Susie
+\.
+\set SHOW_ALL_RESULTS off
+DROP FUNCTION warn(TEXT);

---

From: "Daniel Verite" <[email-masked]>
Date: Wed, 15 May 2019 18:41:32 +0200

> Ok. I did not yet change the default in the attached version, though.

I'd go further and suggest that there shouldn't be a variable
controlling this. All results that come in should be processed, period.
It's not just about \; If the ability of CALL to produce multiple
resultsets gets implemented (it was posted as a POC during v11
development), this will be needed too.

>  - include somehow improved tests

\errverbose seems to no longer work with the patch:

test=> select 1/0;
psql: ERROR:  division by zero

test=> \errverbose
There is no previous error.

as opposed to this output with PG11:

test=> \errverbose
ERROR:	22012: division by zero
LOCATION:  int4div, int.c:820

\errverbose has probably no regression tests because its output
includes these ever-changing line numbers; hence `make check`
cannot be used to find this regression.

Best regards,
-- 
Daniel Vérité
PostgreSQL-powered mailer: http://www.manitou-mail.org
Twitter: @DanielVerite

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 16 May 2019 10:43:08 +0200 (CEST)

Bonjour Daniel,

> development), this will be needed too.

I do agree, but I'm afraid that if there is no opt-out it could be seen as 
a regression by some.

> LOCATION:  int4div, int.c:820

Thanks for the catch. I'll investigate.

> \errverbose has probably no regression tests because its output includes 
> these ever-changing line numbers; hence `make check` cannot be used to 
> find this regression.

What is not tested does not work:-( The TAP infrastructure for psql 
included in some patch (https://commitfest.postgresql.org/23/2100/ I 
guess) would help testing such slightly varying features which cannot be 
tested with a hardcoded reference text.

-- 
Fabien.

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 16 May 2019 17:15:49 +0200 (CEST)

Re-bonjour Daniel,

> \errverbose seems to no longer work with the patch:

Here is a v3 which fixes \errverbose, hopefully.

The feature is still an option which is not enabled by default.

-- 
Fabien.

+      <varlistentry>
index 9579e10630..d35ab91995 100644
 
+static void
 
 /*
  * AcceptResult

-	if (!OK)
-	{
 
 	return OK;
 }
 
 	ResetCancelConn();

 	ResetCancelConn();

-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:

 
+/*
+			ClearOrSaveResult(result);
+		ClearOrSaveResult(result);
 
 /*

 
 			if (pset.timing)
 			{
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}

index 5fb1baadc5..5d0449c26c 100644
index 7c5b45f7cc..42ef739f96 100644
@@ -883,6 +883,12 @@ singlestep_hook(const char *newval)
 
+static bool
@@ -1183,6 +1189,9 @@ EstablishVariableSpace(void)
index e4c03de221..e25afad304 100644
 
 set role to default;

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 23 May 2019 17:11:20 +0200 (CEST)

> Here is a v3 which fixes \errverbose, hopefully.

V5 is a rebase and an slightly improved documentation.

-- 
Fabien.

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index b86764003d..4a1b562f24 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3341,7 +3341,7 @@ select 1\; select 2\; select 3;
         <application>psql</application> prints only the last query result
         it receives for each request; in this example, although all
         three <command>SELECT</command>s are indeed executed, <application>psql</application>
-        only prints the <literal>3</literal>.
+        only prints the <literal>3</literal>, unless <varname>SHOW_ALL_RESULTS</varname> is set.
         </para>
         </listitem>
       </varlistentry>
@@ -3918,6 +3918,17 @@ bar
         </listitem>
       </varlistentry>
 
+      <varlistentry>
index 44a782478d..dc575911ce 100644
 
+static void
 
 /*
  * AcceptResult

-	if (!OK)
-	{
 
 	return OK;
 }
 
 	ResetCancelConn();

 	ResetCancelConn();

-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:

 
+/*
 
 /*

 
 			if (pset.timing)
 			{
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}

index bb9921a894..8b5038e471 100644
 
+static bool
index c6347b6190..4b285363ab 100644
@@ -3664,7 +3664,7 @@ psql_completion(const char *text, int start, int end)
index c53ed3ebf5..9c8c8361f8 100644
 
 set role to default;

---

From: Fabien COELHO <[email-masked]>
Date: Wed, 26 Jun 2019 22:27:28 +0200 (CEST)

> V5 is a rebase and an slightly improved documentation.

It was really v4. v5 is a rebase.

-- 
Fabien.

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index c6c20de243..a7557af0fe 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3344,7 +3344,7 @@ select 1\; select 2\; select 3;
         <application>psql</application> prints only the last query result
         it receives for each request; in this example, although all
         three <command>SELECT</command>s are indeed executed, <application>psql</application>
-        only prints the <literal>3</literal>.
+        only prints the <literal>3</literal>, unless <varname>SHOW_ALL_RESULTS</varname> is set.
         </para>
         </listitem>
       </varlistentry>
@@ -3921,6 +3921,17 @@ bar
         </listitem>
       </varlistentry>
 
+      <varlistentry>
 
+static void
 
 /*
  * AcceptResult

-	if (!OK)
-	{
 
 	return OK;
 }
 
 	ResetCancelConn();

 	ResetCancelConn();

-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:

 
+/*
 
 /*

 
 			if (pset.timing)
 			{
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}

 
+static bool
index 7dcf342413..9d18ea1fa3 100644
@@ -3682,7 +3682,7 @@ psql_completion(const char *text, int start, int end)
index 4bcf0cc5df..e534443489 100644
 drop role regress_partitioning_role;
index 26f436ae40..8e9e5de64b 100644
 
 set role to default;
 drop role regress_partitioning_role;
+
+--

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 22 Jul 2019 13:35:45 +0200

> controlling this. All results that come in should be processed, period.

I agree with that.

> development), this will be needed too.

See previous patch here:
https://www.postgresql.org/message-id/flat/4580ff7b-d610-eaeb-e06f-4d686896b93b%402ndquadrant.com

In that patch, I discussed the specific ways in which \timing works in
psql and how that conflicts with multiple result sets.  What is the
solution to that in this patch?

-- 
Peter Eisentraut              http://www.2ndQuadrant.com/
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

---

From: Fabien COELHO <[email-masked]>
Date: Mon, 22 Jul 2019 13:33:55 +0000 (GMT)

Hello Peter,

>> controlling this. All results that come in should be processed, period.

I kind of agree as well, but I was pretty sure that someone would complain 
if the current behavior was changed.

Should I produce a patch where the behavior is not an option, or turn the 
option on by default, or just keep it like that for the time being?

> solution to that in this patch?

\timing was kind of a ugly feature to work around. The good intention 
behind \timing is that it should reflect the time to perform the query 
from the client perspective, but should not include processing the 
results.

However, if a message results in several queries, they are processed as 
they arrive, so that \timing reports the time to perform all queries and 
the time to process all but the last result.

Although on paper we could try to get all results first, take the time, 
then process them, this does not work in the general case because COPY 
takes on the connection so you have to process its result before switching 
to the next result.

There is also some stuff to handle notices which are basically send as 
events when they occur, so that the notice shown are related to the 
result under processing.

-- 
Fabien.

---

From: "Daniel Verite" <[email-masked]>
Date: Wed, 24 Jul 2019 14:59:05 +0200

> if the current behavior was changed.

If queries in a compound statement must be kept silent,
they can be converted to CTEs or DO-blocks to produce the
same behavior without having to configure anything in psql.
That cost on users doesn't seem too bad, compared to introducing
a knob in psql, and presumably maintaining it forever.

Best regards,

---

From: Fabien COELHO <[email-masked]>
Date: Wed, 24 Jul 2019 19:41:24 +0000 (GMT)

Bonjour Daniel,

> a knob in psql, and presumably maintaining it forever.

Ok.

Attached a "do it always version", which does the necessary refactoring. 
There is seldom new code, it is rather moved around, some functions are 
created for clarity.

-- 
Fabien.

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 7789fc6177..5e4f653f88 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -3355,10 +3355,8 @@ select 1\; select 2\; select 3;
         commands included in the string to divide it into multiple
         transactions.  (See <xref linkend="protocol-flow-multi-statement"/>
         for more details about how the server handles multi-query strings.)
-        <application>psql</application> prints only the last query result
-        it receives for each request; in this example, although all
-        three <command>SELECT</command>s are indeed executed, <application>psql</application>
-        only prints the <literal>3</literal>.
+        <application>psql</application> prints all results it receives, one
+        after the other.
         </para>
         </listitem>
       </varlistentry>
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 44a782478d..4534c45854 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -472,6 +472,16 @@ ResetCancelConn(void)
 #endif
 }
 
+static void
 
 /*
  * AcceptResult

-	if (!OK)
-	{
 
 	return OK;
 }
 
 	ResetCancelConn();

 	ResetCancelConn();

-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*
@@ -1219,43 +1136,44 @@ PrintQueryStatus(PGresult *results)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;
 
 			/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
 			if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
 				strncmp(cmdstatus, "UPDATE", 6) == 0 ||
 				strncmp(cmdstatus, "DELETE", 6) == 0)
 
 		case PGRES_COMMAND_OK:
 
@@ -1265,7 +1183,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1278,7 +1196,7 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}
 
@@ -1287,6 +1205,153 @@ PrintQueryResults(PGresult *results)
 	return success;
 }
 
+/*
 
 /*
@@ -1303,7 +1368,7 @@ PrintQueryResults(PGresult *results)
@@ -1408,28 +1473,7 @@ SendQuery(const char *query)
@@ -1606,7 +1650,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1654,7 +1698,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (pset.timing)
 			{
@@ -1664,7 +1708,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}
@@ -1723,7 +1767,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1737,7 +1781,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1814,7 +1858,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
@@ -1923,7 +1967,7 @@ cleanup:
@@ -1933,7 +1977,7 @@ cleanup:
index d9b982d3a0..61c67b9551 100644
@@ -405,6 +405,8 @@ helpVariables(unsigned short int pager)
index 3f7001fb69..8bb894bafe 100644
@@ -3737,7 +3737,7 @@ psql_completion(const char *text, int start, int end)
diff --git a/src/test/regress/expected/copyselect.out b/src/test/regress/expected/copyselect.out
index 72865fe1eb..39ab8fc87a 100644
--- a/src/test/regress/expected/copyselect.out
+++ b/src/test/regress/expected/copyselect.out
@@ -126,7 +126,7 @@ copy (select 1) to stdout\; select 1/0;	-- row, then error
 ERROR:  division by zero
 select 1/0\; copy (select 1) to stdout; -- error only
 ERROR:  division by zero
-copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; -- 1 2 3
+copy (select 1) to stdout\; copy (select 2) to stdout\; select 3\; select 4; -- 1 2 3 4
 1
 2
  ?column? 
@@ -134,8 +134,18 @@ copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; --
         3
 (1 row)
 
+ ?column? 
+----------
+        4
+(1 row)
+
 create table test3 (c int);
 select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
+ ?column? 
+----------
+        0
+(1 row)
+
  ?column? 
 ----------
         1
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index ef534a36a0..a93736187a 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4760,3 +4760,87 @@ Owning table: "pg_catalog.pg_statistic"
 Indexes:
     "pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
 
+--
+-- combined queries
+--
+\echo '# combined queries tests'
+# combined queries tests
+CREATE FUNCTION warn(msg TEXT) RETURNS BOOLEAN AS $$
+  BEGIN RAISE NOTICE 'warn %', msg ; RETURN TRUE ; END
+$$ LANGUAGE plpgsql;
+-- show both
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+ one 
+-----
+   1
+(1 row)
+
+NOTICE:  warn 1.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ warn 
+------
+ t
+(1 row)
+
+ two 
+-----
+   2
+(1 row)
+
+-- \gset applies to last query only
+SELECT 3 AS three \; SELECT warn('3.5') \; SELECT 4 AS four \gset
+ three 
+-------
+     3
+(1 row)
+
+NOTICE:  warn 3.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ warn 
+------
+ t
+(1 row)
+
+\echo :three :four
+:three 4
+-- syntax error stops all processing
+SELECT 5 \; SELECT 6 + \; SELECT warn('6.5') \; SELECT 7 ;
+ERROR:  syntax error at or near ";"
+LINE 1: SELECT 5 ; SELECT 6 + ; SELECT warn('6.5') ; SELECT 7 ;
+                              ^
+-- with aborted transaction, stop on first error
+BEGIN \; SELECT 8 AS eight \; SELECT 9/0 AS nine \; ROLLBACK \; SELECT 10 AS ten ;
+ eight 
+-------
+     8
+(1 row)
+
+ERROR:  division by zero
+-- close previously aborted transaction
+ROLLBACK;
+-- misc SQL commands
+-- (non SELECT output is sent to stderr, thus is not shown in expected results)
+SELECT 'ok' AS "begin" \;
+CREATE TABLE psql_comics(s TEXT) \;
+INSERT INTO psql_comics VALUES ('Calvin'), ('hobbes') \;
+COPY psql_comics FROM STDIN \;
+UPDATE psql_comics SET s = 'Hobbes' WHERE s = 'hobbes' \;
+DELETE FROM psql_comics WHERE s = 'Moe' \;
+COPY psql_comics TO STDOUT \;
+TRUNCATE psql_comics \;
+DROP TABLE psql_comics \;
+SELECT 'ok' AS "done" ;
+ begin 
+-------
+ ok
+(1 row)
+
+Calvin
+Susie
+Hobbes
+ done 
+------
+ ok
+(1 row)
+
+DROP FUNCTION warn(TEXT);
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 1b316cc9b8..3e62f83918 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -855,11 +855,21 @@ DROP TABLE abc;
 -- tests rely on the fact that psql will not break SQL commands apart at a
 -- backslash-quoted semicolon, but will send them as one Query.
 create temp table i_table (f1 int);
--- psql will show only the last result in a multi-statement Query
-SELECT 1\; SELECT 2\; SELECT 3;
- ?column? 
-----------
-        3
+-- psql will show all results of a multi-statement Query
+SELECT 1 AS one\; SELECT 2 AS two\; SELECT 3 AS three;
+ one 
+-----
+   1
+(1 row)
+
+ two 
+-----
+   2
+(1 row)
+
+ three 
+-------
+     3
 (1 row)
 
 -- this implicitly commits:
@@ -871,6 +881,12 @@ insert into i_table values(1)\; select * from i_table;
 
 -- 1/0 error will cause rolling back the whole implicit transaction
 insert into i_table values(2)\; select * from i_table\; select 1/0;
+ f1 
+----
+  1
+  2
+(2 rows)
+
 ERROR:  division by zero
 select * from i_table;
  f1 
@@ -889,9 +905,19 @@ rollback;  -- we are not in a transaction at this point
 WARNING:  there is no transaction in progress
 -- begin converts implicit transaction into a regular one that
 -- can extend past the end of the Query
-select 1\; begin\; insert into i_table values(5);
+select 1 AS one\; begin\; insert into i_table values(5);
+ one 
+-----
+   1
+(1 row)
+
 commit;
-select 1\; begin\; insert into i_table values(6);
+select 1 AS one\; begin\; insert into i_table values(6);
+ one 
+-----
+   1
+(1 row)
+
 rollback;
 -- commit in implicit-transaction state commits but issues a warning.
 insert into i_table values(7)\; commit\; insert into i_table values(8)\; select 1/0;
@@ -917,23 +943,53 @@ select * from i_table;
 rollback;  -- we are not in a transaction at this point
 WARNING:  there is no transaction in progress
 -- implicit transaction block is still a transaction block, for e.g. VACUUM
-SELECT 1\; VACUUM;
+SELECT 1 AS one\; VACUUM;
+ one 
+-----
+   1
+(1 row)
+
 ERROR:  VACUUM cannot run inside a transaction block
-SELECT 1\; COMMIT\; VACUUM;
+SELECT 2 AS two\; COMMIT\; VACUUM;
 WARNING:  there is no transaction in progress
+ two 
+-----
+   2
+(1 row)
+
 ERROR:  VACUUM cannot run inside a transaction block
 -- we disallow savepoint-related commands in implicit-transaction state
-SELECT 1\; SAVEPOINT sp;
+SELECT 3 AS three\; SAVEPOINT sp;
+ three 
+-------
+     3
+(1 row)
+
 ERROR:  SAVEPOINT can only be used in transaction blocks
-SELECT 1\; COMMIT\; SAVEPOINT sp;
+SELECT 4 AS four\; COMMIT\; SAVEPOINT sp;
 WARNING:  there is no transaction in progress
+ four 
+------
+    4
+(1 row)
+
 ERROR:  SAVEPOINT can only be used in transaction blocks
-ROLLBACK TO SAVEPOINT sp\; SELECT 2;
+ROLLBACK TO SAVEPOINT sp\; SELECT 5 AS five;
 ERROR:  ROLLBACK TO SAVEPOINT can only be used in transaction blocks
-SELECT 2\; RELEASE SAVEPOINT sp\; SELECT 3;
+SELECT 6 AS six\; RELEASE SAVEPOINT sp\; SELECT 7 AS seven;
+ six 
+-----
+   6
+(1 row)
+
 ERROR:  RELEASE SAVEPOINT can only be used in transaction blocks
 -- but this is OK, because the BEGIN converts it to a regular xact
-SELECT 1\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT;
+SELECT 8\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT;
+ ?column? 
+----------
+        8
+(1 row)
+
 -- Test for successful cleanup of an aborted transaction at session exit.
 -- THIS MUST BE THE LAST TEST IN THIS FILE.
 begin;
diff --git a/src/test/regress/sql/copydml.sql b/src/test/regress/sql/copydml.sql
index 9a29f9c9ac..4578342253 100644
--- a/src/test/regress/sql/copydml.sql
+++ b/src/test/regress/sql/copydml.sql
@@ -70,10 +70,10 @@ drop rule qqq on copydml_test;
 create function qqq_trig() returns trigger as $$
 begin
 if tg_op in ('INSERT', 'UPDATE') then
-    raise notice '% %', tg_op, new.id;
+    raise notice '% % %', tg_when, tg_op, new.id;
     return new;
 else
-    raise notice '% %', tg_op, old.id;
+    raise notice '% % %', tg_when, tg_op, old.id;
     return old;
 end if;
 end
diff --git a/src/test/regress/sql/copyselect.sql b/src/test/regress/sql/copyselect.sql
index 1d98dad3c8..abc33904c0 100644
--- a/src/test/regress/sql/copyselect.sql
+++ b/src/test/regress/sql/copyselect.sql
@@ -84,7 +84,7 @@ drop table test1;
 -- psql handling of COPY in multi-command strings
 copy (select 1) to stdout\; select 1/0;	-- row, then error
 select 1/0\; copy (select 1) to stdout; -- error only
-copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; -- 1 2 3
+copy (select 1) to stdout\; copy (select 2) to stdout\; select 3\; select 4; -- 1 2 3 4
 
 create table test3 (c int);
 select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 2e37984962..4cf3d6eb9d 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1134,3 +1134,38 @@ drop role regress_partitioning_role;
 
 -- \d on toast table (use pg_statistic's toast table, which has a known name)
 \d pg_toast.pg_toast_2619
+
+--
+-- combined queries
+--
+\echo '# combined queries tests'
+CREATE FUNCTION warn(msg TEXT) RETURNS BOOLEAN AS $$
+  BEGIN RAISE NOTICE 'warn %', msg ; RETURN TRUE ; END
+$$ LANGUAGE plpgsql;
+-- show both
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+-- \gset applies to last query only
+SELECT 3 AS three \; SELECT warn('3.5') \; SELECT 4 AS four \gset
+\echo :three :four
+-- syntax error stops all processing
+SELECT 5 \; SELECT 6 + \; SELECT warn('6.5') \; SELECT 7 ;
+-- with aborted transaction, stop on first error
+BEGIN \; SELECT 8 AS eight \; SELECT 9/0 AS nine \; ROLLBACK \; SELECT 10 AS ten ;
+-- close previously aborted transaction
+ROLLBACK;
+-- misc SQL commands
+-- (non SELECT output is sent to stderr, thus is not shown in expected results)
+SELECT 'ok' AS "begin" \;
+CREATE TABLE psql_comics(s TEXT) \;
+INSERT INTO psql_comics VALUES ('Calvin'), ('hobbes') \;
+COPY psql_comics FROM STDIN \;
+UPDATE psql_comics SET s = 'Hobbes' WHERE s = 'hobbes' \;
+DELETE FROM psql_comics WHERE s = 'Moe' \;
+COPY psql_comics TO STDOUT \;
+TRUNCATE psql_comics \;
+DROP TABLE psql_comics \;
+SELECT 'ok' AS "done" ;
+Moe
+Susie
+\.
+DROP FUNCTION warn(TEXT);
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 812e40a1a3..5d12fa8e86 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -489,8 +489,8 @@ DROP TABLE abc;
 
 create temp table i_table (f1 int);
 
--- psql will show only the last result in a multi-statement Query
-SELECT 1\; SELECT 2\; SELECT 3;
+-- psql will show all results of a multi-statement Query
+SELECT 1 AS one\; SELECT 2 AS two\; SELECT 3 AS three;
 
 -- this implicitly commits:
 insert into i_table values(1)\; select * from i_table;
@@ -508,9 +508,9 @@ rollback;  -- we are not in a transaction at this point
 
 -- begin converts implicit transaction into a regular one that
 -- can extend past the end of the Query
-select 1\; begin\; insert into i_table values(5);
+select 1 AS one\; begin\; insert into i_table values(5);
 commit;
-select 1\; begin\; insert into i_table values(6);
+select 1 AS one\; begin\; insert into i_table values(6);
 rollback;
 
 -- commit in implicit-transaction state commits but issues a warning.
@@ -523,17 +523,17 @@ select * from i_table;
 rollback;  -- we are not in a transaction at this point
 
 -- implicit transaction block is still a transaction block, for e.g. VACUUM
-SELECT 1\; VACUUM;
-SELECT 1\; COMMIT\; VACUUM;
+SELECT 1 AS one\; VACUUM;
+SELECT 2 AS two\; COMMIT\; VACUUM;
 
 -- we disallow savepoint-related commands in implicit-transaction state
-SELECT 1\; SAVEPOINT sp;
-SELECT 1\; COMMIT\; SAVEPOINT sp;
-ROLLBACK TO SAVEPOINT sp\; SELECT 2;
-SELECT 2\; RELEASE SAVEPOINT sp\; SELECT 3;
+SELECT 3 AS three\; SAVEPOINT sp;
+SELECT 4 AS four\; COMMIT\; SAVEPOINT sp;
+ROLLBACK TO SAVEPOINT sp\; SELECT 5 AS five;
+SELECT 6 AS six\; RELEASE SAVEPOINT sp\; SELECT 7 AS seven;
 
 -- but this is OK, because the BEGIN converts it to a regular xact
-SELECT 1\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT;
+SELECT 8\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT;

 -- Test for successful cleanup of an aborted transaction at session exit.

---

From: "Daniel Verite" <[email-masked]>
Date: Thu, 25 Jul 2019 23:02:28 +0200

> There is seldom new code, it is rather moved around, some functions are 

Thanks for the update!
FYI you forgot to remove that bit:

"SINGLELINE|SINGLESTEP|SHOW_ALL_RESULTS"))

Also copydml does not  seem to be exercised with combined
queries,  so do we need this chunk:

Best regards,

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 25 Jul 2019 21:42:11 +0000 (GMT)

Bonsoir Daniel,

> + "SINGLELINE|SINGLESTEP|SHOW_ALL_RESULTS"))

Indeed. I found another such instance in "help.c".

> --- a/src/test/regress/sql/copydml.sql

Yep, because I reorganized the notice code significantly, and I wanted to 
be sure that the right notices are displayed in the right order, which 
does not show if the trigger just says "NOTICE:  UPDATE 8".

Attached a v2 for the always-show-all-results variant. Thanks for the 
debug!

-- 
Fabien.

+static void
 
 /*
  * AcceptResult

-	if (!OK)
-	{
 
 	return OK;
 }
 
 	ResetCancelConn();

 	ResetCancelConn();

-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:

 
+/*
 
 /*

 
 			if (pset.timing)
 			{
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}

 
+ ?column? 
+----------
+        4
+(1 row)
+
 
+--
+-- combined queries
+--

---

From: Kyotaro Horiguchi <[email-masked]>
Date: Fri, 26 Jul 2019 13:17:04 +0900 (Tokyo Standard Time)

Hello.

At Thu, 25 Jul 2019 21:42:11 +0000 (GMT), Fabien COELHO <[email-masked]> wrote in <alpine.DEB.2.21.1907252135060.21130@lancre>
> Yep, because I reorganized the notice code significantly, and I wanted
> to be sure that the right notices are displayed in the right order,
> which does not show if the trigger just says "NOTICE: UPDATE 8".
> 

I have some comments on this patch.

I'm +1 for always output all results without having knobs.

Documentation (psql-ref.sgml) has another place that needs the
same amendment.

Looking the output for -t, -0, -A or something like, we might need
to introduce result-set separator.

# -eH looks broken for me but it would be another issue.

Valid setting of FETCH_COUNT disables this feature. I think it is
unwanted behavior.

regards.

-- 
Kyotaro Horiguchi
NTT Open Source Software Center

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 26 Jul 2019 08:19:47 +0000 (GMT)

Hello Kyotaro-san,

> I'm +1 for always output all results without having knobs.

That makes 4 opinions expressed towards this change of behavior, and none 
against.

Indeed.

> to introduce result-set separator.

Yep, possibly. I'm not sure this is material for this patch, though.

> # -eH looks broken for me but it would be another issue.

It seems to work for me. Could you be more precise about how it is broken?

> Valid setting of FETCH_COUNT disables this feature. I think it is

Yes and no: this behavior (bug, really) is pre-existing, FETCH_COUNT does 
not work with combined queries:

   sh> /usr/bin/psql
   psql (12beta2 ...)
   fabien=# \set FETCH_COUNT 2
   fabien=# SELECT 1234 \; SELECT 5432 ;
   fabien=#

   same thing with pg 11.4, and probably down to every version of postgres
   since the feature was implemented...

I think that fixing this should be a separate bug report and patch. I'll 
try to look at it.

Thanks for the feedback. Attached v3 with further documentation updates.

-- 
Fabien.

diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 7789fc6177..4e6ab5a0a5 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -127,18 +127,11 @@ echo '\x \\ SELECT * FROM foo;' | psql
        commands included in the string to divide it into multiple
        transactions.  (See <xref linkend="protocol-flow-multi-statement"/>
        for more details about how the server handles multi-query strings.)
-       Also, <application>psql</application> only prints the
-       result of the last <acronym>SQL</acronym> command in the string.
-       This is different from the behavior when the same string is read from
-       a file or fed to <application>psql</application>'s standard input,
-       because then <application>psql</application> sends
-       each <acronym>SQL</acronym> command separately.
       </para>
       <para>
-       Because of this behavior, putting more than one SQL command in a
-       single <option>-c</option> string often has unexpected results.
-       It's better to use repeated <option>-c</option> commands or feed
-       multiple commands to <application>psql</application>'s standard input,
+       If having several commands executed in one transaction is not desired, 
+       use repeated <option>-c</option> commands or feed multiple commands to
+       <application>psql</application>'s standard input,
        either using <application>echo</application> as illustrated above, or
        via a shell here-document, for example:
 <programlisting>
@@ -3355,10 +3348,8 @@ select 1\; select 2\; select 3;
         commands included in the string to divide it into multiple
         transactions.  (See <xref linkend="protocol-flow-multi-statement"/>
         for more details about how the server handles multi-query strings.)
-        <application>psql</application> prints only the last query result
-        it receives for each request; in this example, although all
-        three <command>SELECT</command>s are indeed executed, <application>psql</application>
-        only prints the <literal>3</literal>.
+        <application>psql</application> prints all results it receives, one
+        after the other.
         </para>
         </listitem>
       </varlistentry>
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 44a782478d..4534c45854 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -472,6 +472,16 @@ ResetCancelConn(void)
 #endif
 }
 
+static void
 
 /*
  * AcceptResult

-	if (!OK)
-	{
 
 	return OK;
 }
 
 	ResetCancelConn();

 	ResetCancelConn();

-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:

 
+/*
 
 /*

 
 			if (pset.timing)
 			{
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}

 
+ ?column? 
+----------
+        4
+(1 row)
+
 
+--
+-- combined queries
+--

---

From: "Daniel Verite" <[email-masked]>
Date: Fri, 26 Jul 2019 14:12:39 +0200

> I think that fixing this should be a separate bug report and patch. I'll 

That reminds me that it was already discussed in [1]. I should add the
proposed fix to the next commitfest.

[1]
https://www.postgresql.org/message-id/flat/a0a854b6-563c-4a11-bf1c-d6c6f924004d%40manitou-mail.org

Best regards,

---

From: Fabien COELHO <[email-masked]>
Date: Sun, 28 Jul 2019 23:36:22 +0200 (CEST)

> Thanks for the feedback. Attached v3 with further documentation updates.

Attached v4 also fixes pg_stat_statements non regression tests, per pg 
patch tester travis run.

-- 
Fabien.

diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out
index 6787ec1efd..de59a5cf65 100644
--- a/contrib/pg_stat_statements/expected/pg_stat_statements.out
+++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out
@@ -49,17 +49,42 @@ BEGIN \;
 SELECT 2.0 AS "float" \;
 SELECT 'world' AS "text" \;
 COMMIT;
+ float 
+-------
+   2.0
+(1 row)
+
+ text  
+-------
+ world
+(1 row)
+
 -- compound with empty statements and spurious leading spacing
-\;\;   SELECT 3 + 3 \;\;\;   SELECT ' ' || ' !' \;\;   SELECT 1 + 4 \;;
- ?column? 
-----------
-        5
+\;\;   SELECT 3 + 3 AS "+" \;\;\;   SELECT ' ' || ' !' AS "||" \;\;   SELECT 1 + 4 AS "+" \;;
+ + 
+---
+ 6
+(1 row)
+
+ ||  
+-----
+   !
+(1 row)
+
+ + 
+---
+ 5
 (1 row)
 
 -- non ;-terminated statements
 SELECT 1 + 1 + 1 AS "add" \gset
 SELECT :add + 1 + 1 AS "add" \;
 SELECT :add + 1 + 1 AS "add" \gset
+ add 
+-----
+   5
+(1 row)
+
 -- set operator
 SELECT 1 AS i UNION SELECT 2 ORDER BY i;
  i 
@@ -102,12 +127,12 @@ SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C";
  SELECT $1                                        +|     4 |    4
                                                   +|       | 
    AS "text"                                       |       | 
- SELECT $1 + $2                                    |     2 |    2
  SELECT $1 + $2 + $3 AS "add"                      |     3 |    3
+ SELECT $1 + $2 AS "+"                             |     2 |    2
  SELECT $1 AS "float"                              |     1 |    1
  SELECT $1 AS "int"                                |     2 |    2
  SELECT $1 AS i UNION SELECT $2 ORDER BY i         |     1 |    2
- SELECT $1 || $2                                   |     1 |    1
+ SELECT $1 || $2 AS "||"                           |     1 |    1
  SELECT pg_stat_statements_reset()                 |     1 |    1
  WITH t(f) AS (                                   +|     1 |    2
    VALUES ($1), ($2)                              +|       | 
diff --git a/contrib/pg_stat_statements/sql/pg_stat_statements.sql b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
index 8b527070d4..ea3a31176e 100644
--- a/contrib/pg_stat_statements/sql/pg_stat_statements.sql
+++ b/contrib/pg_stat_statements/sql/pg_stat_statements.sql
@@ -27,7 +27,7 @@ SELECT 'world' AS "text" \;
 COMMIT;
 
 -- compound with empty statements and spurious leading spacing
-\;\;   SELECT 3 + 3 \;\;\;   SELECT ' ' || ' !' \;\;   SELECT 1 + 4 \;;
+\;\;   SELECT 3 + 3 AS "+" \;\;\;   SELECT ' ' || ' !' AS "||" \;\;   SELECT 1 + 4 AS "+" \;;
 
 -- non ;-terminated statements
 SELECT 1 + 1 + 1 AS "add" \gset
 
+static void
 
 /*
  * AcceptResult

-	if (!OK)
-	{
 
 	return OK;
 }
 
 	ResetCancelConn();

 	ResetCancelConn();

-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:

 
+/*
 
 /*

 
 			if (pset.timing)
 			{
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}

 
+ ?column? 
+----------
+        4
+(1 row)
+
 
+--
+-- combined queries
+--

---

From: Kyotaro Horiguchi <[email-masked]>
Date: Mon, 29 Jul 2019 11:59:38 +0900 (Tokyo Standard Time)

Hello, Fabien.

At Fri, 26 Jul 2019 08:19:47 +0000 (GMT), Fabien COELHO <[email-masked]> wrote in <alpine.DEB.2.21.1907260738240.13195@lancre>
> That makes 4 opinions expressed towards this change of behavior, and
> none against.
> 

I'm fine with that.

> It seems to work for me. Could you be more precise about how it is

It emits bare command string before html result. It's not caused
by this patch.

> Yes and no: this behavior (bug, really) is pre-existing, FETCH_COUNT
> does not work with combined queries:
> 
>   sh> /usr/bin/psql
>   psql (12beta2 ...)
> I think that fixing this should be a separate bug report and
> patch. I'll try to look at it.

Ah, I didin't notieced that. Thanks for the explanation.

regards.

---

From: Kyotaro Horiguchi <[email-masked]>
Date: Mon, 29 Jul 2019 14:55:48 +0900 (Tokyo Standard Time)

Hello.

On 2019/07/29 6:36, Fabien COELHO wrote:> 
> Attached v4 also fixes pg_stat_statements non regression tests, per pg 

Thanks. I looked this more closely.

This comment doesn't explain what the result value means.

I think this is not needed. This phrase was needed to explain why
we need to loop over subsequent results after PQexec in the
current code, but in this patch PQsendQuery is used instead,
which doesn't suffer somewhat confusing behavior. All results are
handled without needing an unusual processing.

It seems that the purpose of the returned PGresult is only
printing status of this COPY. If it is true, I'd like to see
something like the following example.

| Returns result in the case where queryFout is safe to output
| result status. That is, in the case of COPY IN, or in the case
| where COPY OUT is written to other than pset.queryFout.

The comment in the original code was:

-      /*

The first half of the comment seems to be true for this
patch. Don't we preserve that comment?

success is always true at thie point so "&& success" is no longer
useful. (It is same for the COPY IN case).

I didn't get "before changing the curren result" in the
comment. Isn't "handle COPY stream if any" enough?

It seems that it is wrong that this ignores the return value of
HandleCopyResult().

I'm not sure whether we reached any consensus with ths
behavior. This means the timing includes result-printing time of
other than the last one. If we don't want include printing time
at all, we can exclude it with a small amount of additional
complexity.

regards.

---

From: Fabien COELHO <[email-masked]>
Date: Mon, 29 Jul 2019 23:44:43 +0200 (CEST)

Hello Kyotaro-san,

> Thanks. I looked this more closely.

Indeed! Thanks for this detailed review.

> This comment doesn't explain what the result value means.

Ok, added.

> handled without needing an unusual processing.

Hmmm. More or less. "COPY" commands have two results, one for taking over 
the input or output streams more or less directly at the protocol level, 
and one for the final summary, which is quite special compared to other 
commands, all that managed in "copy.c". So ISTM that the comment is 
somehow still appropriate.

The difference with the previous behavior is that other results could be 
immediately discarded but these ones, while now they are all processed.

I've kept this comment and added another one to try to make that clear.

> | where COPY OUT is written to other than pset.queryFout.

I have tried to improved the comment based on your suggestion.

> patch. Don't we preserve that comment?

Ok. Some form put back.

> useful.

Ok.

> (It is same for the COPY IN case).

Ok.

> I didn't get "before changing the curren result" in the comment. Isn't 
> "handle COPY stream if any" enough?

Alas, I think not.

The issue is that I need to know whether this is the last result (eg \gset 
applies only on the last result), so I'll call PQgetResult() to get that.

However, on COPY, this is the second "final" result which says how much 
was copied. If I have not send/received the data, the count will not be 
right.

> It seems that it is wrong that this ignores the return value of

Yep. Fixed.

> at all, we can exclude it with a small amount of additional

I think that this point is desperate, because the way timing is 
implemented client-side.

Although we could try to stop timing before each result processing, it 
would not prevent the server to go on with other queries and send back 
results, psql to receive further results (next_result), so the final 
figures would be stupid anyway, just another form of stupid.

Basically the approach cannot work with combined queries: It only worked 
before because the intermediate results were coldly discarded.

Maybe the server to report its execution times for each query somehow, but 
then the communication time would not be included.

I have added a comment about why timing does not make much sense with 
combined queries.

Attached a v5.

-- 
Fabien.

 
 
index 44a782478d..94a2f2d96d 100644
 
+static void
 
 /*
  * AcceptResult

-	if (!OK)
-	{
 
 	return OK;
 }
 
 	ResetCancelConn();

 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		return 0;
@@ -999,199 +1002,114 @@ loop_exit:
 	return success;
 }
 
-
 /*
- * ProcessResult: utility function for use by SendQuery() only
- *
- * When our command string contained a COPY FROM STDIN or COPY TO STDOUT,
- * PQexec() has stopped at the PGresult associated with the first such
- * command.  In that event, we'll marshal data for the COPY and then cycle
- * through any subsequent PGresult objects.
- *
- * When the command string contained no such COPY command, this function
- * degenerates to an AcceptResult() call.
- *
- * Changes its argument to point to the last PGresult of the command string,
- * or NULL if that result was for a COPY TO STDOUT.  (Returning NULL prevents
- * the command status from being printed, which we want in that case so that
- * the status line doesn't get taken as part of the COPY data.)
- *
- * Returns true on complete success, false otherwise.  Possible failure modes
- * include purely client-side problems; check the transaction status for the
- * server-side opinion.
+ * Marshal the COPY data.  Either subroutine will get the
+ * connection out of its COPY state, then call PQresultStatus()
+ * once and report any error. Return whether all was ok.
+ *
+ * For COPY OUT, direct the output to pset.copyStream if it's set,
+ * otherwise to pset.gfname if it's set, otherwise to queryFout.
+ * For COPY IN, use pset.copyStream as data source if it's set,
+ * otherwise cur_cmd_source.
+ *
+ * Update result if further processing is necessary, or NULL otherwise.
+ * Return a result when queryFout can safely output a result status:
+ * on COPY IN, or on COPY OUT if written to something other han pset.queryFout.
+ * Returning NULL prevents the command status from being printed, which
+ * we want if the status line doesn't get taken as part of the COPY data.
  */
 static bool
-ProcessResult(PGresult **results)
+HandleCopyResult(PGresult **result)
 {
-	bool		success = true;
-	bool		first_cycle = true;
+	bool		success;
+	FILE	   *copystream;
+	PGresult   *copy_result;
+	ExecStatusType result_status = PQresultStatus(*result);
 
-	for (;;)

 
-		PQclear(*results);
+							   &copy_result);
+	}
+	ResetCancelConn();

 	return success;
 }
 
-
 /*
@@ -1219,43 +1137,44 @@ PrintQueryStatus(PGresult *results)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:
 
@@ -1265,7 +1184,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1278,7 +1197,7 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}
 
@@ -1287,6 +1206,170 @@ PrintQueryResults(PGresult *results)
 	return success;
 }
 
+/*
+ * the PGresult associated with these commands must be processed by providing
+ * an input or output stream.  In that event, we'll marshal data for the COPY.
+ * For other commands, the results are processed normally, depending on their
+ * status.
+			 * Some error occured, either a server-side failure or
+			 * a failure to submit the command string.  Record that.
+			success &= HandleCopyResult(&result);
+		 * to process.
+		 * Get timing measure before printing the last result.
+		 * It will include the display of previous results, if any.
+		 * This cannot be helped because the server goes on processing
+		 * further queries anyway while the previous ones are being displayed.
+		 * The parallel execution of the client display hides the server time
+		 * when it is shorter.
+		 * With combined queries, timing must be understood as an upper bound
+		 * of the time spent processing them.
 
 /*
  * SendQuery: send the query string to the backend
@@ -1303,9 +1386,9 @@ PrintQueryResults(PGresult *results)
 bool
 SendQuery(const char *query)
 {
-	PGresult   *results;
+	PGresult   *results = NULL;
 	PGTransactionStatusType transaction_status;
-	double		elapsed_msec = 0;
+	double		elapsed_msec = 0.0;
 	bool		OK = false;
 	int			i;
 	bool		on_error_rollback_savepoint = false;
@@ -1408,28 +1491,7 @@ SendQuery(const char *query)
 			 pset.crosstab_flag || !is_select_command(query))
 	{
 		/* Default fetch-it-all-and-print mode */
-		instr_time	before,
-					after;
-
-		if (pset.timing)
-			INSTR_TIME_SET_CURRENT(before);
-
-		results = PQexec(pset.db, query);
-
-		/* these operations are included in the timing result: */
-		ResetCancelConn();
-		OK = ProcessResult(&results);
-
-		if (pset.timing)
-		{
-			INSTR_TIME_SET_CURRENT(after);
-			INSTR_TIME_SUBTRACT(after, before);
-			elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
-		}
-
-		/* but printing results isn't: */
-		if (OK && results)
-			OK = PrintQueryResults(results);
+		OK = SendQueryAndProcessResults(query, &elapsed_msec);
 	}
 	else
 	{
@@ -1606,7 +1668,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1654,7 +1716,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (pset.timing)
 			{
@@ -1664,7 +1726,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}
@@ -1723,7 +1785,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1737,7 +1799,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1814,7 +1876,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
@@ -1923,7 +1985,7 @@ cleanup:
@@ -1933,7 +1995,7 @@ cleanup:
 
+ ?column? 
+----------
+        4
+(1 row)
+
 
+--
+-- combined queries
+--

---

From: Alvaro Herrera <[email-masked]>
Date: Thu, 12 Sep 2019 16:31:11 -0300

This v6 is just Fabien's v5, rebased over a very minor conflict, and
pgindented.  No further changes.  I've marked this Ready for Committer.

-- 
Álvaro Herrera                https://www.2ndQuadrant.com/

 
index 4b2679360f..fa358c8c58 100644
@@ -486,6 +486,16 @@ ResetCancelConn(void)
 #endif
 }
 
+static void
 
 /*
  * AcceptResult
@@ -496,7 +506,7 @@ ResetCancelConn(void)
  * Returns true for valid result, false for error state.
  */
 static bool
-AcceptResult(const PGresult *result)
+AcceptResult(const PGresult *result, bool show_error)
 {
 	bool		OK;
 
@@ -527,15 +537,8 @@ AcceptResult(const PGresult *result)
 				break;
 		}
 
-	if (!OK)
-	{
 
 	return OK;
 }
@@ -715,7 +718,7 @@ PSQLexec(const char *query)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		res = NULL;
@@ -757,7 +760,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		return 0;
@@ -1013,199 +1016,114 @@ loop_exit:
 	return success;
 }
 
-
 /*
 
-	for (;;)

-				if (pset.copyStream)
-				{
-					/* invoked by \copy */
 
-				success = handleCopyOut(pset.db,
-										copystream,
-										&copy_result)
-					&& success
-					&& (copystream != NULL);
+		/*
+		 * Suppress status printing if the report would go to the same place
+		 * as the COPY data just went.  Note this doesn't prevent error
+		 * reporting, since handleCopyOut did that.
+		 */
+		if (copystream == pset.queryFout)
+		{
+			PQclear(copy_result);
+			copy_result = NULL;
+		}
 
-				/*

 	return success;
 }
 
-
 /*
@@ -1233,43 +1151,44 @@ PrintQueryStatus(PGresult *results)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:
 
@@ -1279,7 +1198,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1292,7 +1211,7 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}
 
@@ -1301,6 +1220,174 @@ PrintQueryResults(PGresult *results)
 	return success;
 }
 
+/*
+typedef struct
+	t_notice_messages *notes = (t_notice_messages *) arg;
+			 * Some error occured, either a server-side failure or a failure
+			 * to submit the command string.  Record that.
+		 * It will include the display of previous results, if any. This
+		 * cannot be helped because the server goes on processing further
+		 * queries anyway while the previous ones are being displayed. The
+		 * parallel execution of the client display hides the server time when
+		 * it is shorter.
 
 /*
@@ -1317,9 +1404,9 @@ PrintQueryResults(PGresult *results)
@@ -1422,28 +1509,7 @@ SendQuery(const char *query)
@@ -1620,7 +1686,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1668,7 +1734,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (pset.timing)
 			{
@@ -1678,7 +1744,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}
@@ -1737,7 +1803,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1751,7 +1817,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1828,7 +1894,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
@@ -1937,7 +2003,7 @@ cleanup:
@@ -1947,7 +2013,7 @@ cleanup:
 
+ ?column? 
+----------
+        4
+(1 row)
+
 create table test3 (c int);
 select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
+ ?column? 
+----------
+        0
+(1 row)
+
  ?column? 
 ----------
         1
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 242f817163..ae987b548c 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4809,3 +4809,87 @@ Owning table: "pg_catalog.pg_statistic"
 Indexes:
     "pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
 
+--
+-- combined queries
+--
index 1b03310029..e2b58e9f29 100644
@@ -860,11 +860,21 @@ DROP TABLE abc;
 
 -- this implicitly commits:
@@ -876,6 +886,12 @@ insert into i_table values(1)\; select * from i_table;
 
@@ -894,9 +910,19 @@ rollback;  -- we are not in a transaction at this point
@@ -922,23 +948,53 @@ select * from i_table;
 -- Tests for AND CHAIN in implicit transaction blocks
 SET TRANSACTION READ ONLY\; COMMIT AND CHAIN;  -- error
 ERROR:  COMMIT AND CHAIN can only be used in transaction blocks
 
 create table test3 (c int);
 select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 26a0bcf718..903c413f90 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1182,3 +1182,38 @@ drop role regress_partitioning_role;
 
index bf1016489d..3a7e5d4582 100644
@@ -493,8 +493,8 @@ DROP TABLE abc;

 
 -- this implicitly commits:
 insert into i_table values(1)\; select * from i_table;
@@ -512,9 +512,9 @@ rollback;  -- we are not in a transaction at this point

 -- commit in implicit-transaction state commits but issues a warning.
@@ -527,17 +527,17 @@ select * from i_table;
 rollback;  -- we are not in a transaction at this point

 

 -- Tests for AND CHAIN in implicit transaction blocks

---

From: vignesh C <[email-masked]>
Date: Fri, 20 Sep 2019 09:23:26 +0530

> pgindented.  No further changes.  I've marked this Ready for Committer.
Should we add function header for the below function to maintain the
common standard of this file:
+
+static void

+static void

Regards,
Vignesh
EnterpriseDB: http://www.enterprisedb.com

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 20 Sep 2019 10:10:58 +0200 (CEST)

> common standard of this file:

Yes. Attached v6 does that.

-- 
Fabien.

 
 
index 4b2679360f..db6d031133 100644
@@ -486,6 +486,19 @@ ResetCancelConn(void)
 #endif
 }
 
+/*
+ * Show error message from result, if any, and check connection in passing.
+ */
+static void
+ShowErrorMessage(const PGresult *result)
+{
+	const char *error = PQerrorMessage(pset.db);
+
+	if (strlen(error))
+		pg_log_info("%s", error);
+
+	CheckConnection();
+}
 
 /*
  * AcceptResult
@@ -496,7 +509,7 @@ ResetCancelConn(void)
  * Returns true for valid result, false for error state.
  */
 static bool
-AcceptResult(const PGresult *result)
+AcceptResult(const PGresult *result, bool show_error)
 {
 	bool		OK;
 
@@ -527,15 +540,8 @@ AcceptResult(const PGresult *result)
 				break;
 		}
 
-	if (!OK)
-	{
 
 	return OK;
 }
@@ -715,7 +721,7 @@ PSQLexec(const char *query)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		res = NULL;
@@ -757,7 +763,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		return 0;
@@ -1013,199 +1019,114 @@ loop_exit:
 	return success;
 }
 
-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*
@@ -1233,43 +1154,44 @@ PrintQueryStatus(PGresult *results)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:
 
@@ -1279,7 +1201,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1292,7 +1214,7 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}
 
@@ -1301,6 +1223,176 @@ PrintQueryResults(PGresult *results)
 	return success;
 }
 
+/*
+ * Store notices in appropriate buffer, for later display.
+ * Show notices stored in buffer, which is then reset.
 
 /*
@@ -1317,9 +1409,9 @@ PrintQueryResults(PGresult *results)
@@ -1422,28 +1514,7 @@ SendQuery(const char *query)
@@ -1620,7 +1691,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1668,7 +1739,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (pset.timing)
 			{
@@ -1678,7 +1749,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}
@@ -1737,7 +1808,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1751,7 +1822,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1828,7 +1899,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
-			OK = AcceptResult(results);
+			OK = AcceptResult(results, true);
 			Assert(!OK);
 			SetResultVariables(results, OK);
 			ClearOrSaveResult(results);
@@ -1937,7 +2008,7 @@ cleanup:
 	results = PQexec(pset.db, "CLOSE _psql_cursor");
 	if (OK)
 	{
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 	}
@@ -1947,7 +2018,7 @@ cleanup:
 	if (started_txn)
 	{
 		results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
-		OK &= AcceptResult(results) &&
+		OK &= AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 	}
diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c
index d7c0fc0c1e..6ae9528ddf 100644
--- a/src/bin/psql/describe.c
+++ b/src/bin/psql/describe.c
@@ -3734,6 +3734,11 @@ listTables(const char *tabtypes, const char *pattern, bool verbose, bool showSys
 			translate_columns[cols_so_far] = true;
 		}
 
+		/*
+		 * We don't bother to count cols_so_far below here, as there's no need
+		 * to; this might change with future additions to the output columns.
+		 */
+
 		/*
 		 * We don't bother to count cols_so_far below here, as there's no need
 		 * to; this might change with future additions to the output columns.
 
+ ?column? 
+----------
+        4
+(1 row)
+
 
+--
+-- combined queries
+--

---

From: vignesh C <[email-masked]>
Date: Fri, 20 Sep 2019 14:33:55 +0530

> Yes. Attached v6 does that.
Thanks for fixing it.

The below addition can be removed, it seems to be duplicate:
@@ -3734,6 +3734,11 @@ listTables(const char *tabtypes, const char
*pattern, bool verbose, bool showSys
  translate_columns[cols_so_far] = true;
  }

+ /*

Regards,
Vignesh

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 20 Sep 2019 16:25:40 +0200 (CEST)

> The below addition can be removed, it seems to be duplicate:

Indeed. I'm unsure how this got into the patch, probably some rebase 
mix-up. Attached v7 removes the duplicates.

-- 
Fabien.

 

+/*
 
 /*
  * AcceptResult

-	if (!OK)
-	{
 
 	return OK;
 }
 
 	ResetCancelConn();

 	ResetCancelConn();

-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:

 
+/*
 
 /*

 
 			if (pset.timing)
 			{
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}

 
+ ?column? 
+----------
+        4
+(1 row)
+
 
+--
+-- combined queries
+--

---

From: Fabien COELHO <[email-masked]>
Date: Mon, 2 Dec 2019 11:09:02 +0100 (CET)

> Indeed. I'm unsure how this got into the patch, probably some rebase mix-up. 
> Attached v7 removes the duplicates.

Attached patch v8 is a rebase.

-- 
Fabien.

 
 
index 48b081fd58..924ba93df2 100644
@@ -3361,10 +3354,8 @@ select 1\; select 2\; select 3;
index 9c0d53689e..4876ac132c 100644
@@ -348,6 +348,19 @@ CheckConnection(void)
 }

+/*

 /*
@@ -359,7 +372,7 @@ CheckConnection(void)
  * Returns true for valid result, false for error state.
  */
 static bool
-AcceptResult(const PGresult *result)
+AcceptResult(const PGresult *result, bool show_error)
 {
 	bool		OK;
 
@@ -390,15 +403,8 @@ AcceptResult(const PGresult *result)
 				break;
 		}
 
-	if (!OK)
-	{
 
 	return OK;
 }
@@ -578,7 +584,7 @@ PSQLexec(const char *query)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		res = NULL;
@@ -620,7 +626,7 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		return 0;
@@ -876,199 +882,115 @@ loop_exit:
 	return success;
 }
 
-
 /*
 
-	for (;;)
+	SetCancelConn(pset.db);
 
-			SetCancelConn(pset.db);

-		PQclear(*results);

 	return success;
 }
 
-
 /*
@@ -1096,43 +1018,44 @@ PrintQueryStatus(PGresult *results)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:
 
@@ -1142,7 +1065,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1155,7 +1078,7 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}
 
@@ -1164,6 +1087,176 @@ PrintQueryResults(PGresult *results)
 	return success;
 }
 
+/*
 
 /*
@@ -1180,9 +1273,9 @@ PrintQueryResults(PGresult *results)
@@ -1285,28 +1378,7 @@ SendQuery(const char *query)
@@ -1483,7 +1555,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1531,7 +1603,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (pset.timing)
 			{
@@ -1541,7 +1613,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}
@@ -1600,7 +1672,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1614,7 +1686,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1691,7 +1763,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
@@ -1800,7 +1872,7 @@ cleanup:
@@ -1810,7 +1882,7 @@ cleanup:
 
+ ?column? 
+----------
+        4
+(1 row)
+
 
+--
+-- combined queries
+--

---

From: Tomas Vondra <[email-masked]>
Date: Thu, 16 Jan 2020 18:48:21 +0100

Hi,

This is one of the patches already marked as RFC (since September by
Alvaro). Anyone interested in actually pushing it, so that it does not
fall through to yet another commitfest?

regards

-- 
Tomas Vondra                  http://www.2ndQuadrant.com
PostgreSQL Development, 24x7 Support, Remote DBA, Training & Services

---

From: Tom Lane <[email-masked]>
Date: Thu, 16 Jan 2020 13:08:16 -0500

> fall through to yet another commitfest?

TBH, I think we'd be better off to reject it.  This makes a nontrivial
change in a very long-standing psql behavior, with AFAICS no way to
get back the old semantics.  (The thread title is completely misleading
about that; there's no "option" in the patch as it stands.)  Sure,
in a green field this behavior would likely be more sensible ... but
that has to be weighed against the fact that it's behaved the way it
does for a long time, and any existing scripts that are affected by
that behavior have presumably deliberately chosen to use it.

I can't imagine that changing this will make very many people happier.
It seems much more likely that people who are affected will be unhappy.

The compatibility issue could be resolved by putting in the option
that I suppose was there at the beginning.  But then we'd have to
have a debate about which behavior would be default, and there would
still be the question of who would find this to be an improvement.
If you're chaining together commands with \; then it's likely that
you are happy with the way it behaves today.  Certainly there's been
no drumbeat of bug reports about it.

			regards, tom lane

---

From: Tomas Vondra <[email-masked]>
Date: Thu, 16 Jan 2020 21:53:07 +0100

>no drumbeat of bug reports about it.

I don't know, really, I only pinged this as a CFM who sees a patch
marked as RFC for months ...

The current behavior certainly seems strange/wrong to me - if I send
multiple queries to psql, I'd certainly expect results for all of them,
not just the last one. So the current behavior seems pretty surprising.

I'm unable to make any judgments about risks/benefits of this change. I
can't imagine anyone intentionally relying on the current behavior, so
I'd say the patch is unlikely to break anything (which is not already
broken). But I don't have any data to support this ...

Essentially, I'm just advocating to make a decision - we should either
commit or reject the patch, not just move it to the next commitfest over
and over.

regards

---

From: Alvaro Herrera <[email-masked]>
Date: Thu, 16 Jan 2020 18:05:52 -0300

> no drumbeat of bug reports about it.

The patch originally submitted did indeed have the option (defaulting to
"off", that is, the original behavior), and it was removed at request of
reviewers Daniel Vérité, Peter Eisentraut and Kyotaro Horiguchi.

My own opinion is that any scripts that rely heavily on the current
behavior are stepping on shaky ground anyway.  I'm not saying we should
break them on every chance we get -- just that keeping them unharmed is
not necessarily a priority, and that if this patch enables other psql
features, it might be a good step forward.

My own vote would be to use the initial patch (after applying any
unrelated changes per later review), ie. add the feature with its
disable button.

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 16 Jan 2020 22:33:48 +0100 (CET)

Hello Tom,

> about that; there's no "option" in the patch as it stands.)

The thread title was not misleading, the initial version of the patch did 
offer an option. Then I was said "the current behavior is stupid (which I 
agree), let us change it to the sane behavior without option", then I'm 
told the contrary. Sigh.

I still have the patch with the option, though.

> Sure, in a green field this behavior would likely be more sensible ... 
> but that has to be weighed against the fact that it's behaved the way it 
> does for a long time, and any existing scripts that are affected by that 
> behavior have presumably deliberately chosen to use it.

I cannot imagine many people actually relying on the current insane 
behavior.

> that I suppose was there at the beginning.

Indeed.

> But then we'd have to have a debate about which behavior would be 
> default,

The patch was keeping current behavior as the default because people do 
not like a change whatever.

> and there would still be the question of who would find this to 
> be an improvement. If you're chaining together commands with \; then 
> it's likely that you are happy with the way it behaves today. 
> Certainly there's been no drumbeat of bug reports about it.

Why would there be bug report if this is a feature? :-)

The behavior has been irritating me for a long time. It is plain stupid to 
be able to send queries but not see their results.

-- 
Fabien.

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 16 Jan 2020 22:36:42 +0100 (CET)

> unrelated changes per later review), ie. add the feature with its

I can do that, but not if there is a veto from Tom on the feature.

I wish definite negative opinions by senior committers would be expressed 
earlier, so that people do not spend time rewiewing dead code and 
developing even deader code.

-- 
Fabien.

---

From: "Daniel Verite" <[email-masked]>
Date: Fri, 17 Jan 2020 19:48:00 +0100

Alvaro Herrera wrote:

> if this patch enables other psql features, it might be a good step
> forward.

Yes. For instance if the stored procedures support gets improved to
produce several result sets, how is psql going to benefit from it
while sticking to the old way (PGresult *r = PQexec(query))
of executing queries that discards N-1 out of N result sets?

Best regards,

---

From: Tom Lane <[email-masked]>
Date: Fri, 17 Jan 2020 14:10:26 -0500

> of executing queries that discards N-1 out of N result sets?

I'm not really holding my breath for that to happen, considering
it would involve fundamental breakage of the wire protocol.
(For example, extended query protocol assumes that Describe
Portal only needs to describe one result set.  There might be
more issues, but that one's bad enough.)

When and if we break all the things that would break, it'd be
time enough for incompatible changes in psql's behavior.

			regards, tom lane

---

From: "Daniel Verite" <[email-masked]>
Date: Fri, 17 Jan 2020 21:39:07 +0100

> more issues, but that one's bad enough.)

I'm not sure that CALL can be used at all with the extended protocol
today  (just like multiple queries per statements, except that for these,
I'm sure).
My interpretation is that the extended protocol deliberately
lefts out the possibility of multiple result sets because it doesn't fit
with how it's designed and if you want to have this, you can just use
the old protocol's Query message. There is no need to break anything
or invent anything but on the contrary to use the older way.

Considering these 3 ways to use libpq to send queries:

1. using old protocol with PQexec: only one resultset.

2. using old protocol with PQsendQuery+looping on PQgetResult:
same as #1 except multiple result sets can be processed

3. using extended protocol: not for multiple result sets, not for copy,
possibly not for other things, but  can use bind parameters, binary format,
pipelining,...

The current patch is about using #2 instead of #1.
They have been patches about doing bits of #3 in some cases
(binary output, maybe parameters too?) and none got eventually in.
ISTM that the current situation is that psql is stuck at #1 since forever
so it's not fully using the capabilities of the protocol, both old and new.

Best regards,

---

From: Tomas Vondra <[email-masked]>
Date: Sat, 1 Feb 2020 13:01:36 +0100

This patch was marked as ready for committer, but clearly there's an
ongoin discussion about what should be the default behavoir, if this
breaks existing apps etc. So I've marked it as "needs review" and moved
it to the next CF.

regards

---

From: Fabien COELHO <[email-masked]>
Date: Sun, 2 Feb 2020 09:16:29 +0100 (CET)

Hello Tomas,

> breaks existing apps etc. So I've marked it as "needs review" and moved

The issue is that root (aka Tom) seems to be against the feature, and 
would like the keep it as current. Although my opinion is that the 
previous behavior is close to insane, I'm ready to resurect the guc to 
control the behavior so that it would be possible, or even the default.

Right now I'm waiting for a "I will not veto it on principle" from Tom 
(I'm okay with a reject based on bad implementation) before spending more 
time on it: Although my time is given for free, it is not a good reason to 
send it down the drain if there is a reject coming whatever I do.

Tom, would you consider the feature acceptable with a guc to control it?

-- 
Fabien.

---

From: Fabien COELHO <[email-masked]>
Date: Sat, 6 Jun 2020 16:36:41 +0200 (CEST)

Hello,

> The issue is that root (aka Tom) seems to be against the feature, and would 
> like the keep it as current. Although my opinion is that the previous 
> behavior is close to insane, I'm ready to resurect the guc to control the 
> behavior so that it would be possible, or even the default.
>
> Right now I'm waiting for a "I will not veto it on principle" from Tom (I'm 
> okay with a reject based on bad implementation) before spending more time on 
> it: Although my time is given for free, it is not a good reason to send it 
> down the drain if there is a reject coming whatever I do.
>

Tom, I would appreciate if you could answer this question.

For me, the current behavior is both stupid and irritating: why would pg 
decide to drop the result of a query that I carefully typed?. It makes the 
multi-query feature basically useless from psql, so I did not resurrect 
the guc, but I will if it would remove a veto.

In the meantime, here is a v9 which also fixes the behavior when using 
\watch, so that now one can issue several \;-separated queries and have 
their progress shown. I just needed that a few days ago and was 
disappointed but unsurprised that it did not work.

Watch does not seem to be tested anywhere, I kept it that way. Sigh.

-- 
Fabien.

index f615f8c2bf..2f739445ff 100644
 
  SELECT query, calls, rows FROM pg_stat_statements ORDER BY query COLLATE "C" |     0 |    0
index 75c10554a8..835497b286 100644

index 62fee5a7dd..f02a49d781 100644
@@ -3470,10 +3463,8 @@ select 1\; select 2\; select 3;
index 06f801764b..735a77c01f 100644
@@ -33,6 +33,7 @@ static bool DescribeQuery(const char *query, double *elapsed_msec);
 static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
 static bool command_no_begin(const char *query);
 static bool is_select_command(const char *query);
+static int SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_watch);

 /*
@@ -338,6 +339,19 @@ CheckConnection(void)
 }

+/*

 /*
@@ -349,7 +363,7 @@ CheckConnection(void)
  * Returns true for valid result, false for error state.
  */
 static bool
-AcceptResult(const PGresult *result)
+AcceptResult(const PGresult *result, bool show_error)
 {
 	bool		OK;
 
@@ -380,15 +394,8 @@ AcceptResult(const PGresult *result)
 				break;
 		}
 
-	if (!OK)
-	{
 
 	return OK;
 }
@@ -468,6 +475,16 @@ ClearOrSaveResult(PGresult *result)
 	}
 }
 
+/* consume all results */
+static void
+ClearOrSaveAllResults()
+{
+	PGresult	*result;
+
+	while ((result = PQgetResult(pset.db)) != NULL)
+		ClearOrSaveResult(result);
+}
+
 
 /*
  * Print microtiming output.  Always print raw milliseconds; if the interval
@@ -568,7 +585,7 @@ PSQLexec(const char *query)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		res = NULL;
@@ -590,10 +607,8 @@ PSQLexec(const char *query)
 int
 PSQLexecWatch(const char *query, const printQueryOpt *opt)
 {
-	PGresult   *res;
 	double		elapsed_msec = 0;
-	instr_time	before;
-	instr_time	after;
+	int			res;
 
 	if (!pset.db)
 	{
@@ -602,75 +617,16 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 	}
 
 	SetCancelConn(pset.db);
-
-	if (pset.timing)
-		INSTR_TIME_SET_CURRENT(before);
-
-	res = PQexec(pset.db, query);
-
+	res = SendQueryAndProcessResults(query, &elapsed_msec, true);
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
-	{
-		ClearOrSaveResult(res);
-		return 0;
-	}
-
-	if (pset.timing)
-	{
-		INSTR_TIME_SET_CURRENT(after);
-		INSTR_TIME_SUBTRACT(after, before);
-		elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
-	}
-
-	/*
-	 * If SIGINT is sent while the query is processing, the interrupt will be
-	 * consumed.  The user's intention, though, is to cancel the entire watch
-	 * process, so detect a sent cancellation request and exit in this case.
-	 */
-	if (cancel_pressed)
-	{
-		PQclear(res);
-		return 0;
-	}
-
-	switch (PQresultStatus(res))
-	{
-		case PGRES_TUPLES_OK:
-			printQuery(res, opt, pset.queryFout, false, pset.logfile);
-			break;
-
-		case PGRES_COMMAND_OK:
-			fprintf(pset.queryFout, "%s\n%s\n\n", opt->title, PQcmdStatus(res));
-			break;
-
-		case PGRES_EMPTY_QUERY:
-			pg_log_error("\\watch cannot be used with an empty query");
-			PQclear(res);
-			return -1;
-
-		case PGRES_COPY_OUT:
-		case PGRES_COPY_IN:
-		case PGRES_COPY_BOTH:
-			pg_log_error("\\watch cannot be used with COPY");
-			PQclear(res);
-			return -1;
-
-		default:
-			pg_log_error("unexpected result status for \\watch");
-			PQclear(res);
-			return -1;
-	}
-
-	PQclear(res);
-
 	fflush(pset.queryFout);
 
 	/* Possible microtiming output */
 	if (pset.timing)
 		PrintTiming(elapsed_msec);
 
-	return 1;
+	return res;
 }

@@ -874,199 +830,115 @@ loop_exit:
 	return success;
 }
 
-
 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*
@@ -1094,43 +966,44 @@ PrintQueryStatus(PGresult *results)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:
 
@@ -1140,7 +1013,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1153,7 +1026,7 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}
 
@@ -1162,6 +1035,199 @@ PrintQueryResults(PGresult *results)
 	return success;
 }
 
+/*
+ * Data structure and functions to record notices while they are
+ * emitted, so that they can be shown later.
+ *
+ * We need to know which result is last, which requires to extract
+ * one result in advance, hence two buffers are needed.
+ */
+typedef struct {
+	bool			in_flip;
+	PQExpBufferData	flip;
+	PQExpBufferData	flop;
+} t_notice_messages;
+
+/*
+ * Store notices in appropriate buffer, for later display.
+ */
+static void
+AppendNoticeMessage(void *arg, const char *msg)
+{
+	t_notice_messages *notes = (t_notice_messages*) arg;
+	appendPQExpBufferStr(notes->in_flip ? &notes->flip : &notes->flop, msg);
+}
+
+/*
+ * Show notices stored in buffer, which is then reset.
+ */
+static void
+ShowNoticeMessage(t_notice_messages *notes)
+{
+	PQExpBufferData	*current = notes->in_flip ? &notes->flip : &notes->flop;
+	if (current->data != NULL && *current->data != '\0')
+		pg_log_info("%s", current->data);
+	resetPQExpBuffer(current);
+}
+
+/*
+ * SendQueryAndProcessResults: utility function for use by SendQuery()
+ * and PSQLexecWatch().
+ *
+ * Sends query and cycles through PGresult objects.
+ *
+ * When not under \watch and if our command string contained a COPY FROM STDIN
+ * or COPY TO STDOUT, the PGresult associated with these commands must be
+ * processed by providing an input or output stream.  In that event, we'll
+ * marshal data for the COPY.
+ *
+ * For other commands, the results are processed normally, depending on their
+ * status.
+ *
+ * Returns 1 on complete success, 0 on interrupt and -1 or errors.  Possible
+ * failure modes include purely client-side problems; check the transaction
+ * status for the server-side opinion.
+ *
+ * Note that on a combined query, failure does not mean that nothing was
+ * committed.
+ */
+static int
+SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_watch)
+{
+	bool				success;
+	instr_time			before;
+	PGresult		   *result;
+	t_notice_messages	notes;
+
+	if (pset.timing)
+		INSTR_TIME_SET_CURRENT(before);
+
+	success = PQsendQuery(pset.db, query);
+	ResetCancelConn();
+
+	if (!success)
+		return -1;
+
+	/*
+	 * If SIGINT is sent while the query is processing, the interrupt will be
+	 * consumed.  The user's intention, though, is to cancel the entire watch
+	 * process, so detect a sent cancellation request and exit in this case.
+	 */
+	if (is_watch && cancel_pressed)
+	{
+		ClearOrSaveAllResults();
+		return 0;
+	}
+
+	/* intercept notices */
+	notes.in_flip = true;
+	initPQExpBuffer(&notes.flip);
+	initPQExpBuffer(&notes.flop);
+	PQsetNoticeProcessor(pset.db, AppendNoticeMessage, &notes);
+
+	/* first result */
+	result = PQgetResult(pset.db);
+
+	while (result != NULL)
+	{
+		ExecStatusType result_status;
+		PGresult   *next_result;
+		bool		last;
+
+		if (!AcceptResult(result, false))
+		{
+			/*
+			 * Some error occured, either a server-side failure or
+			 * a failure to submit the command string.  Record that.
+			 */
+			ShowNoticeMessage(&notes);
+			ShowErrorMessage(result);
+			if (!is_watch)
+				SetResultVariables(result, false);
+			ClearOrSaveResult(result);
+			success = false;
+
+			/* and switch to next result */
+			result = PQgetResult(pset.db);
+			continue;
+		}
+
+		/* must handle COPY before changing the current result */
+		result_status = PQresultStatus(result);
+		Assert(result_status != PGRES_COPY_BOTH);
+		if (result_status == PGRES_COPY_IN ||
+			result_status == PGRES_COPY_OUT)
+		{
+			ShowNoticeMessage(&notes);
+
+			if (is_watch)
+			{
+				ClearOrSaveAllResults();
+				pg_log_error("\\watch cannot be used with COPY");
+				return -1;
+			}
+
+			success &= HandleCopyResult(&result);
+		}
+
+		/*
+		 * Check PQgetResult() again.  In the typical case of a single-command
+		 * string, it will return NULL.  Otherwise, we'll have other results
+		 * to process.
+		 */
+		notes.in_flip = !notes.in_flip;
+		next_result = PQgetResult(pset.db);
+		notes.in_flip = !notes.in_flip;
+		last = (next_result == NULL);
+
+		/*
+		 * Get timing measure before printing the last result.
+		 *
+		 * It will include the display of previous results, if any.
+		 * This cannot be helped because the server goes on processing
+		 * further queries anyway while the previous ones are being displayed.
+		 * The parallel execution of the client display hides the server time
+		 * when it is shorter.
+		 *
+		 * With combined queries, timing must be understood as an upper bound
+		 * of the time spent processing them.
+		 */
+		if (last && pset.timing)
+		{
+			instr_time	now;
+			INSTR_TIME_SET_CURRENT(now);
+			INSTR_TIME_SUBTRACT(now, before);
+			*pelapsed_msec = INSTR_TIME_GET_MILLISEC(now);
+		}
+
+		/* notices already shown above for copy */
+		ShowNoticeMessage(&notes);
+
+		/* this may or may not print something depending on settings */
+		if (result != NULL)
+			success &= HandleQueryResult(result, last);
+
+		/* set variables on last result if all went well */
+		if (!is_watch && last && success)
+			SetResultVariables(result, true);
+
+		ClearOrSaveResult(result);
+		notes.in_flip = !notes.in_flip;
+		result = next_result;
+	}
+
+	/* reset notice hook */
+	PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
+	termPQExpBuffer(&notes.flip);
+	termPQExpBuffer(&notes.flop);
+
+	/* may need this to recover from conn loss during COPY */
+	if (!CheckConnection())
+		return -1;
+
+	return success ? 1 : -1;
+}
+
 
 /*
@@ -1178,9 +1244,9 @@ PrintQueryResults(PGresult *results)
@@ -1283,28 +1349,8 @@ SendQuery(const char *query)
+		int res = SendQueryAndProcessResults(query, &elapsed_msec, false);
+		OK = (res >= 0);
 	}
 	else
 	{
@@ -1485,7 +1531,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1533,7 +1579,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (pset.timing)
 			{
@@ -1543,7 +1589,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}
@@ -1602,7 +1648,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1616,7 +1662,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1689,7 +1735,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
@@ -1798,7 +1844,7 @@ cleanup:
@@ -1808,7 +1854,7 @@ cleanup:
index e40287d25a..1b31edc9ea 100644
 
+ ?column? 
+----------
+        4
+(1 row)
+
 create table test3 (c int);
 select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
+ ?column? 
+----------
+        0
+(1 row)
+
  ?column? 
 ----------
         1
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7d2d6328fc..0c746de3fe 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5012,3 +5012,87 @@ List of access methods
  hash  | uuid_ops        | uuid          | uuid           |      2 | uuid_hash_extended
 (5 rows)
 
+--
+-- combined queries
+--

 
index bd10aec6d6..8dbe2322ab 100644
@@ -1207,3 +1207,38 @@ drop role regress_partitioning_role;
 \dAo * pg_catalog.jsonb_path_ops
 \dAp btree float_ops
 \dAp * pg_catalog.uuid_ops

---

From: Thomas Munro <[email-masked]>
Date: Mon, 20 Jul 2020 16:57:34 +1200

Hi Fabien,

This seems to break the 013_crash_restart.pl test.

---

From: Fabien COELHO <[email-masked]>
Date: Mon, 20 Jul 2020 07:48:42 +0200 (CEST)

> This seems to break the 013_crash_restart.pl test.

Yes, indeed. I'm planning to investigate, hopefully this week.

-- 
Fabien.

---

From: Michael Paquier <[email-masked]>
Date: Wed, 30 Sep 2020 15:21:05 +0900

> Yes, indeed. I'm planning to investigate, hopefully this week.

This reply was two months ago, and nothing has happened, so I have
marked the patch as RwF.
--
Michael

---

From: Peter Eisentraut <[email-masked]>
Date: Sat, 27 Feb 2021 16:19:30 +0100

> This reply was two months ago, and nothing has happened, so I have

Given the ongoing work on returning multiple result sets from stored 
procedures[0], I went to dust off this patch.

Based on the feedback, I put back the titular SHOW_ALL_RESULTS option, 
but set the default to on.  I fixed the test failure in 
013_crash_restart.pl.  I also trimmed back the test changes a bit so 
that the resulting test output changes are visible better.  (We could 
make those stylistic changes separately in another patch.)  I'll put 
this back into the commitfest for another look.

[0]: 
https://www.postgresql.org/message-id/flat/[email-masked]

---

From: Fabien COELHO <[email-masked]>
Date: Sun, 14 Mar 2021 10:54:28 +0100 (CET)

Hello Peter,

> this back into the commitfest for another look.

Thanks a lot for the fixes and pushing it forward!

My 0.02€: I tested this updated version and do not have any comment on 
this version. From my point of view it could be committed. I would not 
bother to separate the test style ajustments.

-- 
Fabien.

---

From: Peter Eisentraut <[email-masked]>
Date: Tue, 6 Apr 2021 17:35:20 +0200

> bother to separate the test style ajustments.

Committed.  The last thing I fixed was the diff in the copy2.out 
regression test.  The order of the notices with respect to the error 
messages was wrong.  I fixed that by switching back to the regular 
notice processor during COPY handling.

Btw., not sure if that was mentioned before, but a cool use of this is 
to \watch multiple queries at once, like

select current_date \; select current_time \watch

---

From: Fabien COELHO <[email-masked]>
Date: Tue, 6 Apr 2021 23:29:01 +0200 (CEST)

Hello Peter,

>> My 0.02€: I tested this updated version and do not have any comment on this 
>> version. From my point of view it could be committed. I would not bother to 
>> separate the test style ajustments.
>
> Committed.  The last thing I fixed was the diff in the copy2.out regression 
> test.  The order of the notices with respect to the error messages was wrong. 
> I fixed that by switching back to the regular notice processor during COPY 
> handling.
>
> Btw., not sure if that was mentioned before, but a cool use of this is to 
> \watch multiple queries at once, like
>

Indeed, that was one of the things I tested on the patch. I'm wondering 
whether the documentation should point this out explicitely.

Anyway Thanks for the push!

-- 
Fabien.

---

From: "[email-masked]" <[email-masked]>
Date: Wed, 7 Apr 2021 07:40:57 +0000

Hi

I met a problem after commit 3a51306722.

While executing  a SQL statement with psql,  I can't interrupt it by pressing ctrl+c.

For example:
postgres=# insert into test select generate_series(1,10000000);
^C^CINSERT 0 10000000

Press ctrl+c before finishing INSERT, and psql still continuing to INSERT.

Is it the result expected? And I think maybe it is better to allow users to interrupt by pressing ctrl+c.

Regards,
Shi yu

---

From: Fabien COELHO <[email-masked]>
Date: Wed, 7 Apr 2021 16:02:10 +0200 (CEST)

Hello,

> Press ctrl+c before finishing INSERT, and psql still continuing to INSERT.

I can confirm this unexpected change of behavior on this commit. This is 
indeed e bug.

> Is it the result expected?

Obviously not.

> And I think maybe it is better to allow users to interrupt by pressing 
> ctrl+c.

Obviously yes.

The problem is that the cancellation stuff is cancelled too early after 
sending an asynchronous request.

Attached a patch which attempts to fix this by moving the cancellation 
cancelling request after processing results.

-- 
Fabien.

diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 028a357991..0482e57d45 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1117,7 +1117,6 @@ SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_wat
 		INSTR_TIME_SET_CURRENT(before);
 
 	success = PQsendQuery(pset.db, query);
-	ResetCancelConn();
 
 	if (!success)
 	{
@@ -1257,6 +1256,9 @@ SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_wat
 	if (!CheckConnection())
 		return -1;
 
+	/* all results are processed, nothing to cancel anymore */
+	ResetCancelConn();
+
 	return success ? 1 : -1;
 }

---

From: "[email-masked]" <[email-masked]>
Date: Thu, 8 Apr 2021 01:32:12 +0000

> cancelling request after processing results.

Thank you for your fixing. I tested and the problem has been solved after applying your patch.

Regards,
Shi yu

---

From: Julien Rouhaud <[email-masked]>
Date: Fri, 9 Apr 2021 00:30:50 +0800

> Thank you for your fixing. I tested and the problem has been solved after applying your patch.

Thanks for the patch Fabien.  I've hit this issue multiple time and this is
indeed unwelcome.  Should we add it as an open item?

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 8 Apr 2021 19:04:01 +0200 (CEST)

Bonjour Julien,

> indeed unwelcome.  Should we add it as an open item?

It is definitely a open item. I'm not sure where you want to add it… 
possibly the "Pg 14 Open Items" wiki page? I tried but I do not have 
enough privileges, if you can do it please proceed. I added an entry in 
the next CF in the bugfix section.

-- 
Fabien.

---

From: Julien Rouhaud <[email-masked]>
Date: Fri, 9 Apr 2021 01:11:35 +0800

Bonjour Fabien,

On Thu, Apr 08, 2021 at 07:04:01PM +0200, Fabien COELHO wrote:
> possibly the "Pg 14 Open Items" wiki page?

Correct.

> I tried but I do not have enough
> privileges, if you can do it please proceed. I added an entry in the next CF
> in the bugfix section.

That's strange, I don't think you need special permission there.  It's
working for me so I added an item with a link to the patch!

---

From: Michael Paquier <[email-masked]>
Date: Fri, 9 Apr 2021 10:06:24 +0900

>> possibly the "Pg 14 Open Items" wiki page?

I was running a long query this morning and wondered why the
cancellation was suddenly broken.  So I am not alone, and here you are
with already a solution :)

So, studying through 3a51306, this stuff has changed the query
execution from a sync PQexec() to an async PQsendQuery().  And the
proposed fix changes back to the behavior where the cancellation
reset happens after getting a result, as there is no need to cancel
anything.

No strong objections from here if the consensus is to make
SendQueryAndProcessResults() handle the cancel reset properly though I
am not sure if this is the cleanest way to do things, but let's make
at least the whole business consistent in the code for all those code
paths.  For example, PSQLexecWatch() does an extra ResetCancelConn()
that would be useless once we are done with
SendQueryAndProcessResults().  Also, I can see that
SendQueryAndProcessResults() would not issue a cancel reset if the
query fails, for \watch when cancel is pressed, and for \watch with
COPY.  So, my opinion here would be to keep ResetCancelConn() within
PSQLexecWatch(), just add an extra one in SendQuery() to make all the
three code paths printing results consistent, and leave
SendQueryAndProcessResults() out of the cancellation logic.

> working for me so I added an item with a link to the patch!

As long as you have a community account, you should have the
possibility to edit the page.  So if you feel that any change is
required, please feel free to do so, of course.
--
Michael

---

From: Bharath Rupireddy <[email-masked]>
Date: Fri, 9 Apr 2021 08:50:29 +0530

> SendQueryAndProcessResults() out of the cancellation logic.

Hi, I'm also facing the query cancellation issue, I have to kill the
backend everytime to cancel a query, it's becoming difficult.

With Regards,
Bharath Rupireddy.

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 9 Apr 2021 08:47:07 +0200 (CEST)

Bonjour Michaël,

> execution from a sync PQexec() to an async PQsendQuery().

Yes, because we want to handle all results whereas PQexec jumps to the 
last one.

> And the proposed fix changes back to the behavior where the cancellation 

Yep. ISTM that was what happens internally in PQexec.

> am not sure if this is the cleanest way to do things,

I was wondering as well, I did a quick fix because it can be irritating 
and put off looking at it more precisely over the week-end.

> but let's make at least the whole business consistent in the code for 
> all those code paths.

There are quite a few of them, some which reset the stuff and some which 
do not depending on various conditions, some with early exits, all of 
which required brain cells and a little time to investigate…

> For example, PSQLexecWatch() does an extra ResetCancelConn() that would 
> be useless once we are done with SendQueryAndProcessResults().  Also, I 
> can see that SendQueryAndProcessResults() would not issue a cancel reset 
> if the query fails, for \watch when cancel is pressed, and for \watch 
> with COPY.

> So, my opinion here would be to keep ResetCancelConn() within 

Yep, it looks much better. I found it strange that the later did a reset 
but was not doing the set.

Attached v2 does as you suggest.

> possibility to edit the page.

After login as "calvin", I have "Want to edit, but don't see an edit 
button when logged in? Click here.".

> So if you feel that any change is required, please feel free to do so, 
> of course.

-- 
Fabien.

index 028a357991..5355086fe2 100644

 	if (!success)
 	{
@@ -1486,6 +1485,8 @@ SendQuery(const char *query)
 
 sendquery_cleanup:
 
+	ResetCancelConn();
+
 	/* reset \g's output-to-filename trigger */
 	if (pset.gfname)
 	{

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 9 Apr 2021 08:52:20 +0200 (CEST)

> Attached v2 does as you suggest.

There is not a single test of "ctrl-c" which would have caught this 
trivial and irritating regression. ISTM that a TAP test is doable. Should 
one be added?

-- 
Fabien.

---

From: Michael Paquier <[email-masked]>
Date: Fri, 9 Apr 2021 19:51:25 +0900

On Fri, Apr 09, 2021 at 08:47:07AM +0200, Fabien COELHO wrote:
> Yep, it looks much better. I found it strange that the later did a reset but
> was not doing the set.
> 

Close enough.  I was thinking about this position of the attached,
which is more consistent with the rest.
--
Michael
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 028a357991..f289f05843 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1117,7 +1117,6 @@ SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_wat
 		INSTR_TIME_SET_CURRENT(before);

 	if (!success)
 	{
@@ -1382,6 +1381,7 @@ SendQuery(const char *query)
 	{
 		/* Default fetch-it-all-and-print mode */
 		int res = SendQueryAndProcessResults(query, &elapsed_msec, false);
+		ResetCancelConn();
 		OK = (res >= 0);
 		results = NULL;
 	}

---

From: Alvaro Herrera <[email-masked]>
Date: Fri, 9 Apr 2021 12:12:03 -0400

On 2021-Apr-08, Fabien COELHO wrote:

> possibly the "Pg 14 Open Items" wiki page? I tried but I do not have enough

User "calvin" has privs of wiki editor.  If that's not your Wiki
username, please state what it is.

-- 
Álvaro Herrera       Valdivia, Chile
"Cuando mañana llegue pelearemos segun lo que mañana exija" (Mowgli)

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 9 Apr 2021 20:04:11 +0200 (CEST)

> which is more consistent with the rest.

Given the structural complexity of the function, the end of the file 
seemed like a good place to have an all-path-guaranteed reset.

I find it a little bit strange to have the Set at the upper level and the 
Reset in many… but not all branches, though.

For instance the on_error_rollback_savepoint/svptcmd branch includes a 
reset long after many other conditional resets, I cannot guess whether the 
initial set is still active or has been long wiped out and this query is 
just not cancellable.

Also, ISTM that in the worst case a cancellation request is sent to a 
server which is idle, in which case it will be ignored, so the code should 
be in no hurry to clean it, at least not at the price of code clarity.

Anyway, the place you suggest seems ok.

-- 
Fabien.

---

From: Tom Lane <[email-masked]>
Date: Sun, 11 Apr 2021 11:14:07 -0400

Coverity has pointed out another problem with this patch:

/srv/coverity/git/pgsql-git/postgresql/src/bin/psql/common.c: 1425 in SendQuery()
1419     				/*
1420     				 * Do nothing if they are messing with savepoints themselves:
1421     				 * If the user did COMMIT AND CHAIN, RELEASE or ROLLBACK, our
1422     				 * savepoint is gone. If they issued a SAVEPOINT, releasing
1423     				 * ours would remove theirs.
1424     				 */
>>>     CID 1476042:  Control flow issues  (DEADCODE)
>>>     Execution cannot reach the expression "strcmp(PQcmdStatus(results), "COMMIT") == 0" inside this statement: "if (results && (strcmp(PQcm...".
1425     				if (results &&
1426     					(strcmp(PQcmdStatus(results), "COMMIT") == 0 ||
1427     					 strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
1428     					 strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
1429     					 strcmp(PQcmdStatus(results), "ROLLBACK") == 0))
1430     					svptcmd = NULL;

It's right: this is dead code because all paths through the if-nest
starting at line 1373 now leave results = NULL.  Hence, this patch
has broken the autocommit logic; it's no longer possible to tell
whether we should do anything with our savepoint.

Between this and the known breakage of control-C, it seems clear
to me that this patch was nowhere near ready for prime time.
I think shoving it in on the last day before feature freeze was
ill-advised, and it ought to be reverted.  We can try again later.

			regards, tom lane

---

From: Tom Lane <[email-masked]>
Date: Sun, 11 Apr 2021 12:16:04 -0400

... btw, Coverity also doesn't like this fragment of the patch:

/srv/coverity/git/pgsql-git/postgresql/src/bin/psql/common.c: 1084 in ShowNoticeMessage()
1078     static void
1079     ShowNoticeMessage(t_notice_messages *notes)
1080     {
1081     	PQExpBufferData	*current = notes->in_flip ? &notes->flip : &notes->flop;
1082     	if (current->data != NULL && *current->data != '\0')
1083     		pg_log_info("%s", current->data);
>>>     CID 1476041:  Null pointer dereferences  (FORWARD_NULL)
>>>     Passing "current" to "resetPQExpBuffer", which dereferences null "current->data".
1084     	resetPQExpBuffer(current);
1085     }
1086     
1087     /*
1088      * SendQueryAndProcessResults: utility function for use by SendQuery()
1089      * and PSQLexecWatch().

Its point here is that either the test of "current->data != NULL" is
useless, or resetPQExpBuffer needs such a test too.  I'm inclined
to guess the former.

(Just as a matter of style, I don't care for the flip/flop terminology
here, not least because it's not clear why exactly two buffers suffice
and will suffice forevermore.  I'd be inclined to use an array of
two buffers with an index variable.)

			regards, tom lane

---

From: Michael Paquier <[email-masked]>
Date: Mon, 12 Apr 2021 12:10:11 +0900

On Fri, Apr 09, 2021 at 08:52:20AM +0200, Fabien COELHO wrote:
> There is not a single test of "ctrl-c" which would have caught this trivial
> and irritating regression. ISTM that a TAP test is doable. Should one be
> added?

If you can design something reliable, I would welcome that.
--
Michael

---

From: Tom Lane <[email-masked]>
Date: Sun, 11 Apr 2021 23:44:26 -0400

> If you can design something reliable, I would welcome that.

+1, there's a lot of moving parts there.

I think avoiding any timing issues wouldn't be hard; the
query-to-be-interrupted could be "select pg_sleep(1000)" or so.
What's less clear is whether we can trigger the control-C
response reliably across platforms.

			regards, tom lane

---

From: Michael Paquier <[email-masked]>
Date: Mon, 12 Apr 2021 13:19:27 +0900

> whether we should do anything with our savepoint.

Ugh, that's a good catch from Coverity here.

> ill-advised, and it ought to be reverted.  We can try again later.

Yes, I agree that a revert would be more adapted at this stage.
Peter?
--
Michael

---

From: Fabien COELHO <[email-masked]>
Date: Mon, 12 Apr 2021 16:25:33 +0200 (CEST)

Hello Tom,

> has broken the autocommit logic;

Do you mean yet another feature without a single non-regression test? :-(

I tend to rely on non regression tests to catch bugs in complex 
multi-purpose hard-to-maintain functions when the code is modified.

I have submitted a patch to improve psql coverage to about 90%, but given 
the lack of enthousiasm, I simply dropped it. Not sure I was right not 
to insist.

> it's no longer possible to tell whether we should do anything with our 
> savepoint.

> ill-advised, and it ought to be reverted.  We can try again later.

The patch has been asleep for quite a while, and was resurrected, possibly 
too late in the process. ISTM that fixing it for 14 is manageable, 
but this is not my call.

-- 
Fabien.

---

From: Tom Lane <[email-masked]>
Date: Mon, 12 Apr 2021 12:23:26 -0400

> too late in the process. ISTM that fixing it for 14 is manageable, 

I just observed an additional issue that I assume was introduced by this
patch, which is that psql's response to a server crash has gotten
repetitive:

regression=# CREATE VIEW v1(c1) AS (SELECT ('4' COLLATE "C")::INT FROM generate_series(1, 10));
server closed the connection unexpectedly
        This probably means the server terminated abnormally
        before or while processing the request.
The connection to the server was lost. Attempting reset: Failed.
The connection to the server was lost. Attempting reset: Failed.
!?> 

I've never seen that before, and it's not because I don't see
server crashes regularly.

			regards, tom lane

---

From: "Bossart, Nathan" <[email-masked]>
Date: Mon, 12 Apr 2021 19:08:21 +0000

> server crashes regularly.

I think I've found another issue with this patch.  If AcceptResult()
returns false in SendQueryAndProcessResults(), it seems to result in
an infinite loop of "unexpected PQresultStatus" messages.  This can be
reproduced by trying to run "START_REPLICATION" via psql.

The following patch seems to resolve the issue, although I'll admit I
haven't dug into this too deeply.  In any case, +1 for reverting the
patch for now.

diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 028a357991..abafd41763 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -1176,7 +1176,7 @@ SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_wat

             /* and switch to next result */
             result = PQgetResult(pset.db);
-            continue;
+            break;
         }

         /* must handle COPY before changing the current result */

Nathan

---

From: Alvaro Herrera <[email-masked]>
Date: Mon, 12 Apr 2021 15:33:01 -0400

> haven't dug into this too deeply.  In any case, +1 for reverting the

Please note that there's no "for now" about it -- if the patch is
reverted, the only way to get it back is to wait for PG15.  That's
undesirable.  A better approach is to collect all those bugs and get
them fixed.  There's plenty of time to do that.

I, for one, would prefer to see the feature repaired in this cycle.

---

From: Michael Paquier <[email-masked]>
Date: Tue, 13 Apr 2021 08:31:04 +0900

> reproduced by trying to run "START_REPLICATION" via psql.

Yes, that's another problem, and this causes an infinite loop where
we would just report one error previously :/
--
Michael

---

From: Michael Paquier <[email-masked]>
Date: Thu, 15 Apr 2021 10:21:32 +0900

> I, for one, would prefer to see the feature repaired in this cycle.

If it is possible to get that fixed, I would not mind waiting a bit
more but it would be nice to see some actual proposals.  There are
already three identified bugs in psql introduced by this commit,
including the query cancellation.

That's a lot IMO, so my vote would be to discard this feature for now
and revisit it properly in the 15 dev cycle, so as resources are
redirected into more urgent issues (13 open items as of the moment of
writing this email).
--
Michael

---

From: Tom Lane <[email-masked]>
Date: Wed, 14 Apr 2021 21:51:47 -0400

> redirected into more urgent issues (13 open items as of the moment of

I don't wish to tell people which open issues they ought to work on
... but this patch seems like it could be quite a large can of worms,
and I'm not detecting very much urgency about getting it fixed.
If it's not to be reverted then some significant effort needs to be
put into it soon.

			regards, tom lane

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 15 Apr 2021 13:51:20 +0200 (CEST)

Hello Tom,

> If it's not to be reverted then some significant effort needs to be

My overly naive trust in non regression test to catch any issues has been 
largely proven wrong. Three key features do not have a single tests. Sigh.

I'll have some time to look at it over next week-end, but not before.

-- 
Fabien.

---

From: Peter Eisentraut <[email-masked]>
Date: Thu, 15 Apr 2021 20:06:59 +0200

On 15.04.21 13:51, Fabien COELHO wrote:
> My overly naive trust in non regression test to catch any issues has 
> been largely proven wrong. Three key features do not have a single 
> tests. Sigh.
> 

I have reverted the patch and moved the commit fest entry to CF 2021-07.

---

From: Fabien COELHO <[email-masked]>
Date: Sat, 12 Jun 2021 11:41:24 +0200 (CEST)

Hello Peter,

> I have reverted the patch and moved the commit fest entry to CF 2021-07.

Attached a v7 which fixes known issues.

I've tried to simplify the code and added a few comments. I've moved query 
cancellation reset in one place in SendQuery. I've switched to an array of 
buffers for notices, as suggested by Tom.

The patch includes basic AUTOCOMMIT and ON_ERROR_ROLLBACK tests, which did 
not exist before, at all. I tried cancelling queries manually, but did not 
develop a test for this, mostly because last time I submitted a TAP test 
about psql to raise its coverage it was rejected.

As usual, what is not tested does not work…

-- 
Fabien.

index 40b5109b55..ccce72fb85 100644
@@ -50,8 +50,28 @@ BEGIN \;
 \;\;   SELECT 3 + 3 \;\;\;   SELECT ' ' || ' !' \;\;   SELECT 1 + 4 \;;
         5
@@ -61,6 +81,11 @@ COMMIT;
index a8dfc41e40..1dbfb6a52a 100644
@@ -3532,10 +3525,6 @@ select 1\; select 2\; select 3;
@@ -4122,6 +4111,18 @@ bar
       </varlistentry>
 
       <varlistentry>
+        <term><varname>SHOW_ALL_RESULTS</varname></term>
+        <listitem>
+        <para>
+        When this variable is set to <literal>off</literal>, only the last
+        result of a combined query (<literal>\;</literal>) is shown instead of
+        all of them.  The default is <literal>on</literal>.  The off behavior
+        is for compatibility with older versions of psql.
+        </para>
+        </listitem>
+      </varlistentry>
+
+       <varlistentry>
         <term><varname>SHOW_CONTEXT</varname></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 9a00499510..54a097a493 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -33,6 +33,7 @@ static bool DescribeQuery(const char *query, double *elapsed_msec);
 static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
 static bool command_no_begin(const char *query);
 static bool is_select_command(const char *query);
+static int SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_watch);

 /*
@@ -353,7 +354,7 @@ CheckConnection(void)
  * Returns true for valid result, false for error state.
  */
 static bool
-AcceptResult(const PGresult *result)
+AcceptResult(const PGresult *result, bool show_error)
 {
 	bool		OK;
 
@@ -384,7 +385,7 @@ AcceptResult(const PGresult *result)
 				break;
 		}
 
-	if (!OK)
+	if (!OK && show_error)
 	{
 		const char *error = PQerrorMessage(pset.db);
 
@@ -472,6 +473,18 @@ ClearOrSaveResult(PGresult *result)
 	}
 }
 
+/*
+ * Consume all results
+ */
+static void
+ClearOrSaveAllResults()
+{
+	PGresult	*result;
+
 
 /*
  * Print microtiming output.  Always print raw milliseconds; if the interval
@@ -572,7 +585,7 @@ PSQLexec(const char *query)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		res = NULL;
@@ -594,10 +607,8 @@ PSQLexec(const char *query)
 int
 PSQLexecWatch(const char *query, const printQueryOpt *opt)
 {
-	PGresult   *res;
 	double		elapsed_msec = 0;
-	instr_time	before;
-	instr_time	after;
+	int			res;
 
 	if (!pset.db)
 	{
@@ -606,75 +617,16 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
 	}
 
 	SetCancelConn(pset.db);
-
-	if (pset.timing)

 
-	return 1;
+	return res;
 }

@@ -887,197 +839,114 @@ loop_exit:

 /*
+ * on COPY IN, or on COPY OUT if written to something other than pset.queryFout.
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*
@@ -1105,43 +974,50 @@ PrintQueryStatus(PGresult *results)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:
 
@@ -1151,7 +1027,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1164,7 +1040,7 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}
 
@@ -1173,6 +1049,216 @@ PrintQueryResults(PGresult *results)
 	return success;
 }
 
+/*
+	PQExpBufferData	messages[2];
+	int				current;
+	appendPQExpBufferStr(&notes->messages[notes->current], msg);
+	PQExpBufferData	*current = &notes->messages[notes->current];
+	if (*current->data != '\0')
+	notes.current = 0;
+	initPQExpBuffer(&notes.messages[0]);
+	initPQExpBuffer(&notes.messages[1]);
+			/* use normal notice processor during COPY */
+		notes.current ^= 1;
+		notes.current ^= 1;
+		notes.current ^= 1;
+	termPQExpBuffer(&notes.messages[0]);
+	termPQExpBuffer(&notes.messages[1]);
 
 /*
  * SendQuery: send the query string to the backend
@@ -1189,10 +1275,11 @@ PrintQueryResults(PGresult *results)
 bool
 SendQuery(const char *query)
 {
-	PGresult   *results;
+	PGresult   *results = NULL;
 	PGTransactionStatusType transaction_status;
 	double		elapsed_msec = 0;
 	bool		OK = false;
+	bool		res_error;
 	int			i;
 	bool		on_error_rollback_savepoint = false;
 	static bool on_error_rollback_warning = false;
@@ -1234,26 +1321,32 @@ SendQuery(const char *query)
 		fflush(pset.logfile);
 	}
 
+	/* global query cancellation for this query */
 	SetCancelConn(pset.db);
 
 	transaction_status = PQtransactionStatus(pset.db);
 
+	/* issue a BEGIN if needed, corresponding COMMIT/ROLLBACK by user */
 	if (transaction_status == PQTRANS_IDLE &&
 		!pset.autocommit &&
 		!command_no_begin(query))
 	{
-		results = PQexec(pset.db, "BEGIN");
-		if (PQresultStatus(results) != PGRES_COMMAND_OK)
+		PGresult    *result;
+		result = PQexec(pset.db, "BEGIN");
+		res_error = PQresultStatus(result) != PGRES_COMMAND_OK;
+		ClearOrSaveResult(result);
+
+		if (res_error)
 		{
 			pg_log_info("%s", PQerrorMessage(pset.db));
-			ClearOrSaveResult(results);
-			ResetCancelConn();
 			goto sendquery_cleanup;
 		}
-		ClearOrSaveResult(results);
+
+		/* must be PQTRANS_INTRANS after a BEGIN */
 		transaction_status = PQtransactionStatus(pset.db);
 	}
 
+	/* create automatic savepoint if needed and possible */
 	if (transaction_status == PQTRANS_INTRANS &&
 		pset.on_error_rollback != PSQL_ERROR_ROLLBACK_OFF &&
 		(pset.cur_cmd_interactive ||
@@ -1270,58 +1363,40 @@ SendQuery(const char *query)
 		}
 		else
 		{
-			results = PQexec(pset.db, "SAVEPOINT pg_psql_temporary_savepoint");
-			if (PQresultStatus(results) != PGRES_COMMAND_OK)
+			PGresult   *result;
+			result = PQexec(pset.db, "SAVEPOINT pg_psql_temporary_savepoint");
+			res_error = PQresultStatus(result) != PGRES_COMMAND_OK;
+			ClearOrSaveResult(result);
+
+			if (res_error)
 			{
 				pg_log_info("%s", PQerrorMessage(pset.db));
-				ClearOrSaveResult(results);
-				ResetCancelConn();
 				goto sendquery_cleanup;
 			}
-			ClearOrSaveResult(results);
+
 			on_error_rollback_savepoint = true;
 		}
 	}
 
+	/* process the query, one way or the other */
 	if (pset.gdesc_flag)
 	{
 		/* Describe query's result columns, without executing it */
 		OK = DescribeQuery(query, &elapsed_msec);
-		ResetCancelConn();
 		results = NULL;			/* PQclear(NULL) does nothing */
 	}
 	else if (pset.fetch_count <= 0 || pset.gexec_flag ||
 			 pset.crosstab_flag || !is_select_command(query))
 	{
 		/* Default fetch-it-all-and-print mode */
-		instr_time	before,
-					after;
-
-		if (pset.timing)
-			INSTR_TIME_SET_CURRENT(before);
-
-		results = PQexec(pset.db, query);
-
-		/* these operations are included in the timing result: */
-		ResetCancelConn();
-		OK = ProcessResult(&results);
-
-		if (pset.timing)
-		{
-			INSTR_TIME_SET_CURRENT(after);
-			INSTR_TIME_SUBTRACT(after, before);
-			elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
-		}
-
-		/* but printing results isn't: */
-		if (OK && results)
-			OK = PrintQueryResults(results);
+		int res = SendQueryAndProcessResults(query, &elapsed_msec, false);
+		OK = (res >= 0);
+		/* do NOT overwrite results here, for on_error_rollback_savepoint */
 	}
 	else
 	{
 		/* Fetch-in-segments mode */
 		OK = ExecQueryUsingCursor(query, &elapsed_msec);
-		ResetCancelConn();
 		results = NULL;			/* PQclear(NULL) does nothing */
 	}
 
@@ -1380,14 +1455,15 @@ SendQuery(const char *query)
 			PGresult   *svptres;
 
 			svptres = PQexec(pset.db, svptcmd);
-			if (PQresultStatus(svptres) != PGRES_COMMAND_OK)
+			res_error = PQresultStatus(svptres) != PGRES_COMMAND_OK;
+
+			if (res_error)
 			{
 				pg_log_info("%s", PQerrorMessage(pset.db));
 				ClearOrSaveResult(svptres);
 				OK = false;
 
 				PQclear(results);
-				ResetCancelConn();
 				goto sendquery_cleanup;
 			}
 			PQclear(svptres);
@@ -1418,6 +1494,9 @@ SendQuery(const char *query)
 
 sendquery_cleanup:
 
+	/* global cancellation reset */
+	ResetCancelConn();
+
 	/* reset \g's output-to-filename trigger */
 	if (pset.gfname)
 	{
@@ -1497,7 +1576,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1545,7 +1624,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (pset.timing)
 			{
@@ -1555,7 +1634,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}
@@ -1614,7 +1693,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1628,7 +1707,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1701,7 +1780,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
-			OK = AcceptResult(results);
+			OK = AcceptResult(results, true);
 			Assert(!OK);
 			SetResultVariables(results, OK);
 			ClearOrSaveResult(results);
@@ -1810,7 +1889,7 @@ cleanup:
 	results = PQexec(pset.db, "CLOSE _psql_cursor");
 	if (OK)
 	{
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 	}
@@ -1820,7 +1899,7 @@ cleanup:
 	if (started_txn)
 	{
 		results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
-		OK &= AcceptResult(results) &&
+		OK &= AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 	}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 3c250d11cf..bbca057202 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -412,6 +412,8 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  SERVER_VERSION_NAME\n"
 					  "  SERVER_VERSION_NUM\n"
 					  "    server's version (in short string or numeric format)\n"));
+	fprintf(output, _("  SHOW_ALL_RESULTS\n"
+					  "    show all results of a combined query (\\;) instead of only the last\n"));
 	fprintf(output, _("  SHOW_CONTEXT\n"
 					  "    controls display of message context fields [never, errors, always]\n"));
 	fprintf(output, _("  SINGLELINE\n"
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 83f2e6f254..62583ad6ca 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -148,6 +148,7 @@ typedef struct _psqlSettings
 	const char *prompt2;
 	const char *prompt3;
 	PGVerbosity verbosity;		/* current error verbosity level */
+	bool		show_all_results;
 	PGContextVisibility show_context;	/* current context display level */
 } PsqlSettings;
 
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 110906a4e9..17437ce07d 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -196,6 +196,7 @@ main(int argc, char *argv[])
 	SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1);
 	SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2);
 	SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3);
+	SetVariableBool(pset.vars, "SHOW_ALL_RESULTS");
 
 	parse_psql_options(argc, argv, &options);
 
@@ -1130,6 +1131,12 @@ verbosity_hook(const char *newval)
 	return true;
 }
 
+static bool
+show_all_results_hook(const char *newval)
+{
+	return ParseVariableBool(newval, "SHOW_ALL_RESULTS", &pset.show_all_results);
+}
+
 static char *
 show_context_substitute_hook(char *newval)
 {
@@ -1231,6 +1238,9 @@ EstablishVariableSpace(void)
 	SetVariableHooks(pset.vars, "VERBOSITY",
 					 verbosity_substitute_hook,
 					 verbosity_hook);
+	SetVariableHooks(pset.vars, "SHOW_ALL_RESULTS",
+					 bool_substitute_hook,
+					 show_all_results_hook);
 	SetVariableHooks(pset.vars, "SHOW_CONTEXT",
 					 show_context_substitute_hook,
 					 show_context_hook);
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index bd8e9ea2f8..450f0e2ac1 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4124,7 +4124,7 @@ psql_completion(const char *text, int start, int end)
 		matches = complete_from_variables(text, "", "", false);
 	else if (TailMatchesCS("\\set", MatchAny))
 	{
-		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
+		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|SHOW_ALL_RESULTS|"
 						  "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_CS("on", "off");
 		else if (TailMatchesCS("COMP_KEYWORD_CASE"))
diff --git a/src/test/regress/expected/copyselect.out b/src/test/regress/expected/copyselect.out
index 72865fe1eb..bb9e026f91 100644
--- a/src/test/regress/expected/copyselect.out
+++ b/src/test/regress/expected/copyselect.out
@@ -126,7 +126,7 @@ copy (select 1) to stdout\; select 1/0;	-- row, then error
 ERROR:  division by zero
 select 1/0\; copy (select 1) to stdout; -- error only
 ERROR:  division by zero
-copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; -- 1 2 3
+copy (select 1) to stdout\; copy (select 2) to stdout\; select 3\; select 4; -- 1 2 3 4
 1
 2
  ?column? 
@@ -134,8 +134,18 @@ copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; --
         3
 (1 row)
 
+ ?column? 
+----------
+        4
+(1 row)
+
 create table test3 (c int);
-select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
+select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 0 1
+ ?column? 
+----------
+        0
+(1 row)
+
  ?column? 
 ----------
         1
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..71e3b1e9fe 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5179,3 +5179,243 @@ List of access methods
  pg_catalog | &&   | anyarray      | anyarray       | boolean     | overlaps
 (1 row)
 
+--
+-- combined queries
+--
+CREATE FUNCTION warn(msg TEXT) RETURNS BOOLEAN LANGUAGE plpgsql
+AS $$
+  BEGIN RAISE NOTICE 'warn %', msg ; RETURN TRUE ; END
+$$;
+-- show both
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+ one 
+-----
+   1
+(1 row)
+
+NOTICE:  warn 1.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ warn 
+------
+ t
+(1 row)
+
+ two 
+-----
+   2
+(1 row)
+
+-- \gset applies to last query only
+SELECT 3 AS three \; SELECT warn('3.5') \; SELECT 4 AS four \gset
+ three 
+-------
+     3
+(1 row)
+
+NOTICE:  warn 3.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ warn 
+------
+ t
+(1 row)
+
+\echo :three :four
+:three 4
+-- syntax error stops all processing
+SELECT 5 \; SELECT 6 + \; SELECT warn('6.5') \; SELECT 7 ;
+ERROR:  syntax error at or near ";"
+LINE 1: SELECT 5 ; SELECT 6 + ; SELECT warn('6.5') ; SELECT 7 ;
+                              ^
+-- with aborted transaction, stop on first error
+BEGIN \; SELECT 8 AS eight \; SELECT 9/0 AS nine \; ROLLBACK \; SELECT 10 AS ten ;
+ eight 
+-------
+     8
+(1 row)
+
+ERROR:  division by zero
+-- close previously aborted transaction
+ROLLBACK;
+-- misc SQL commands
+-- (non SELECT output is sent to stderr, thus is not shown in expected results)
+SELECT 'ok' AS "begin" \;
+CREATE TABLE psql_comics(s TEXT) \;
+INSERT INTO psql_comics VALUES ('Calvin'), ('hobbes') \;
+COPY psql_comics FROM STDIN \;
+UPDATE psql_comics SET s = 'Hobbes' WHERE s = 'hobbes' \;
+DELETE FROM psql_comics WHERE s = 'Moe' \;
+COPY psql_comics TO STDOUT \;
+TRUNCATE psql_comics \;
+DROP TABLE psql_comics \;
+SELECT 'ok' AS "done" ;
+ begin 
+-------
+ ok
+(1 row)
+
+Calvin
+Susie
+Hobbes
+ done 
+------
+ ok
+(1 row)
+
+\set SHOW_ALL_RESULTS off
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+NOTICE:  warn 1.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ two 
+-----
+   2
+(1 row)
+
+\set SHOW_ALL_RESULTS on
+DROP FUNCTION warn(TEXT);
+--
+-- autocommit
+--
+\echo '# initial AUTOCOMMIT:' :AUTOCOMMIT
+# initial AUTOCOMMIT: on
+\set AUTOCOMMIT off
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+# AUTOCOMMIT: off
+-- implicit BEGIN
+CREATE TABLE foo(s TEXT);
+ROLLBACK;
+CREATE TABLE foo(s TEXT);
+INSERT INTO foo(s) VALUES ('hello'), ('world');
+COMMIT;
+DROP TABLE foo;
+ROLLBACK;
+SELECT * FROM foo ORDER BY 1;
+   s   
+-------
+ hello
+ world
+(2 rows)
+
+DROP TABLE foo;
+COMMIT;
+\set AUTOCOMMIT on
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+# AUTOCOMMIT: on
+-- explicit BEGIN
+BEGIN;
+CREATE TABLE foo(s TEXT);
+INSERT INTO foo(s) VALUES ('hello'), ('world');
+COMMIT;
+BEGIN;
+DROP TABLE foo;
+ROLLBACK;
+-- implicit transactions
+SELECT * FROM foo ORDER BY 1;
+   s   
+-------
+ hello
+ world
+(2 rows)
+
+DROP TABLE foo;
+\echo '# final AUTOCOMMIT:' :AUTOCOMMIT
+# final AUTOCOMMIT: on
+--
+-- test ON_ERROR_ROLLBACK
+--
+CREATE FUNCTION psql_error(msg TEXT) RETURNS BOOLEAN AS $$
+  BEGIN
+    RAISE EXCEPTION 'error %', msg;
+  END;
+$$ LANGUAGE plpgsql;
+\echo '# initial ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+# initial ON_ERROR_ROLLBACK: off
+\set ON_ERROR_ROLLBACK on
+\echo '# ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+# ON_ERROR_ROLLBACK: on
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+# AUTOCOMMIT: on
+BEGIN;
+CREATE TABLE bla(s NO_SUCH_TYPE);         -- fails
+ERROR:  type "no_such_type" does not exist
+LINE 1: CREATE TABLE bla(s NO_SUCH_TYPE);
+                           ^
+CREATE TABLE bla(s TEXT);                 -- succeeds
+SELECT psql_error('oops!');               -- fails
+ERROR:  error oops!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+INSERT INTO bla VALUES ('Calvin'), ('Hobbes');
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+   s    
+--------
+ Calvin
+ Hobbes
+(2 rows)
+
+BEGIN;
+INSERT INTO bla VALUES ('Susie');         -- succeeds
+-- combined queries
+INSERT INTO bla VALUES ('Rosalyn') \;     -- will rollback
+SELECT 'before error' AS show \;          -- will show!
+  SELECT psql_error('boum!') \;           -- failure
+  SELECT 'after error' AS noshow;         -- hidden by preceeding error
+     show     
+--------------
+ before error
+(1 row)
+
+ERROR:  error boum!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+INSERT INTO bla(s) VALUES ('Moe') \;      -- will rollback
+  SELECT psql_error('bam!');
+ERROR:  error bam!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+INSERT INTO bla VALUES ('Miss Wormwood'); -- succeeds
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+       s       
+---------------
+ Calvin
+ Hobbes
+ Miss Wormwood
+ Susie
+(4 rows)
+
+-- some with autocommit off
+\set AUTOCOMMIT off
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+# AUTOCOMMIT: off
+-- implicit BEGIN
+INSERT INTO bla VALUES ('Dad');           -- succeeds
+SELECT psql_error('bad!');                -- implicit partial rollback
+ERROR:  error bad!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+INSERT INTO bla VALUES ('Mum') \;         -- will rollback
+SELECT COUNT(*) AS "#mum"
+FROM bla WHERE s = 'Mum' \;               -- but be counted here
+SELECT psql_error('bad!');                -- implicit partial rollback
+ #mum 
+------
+    1
+(1 row)
+
+ERROR:  error bad!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+       s       
+---------------
+ Calvin
+ Dad
+ Hobbes
+ Miss Wormwood
+ Susie
+(5 rows)
+
+-- reset
+\set AUTOCOMMIT on
+\set ON_ERROR_ROLLBACK off
+\echo '# final ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+# final ON_ERROR_ROLLBACK: off
+DROP TABLE bla;
+DROP FUNCTION psql_error;
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 61862d595d..be1db0d5c0 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -900,8 +900,18 @@ DROP TABLE abc;
 -- tests rely on the fact that psql will not break SQL commands apart at a
 -- backslash-quoted semicolon, but will send them as one Query.
 create temp table i_table (f1 int);
--- psql will show only the last result in a multi-statement Query
+-- psql will show all results of a multi-statement Query
 SELECT 1\; SELECT 2\; SELECT 3;
+ ?column? 
+----------
+        1
+(1 row)
+
+ ?column? 
+----------
+        2
+(1 row)
+
  ?column? 
 ----------
         3
@@ -916,6 +926,12 @@ insert into i_table values(1)\; select * from i_table;
 
 -- 1/0 error will cause rolling back the whole implicit transaction
 insert into i_table values(2)\; select * from i_table\; select 1/0;
+ f1 
+----
+  1
+  2
+(2 rows)
+
 ERROR:  division by zero
 select * from i_table;
  f1 
@@ -935,8 +951,18 @@ WARNING:  there is no transaction in progress
 -- begin converts implicit transaction into a regular one that
 -- can extend past the end of the Query
 select 1\; begin\; insert into i_table values(5);
+ ?column? 
+----------
+        1
+(1 row)
+
 commit;
 select 1\; begin\; insert into i_table values(6);
+ ?column? 
+----------
+        1
+(1 row)
+
 rollback;
 -- commit in implicit-transaction state commits but issues a warning.
 insert into i_table values(7)\; commit\; insert into i_table values(8)\; select 1/0;
@@ -963,22 +989,52 @@ rollback;  -- we are not in a transaction at this point
 WARNING:  there is no transaction in progress
 -- implicit transaction block is still a transaction block, for e.g. VACUUM
 SELECT 1\; VACUUM;
+ ?column? 
+----------
+        1
+(1 row)
+
 ERROR:  VACUUM cannot run inside a transaction block
 SELECT 1\; COMMIT\; VACUUM;
 WARNING:  there is no transaction in progress
+ ?column? 
+----------
+        1
+(1 row)
+
 ERROR:  VACUUM cannot run inside a transaction block
 -- we disallow savepoint-related commands in implicit-transaction state
 SELECT 1\; SAVEPOINT sp;
+ ?column? 
+----------
+        1
+(1 row)
+
 ERROR:  SAVEPOINT can only be used in transaction blocks
 SELECT 1\; COMMIT\; SAVEPOINT sp;
 WARNING:  there is no transaction in progress
+ ?column? 
+----------
+        1
+(1 row)
+
 ERROR:  SAVEPOINT can only be used in transaction blocks
 ROLLBACK TO SAVEPOINT sp\; SELECT 2;
 ERROR:  ROLLBACK TO SAVEPOINT can only be used in transaction blocks
 SELECT 2\; RELEASE SAVEPOINT sp\; SELECT 3;
+ ?column? 
+----------
+        2
+(1 row)
+
 ERROR:  RELEASE SAVEPOINT can only be used in transaction blocks
 -- but this is OK, because the BEGIN converts it to a regular xact
 SELECT 1\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT;
+ ?column? 
+----------
+        1
+(1 row)
+
 -- Tests for AND CHAIN in implicit transaction blocks
 SET TRANSACTION READ ONLY\; COMMIT AND CHAIN;  -- error
 ERROR:  COMMIT AND CHAIN can only be used in transaction blocks
diff --git a/src/test/regress/sql/copyselect.sql b/src/test/regress/sql/copyselect.sql
index 1d98dad3c8..e32a4f8e38 100644
--- a/src/test/regress/sql/copyselect.sql
+++ b/src/test/regress/sql/copyselect.sql
@@ -84,10 +84,10 @@ drop table test1;
 -- psql handling of COPY in multi-command strings
 copy (select 1) to stdout\; select 1/0;	-- row, then error
 select 1/0\; copy (select 1) to stdout; -- error only
-copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; -- 1 2 3
+copy (select 1) to stdout\; copy (select 2) to stdout\; select 3\; select 4; -- 1 2 3 4
 
 create table test3 (c int);
-select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
+select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 0 1
 1
 \.
 2
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 68121d171c..b2f3f32404 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1241,3 +1241,123 @@ drop role regress_partitioning_role;
 \dfa bit* small*
 \do - pg_catalog.int4
 \do && anyarray *
+
+--
+-- combined queries
+--
+CREATE FUNCTION warn(msg TEXT) RETURNS BOOLEAN LANGUAGE plpgsql
+AS $$
+  BEGIN RAISE NOTICE 'warn %', msg ; RETURN TRUE ; END
+$$;
+-- show both
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+-- \gset applies to last query only
+SELECT 3 AS three \; SELECT warn('3.5') \; SELECT 4 AS four \gset
+\echo :three :four
+-- syntax error stops all processing
+SELECT 5 \; SELECT 6 + \; SELECT warn('6.5') \; SELECT 7 ;
+-- with aborted transaction, stop on first error
+BEGIN \; SELECT 8 AS eight \; SELECT 9/0 AS nine \; ROLLBACK \; SELECT 10 AS ten ;
+-- close previously aborted transaction
+ROLLBACK;
+-- misc SQL commands
+-- (non SELECT output is sent to stderr, thus is not shown in expected results)
+SELECT 'ok' AS "begin" \;
+CREATE TABLE psql_comics(s TEXT) \;
+INSERT INTO psql_comics VALUES ('Calvin'), ('hobbes') \;
+COPY psql_comics FROM STDIN \;
+UPDATE psql_comics SET s = 'Hobbes' WHERE s = 'hobbes' \;
+DELETE FROM psql_comics WHERE s = 'Moe' \;
+COPY psql_comics TO STDOUT \;
+TRUNCATE psql_comics \;
+DROP TABLE psql_comics \;
+SELECT 'ok' AS "done" ;
+Moe
+Susie
+\.
+\set SHOW_ALL_RESULTS off
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+\set SHOW_ALL_RESULTS on
+DROP FUNCTION warn(TEXT);
+
+--
+-- autocommit
+--
+\echo '# initial AUTOCOMMIT:' :AUTOCOMMIT
+\set AUTOCOMMIT off
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+-- implicit BEGIN
+CREATE TABLE foo(s TEXT);
+ROLLBACK;
+CREATE TABLE foo(s TEXT);
+INSERT INTO foo(s) VALUES ('hello'), ('world');
+COMMIT;
+DROP TABLE foo;
+ROLLBACK;
+SELECT * FROM foo ORDER BY 1;
+DROP TABLE foo;
+COMMIT;
+\set AUTOCOMMIT on
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+-- explicit BEGIN
+BEGIN;
+CREATE TABLE foo(s TEXT);
+INSERT INTO foo(s) VALUES ('hello'), ('world');
+COMMIT;
+BEGIN;
+DROP TABLE foo;
+ROLLBACK;
+-- implicit transactions
+SELECT * FROM foo ORDER BY 1;
+DROP TABLE foo;
+\echo '# final AUTOCOMMIT:' :AUTOCOMMIT
+
+--
+-- test ON_ERROR_ROLLBACK
+--
+CREATE FUNCTION psql_error(msg TEXT) RETURNS BOOLEAN AS $$
+  BEGIN
+    RAISE EXCEPTION 'error %', msg;
+  END;
+$$ LANGUAGE plpgsql;
+\echo '# initial ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK on
+\echo '# ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+BEGIN;
+CREATE TABLE bla(s NO_SUCH_TYPE);         -- fails
+CREATE TABLE bla(s TEXT);                 -- succeeds
+SELECT psql_error('oops!');               -- fails
+INSERT INTO bla VALUES ('Calvin'), ('Hobbes');
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+BEGIN;
+INSERT INTO bla VALUES ('Susie');         -- succeeds
+-- combined queries
+INSERT INTO bla VALUES ('Rosalyn') \;     -- will rollback
+SELECT 'before error' AS show \;          -- will show!
+  SELECT psql_error('boum!') \;           -- failure
+  SELECT 'after error' AS noshow;         -- hidden by preceeding error
+INSERT INTO bla(s) VALUES ('Moe') \;      -- will rollback
+  SELECT psql_error('bam!');
+INSERT INTO bla VALUES ('Miss Wormwood'); -- succeeds
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+-- some with autocommit off
+\set AUTOCOMMIT off
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+-- implicit BEGIN
+INSERT INTO bla VALUES ('Dad');           -- succeeds
+SELECT psql_error('bad!');                -- implicit partial rollback
+INSERT INTO bla VALUES ('Mum') \;         -- will rollback
+SELECT COUNT(*) AS "#mum"
+FROM bla WHERE s = 'Mum' \;               -- but be counted here
+SELECT psql_error('bad!');                -- implicit partial rollback
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+-- reset
+\set AUTOCOMMIT on
+\set ON_ERROR_ROLLBACK off
+\echo '# final ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+DROP TABLE bla;
+DROP FUNCTION psql_error;
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 8886280c0a..7fc9f09468 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -504,7 +504,7 @@ DROP TABLE abc;

--- psql will show only the last result in a multi-statement Query
+-- psql will show all results of a multi-statement Query
 SELECT 1\; SELECT 2\; SELECT 3;

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 5 Jul 2021 12:17:16 +0200

On 12.06.21 11:41, Fabien COELHO wrote:
> The patch includes basic AUTOCOMMIT and ON_ERROR_ROLLBACK tests, which 
> did not exist before, at all.

I looked at these tests first.  The tests are good, they increase 
coverage.  But they don't actually test the issue that was broken by the 
previous patch, namely the situation where autocommit is off and the 
user manually messes with the savepoints.  I applied the tests against 
the previous patch and there was no failure.  So the tests are useful, 
but they don't really help this patch.  Would you like to enhance the 
tests a bit to cover this case?  I think we could move forward with 
these tests then.

---

From: vignesh C <[email-masked]>
Date: Thu, 15 Jul 2021 17:44:38 +0530

> Attached a v7 which fixes known issues.

The patch does not apply on Head anymore, could you rebase and post a
patch. I'm changing the status to "Waiting for Author".

Regards,
Vignesh

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 15 Jul 2021 17:46:08 +0200 (CEST)

> patch. I'm changing the status to "Waiting for Author".

Ok. I noticed. The patch got significantly broken by the watch pager 
commit. I also have to enhance the added tests (per Peter request).

-- 
Fabien.

---

From: Peter Eisentraut <[email-masked]>
Date: Wed, 21 Jul 2021 21:55:30 +0200

> commit. I also have to enhance the added tests (per Peter request).

I wrote a test to check psql query cancel support.  I checked that it 
fails against the patch that was reverted.  Maybe this is useful.
From 6ff6f8b0246cea08b7e329a3e5f49cec3f83a5bc Mon Sep 17 00:00:00 2001
From: Peter Eisentraut <[email-masked]>
Date: Wed, 21 Jul 2021 21:46:11 +0200
Subject: [PATCH] psql: Add test for query canceling

---
 src/bin/psql/t/020_cancel.pl | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)
 create mode 100644 src/bin/psql/t/020_cancel.pl

diff --git a/src/bin/psql/t/020_cancel.pl b/src/bin/psql/t/020_cancel.pl
new file mode 100644
index 0000000000..0d56b47ff3
--- /dev/null
+++ b/src/bin/psql/t/020_cancel.pl
@@ -0,0 +1,32 @@
+use strict;
+use warnings;
+
+use PostgresNode;
+use TestLib;
+use Test::More tests => 2;
+
+my $tempdir = TestLib::tempdir;
+
+my $node = get_new_node('main');
+$node->init;
+$node->start;
+
+# test query canceling by sending SIGINT to a running psql
+SKIP: {
+	skip "cancel test requires a Unix shell", 2 if $windows_os;
+
+	local %ENV = $node->_get_env();
+
+	local $SIG{ALRM} = sub {
+		my $psql_pid = TestLib::slurp_file("$tempdir/psql.pid");
+		kill 'INT', $psql_pid;
+	};
+	alarm 1;
+
+	my $stdin = "\\! echo \$PPID >$tempdir/psql.pid\nselect pg_sleep(5);";
+	my ($stdout, $stderr);
+	my $result = IPC::Run::run(['psql', '-v', 'ON_ERROR_STOP=1'], '<', \$stdin, '>', \$stdout, '2>', \$stderr);
+
+	ok(!$result, 'query failed');
+	like($stderr, qr/canceling statement due to user request/, 'query was canceled');
+}
-- 
2.32.0

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 22 Jul 2021 07:52:02 +0200 (CEST)

> I wrote a test to check psql query cancel support.  I checked that it fails 
> against the patch that was reverted.  Maybe this is useful.

Thank you! The patch update is in progress…

The newly added PSQL_WATCH_PAGER feature which broke the patch does not 
seem to be tested anywhere, this is tiring:-(

-- 
Fabien.

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 22 Jul 2021 08:33:09 +0200

Hi

čt 22. 7. 2021 v 7:52 odesílatel Fabien COELHO <[email-masked]> napsal:

> fails

Do you have any idea how this can be tested? It requires some pager that
doesn't use blocking reading, and you need to do remote control of this
pager. So it requires a really especially written pager just for this
purpose. It is solvable, but I am not sure if it is adequate to this
patch.

Regards

Pavel

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 22 Jul 2021 11:00:41 +0200 (CEST)

Hello Pavel,

> Do you have any idea how this can be tested?

The TAP patch sent by Peter on this thread is a very good start.

> It requires some pager that doesn't use blocking reading, and you need 
> to do remote control of this pager. So it requires a really especially 
> written pager just for this purpose. It is solvable, but I am not sure 
> if it is adequate to this patch.

Not really: The point would not be to test the pager itself (that's for 
the people who develop the pager, not for psql), but just to test that the 
pager is actually started or not started by psql depending on conditions 
(eg pset pager…) and that it does *something* when started. See for 
instance the simplistic pager.pl script attached, the output of which 
could be tested. Note that PSQL_PAGER is not tested at all either. 
Basically "psql" is not tested, which is a pain when developing a non 
trivial patch.

-- 
Fabien.

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 22 Jul 2021 11:28:49 +0200

> Basically "psql" is not tested, which is a pain when developing a non

Minimally for PSQL_WATCH_PAGER, the pager should exit after some time, but
before it has to repeat data reading. Elsewhere the psql will hang.

can be solution to use special mode for psql, when psql will do write to
logfile and redirect to file instead using any (simplified) pager?
Theoretically, there is nothing special on usage of pager, and just you can
test redirecting to file. That is not tested too. In this mode, you can
send sigint to psql - and it can be emulation of sigint to pager in
PSQL_WATCH_PAGER mode,

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 22 Jul 2021 16:49:30 +0200 (CEST)

Hello,

> before it has to repeat data reading. Elsewhere the psql will hang.

Sure. The "pager.pl" script I sent exits after reading a few lines.

> logfile and redirect to file instead using any (simplified) pager?

I do not want a special psql mode, I just would like "make check" to tell 
me if I broke the PSQL_WATCH_PAGER feature after reworking the 
multi-results patch.

> test redirecting to file.

I do not follow. For what I seen the watch pager feature is somehow a 
little different, and I'd like to be sure I'm not breaking anything.

For your information, pspg does not seem to like being fed two results

   sh> PSQL_WATCH_PAGER="pspg --stream"
   psql> SELECT NOW() \; SELECT RANDOM() \watch 1

The first table is shown, the second seems ignored.

-- 
Fabien.

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 22 Jul 2021 16:58:40 +0200 (CEST)

> against the patch that was reverted.  Maybe this is useful.

Here is the updated version (v8? I'm not sure what the right count is), 
which works for me and for "make check", including some tests added for 
uncovered paths.

I included your tap test (thanks again!) with some more comments and 
cleanup.

I tested manually for the pager feature, which mostly work, althoug 
"pspg --stream" does not seem to expect two tables, or maybe there is a 
way to switch between these that I have not found.

-- 
Fabien.

index fcab5c0d51..7c5504bc74 100644
@@ -3542,10 +3535,6 @@ select 1\; select 2\; select 3;
@@ -4132,6 +4121,18 @@ bar
       </varlistentry>
 
       <varlistentry>
+        <term><varname>SHOW_ALL_RESULTS</varname></term>
+        <listitem>
+        <para>
+        When this variable is set to <literal>off</literal>, only the last
+        result of a combined query (<literal>\;</literal>) is shown instead of
+        all of them.  The default is <literal>on</literal>.  The off behavior
+        is for compatibility with older versions of psql.
+        </para>
+        </listitem>
+      </varlistentry>
+
+       <varlistentry>
         <term><varname>SHOW_CONTEXT</varname></term>
         <listitem>
         <para>
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 5640786678..564a4816de 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -33,6 +33,8 @@ static bool DescribeQuery(const char *query, double *elapsed_msec);
 static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
 static bool command_no_begin(const char *query);
 static bool is_select_command(const char *query);
+static int SendQueryAndProcessResults(const char *query, double *pelapsed_msec,
+	bool is_watch, const printQueryOpt *opt, FILE *printQueryFout);

 /*
@@ -353,7 +355,7 @@ CheckConnection(void)
  * Returns true for valid result, false for error state.
  */
 static bool
-AcceptResult(const PGresult *result)
+AcceptResult(const PGresult *result, bool show_error)
 {
 	bool		OK;
 
@@ -384,7 +386,7 @@ AcceptResult(const PGresult *result)
 				break;
 		}
 
-	if (!OK)
+	if (!OK && show_error)
 	{
 
@@ -472,6 +474,18 @@ ClearOrSaveResult(PGresult *result)
 	}
 }
 
+/*
+ * Consume all results
+ */
+static void
+ClearOrSaveAllResults()
+{
+	PGresult	*result;
+
 
 /*
  * Print microtiming output.  Always print raw milliseconds; if the interval
@@ -572,7 +586,7 @@ PSQLexec(const char *query)
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		res = NULL;
@@ -594,11 +608,8 @@ PSQLexec(const char *query)
 int
 PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
 {
-	PGresult   *res;
 	double		elapsed_msec = 0;
-	instr_time	before;
-	instr_time	after;
-	FILE	   *fout;
+	int			res;
 
 	if (!pset.db)
 	{
@@ -607,77 +618,14 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
 	}
 
 	SetCancelConn(pset.db);
-
-	if (pset.timing)
-		INSTR_TIME_SET_CURRENT(before);
-
-	res = PQexec(pset.db, query);
-
+	res = SendQueryAndProcessResults(query, &elapsed_msec, true, opt, printQueryFout);
 	ResetCancelConn();
 
-	fout = printQueryFout ? printQueryFout : pset.queryFout;
-			printQuery(res, opt, fout, false, pset.logfile);
-			fprintf(fout, "%s\n%s\n\n", opt->title, PQcmdStatus(res));
-	fflush(fout);
 
-	return 1;
+	return res;
 }

@@ -712,7 +660,7 @@ PrintNotifications(void)
  * Returns true if successful, false otherwise.
  */
 static bool
-PrintQueryTuples(const PGresult *results)
+PrintQueryTuples(const PGresult *results, const printQueryOpt *opt, FILE *printQueryFout)
 {
 	bool		result = true;
 
@@ -744,8 +692,9 @@ PrintQueryTuples(const PGresult *results)
 	}
 	else
 	{
-		printQuery(results, &pset.popt, pset.queryFout, false, pset.logfile);
-		if (ferror(pset.queryFout))
+		FILE *fout = printQueryFout ? printQueryFout : pset.queryFout;
+		printQuery(results, opt ? opt : &pset.popt, fout, false, pset.logfile);
+		if (ferror(fout))
 		{
 			pg_log_error("could not print result table: %m");
 			result = false;
@@ -890,213 +839,131 @@ loop_exit:

 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*
  * PrintQueryStatus: report command status as required
  *
- * Note: Utility function for use by PrintQueryResults() only.
+ * Note: Utility function for use by HandleQueryResult() only.
  */
 static void
-PrintQueryStatus(PGresult *results)
+PrintQueryStatus(PGresult *results, FILE *printQueryFout)
 {
 	char		buf[16];
+	FILE	   *fout = printQueryFout ? printQueryFout : pset.queryFout;
 
 	if (!pset.quiet)
 	{
 		if (pset.popt.topt.format == PRINT_HTML)
 		{
-			fputs("<p>", pset.queryFout);
-			html_escaped_print(PQcmdStatus(results), pset.queryFout);
-			fputs("</p>\n", pset.queryFout);
+			fputs("<p>", fout);
+			html_escaped_print(PQcmdStatus(results), fout);
+			fputs("</p>\n", fout);
 		}
 		else
-			fprintf(pset.queryFout, "%s\n", PQcmdStatus(results));
+			fprintf(fout, "%s\n", PQcmdStatus(results));
 	}
 
 	if (pset.logfile)
@@ -1108,43 +975,50 @@ PrintQueryStatus(PGresult *results)

 /*
+HandleQueryResult(PGresult *result, bool last, bool is_watch, const printQueryOpt *opt, FILE *printQueryFout)
 {
 	bool		success;
 	const char *cmdstatus;
 
-	if (!results)
+	if (result == NULL)
 		return false;
 
+				success = PrintQueryTuples(result, opt, printQueryFout);
+					PrintQueryStatus(result, printQueryFout);
+			}
+
 			break;
 
 		case PGRES_COMMAND_OK:
-			PrintQueryStatus(results);
+			if (last || pset.show_all_results)
+				PrintQueryStatus(result, printQueryFout);
 			success = true;
 			break;
 
@@ -1154,7 +1028,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1167,15 +1041,232 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}
 
-	fflush(pset.queryFout);
+	fflush(printQueryFout ? printQueryFout : pset.queryFout);
 
 	return success;
 }
 
+/*
+SendQueryAndProcessResults(const char *query, double *pelapsed_msec,
+	bool is_watch, const printQueryOpt *opt, FILE *printQueryFout)
+		 * to process. We need to do that to check whether this is the last.
+			success &= HandleQueryResult(result, last, false, opt, printQueryFout);
+		if (cancel_pressed)
+	return cancel_pressed ? 0 : success ? 1 : -1;
+}
+
 
 /*
  * SendQuery: send the query string to the backend
@@ -1192,10 +1283,11 @@ PrintQueryResults(PGresult *results)
 bool
 SendQuery(const char *query)
 {
-	PGresult   *results;
+	PGresult   *results = NULL;
 	PGTransactionStatusType transaction_status;
 	double		elapsed_msec = 0;
 	bool		OK = false;
+	bool		res_error;
 	int			i;
 	bool		on_error_rollback_savepoint = false;
 	static bool on_error_rollback_warning = false;
@@ -1237,26 +1329,32 @@ SendQuery(const char *query)
 		fflush(pset.logfile);
 	}

 
 
@@ -1273,58 +1371,40 @@ SendQuery(const char *query)
 
+		int res = SendQueryAndProcessResults(query, &elapsed_msec, false, NULL, NULL);
 
@@ -1383,14 +1463,15 @@ SendQuery(const char *query)
 			PGresult   *svptres;

 				PQclear(results);
-				ResetCancelConn();
 				goto sendquery_cleanup;
 			}
 			PQclear(svptres);
@@ -1421,6 +1502,9 @@ SendQuery(const char *query)
 
 sendquery_cleanup:
 
+	/* global cancellation reset */
+	ResetCancelConn();
+
 	/* reset \g's output-to-filename trigger */
 	if (pset.gfname)
 	{
@@ -1500,7 +1584,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1548,7 +1632,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (pset.timing)
 			{
@@ -1558,7 +1642,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
-				OK = PrintQueryResults(results);
+				OK = HandleQueryResult(results, true, false, NULL, NULL);
 
 			termPQExpBuffer(&buf);
 		}
@@ -1617,7 +1701,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1631,7 +1715,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1704,7 +1788,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
@@ -1813,7 +1897,7 @@ cleanup:
@@ -1823,7 +1907,7 @@ cleanup:
index d3fda67edd..46b4a33a5b 100644
 
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 2931530f33..0afd97cf8f 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -203,6 +203,7 @@ main(int argc, char *argv[])
 	SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1);
 	SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2);
 	SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3);
+	SetVariableBool(pset.vars, "SHOW_ALL_RESULTS");

@@ -1149,6 +1150,12 @@ verbosity_hook(const char *newval)
 	return true;
 }
 
+static bool
@@ -1250,6 +1257,9 @@ EstablishVariableSpace(void)
index 0000000000..06bad74161
@@ -0,0 +1,39 @@
+	# use a clean environment
+	# send a kill after 1 second
+	# run psql to export its pid and wait for the kill
+	# query must have been interrupted
+$node->stop();
+done_testing();
index d6bf725971..ffe54754a6 100644
@@ -4130,7 +4130,7 @@ psql_completion(const char *text, int start, int end)
 
+ ?column? 
+----------
+        4
+(1 row)
+
 
+--
+-- combined queries
+--

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 22 Jul 2021 17:13:45 +0200

> The first table is shown, the second seems ignored.

pspg cannot show multitable results, so it is not surprising.  And I don't
think about supporting this. Unfortunately I am not able to detect this
situation and show some warnings, just because psql doesn't send enough
data for it. Can be nice if psql sends some invisible characters, that
allows synchronization. But there is nothing. I just detect the timestamp
line and empty lines.

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 22 Jul 2021 17:23:30 +0200

> way to switch between these that I have not found.

pspg doesn't support this feature. Theoretically it can be implementable (I
am able to hold two datasets now), but without any help with
synchronization I don't want to implement any more complex parsing. On the
pspg side I am not able to detect what is the first result in the batch,
what is the last result (without some hard heuristics - probably I can read
some information from timestamps). And if you need two or more results in
one terminal, then mode without pager is better.

---

From: Pavel Stehule <[email-masked]>
Date: Thu, 22 Jul 2021 17:28:58 +0200

čt 22. 7. 2021 v 17:23 odesílatel Pavel Stehule <[email-masked]>
napsal:

> pspg doesn't support this feature. Theoretically it can be implementable
> (I am able to hold two datasets now), but without any help with

but the timestamps are localized, and again I have not enough information
on the pspg side for correct parsing.

So until psql will use some tags that allow more simple detection of start
and end batch or relation, this feature will not be supported by pspg :-/.
There are some invisible ascii codes that can be used for this purpose.

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 23 Jul 2021 09:41:05 +0200 (CEST)

>>> "pspg --stream" does not seem to expect two tables, or maybe there is 
>>> a way to switch between these that I have not found.
>>
>> pspg doesn't support this feature.

Sure. Note that it is not a feature yet:-)

ISTM that having some configurable pager-targetted marker would greatly 
help parsing on the pager side, so this might be the way to go, if this
finally becomes a feature.

-- 
Fabien.

---

From: Pavel Stehule <[email-masked]>
Date: Fri, 23 Jul 2021 09:56:41 +0200

> finally becomes a feature.

yes, It can help me lot of, and pspg can be less sensitive (or immune)
against synchronization errors.

Pavel

---

From: Peter Eisentraut <[email-masked]>
Date: Fri, 20 Aug 2021 13:28:10 +0200

> I included your tap test (thanks again!) with some more comments and 

The tap test had a merge conflict, so I fixed that and committed it 
separately.  I was wondering about its portability, so it's good to sort 
that out separately from your main patch.  There are already a few 
failures on the build farm right now, so let's see where this is heading.

---

From: Peter Eisentraut <[email-masked]>
Date: Fri, 24 Sep 2021 14:42:33 +0200

> which works for me and for "make check", including some tests added for 

I was looking at adding test coverage for the issue complained about in 
[0].  That message said that the autocommit logic was broken, but 
actually the issue was with the ON_ERROR_ROLLBACK logic.  However, it 
turned out that neither feature had any test coverage, and they are 
easily testable using the pg_regress setup, so I wrote tests for both 
and another little thing I found nearby.

It turns out that your v8 patch still has the issue complained about in 
[0].  The issue is that after COMMIT AND CHAIN, the internal savepoint 
is gone, but the patched psql still thinks it should be there and tries 
to release it, which leads to errors.

[0]: https://www.postgresql.org/message-id/[email-masked]

---

From: Fabien COELHO <[email-masked]>
Date: Sat, 25 Sep 2021 11:28:52 +0200 (CEST)

Hallo Peter,

> It turns out that your v8 patch still has the issue complained about in [0]. 
> The issue is that after COMMIT AND CHAIN, the internal savepoint is gone, but 
> the patched psql still thinks it should be there and tries to release it, 
> which leads to errors.

Indeed. Thanks for the catch.

Attached v9 integrates your tests and makes them work.

-- 
Fabien.

index b52d187722..0cf4a37a5f 100644
 
       <varlistentry>
index 5640786678..481a316fed 100644
+	bool is_watch, const printQueryOpt *opt, FILE *printQueryFout, bool *tx_ended);

 /*

-	if (!OK)
+	if (!OK && show_error)
 	{

+/*
+ * Consume all results
+ */
+static void
+ClearOrSaveAllResults()
+{
+	PGresult	*result;
+
 
 /*
 
 	ResetCancelConn();

 	if (!pset.db)
 	{
 
 	SetCancelConn(pset.db);
-
-	if (pset.timing)
-		INSTR_TIME_SET_CURRENT(before);
-
-	res = PQexec(pset.db, query);
-
+	res = SendQueryAndProcessResults(query, &elapsed_msec, true, opt, printQueryFout, NULL);
 	ResetCancelConn();

-	return 1;
+	return res;
 }

 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*
 
 	if (!pset.quiet)
 	{
 
 	if (pset.logfile)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1167,15 +1041,239 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}

 	return success;
 }
 
+/*
+	bool is_watch, const printQueryOpt *opt, FILE *printQueryFout, bool *tx_ended)
+		else if (tx_ended != NULL && ! *tx_ended)
+			/* on success, tell whether the "current" transaction sequence ended */
+			const char *cmd = PQcmdStatus(result);
+			*tx_ended = strcmp(cmd, "COMMIT") == 0 || strcmp(cmd, "ROLLBACK") == 0 ||
+				strcmp(cmd, "SAVEPOINT") == 0 || strcmp(cmd, "RELEASE") == 0;
 
 /*
@@ -1192,13 +1290,15 @@ PrintQueryResults(PGresult *results)
+	bool		tx_ended = false;
 
 	if (!pset.db)
 	{
@@ -1237,26 +1337,32 @@ SendQuery(const char *query)
 		fflush(pset.logfile);
 	}

 
 
@@ -1273,58 +1379,39 @@ SendQuery(const char *query)
 
+		int res = SendQueryAndProcessResults(query, &elapsed_msec, false, NULL, NULL, &tx_ended);
 
@@ -1357,11 +1444,7 @@ SendQuery(const char *query)
 				 * savepoint is gone. If they issued a SAVEPOINT, releasing
 				 * ours would remove theirs.
 				 */
-				if (results &&
-					(strcmp(PQcmdStatus(results), "COMMIT") == 0 ||
-					 strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
-					 strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
-					 strcmp(PQcmdStatus(results), "ROLLBACK") == 0))
+				if (tx_ended)
 					svptcmd = NULL;
 				else
 					svptcmd = "RELEASE pg_psql_temporary_savepoint";
@@ -1383,14 +1466,15 @@ SendQuery(const char *query)
 			PGresult   *svptres;

 				PQclear(results);
-				ResetCancelConn();
 				goto sendquery_cleanup;
 			}
 			PQclear(svptres);
@@ -1421,6 +1505,9 @@ SendQuery(const char *query)
 
 sendquery_cleanup:
 
+	/* global cancellation reset */
+	ResetCancelConn();
+
 	/* reset \g's output-to-filename trigger */
 	if (pset.gfname)
 	{
@@ -1500,7 +1587,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1548,7 +1635,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (pset.timing)
 			{
@@ -1558,7 +1645,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}
@@ -1617,7 +1704,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1631,7 +1718,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1704,7 +1791,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
@@ -1813,7 +1900,7 @@ cleanup:
@@ -1823,7 +1910,7 @@ cleanup:
index db12a8b2f3..d1a638b8f2 100644

 
 
+static bool
index 5cd5838668..a231dce3eb 100644
@@ -4190,7 +4190,7 @@ psql_completion(const char *text, int start, int end)
 
+ ?column? 
+----------
+        4
+(1 row)
+
 create table test3 (c int);
-select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
+select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 0 1
+ ?column? 
+----------
+        0
+(1 row)
+
  ?column? 
 ----------
         1
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 1b2f6bc418..1029ab2f26 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5179,3 +5179,342 @@ List of access methods
  pg_catalog | &&   | anyarray      | anyarray       | boolean     | overlaps
 (1 row)
 
+--
+-- combined queries
+--
+CREATE FUNCTION warn(msg TEXT) RETURNS BOOLEAN LANGUAGE plpgsql
+AS $$
+  BEGIN RAISE NOTICE 'warn %', msg ; RETURN TRUE ; END
+$$;
+-- show both
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+ one 
+-----
+   1
+(1 row)
+
+NOTICE:  warn 1.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ warn 
+------
+ t
+(1 row)
+
+ two 
+-----
+   2
+(1 row)
+
+-- \gset applies to last query only
+SELECT 3 AS three \; SELECT warn('3.5') \; SELECT 4 AS four \gset
+ three 
+-------
+     3
+(1 row)
+
+NOTICE:  warn 3.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ warn 
+------
+ t
+(1 row)
+
+\echo :three :four
+:three 4
+-- syntax error stops all processing
+SELECT 5 \; SELECT 6 + \; SELECT warn('6.5') \; SELECT 7 ;
+ERROR:  syntax error at or near ";"
+LINE 1: SELECT 5 ; SELECT 6 + ; SELECT warn('6.5') ; SELECT 7 ;
+                              ^
+-- with aborted transaction, stop on first error
+BEGIN \; SELECT 8 AS eight \; SELECT 9/0 AS nine \; ROLLBACK \; SELECT 10 AS ten ;
+ eight 
+-------
+     8
+(1 row)
+
+ERROR:  division by zero
+-- close previously aborted transaction
+ROLLBACK;
+-- misc SQL commands
+-- (non SELECT output is sent to stderr, thus is not shown in expected results)
+SELECT 'ok' AS "begin" \;
+CREATE TABLE psql_comics(s TEXT) \;
+INSERT INTO psql_comics VALUES ('Calvin'), ('hobbes') \;
+COPY psql_comics FROM STDIN \;
+UPDATE psql_comics SET s = 'Hobbes' WHERE s = 'hobbes' \;
+DELETE FROM psql_comics WHERE s = 'Moe' \;
+COPY psql_comics TO STDOUT \;
+TRUNCATE psql_comics \;
+DROP TABLE psql_comics \;
+SELECT 'ok' AS "done" ;
+ begin 
+-------
+ ok
+(1 row)
+
+Calvin
+Susie
+Hobbes
+ done 
+------
+ ok
+(1 row)
+
+\set SHOW_ALL_RESULTS off
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+NOTICE:  warn 1.5
+CONTEXT:  PL/pgSQL function warn(text) line 2 at RAISE
+ two 
+-----
+   2
+(1 row)
+
+\set SHOW_ALL_RESULTS on
+DROP FUNCTION warn(TEXT);
+--
+-- autocommit
+--
+\echo '# initial AUTOCOMMIT:' :AUTOCOMMIT
+# initial AUTOCOMMIT: on
+\set AUTOCOMMIT off
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+# AUTOCOMMIT: off
+-- implicit BEGIN
+CREATE TABLE foo(s TEXT);
+ROLLBACK;
+CREATE TABLE foo(s TEXT);
+INSERT INTO foo(s) VALUES ('hello'), ('world');
+COMMIT;
+DROP TABLE foo;
+ROLLBACK;
+SELECT * FROM foo ORDER BY 1;
+   s   
+-------
+ hello
+ world
+(2 rows)
+
+DROP TABLE foo;
+COMMIT;
+\set AUTOCOMMIT on
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+# AUTOCOMMIT: on
+-- explicit BEGIN
+BEGIN;
+CREATE TABLE foo(s TEXT);
+INSERT INTO foo(s) VALUES ('hello'), ('world');
+COMMIT;
+BEGIN;
+DROP TABLE foo;
+ROLLBACK;
+-- implicit transactions
+SELECT * FROM foo ORDER BY 1;
+   s   
+-------
+ hello
+ world
+(2 rows)
+
+DROP TABLE foo;
+\echo '# final AUTOCOMMIT:' :AUTOCOMMIT
+# final AUTOCOMMIT: on
+--
+-- test ON_ERROR_ROLLBACK
+--
+CREATE FUNCTION psql_error(msg TEXT) RETURNS BOOLEAN AS $$
+  BEGIN
+    RAISE EXCEPTION 'error %', msg;
+  END;
+$$ LANGUAGE plpgsql;
+\echo '# initial ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+# initial ON_ERROR_ROLLBACK: off
+\set ON_ERROR_ROLLBACK on
+\echo '# ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+# ON_ERROR_ROLLBACK: on
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+# AUTOCOMMIT: on
+BEGIN;
+CREATE TABLE bla(s NO_SUCH_TYPE);         -- fails
+ERROR:  type "no_such_type" does not exist
+LINE 1: CREATE TABLE bla(s NO_SUCH_TYPE);
+                           ^
+CREATE TABLE bla(s TEXT);                 -- succeeds
+SELECT psql_error('oops!');               -- fails
+ERROR:  error oops!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+INSERT INTO bla VALUES ('Calvin'), ('Hobbes');
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+   s    
+--------
+ Calvin
+ Hobbes
+(2 rows)
+
+BEGIN;
+INSERT INTO bla VALUES ('Susie');         -- succeeds
+-- combined queries
+INSERT INTO bla VALUES ('Rosalyn') \;     -- will rollback
+SELECT 'before error' AS show \;          -- will show!
+  SELECT psql_error('boum!') \;           -- failure
+  SELECT 'after error' AS noshow;         -- hidden by preceeding error
+     show     
+--------------
+ before error
+(1 row)
+
+ERROR:  error boum!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+INSERT INTO bla(s) VALUES ('Moe') \;      -- will rollback
+  SELECT psql_error('bam!');
+ERROR:  error bam!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+INSERT INTO bla VALUES ('Miss Wormwood'); -- succeeds
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+       s       
+---------------
+ Calvin
+ Hobbes
+ Miss Wormwood
+ Susie
+(4 rows)
+
+-- some with autocommit off
+\set AUTOCOMMIT off
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+# AUTOCOMMIT: off
+-- implicit BEGIN
+INSERT INTO bla VALUES ('Dad');           -- succeeds
+SELECT psql_error('bad!');                -- implicit partial rollback
+ERROR:  error bad!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+INSERT INTO bla VALUES ('Mum') \;         -- will rollback
+SELECT COUNT(*) AS "#mum"
+FROM bla WHERE s = 'Mum' \;               -- but be counted here
+SELECT psql_error('bad!');                -- implicit partial rollback
+ #mum 
+------
+    1
+(1 row)
+
+ERROR:  error bad!
+CONTEXT:  PL/pgSQL function psql_error(text) line 3 at RAISE
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+       s       
+---------------
+ Calvin
+ Dad
+ Hobbes
+ Miss Wormwood
+ Susie
+(5 rows)
+
+-- reset
+\set AUTOCOMMIT on
+\set ON_ERROR_ROLLBACK off
+\echo '# final ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+# final ON_ERROR_ROLLBACK: off
+DROP TABLE bla;
+DROP FUNCTION psql_error;
+-- AUTOCOMMIT
+CREATE TABLE ac_test (a int);
+\set AUTOCOMMIT off
+INSERT INTO ac_test VALUES (1);
+COMMIT;
+SELECT * FROM ac_test;
+ a 
+---
+ 1
+(1 row)
+
+COMMIT;
+INSERT INTO ac_test VALUES (2);
+ROLLBACK;
+SELECT * FROM ac_test;
+ a 
+---
+ 1
+(1 row)
+
+COMMIT;
+BEGIN;
+INSERT INTO ac_test VALUES (3);
+COMMIT;
+SELECT * FROM ac_test;
+ a 
+---
+ 1
+ 3
+(2 rows)
+
+COMMIT;
+BEGIN;
+INSERT INTO ac_test VALUES (4);
+ROLLBACK;
+SELECT * FROM ac_test;
+ a 
+---
+ 1
+ 3
+(2 rows)
+
+COMMIT;
+\set AUTOCOMMIT on
+DROP TABLE ac_test;
+SELECT * FROM ac_test;  -- should be gone now
+ERROR:  relation "ac_test" does not exist
+LINE 1: SELECT * FROM ac_test;
+                      ^
+-- ON_ERROR_ROLLBACK
+\set ON_ERROR_ROLLBACK on
+CREATE TABLE oer_test (a int);
+BEGIN;
+INSERT INTO oer_test VALUES (1);
+INSERT INTO oer_test VALUES ('foo');
+ERROR:  invalid input syntax for type integer: "foo"
+LINE 1: INSERT INTO oer_test VALUES ('foo');
+                                     ^
+INSERT INTO oer_test VALUES (3);
+COMMIT;
+SELECT * FROM oer_test;
+ a 
+---
+ 1
+ 3
+(2 rows)
+
+BEGIN;
+INSERT INTO oer_test VALUES (4);
+ROLLBACK;
+SELECT * FROM oer_test;
+ a 
+---
+ 1
+ 3
+(2 rows)
+
+BEGIN;
+INSERT INTO oer_test VALUES (5);
+COMMIT AND CHAIN;
+INSERT INTO oer_test VALUES (6);
+COMMIT;
+SELECT * FROM oer_test;
+ a 
+---
+ 1
+ 3
+ 5
+ 6
+(4 rows)
+
+DROP TABLE oer_test;
+\set ON_ERROR_ROLLBACK off
+-- ECHO errors
+\set ECHO errors
+ERROR:  relation "notexists" does not exist
+LINE 1: SELECT * FROM notexists;
+                      ^
+STATEMENT:  SELECT * FROM notexists;
diff --git a/src/test/regress/expected/transactions.out b/src/test/regress/expected/transactions.out
index 61862d595d..be1db0d5c0 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -900,8 +900,18 @@ DROP TABLE abc;
 -- tests rely on the fact that psql will not break SQL commands apart at a
 -- backslash-quoted semicolon, but will send them as one Query.
 create temp table i_table (f1 int);
--- psql will show only the last result in a multi-statement Query
+-- psql will show all results of a multi-statement Query
 SELECT 1\; SELECT 2\; SELECT 3;
+ ?column? 
+----------
+        1
+(1 row)
+
+ ?column? 
+----------
+        2
+(1 row)
+
  ?column? 
 ----------
         3
@@ -916,6 +926,12 @@ insert into i_table values(1)\; select * from i_table;

index 68121d171c..c78fee03be 100644
@@ -1241,3 +1241,186 @@ drop role regress_partitioning_role;
+-- AUTOCOMMIT
+CREATE TABLE ac_test (a int);
+INSERT INTO ac_test VALUES (1);
+SELECT * FROM ac_test;
+INSERT INTO ac_test VALUES (2);
+SELECT * FROM ac_test;
+INSERT INTO ac_test VALUES (3);
+SELECT * FROM ac_test;
+INSERT INTO ac_test VALUES (4);
+SELECT * FROM ac_test;
+DROP TABLE ac_test;
+SELECT * FROM ac_test;  -- should be gone now
+-- ON_ERROR_ROLLBACK
+CREATE TABLE oer_test (a int);
+INSERT INTO oer_test VALUES (1);
+INSERT INTO oer_test VALUES ('foo');
+INSERT INTO oer_test VALUES (3);
+SELECT * FROM oer_test;
+INSERT INTO oer_test VALUES (4);
+SELECT * FROM oer_test;
+INSERT INTO oer_test VALUES (5);
+COMMIT AND CHAIN;
+INSERT INTO oer_test VALUES (6);
+SELECT * FROM oer_test;
+DROP TABLE oer_test;
+-- ECHO errors
+\set ECHO errors
+SELECT * FROM notexists;
+\set ECHO none

---

From: Fabien COELHO <[email-masked]>
Date: Sat, 2 Oct 2021 16:31:04 +0200 (CEST)

Hello Peter,

> Attached v9 integrates your tests and makes them work.

Attached v11 is a rebase.

-- 
Fabien.

index 14e0a4dbe3..6866a06f2d 100644
 
       <varlistentry>

 /*

-	if (!OK)
+	if (!OK && show_error)
 	{

+/*
+ * Consume all results
+ */
+static void
+ClearOrSaveAllResults()
+{
+	PGresult	*result;
+
 
 /*
 
 	ResetCancelConn();

 	if (!pset.db)
 	{
 
 	SetCancelConn(pset.db);
-
-	if (pset.timing)

-	return 1;
+	return res;
 }

 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*
 
 	if (!pset.quiet)
 	{
 
 	if (pset.logfile)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:

 
 	return success;
 }
 
+/*
 
 /*
 
 	if (!pset.db)
 	{

 

 

 				PQclear(results);
-				ResetCancelConn();
 				goto sendquery_cleanup;
 			}
 			PQclear(svptres);
 
 sendquery_cleanup:

 
 
 			if (pset.timing)
 			{
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}

 

 
+static bool
 
+ ?column? 
+----------
+        4
+(1 row)
+
index 930ce8597a..8d77700ce6 100644
@@ -5278,3 +5278,129 @@ ERROR:  relation "notexists" does not exist
 LINE 1: SELECT * FROM notexists;
                       ^
 STATEMENT:  SELECT * FROM notexists;

index e9d504baf2..0e9a57983b 100644
@@ -1304,3 +1304,123 @@ DROP TABLE oer_test;
 \set ECHO errors
 SELECT * FROM notexists;
 \set ECHO none

---

From: Peter Eisentraut <[email-masked]>
Date: Fri, 8 Oct 2021 14:15:31 +0200

> Attached v11 is a rebase.

This patch still has a few of the problems reported earlier this year.

In [0], it was reported that certain replication commands result in 
infinite loops because of faulty error handling.  This still happens.  I 
wrote a test for it, attached here.  (I threw in a few more basic tests, 
just to have some more coverage that was lacking, and to have a file to 
put the new test in.)

In [1], it was reported that server crashes produce duplicate error 
messages.  This also still happens.  I didn't write a test for it, maybe 
you have an idea.  (Obviously, we could check whether the error message 
is literally there twice in the output, but that doesn't seem very 
general.)  But it's easy to test manually: just have psql connect, shut 
down the server, then run a query.

Additionally, I looked into the Coverity issue reported in [2].  That 
one is fixed, but I figured it would be good to be able to check your 
patches with a static analyzer in a similar way.  I don't have the 
ability to run Coverity locally, so I looked at scan-build and fixed a 
few minor warnings, also attached as a patch.  Your current patch 
appears to be okay in that regard.

[0]: 
https://www.postgresql.org/message-id/[email-masked]

[1]: https://www.postgresql.org/message-id/[email-masked]

[2]: https://www.postgresql.org/message-id/[email-masked]

---

From: Daniel Gustafsson <[email-masked]>
Date: Tue, 23 Nov 2021 10:17:23 +0100

> On 8 Oct 2021, at 14:15, Peter Eisentraut <[email-masked]> wrote:
> 

The patch fails to apply and the thread seems to have taken a nap.  You
mentioned on the "dynamic result sets support in extended query protocol"
thread [0] that you were going to work on this as a pre-requisite for that
patch. Is that still the plan so we should keep this in the Commitfest?

--
Daniel Gustafsson		https://vmware.com/

[0] https://www.postgresql.org/message-id/6f038f18-0f2b-5271-a56f-1770577f246c%40enterprisedb.com

---

From: Fabien COELHO <[email-masked]>
Date: Wed, 24 Nov 2021 17:21:19 +0100 (CET)

Hello Daniel,

> The patch fails to apply and the thread seems to have taken a nap.

I'm not napping:-) I just do not have enough time available this month. I 
intend to work on the patch in the next CF (January). AFAICR there is one 
necessary rebase and one bug to fix.

-- 
Fabien.

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 23 Dec 2021 07:40:37 -0400 (AST)

Hello Peter,

I finally took some time to look at this.

> In [0], it was reported that certain replication commands result in infinite 
> loops because of faulty error handling.  This still happens.  I wrote a test 
> for it, attached here.  (I threw in a few more basic tests, just to have some 
> more coverage that was lacking, and to have a file to put the new test in.)

Hmmm… For some unclear reason on errors on a PGRES_COPY_* state 
PQgetResult keeps on returning an empty result. PQexec manually ignores 
it, so I did the same with a comment, but for me the real bug is somehow 
in PQgetResult behavior…

> In [1], it was reported that server crashes produce duplicate error messages. 
> This also still happens.  I didn't write a test for it, maybe you have an 
> idea.  (Obviously, we could check whether the error message is literally 
> there twice in the output, but that doesn't seem very general.)  But it's 
> easy to test manually: just have psql connect, shut down the server, then run 
> a query.

This is also a feature/bug of libpq which happens to be hidden by PQexec: 
when one command crashes PQgetResult actually returns *2* results. First 
one with the FATAL message, second one when libpq figures out that the 
connection was lost with the second message appended to the first. PQexec 
just happen to silently ignore the first result. I added a manual reset of 
the error message when first shown so that it is not shown twice. It is 
unclear to me whether the reset should be somewhere in libpq instead. I 
added a voluntary crash at the end of the psql test.

Attached v12 somehow fixes these issues in "psql" code rather than in 
libpq.

-- 
Fabien.

index e0abe34bb6..8f7f93172a 100644
index ae38d3dcc3..1d411ae124 100644
@@ -3564,10 +3557,6 @@ select 1\; select 2\; select 3;
@@ -4154,6 +4143,18 @@ bar
       </varlistentry>
 
       <varlistentry>
index ec975c3e2a..e06699878b 100644

 /*

-	if (!OK)
+	if (!OK && show_error)
 	{

+/*
+ * Consume all results
+ */
+static void
+ClearOrSaveAllResults()
+{
+	PGresult	*result;
+
 
 /*
 
 	ResetCancelConn();
 
-	if (!AcceptResult(res))
+	if (!AcceptResult(res, true))
 	{
 		ClearOrSaveResult(res);
 		res = NULL;
@@ -595,11 +609,8 @@ int
 PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
 {
 	bool		timing = pset.timing;
-	PGresult   *res;
 	double		elapsed_msec = 0;
-	instr_time	before;
-	instr_time	after;
-	FILE	   *fout;
+	int			res;
 
 	if (!pset.db)
 	{
@@ -608,77 +619,14 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
 	}
 
 	SetCancelConn(pset.db);
-
-	if (timing)
 
-	if (timing)
 	if (timing)
 
-	return 1;
+	return res;
 }

@@ -713,7 +661,7 @@ PrintNotifications(void)
  * Returns true if successful, false otherwise.
  */
 static bool
-PrintQueryTuples(const PGresult *results)
+PrintQueryTuples(const PGresult *results, const printQueryOpt *opt, FILE *printQueryFout)
 {
 	bool		result = true;
 
@@ -745,8 +693,9 @@ PrintQueryTuples(const PGresult *results)
 	}
 	else
 	{
-		printQuery(results, &pset.popt, pset.queryFout, false, pset.logfile);
-		if (ferror(pset.queryFout))
+		FILE *fout = printQueryFout ? printQueryFout : pset.queryFout;
+		printQuery(results, opt ? opt : &pset.popt, fout, false, pset.logfile);
+		if (ferror(fout))
 		{
 			pg_log_error("could not print result table: %m");
 			result = false;
@@ -891,213 +840,131 @@ loop_exit:

 /*
 
-	for (;;)

 
-		PQclear(*results);

 	return success;
 }
 
-
 /*
 
 	if (!pset.quiet)
 	{
 
 	if (pset.logfile)
@@ -1109,43 +976,50 @@ PrintQueryStatus(PGresult *results)

 /*
 
-	if (!results)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:
 
@@ -1155,7 +1029,7 @@ PrintQueryResults(PGresult *results)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1168,15 +1042,261 @@ PrintQueryResults(PGresult *results)
 		default:
 			success = false;
 			pg_log_error("unexpected PQresultStatus: %d",
-						 PQresultStatus(results));
+						 PQresultStatus(result));
 			break;
 	}

 	return success;
 }
 
+/*
+	bool				timing = pset.timing;
+	if (timing)
+				 * On connection loss another result with a message will be
+				 * generated, we do not want to see this error again.
+				PQclearErrorMessage(pset.db);
+			if (result_status == PGRES_COPY_BOTH ||
+				result_status == PGRES_COPY_OUT ||
+				result_status == PGRES_COPY_IN)
+				 * Hmmm... for some obscure reason PQgetResult does *not*
+				 * return a NULL in these cases despite the result having
+				 * been cleared, but keeps returning an "empty" result that
+				 * we have to ignore manually.
+				result = NULL;
+		if (last && timing)
 
 /*
  * SendQuery: send the query string to the backend
@@ -1194,12 +1314,14 @@ bool
 SendQuery(const char *query)
 {
 	bool		timing = pset.timing;
-	PGresult   *results;
+	PGresult   *results = NULL;
 	PGTransactionStatusType transaction_status;
 	double		elapsed_msec = 0;
 	bool		OK = false;
+	bool		res_error;
 	int			i;
 	bool		on_error_rollback_savepoint = false;
+	bool		tx_ended = false;
 
 	if (!pset.db)
 	{
@@ -1238,82 +1360,69 @@ SendQuery(const char *query)
 		fflush(pset.logfile);
 	}

 
 
 		 pset.on_error_rollback == PSQL_ERROR_ROLLBACK_ON))
 
-		if (timing)
-		if (timing)
 
@@ -1346,11 +1455,7 @@ SendQuery(const char *query)
 				 * savepoint is gone. If they issued a SAVEPOINT, releasing
 				 * ours would remove theirs.
 				 */
-				if (results &&
-					(strcmp(PQcmdStatus(results), "COMMIT") == 0 ||
-					 strcmp(PQcmdStatus(results), "SAVEPOINT") == 0 ||
-					 strcmp(PQcmdStatus(results), "RELEASE") == 0 ||
-					 strcmp(PQcmdStatus(results), "ROLLBACK") == 0))
+				if (tx_ended)
 					svptcmd = NULL;
 				else
 					svptcmd = "RELEASE pg_psql_temporary_savepoint";
@@ -1372,14 +1477,15 @@ SendQuery(const char *query)
 			PGresult   *svptres;

 				PQclear(results);
-				ResetCancelConn();
 				goto sendquery_cleanup;
 			}
 			PQclear(svptres);
@@ -1410,6 +1516,9 @@ SendQuery(const char *query)
 
 sendquery_cleanup:
 
+	/* global cancellation reset */
+	ResetCancelConn();
+
 	/* reset \g's output-to-filename trigger */
 	if (pset.gfname)
 	{
@@ -1490,7 +1599,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(results);
 
@@ -1538,7 +1647,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(results);

 			if (timing)
 			{
@@ -1548,7 +1657,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
 
 			termPQExpBuffer(&buf);
 		}
@@ -1608,7 +1717,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		results = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(results) &&
+		OK = AcceptResult(results, true) &&
 			(PQresultStatus(results) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(results);
 		if (!OK)
@@ -1622,7 +1731,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
@@ -1695,7 +1804,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
@@ -1804,7 +1913,7 @@ cleanup:
@@ -1814,7 +1923,7 @@ cleanup:
 
index f7ea4ce3d4..6940fe2999 100644

@@ -1150,6 +1151,12 @@ verbosity_hook(const char *newval)
 	return true;
 }
 
+static bool
+show_all_results_hook(const char *newval)
+{
+	return ParseVariableBool(newval, "SHOW_ALL_RESULTS", &pset.show_all_results);
+}
+
 static char *
 show_context_substitute_hook(char *newval)
 {
@@ -1251,6 +1258,9 @@ EstablishVariableSpace(void)
 	SetVariableHooks(pset.vars, "VERBOSITY",
 					 verbosity_substitute_hook,
 					 verbosity_hook);
+	SetVariableHooks(pset.vars, "SHOW_ALL_RESULTS",
+					 bool_substitute_hook,
+					 show_all_results_hook);
 	SetVariableHooks(pset.vars, "SHOW_CONTEXT",
 					 show_context_substitute_hook,
 					 show_context_hook);
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 6ca0bc75d0..760dc5ea21 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -6,6 +6,7 @@ use warnings;
 
 use PostgreSQL::Test::Cluster;
 use PostgreSQL::Test::Utils;
+
 use Test::More tests => 25;
 
 program_help_ok('psql');
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index b524dc87fc..432e9f6635 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4336,7 +4336,7 @@ psql_completion(const char *text, int start, int end)
 		matches = complete_from_variables(text, "", "", false);
 	else if (TailMatchesCS("\\set", MatchAny))
 	{
-		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
+		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|SHOW_ALL_RESULTS|"
 						  "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_CS("on", "off");
 		else if (TailMatchesCS("COMP_KEYWORD_CASE"))
diff --git a/src/interfaces/libpq/exports.txt b/src/interfaces/libpq/exports.txt
index e8bcc88370..2a3d29aee5 100644
--- a/src/interfaces/libpq/exports.txt
+++ b/src/interfaces/libpq/exports.txt
@@ -186,3 +186,4 @@ PQpipelineStatus          183
 PQsetTraceFlags           184
 PQmblenBounded            185
 PQsendFlushRequest        186
+PQclearErrorMessage	      187
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9b6a6939f0..7ca2048144 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -6783,6 +6783,12 @@ PQerrorMessage(const PGconn *conn)
 	return conn->errorMessage.data;
 }
 
+void
+PQclearErrorMessage(PGconn *conn)
+{
+	resetPQExpBuffer(&conn->errorMessage);
+}
+
 /*
  * In Windows, socket values are unsigned, and an invalid socket value
  * (INVALID_SOCKET) is ~0, which equals -1 in comparisons (with no compiler
diff --git a/src/interfaces/libpq/libpq-fe.h b/src/interfaces/libpq/libpq-fe.h
index a6fd69aceb..b736ece386 100644
--- a/src/interfaces/libpq/libpq-fe.h
+++ b/src/interfaces/libpq/libpq-fe.h
@@ -347,6 +347,7 @@ extern const char *PQparameterStatus(const PGconn *conn,
 extern int	PQprotocolVersion(const PGconn *conn);
 extern int	PQserverVersion(const PGconn *conn);
 extern char *PQerrorMessage(const PGconn *conn);
+extern void	PQclearErrorMessage(PGconn *conn);
 extern int	PQsocket(const PGconn *conn);
 extern int	PQbackendPID(const PGconn *conn);
 extern PGpipelineStatus PQpipelineStatus(const PGconn *conn);
diff --git a/src/test/regress/expected/copyselect.out b/src/test/regress/expected/copyselect.out
index 72865fe1eb..bb9e026f91 100644
--- a/src/test/regress/expected/copyselect.out
+++ b/src/test/regress/expected/copyselect.out
@@ -126,7 +126,7 @@ copy (select 1) to stdout\; select 1/0;	-- row, then error
 ERROR:  division by zero
 select 1/0\; copy (select 1) to stdout; -- error only
 ERROR:  division by zero
-copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; -- 1 2 3
+copy (select 1) to stdout\; copy (select 2) to stdout\; select 3\; select 4; -- 1 2 3 4
 1
 2
  ?column? 
@@ -134,8 +134,18 @@ copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; --
         3
 (1 row)
 
+ ?column? 
+----------
+        4
+(1 row)
+
index 6428ebc507..38cabf1a14 100644
@@ -5290,3 +5290,134 @@ ERROR:  relation "notexists" does not exist
+FATAL:  terminating connection due to administrator command
+server closed the connection unexpectedly
+	This probably means the server terminated abnormally
+	before or while processing the request.
+connection to server was lost

index d4e4fdbbb7..82e610ae7d 100644
@@ -1316,3 +1316,130 @@ DROP TABLE oer_test;
+-- voluntary crash!
+SELECT pg_terminate_backend(pg_backend_pid());
+-- because the test is not interactive this will not proceed further
+SELECT 'this must be ignored' AS "after crash";

---

From: Peter Eisentraut <[email-masked]>
Date: Mon, 27 Dec 2021 13:44:29 +0100

On 23.12.21 12:40, Fabien COELHO wrote:
>> infinite loops because of faulty error handling.  This still happens.  
>> I wrote a test for it, attached here.  (I threw in a few more basic 
>> tests, just to have some more coverage that was lacking, and to have a 
>> file to put the new test in.)
> 
>> messages. This also still happens.  I didn't write a test for it, 
>> maybe you have an idea.  (Obviously, we could check whether the error 
>> message is literally there twice in the output, but that doesn't seem 
>> very general.)  But it's easy to test manually: just have psql 
>> connect, shut down the server, then run a query.
> 
> This is also a feature/bug of libpq which happens to be hidden by 
> PQexec: when one command crashes PQgetResult actually returns *2* 
> results. First one with the FATAL message, second one when libpq figures 
> out that the connection was lost with the second message appended to the 
> first. PQexec just happen to silently ignore the first result. I added a 
> manual reset of the error message when first shown so that it is not 
> shown twice. It is unclear to me whether the reset should be somewhere 
> in libpq instead. I added a voluntary crash at the end of the psql test.

I agree that these two behaviors in libpq are dubious, especially the 
second one.  I want to spend some time analyzing this more and see if 
fixes in libpq might be appropriate.

---

From: Fabien COELHO <[email-masked]>
Date: Wed, 29 Dec 2021 08:42:53 +0100 (CET)

>> [...]

Ok.

My analysis is that fixing libpq behavior is not in the scope of a psql 
patch, and that if I was to do that it was sure delay the patch even 
further. Also these issues/features are corner cases that provably very 
few people bumped into.

-- 
Fabien.

---

From: Fabien COELHO <[email-masked]>
Date: Wed, 23 Mar 2022 13:58:36 +0100 (CET)

Hello Peter,

>> Attached v17 is another try. The point is to record the current status, 
>> whatever it is, buggy or not, and to update the test when libpq fixes 
>> things, whenever this is done.
>
> [...]
>
> The expected output (which passes) contains this line twice:
>
> psql:<stdin>:2: FATAL:  terminating connection due to administrator command
> psql:<stdin>:2: FATAL:  terminating connection due to administrator command

> If I paste this test case into current master without your patch, I only get 
> this line once.  So your patch is changing this output.  The whole point of 
> the libpq fixes was to not have this duplicate output.  So I think something 
> is still wrong somewhere.

Hmmm. Yes and no:-)

The previous path inside libpq silently ignores intermediate results, it 
skips all results to keep only the last one. The new approach does not 
discard resultss silently, hence the duplicated output, because they are 
actually there and have always been there in the first place, they were 
just ignored: The previous "good" result is really a side effect of a bad 
implementation in a corner case, which just becomes apparent when opening 
the list of results.

So my opinion is still to dissociate the libpq "bug/behavior" fix from 
this feature, as they are only loosely connected, because it is a very 
corner case anyway.

An alternative would be to remove the test case, but I'd prefer that it is 
kept.

If you want to wait for libpq to provide a solution for this corner case, 
I'm afraid that "never" is the likely result, especially as no test case 
exercices this path to show that there is a problem somewhere, so nobody 
should care to fix it. I'm not sure it is even worth it given the highly 
special situation which triggers the issue, which is not such an actual 
problem (ok, the user is told twice that there was a connection loss, no 
big deal).

-- 
Fabien.

---

From: Peter Eisentraut <[email-masked]>
Date: Fri, 25 Mar 2022 16:04:21 +0100

On 23.03.22 13:58, Fabien COELHO wrote:
> If you want to wait for libpq to provide a solution for this corner 
> case, I'm afraid that "never" is the likely result, especially as no 
> test case exercices this path to show that there is a problem somewhere, 
> so nobody should care to fix it. I'm not sure it is even worth it given 
> the highly special situation which triggers the issue, which is not such 
> an actual problem (ok, the user is told twice that there was a 
> connection loss, no big deal).

As Tom said earlier, wasn't this fixed by 618c16707?  If not, is there 
any other discussion on the specifics of this issue?  I'm not aware of one.

---

From: Fabien COELHO <[email-masked]>
Date: Fri, 25 Mar 2022 16:46:52 +0100 (CET)

> As Tom said earlier, wasn't this fixed by 618c16707?  If not, is there any 
> other discussion on the specifics of this issue?  I'm not aware of one.

Hmmm… I'll try to understand why the doubled message seems to be still 
there.

-- 
Fabien.

---

From: Fabien COELHO <[email-masked]>
Date: Sat, 26 Mar 2022 16:55:27 +0100 (CET)

Hello Peter,

>> other discussion on the specifics of this issue?  I'm not aware of one.

This answer is that I had kept psql's calls to PQerrorMessage which 
reports errors from the connection, whereas it needed to change to 
PQresultErrorMessage to benefit from the libpq improvement.

I made the added autocommit/on_error_rollback tests at the end really 
focus on multi-statement queries (\;), as was more or less intended.

I updated the tap test.

-- 
Fabien.

index caabb06c53..f01adb1fd2 100644
@@ -3570,10 +3563,6 @@ select 1\; select 2\; select 3;
@@ -4160,6 +4149,18 @@ bar
       </varlistentry>
 
       <varlistentry>
index d65b9a124f..c84688e89c 100644
@@ -34,6 +34,8 @@ static bool DescribeQuery(const char *query, double *elapsed_msec);

 /*
@@ -354,7 +356,7 @@ CheckConnection(void)
  * Returns true for valid result, false for error state.
  */
 static bool
-AcceptResult(const PGresult *result)
+AcceptResult(const PGresult *result, bool show_error)
 {
 	bool		OK;
 
@@ -385,7 +387,7 @@ AcceptResult(const PGresult *result)
 				break;
 		}
 
-	if (!OK)
+	if (!OK && show_error)
 	{
 
@@ -473,6 +475,18 @@ ClearOrSaveResult(PGresult *result)
 	}
 }
 
+/*
+ * Consume all results
+ */
+static void
+ClearOrSaveAllResults()
+{
+	PGresult	*result;
+
 
 /*
  * Print microtiming output.  Always print raw milliseconds; if the interval
@@ -573,7 +587,7 @@ PSQLexec(const char *query)
 
 	ResetCancelConn();
 
@@ -596,11 +610,8 @@ int
 
 	if (!pset.db)
 	{
@@ -609,77 +620,14 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
 	}
 
 	SetCancelConn(pset.db);
-
-	if (timing)

-	return 1;
+	return res;
 }

@@ -714,7 +662,7 @@ PrintNotifications(void)
  * Returns true if successful, false otherwise.
  */
 static bool
-PrintQueryTuples(const PGresult *result)
+PrintQueryTuples(const PGresult *result, const printQueryOpt *opt, FILE *printQueryFout)
 {
 	bool		ok = true;
 
@@ -746,8 +694,9 @@ PrintQueryTuples(const PGresult *result)
 	}
 	else
 	{
-		printQuery(result, &pset.popt, pset.queryFout, false, pset.logfile);
-		if (ferror(pset.queryFout))
+		FILE *fout = printQueryFout ? printQueryFout : pset.queryFout;
+		printQuery(result, opt ? opt : &pset.popt, fout, false, pset.logfile);
+		if (ferror(fout))
 		{
 			pg_log_error("could not print result table: %m");
 			ok = false;
@@ -892,213 +841,131 @@ loop_exit:

 /*
-ProcessResult(PGresult **resultp)
+HandleCopyResult(PGresult **resultp)
+	ExecStatusType result_status = PQresultStatus(*resultp);
 
-	for (;;)
 
-		if (!AcceptResult(*resultp))
+		if (pset.copyStream)
 		{
-			/*
-		result_status = PQresultStatus(*resultp);
-									   PQbinaryTuples(*resultp),
-			PQclear(*resultp);
-			*resultp = copy_result;

-		PQclear(*resultp);
-		*resultp = next_result;
-		first_cycle = false;
+		if (need_close)
+		{
+			/* close \g argument file/pipe */
+			if (is_pipe)
+			{
+				pclose(copystream);
+				restore_sigpipe_trap();
+			}
+			else
+			{
+				fclose(copystream);
+			}
+		}
+	}
+	else
+	{
+		/* COPY IN */
+		copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
+		success = handleCopyIn(pset.db,
+							   copystream,
+							   PQbinaryTuples(*resultp),
+							   &copy_result);
 	}
 
-	SetResultVariables(*resultp, success);
-
-	/* may need this to recover from conn loss during COPY */
-	if (!first_cycle && !CheckConnection())
-		return false;
+	ResetCancelConn();
+	PQclear(*resultp);
+	*resultp = copy_result;
 
 	return success;
 }
 
-
 /*
  * PrintQueryStatus: report command status as required
  *
- * Note: Utility function for use by PrintQueryResult() only.
+ * Note: Utility function for use by HandleQueryResult() only.
  */
 static void
-PrintQueryStatus(PGresult *result)
+PrintQueryStatus(PGresult *result, FILE *printQueryFout)
 {
 	char		buf[16];
+	FILE	   *fout = printQueryFout ? printQueryFout : pset.queryFout;
 
 	if (!pset.quiet)
 	{
 		if (pset.popt.topt.format == PRINT_HTML)
 		{
-			fputs("<p>", pset.queryFout);
-			html_escaped_print(PQcmdStatus(result), pset.queryFout);
-			fputs("</p>\n", pset.queryFout);
+			fputs("<p>", fout);
+			html_escaped_print(PQcmdStatus(result), fout);
+			fputs("</p>\n", fout);
 		}
 		else
-			fprintf(pset.queryFout, "%s\n", PQcmdStatus(result));
+			fprintf(fout, "%s\n", PQcmdStatus(result));
 	}
 
 	if (pset.logfile)
@@ -1110,43 +977,50 @@ PrintQueryStatus(PGresult *result)

 /*
- * PrintQueryResult: print out (or store or execute) query result as required
- *
- * Note: Utility function for use by SendQuery() only.
+ * HandleQueryResult: print out, store or execute one query result
+ * as required.
  *
  * Returns true if the query executed successfully, false otherwise.
  */
 static bool
-PrintQueryResult(PGresult *result)
+HandleQueryResult(PGresult *result, bool last, bool is_watch, const printQueryOpt *opt, FILE *printQueryFout)
 {
 	bool		success;
 	const char *cmdstatus;
 
-	if (!result)
+	if (result == NULL)
 		return false;
 
 	switch (PQresultStatus(result))
 	{
 		case PGRES_TUPLES_OK:
 			/* store or execute or print the data ... */
-			if (pset.gset_prefix)
+			if (last && pset.gset_prefix)
 				success = StoreQueryTuple(result);
-			else if (pset.gexec_flag)
+			else if (last && pset.gexec_flag)
 				success = ExecQueryTuples(result);
-			else if (pset.crosstab_flag)
+			else if (last && pset.crosstab_flag)
 				success = PrintResultInCrosstab(result);
+			else if (last || pset.show_all_results)
+				success = PrintQueryTuples(result, opt, printQueryFout);
 			else
-				success = PrintQueryTuples(result);
+				success = true;
+
 			/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
-			cmdstatus = PQcmdStatus(result);
-			if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
-				strncmp(cmdstatus, "UPDATE", 6) == 0 ||
-				strncmp(cmdstatus, "DELETE", 6) == 0)
-				PrintQueryStatus(result);
+			if (last || pset.show_all_results)
+			{
+				cmdstatus = PQcmdStatus(result);
+				if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
+					strncmp(cmdstatus, "UPDATE", 6) == 0 ||
+					strncmp(cmdstatus, "DELETE", 6) == 0)
+					PrintQueryStatus(result, printQueryFout);
+			}
+
 			break;
 
 		case PGRES_COMMAND_OK:
-			PrintQueryStatus(result);
+			if (last || pset.show_all_results)
+				PrintQueryStatus(result, printQueryFout);
 			success = true;
 			break;
 
@@ -1156,7 +1030,7 @@ PrintQueryResult(PGresult *result)
 
 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:
 
@@ -1173,11 +1047,252 @@ PrintQueryResult(PGresult *result)
 			break;
 	}

 	return success;
 }
 
+/*
+			const char *error = PQresultErrorMessage(result);
+			/* keep the result status before clearing it */
+			/* switch to next result */
+				 * For some obscure reason PQgetResult does *not* return a NULL in copy
+				 * cases despite the result having been cleared, but keeps returning an
+				 * "empty" result that we have to ignore manually.
+				;
 
 /*
  * SendQuery: send the query string to the backend
@@ -1195,12 +1310,14 @@ bool
 SendQuery(const char *query)
 {
 	bool		timing = pset.timing;
-	PGresult   *result;
+	PGresult    *result = NULL;
 	PGTransactionStatusType transaction_status;
 	double		elapsed_msec = 0;
 	bool		OK = false;
+	bool		res_error;
 	int			i;
 	bool		on_error_rollback_savepoint = false;
+	bool		tx_ended = false;
 
 	if (!pset.db)
 	{
@@ -1239,82 +1356,71 @@ SendQuery(const char *query)
 		fflush(pset.logfile);
 	}

 
+	/* issue a BEGIN if needed, corresponding COMMIT/ROLLBACK by user */
 	if (transaction_status == PQTRANS_IDLE &&
 		!pset.autocommit &&
 		!command_no_begin(query))
 	{
+		PGresult    *result;
 		result = PQexec(pset.db, "BEGIN");
-		if (PQresultStatus(result) != PGRES_COMMAND_OK)
+		res_error = PQresultStatus(result) != PGRES_COMMAND_OK;
+		ClearOrSaveResult(result);
+		result = NULL;
+
+		if (res_error)
 		{
 			pg_log_info("%s", PQerrorMessage(pset.db));
-			ClearOrSaveResult(result);
-			ResetCancelConn();
 			goto sendquery_cleanup;
 		}
-		ClearOrSaveResult(result);
+
+		/* must be PQTRANS_INTRANS after a BEGIN */
 		transaction_status = PQtransactionStatus(pset.db);
 	}
 
+	/* create automatic savepoint if needed and possible */
 	if (transaction_status == PQTRANS_INTRANS &&
 		pset.on_error_rollback != PSQL_ERROR_ROLLBACK_OFF &&
 		(pset.cur_cmd_interactive ||
 		 pset.on_error_rollback == PSQL_ERROR_ROLLBACK_ON))
 	{
+		PGresult   *result;
 		result = PQexec(pset.db, "SAVEPOINT pg_psql_temporary_savepoint");
-		if (PQresultStatus(result) != PGRES_COMMAND_OK)
+		res_error = PQresultStatus(result) != PGRES_COMMAND_OK;
+		ClearOrSaveResult(result);
+		result = NULL;
+
+		if (res_error)
 		{
 			pg_log_info("%s", PQerrorMessage(pset.db));
-			ClearOrSaveResult(result);
-			ResetCancelConn();
 			goto sendquery_cleanup;
 		}
-		ClearOrSaveResult(result);
+
 		on_error_rollback_savepoint = true;
 	}
 
+	/* process the query, one way or the other */
 	if (pset.gdesc_flag)
 	{
 		/* Describe query's result columns, without executing it */
 		OK = DescribeQuery(query, &elapsed_msec);
-		ResetCancelConn();
 		result = NULL;			/* PQclear(NULL) does nothing */
 	}
 	else if (pset.fetch_count <= 0 || pset.gexec_flag ||
 			 pset.crosstab_flag || !is_select_command(query))
 	{
 		/* Default fetch-it-all-and-print mode */
-		instr_time	before,
-					after;
-
-		if (timing)
-			INSTR_TIME_SET_CURRENT(before);
-
-		result = PQexec(pset.db, query);
-
-		/* these operations are included in the timing result: */
-		ResetCancelConn();
-		OK = ProcessResult(&result);
-
-		if (timing)
-		{
-			INSTR_TIME_SET_CURRENT(after);
-			INSTR_TIME_SUBTRACT(after, before);
-			elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
-		}
-
-		/* but printing result isn't: */
-		if (OK && result)
-			OK = PrintQueryResult(result);
+		int res = SendQueryAndProcessResults(query, &elapsed_msec, false, NULL, NULL, &tx_ended);
+		OK = (res >= 0);
 	}
 	else
 	{
 		/* Fetch-in-segments mode */
 		OK = ExecQueryUsingCursor(query, &elapsed_msec);
-		ResetCancelConn();
 		result = NULL;			/* PQclear(NULL) does nothing */
 	}
 
@@ -1347,11 +1453,7 @@ SendQuery(const char *query)
 				 * savepoint is gone. If they issued a SAVEPOINT, releasing
 				 * ours would remove theirs.
 				 */
-				if (result &&
-					(strcmp(PQcmdStatus(result), "COMMIT") == 0 ||
-					 strcmp(PQcmdStatus(result), "SAVEPOINT") == 0 ||
-					 strcmp(PQcmdStatus(result), "RELEASE") == 0 ||
-					 strcmp(PQcmdStatus(result), "ROLLBACK") == 0))
+				if (tx_ended)
 					svptcmd = NULL;
 				else
 					svptcmd = "RELEASE pg_psql_temporary_savepoint";
@@ -1373,14 +1475,15 @@ SendQuery(const char *query)
 			PGresult   *svptres;

 				PQclear(result);
-				ResetCancelConn();
 				goto sendquery_cleanup;
 			}
 			PQclear(svptres);
@@ -1411,6 +1514,9 @@ SendQuery(const char *query)
 
 sendquery_cleanup:
 
+	/* global cancellation reset */
+	ResetCancelConn();
+
 	/* reset \g's output-to-filename trigger */
 	if (pset.gfname)
 	{
@@ -1491,7 +1597,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 	PQclear(result);
 
 	result = PQdescribePrepared(pset.db, "");
-	OK = AcceptResult(result) &&
+	OK = AcceptResult(result, true) &&
 		(PQresultStatus(result) == PGRES_COMMAND_OK);
 	if (OK && result)
 	{
@@ -1539,7 +1645,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			PQclear(result);
 
 			result = PQexec(pset.db, buf.data);
-			OK = AcceptResult(result);
+			OK = AcceptResult(result, true);
 
 			if (timing)
 			{
@@ -1549,7 +1655,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && result)
-				OK = PrintQueryResult(result);
+				OK = HandleQueryResult(result, true, false, NULL, NULL);
 
 			termPQExpBuffer(&buf);
 		}
@@ -1609,7 +1715,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 	if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
 	{
 		result = PQexec(pset.db, "BEGIN");
-		OK = AcceptResult(result) &&
+		OK = AcceptResult(result, true) &&
 			(PQresultStatus(result) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(result);
 		if (!OK)
@@ -1623,7 +1729,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 					  query);
 
 	result = PQexec(pset.db, buf.data);
-	OK = AcceptResult(result) &&
+	OK = AcceptResult(result, true) &&
 		(PQresultStatus(result) == PGRES_COMMAND_OK);
 	if (!OK)
 		SetResultVariables(result, OK);
@@ -1696,7 +1802,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
 				is_pager = false;
 			}
 
-			OK = AcceptResult(result);
+			OK = AcceptResult(result, true);
 			Assert(!OK);
 			SetResultVariables(result, OK);
 			ClearOrSaveResult(result);
@@ -1805,7 +1911,7 @@ cleanup:
 	result = PQexec(pset.db, "CLOSE _psql_cursor");
 	if (OK)
 	{
-		OK = AcceptResult(result) &&
+		OK = AcceptResult(result, true) &&
 			(PQresultStatus(result) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(result);
 	}
@@ -1815,7 +1921,7 @@ cleanup:
 	if (started_txn)
 	{
 		result = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
-		OK &= AcceptResult(result) &&
+		OK &= AcceptResult(result, true) &&
 			(PQresultStatus(result) == PGRES_COMMAND_OK);
 		ClearOrSaveResult(result);
 	}
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 56afa6817e..b3971bce64 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -413,6 +413,8 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  SERVER_VERSION_NAME\n"
 					  "  SERVER_VERSION_NUM\n"
 					  "    server's version (in short string or numeric format)\n"));
+	fprintf(output, _("  SHOW_ALL_RESULTS\n"
+					  "    show all results of a combined query (\\;) instead of only the last\n"));
 	fprintf(output, _("  SHOW_CONTEXT\n"
 					  "    controls display of message context fields [never, errors, always]\n"));
 	fprintf(output, _("  SINGLELINE\n"
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 80dbea9efd..2399cffa3f 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -148,6 +148,7 @@ typedef struct _psqlSettings
 	const char *prompt2;
 	const char *prompt3;
 	PGVerbosity verbosity;		/* current error verbosity level */
+	bool		show_all_results;
 	PGContextVisibility show_context;	/* current context display level */
 } PsqlSettings;
 
index be9dec749d..d08b15886a 100644

 
+static bool
+show_all_results_hook(const char *newval)
+{
+	return ParseVariableBool(newval, "SHOW_ALL_RESULTS", &pset.show_all_results);
+}
+
 static char *
 show_context_substitute_hook(char *newval)
 {
@@ -1251,6 +1258,9 @@ EstablishVariableSpace(void)
 	SetVariableHooks(pset.vars, "VERBOSITY",
 					 verbosity_substitute_hook,
 					 verbosity_hook);
+	SetVariableHooks(pset.vars, "SHOW_ALL_RESULTS",
+					 bool_substitute_hook,
+					 show_all_results_hook);
 	SetVariableHooks(pset.vars, "SHOW_CONTEXT",
 					 show_context_substitute_hook,
 					 show_context_hook);
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 44ecd05add..dd35cafa9d 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -115,4 +115,20 @@ NOTIFY foo, 'bar';",
 	qr/^Asynchronous notification "foo" with payload "bar" received from server process with PID \d+\.$/,
 	'notification with payload');
 
+# Test voluntary crash
+my ($ret, $out, $err) = $node->psql(
+	'postgres',
+	"SELECT 'before' AS running;\n" .
+	"SELECT pg_terminate_backend(pg_backend_pid());\n" .
+	"SELECT 'AFTER' AS not_running;\n");
+
+is($ret, 2, "server stopped");
+like($out, qr/before/, "output before crash");
+ok($out !~ qr/AFTER/, "no output after crash");
+is($err, 'psql:<stdin>:2: FATAL:  terminating connection due to administrator command
+psql:<stdin>:2: server closed the connection unexpectedly
+	This probably means the server terminated abnormally
+	before or while processing the request.
+psql:<stdin>:2: fatal: connection to server was lost', "expected error message");
+
 done_testing();
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 63bfdf11c6..9c93471aec 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4564,7 +4564,7 @@ psql_completion(const char *text, int start, int end)
 		matches = complete_from_variables(text, "", "", false);
 	else if (TailMatchesCS("\\set", MatchAny))
 	{
-		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
+		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|SHOW_ALL_RESULTS|"
 						  "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_CS("on", "off");
 		else if (TailMatchesCS("COMP_KEYWORD_CASE"))
diff --git a/src/test/regress/expected/copyselect.out b/src/test/regress/expected/copyselect.out
index 72865fe1eb..bb9e026f91 100644
--- a/src/test/regress/expected/copyselect.out
+++ b/src/test/regress/expected/copyselect.out
@@ -126,7 +126,7 @@ copy (select 1) to stdout\; select 1/0;	-- row, then error
 ERROR:  division by zero
 select 1/0\; copy (select 1) to stdout; -- error only
 ERROR:  division by zero
-copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; -- 1 2 3
+copy (select 1) to stdout\; copy (select 2) to stdout\; select 3\; select 4; -- 1 2 3 4
 1
 2
  ?column? 
@@ -134,8 +134,18 @@ copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; --
         3
 (1 row)
 
+ ?column? 
+----------
+        4
+(1 row)
+
index 6428ebc507..185c505312 100644
@@ -5290,3 +5290,245 @@ ERROR:  relation "notexists" does not exist
+-- miscellaneous SQL commands
+-- AUTOCOMMIT and combined queries
+-- BEGIN is now implicit
+CREATE TABLE foo(s TEXT) \;
+CREATE TABLE foo(s TEXT) \;
+INSERT INTO foo(s) VALUES ('hello'), ('world') \;
+DROP TABLE foo \;
+-- table foo is still there
+SELECT * FROM foo ORDER BY 1 \;
+DROP TABLE foo \;
+-- BEGIN now explicit for multi-statement transactions
+BEGIN \;
+CREATE TABLE foo(s TEXT) \;
+INSERT INTO foo(s) VALUES ('hello'), ('world') \;
+BEGIN \;
+DROP TABLE foo \;
+ROLLBACK \;
+SELECT * FROM foo ORDER BY 1 \;
+-- test ON_ERROR_ROLLBACK and combined queries
+-- now with combined queries
+SELECT 'before error' AS show \;          -- will show nevertheless!
+FROM bla WHERE s = 'Mum' \;               -- no mum here
+-- reset all
index 599d511a67..a46fa5d48a 100644
@@ -904,8 +904,18 @@ DROP TABLE abc;
@@ -920,6 +930,12 @@ insert into i_table values(1)\; select * from i_table;
 
@@ -939,8 +955,18 @@ WARNING:  there is no transaction in progress
@@ -967,22 +993,52 @@ rollback;  -- we are not in a transaction at this point
 
 create table test3 (c int);
-select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 1
+select 0\; copy test3 from stdin\; copy test3 from stdin\; select 1; -- 0 1
 1
 \.
 2
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 0f5287f77b..8f49a5f347 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,144 @@ DROP TABLE oer_test;
 \set ECHO errors
 SELECT * FROM notexists;
 \set ECHO all
+
+--
+-- combined queries
+--
+CREATE FUNCTION warn(msg TEXT) RETURNS BOOLEAN LANGUAGE plpgsql
+AS $$
+  BEGIN RAISE NOTICE 'warn %', msg ; RETURN TRUE ; END
+$$;
+
+-- show both
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+-- \gset applies to last query only
+SELECT 3 AS three \; SELECT warn('3.5') \; SELECT 4 AS four \gset
+\echo :three :four
+-- syntax error stops all processing
+SELECT 5 \; SELECT 6 + \; SELECT warn('6.5') \; SELECT 7 ;
+-- with aborted transaction, stop on first error
+BEGIN \; SELECT 8 AS eight \; SELECT 9/0 AS nine \; ROLLBACK \; SELECT 10 AS ten ;
+-- close previously aborted transaction
+ROLLBACK;
+
+-- miscellaneous SQL commands
+-- (non SELECT output is sent to stderr, thus is not shown in expected results)
+SELECT 'ok' AS "begin" \;
+CREATE TABLE psql_comics(s TEXT) \;
+INSERT INTO psql_comics VALUES ('Calvin'), ('hobbes') \;
+COPY psql_comics FROM STDIN \;
+UPDATE psql_comics SET s = 'Hobbes' WHERE s = 'hobbes' \;
+DELETE FROM psql_comics WHERE s = 'Moe' \;
+COPY psql_comics TO STDOUT \;
+TRUNCATE psql_comics \;
+DROP TABLE psql_comics \;
+SELECT 'ok' AS "done" ;
+Moe
+Susie
+\.
+
+\set SHOW_ALL_RESULTS off
+SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
+
+\set SHOW_ALL_RESULTS on
+DROP FUNCTION warn(TEXT);
+
+--
+-- AUTOCOMMIT and combined queries
+--
+\set AUTOCOMMIT off
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+-- BEGIN is now implicit
+
+CREATE TABLE foo(s TEXT) \;
+ROLLBACK;
+
+CREATE TABLE foo(s TEXT) \;
+INSERT INTO foo(s) VALUES ('hello'), ('world') \;
+COMMIT;
+
+DROP TABLE foo \;
+ROLLBACK;
+
+-- table foo is still there
+SELECT * FROM foo ORDER BY 1 \;
+DROP TABLE foo \;
+COMMIT;
+
+\set AUTOCOMMIT on
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+-- BEGIN now explicit for multi-statement transactions
+
+BEGIN \;
+CREATE TABLE foo(s TEXT) \;
+INSERT INTO foo(s) VALUES ('hello'), ('world') \;
+COMMIT;
+
+BEGIN \;
+DROP TABLE foo \;
+ROLLBACK \;
+
+-- implicit transactions
+SELECT * FROM foo ORDER BY 1 \;
+DROP TABLE foo;
+
+--
+-- test ON_ERROR_ROLLBACK and combined queries
+--
+CREATE FUNCTION psql_error(msg TEXT) RETURNS BOOLEAN AS $$
+  BEGIN
+    RAISE EXCEPTION 'error %', msg;
+  END;
+$$ LANGUAGE plpgsql;
+
+\set ON_ERROR_ROLLBACK on
+\echo '# ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+
+BEGIN;
+CREATE TABLE bla(s NO_SUCH_TYPE);               -- fails
+CREATE TABLE bla(s TEXT);                       -- succeeds
+SELECT psql_error('oops!');                     -- fails
+INSERT INTO bla VALUES ('Calvin'), ('Hobbes');
+COMMIT;
+
+SELECT * FROM bla ORDER BY 1;
+
+BEGIN;
+INSERT INTO bla VALUES ('Susie');         -- succeeds
+-- now with combined queries
+INSERT INTO bla VALUES ('Rosalyn') \;     -- will rollback
+SELECT 'before error' AS show \;          -- will show nevertheless!
+  SELECT psql_error('boum!') \;           -- failure
+  SELECT 'after error' AS noshow;         -- hidden by preceeding error
+INSERT INTO bla(s) VALUES ('Moe') \;      -- will rollback
+  SELECT psql_error('bam!');
+INSERT INTO bla VALUES ('Miss Wormwood'); -- succeeds
+COMMIT;
+SELECT * FROM bla ORDER BY 1;
+
+-- some with autocommit off
+\set AUTOCOMMIT off
+\echo '# AUTOCOMMIT:' :AUTOCOMMIT
+
+-- implicit BEGIN
+INSERT INTO bla VALUES ('Dad');           -- succeeds
+SELECT psql_error('bad!');                -- implicit partial rollback
+
+INSERT INTO bla VALUES ('Mum') \;         -- will rollback
+SELECT COUNT(*) AS "#mum"
+FROM bla WHERE s = 'Mum' \;               -- but be counted here
+SELECT psql_error('bad!');                -- implicit partial rollback
+COMMIT;
+
+SELECT COUNT(*) AS "#mum"
+FROM bla WHERE s = 'Mum' \;               -- no mum here
+SELECT * FROM bla ORDER BY 1;
+
+-- reset all
+\set AUTOCOMMIT on
+\set ON_ERROR_ROLLBACK off
+\echo '# final ON_ERROR_ROLLBACK:' :ON_ERROR_ROLLBACK
+DROP TABLE bla;
+DROP FUNCTION psql_error;
diff --git a/src/test/regress/sql/transactions.sql b/src/test/regress/sql/transactions.sql
index 0a716b506b..d71c3ce93e 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -506,7 +506,7 @@ DROP TABLE abc;

---

From: Fabien COELHO <[email-masked]>
Date: Thu, 31 Mar 2022 18:54:17 +0200 (CEST)

Attached a rebase.

-- 
Fabien.

       <varlistentry>

 /*

-	if (!OK)
+	if (!OK && show_error)
 	{

+/*
+ * Consume all results
+ */
+static void
+ClearOrSaveAllResults()
+{
+	PGresult	*result;
+
 
 /*
 
 	ResetCancelConn();

 	if (!pset.db)
 	{
 
 	SetCancelConn(pset.db);
-
-	if (timing)

-	return 1;
+	return res;
 }

 /*
 
-	for (;;)

 
-		PQclear(*resultp);

 	return success;
 }
 
-
 /*
 
 	if (!pset.quiet)
 	{
 
 	if (pset.logfile)

 /*
 
-	if (!result)
+	if (result == NULL)
 		return false;

 		case PGRES_COMMAND_OK:

 		case PGRES_COPY_OUT:
 		case PGRES_COPY_IN:

 
 	return success;
 }
 
+/*
 
 /*
 
 	if (!pset.db)
 	{

 

 

 				PQclear(result);
-				ResetCancelConn();
 				goto sendquery_cleanup;
 			}
 			PQclear(svptres);
 
 sendquery_cleanup:

 
 
 			if (timing)
 			{
 
 			if (OK && result)
 
 			termPQExpBuffer(&buf);
 		}

 

 
+static bool
+show_all_results_hook(const char *newval)
+{
+	return ParseVariableBool(newval, "SHOW_ALL_RESULTS", &pset.show_all_results);
+}
+
 static char *
 show_context_substitute_hook(char *newval)
 {
@@ -1251,6 +1258,9 @@ EstablishVariableSpace(void)
 	SetVariableHooks(pset.vars, "VERBOSITY",
 					 verbosity_substitute_hook,
 					 verbosity_hook);
+	SetVariableHooks(pset.vars, "SHOW_ALL_RESULTS",
+					 bool_substitute_hook,
+					 show_all_results_hook);
 	SetVariableHooks(pset.vars, "SHOW_CONTEXT",
 					 show_context_substitute_hook,
 					 show_context_hook);
diff --git a/src/bin/psql/t/001_basic.pl b/src/bin/psql/t/001_basic.pl
index 66796f4978..8ecb9b2583 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -126,7 +126,7 @@ is($ret, 2, 'server crash: psql exit code');
 like($out, qr/before/, 'server crash: output before crash');
 ok($out !~ qr/AFTER/, 'server crash: no output after crash');
 is($err, 'psql:<stdin>:2: FATAL:  terminating connection due to administrator command
-server closed the connection unexpectedly
+psql:<stdin>:2: server closed the connection unexpectedly
 	This probably means the server terminated abnormally
 	before or while processing the request.
 psql:<stdin>:2: fatal: connection to server was lost',
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 3f9dfffd57..47cdd2eb7e 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4627,7 +4627,7 @@ psql_completion(const char *text, int start, int end)
 		matches = complete_from_variables(text, "", "", false);
 	else if (TailMatchesCS("\\set", MatchAny))
 	{
-		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|"
+		if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|SHOW_ALL_RESULTS|"
 						  "SINGLELINE|SINGLESTEP"))
 			COMPLETE_WITH_CS("on", "off");
 		else if (TailMatchesCS("COMP_KEYWORD_CASE"))
diff --git a/src/test/regress/expected/copyselect.out b/src/test/regress/expected/copyselect.out
index 72865fe1eb..bb9e026f91 100644
--- a/src/test/regress/expected/copyselect.out
+++ b/src/test/regress/expected/copyselect.out
@@ -126,7 +126,7 @@ copy (select 1) to stdout\; select 1/0;	-- row, then error
 ERROR:  division by zero
 select 1/0\; copy (select 1) to stdout; -- error only
 ERROR:  division by zero
-copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; -- 1 2 3
+copy (select 1) to stdout\; copy (select 2) to stdout\; select 3\; select 4; -- 1 2 3 4
 1
 2
  ?column? 
@@ -134,8 +134,18 @@ copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; --
         3
 (1 row)
 
+ ?column? 
+----------
+        4
+(1 row)
+