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
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 4b2679360f..fa358c8c58 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -486,6 +486,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
@@ -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)
-	{
-		const char *error = PQerrorMessage(pset.db);
-
-		if (strlen(error))
-			pg_log_info("%s", error);
-
-		CheckConnection();
-	}
+	if (!OK && show_error)
+		ShowErrorMessage(result);
 
 	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;
 }
 
-
 /*
- * ProcessResult: utility function for use by SendQuery() only
+ * 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.
  *
- * 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.
+ * 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.
  *
- * 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.
+ * 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 (;;)
+	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;
+				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;
+		}
 
-				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)
+			&& (copystream != NULL);
 
-				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;
+		}
 
-				/*
-				 * 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);
-					}
-				}
+		if (need_close)
+		{
+			/* close \g argument file/pipe */
+			if (is_pipe)
+			{
+				pclose(copystream);
+				restore_sigpipe_trap();
 			}
 			else
 			{
-				/* COPY IN */
-				copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
-				success = handleCopyIn(pset.db,
-									   copystream,
-									   PQbinaryTuples(*results),
-									   &copy_result) && success;
+				fclose(copystream);
 			}
-			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;
 		}
-		else if (first_cycle)
-		{
-			/* fast path: no COPY commands; PQexec visited all results */
-			break;
-		}
-
-		/*
-		 * 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.
-		 */
-		next_result = PQgetResult(pset.db);
-		if (!next_result)
-			break;
-
-		PQclear(*results);
-		*results = next_result;
-		first_cycle = false;
 	}
+	else
+	{
+		/* COPY IN */
+		copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
+		success = handleCopyIn(pset.db,
+							   copystream,
+							   PQbinaryTuples(*result),
+							   &copy_result);
+	}
+	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)
@@ -1233,43 +1151,44 @@ 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
-				success = PrintQueryTuples(results);
+				success = PrintQueryTuples(result);
+
 			/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
-			cmdstatus = PQcmdStatus(results);
+			cmdstatus = PQcmdStatus(result);
 			if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
 				strncmp(cmdstatus, "UPDATE", 6) == 0 ||
 				strncmp(cmdstatus, "DELETE", 6) == 0)
-				PrintQueryStatus(results);
+				PrintQueryStatus(result);
+
 			break;
 
 		case PGRES_COMMAND_OK:
-			PrintQueryStatus(results);
+			PrintQueryStatus(result);
 			success = true;
 			break;
 
@@ -1279,7 +1198,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;
 
@@ -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;
 }
 
+/*
+ * 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 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 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, either a server-side failure or a failure
+			 * to submit the command string.  Record that.
+			 */
+			ShowNoticeMessage(&notes);
+			ShowErrorMessage(result);
+			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);
+		if (result_status == PGRES_COPY_IN ||
+			result_status == PGRES_COPY_OUT)
+		{
+			ShowNoticeMessage(&notes);
+			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 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);
+
+		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 false;
+
+	return success;
+}
+
 
 /*
  * SendQuery: send the query string to the backend
@@ -1317,9 +1404,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;
@@ -1422,28 +1509,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
 	{
@@ -1620,7 +1686,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)
 	{
@@ -1668,7 +1734,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)
 			{
@@ -1678,7 +1744,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
 			}
 
 			if (OK && results)
-				OK = PrintQueryResults(results);
+				OK = HandleQueryResult(results, true);
 
 			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);
 
 	results = PQexec(pset.db, buf.data);
-	OK = AcceptResult(results) &&
+	OK = AcceptResult(results, true) &&
 		(PQresultStatus(results) == PGRES_COMMAND_OK);
 	if (!OK)
 		SetResultVariables(results, OK);
@@ -1828,7 +1894,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 +2003,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 +2013,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/test/regress/expected/copy2.out b/src/test/regress/expected/copy2.out
index c53ed3ebf5..9c8c8361f8 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/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 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
+--
+\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 1b03310029..e2b58e9f29 100644
--- a/src/test/regress/expected/transactions.out
+++ b/src/test/regress/expected/transactions.out
@@ -860,11 +860,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:
@@ -876,6 +886,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 
@@ -894,9 +910,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;
@@ -922,23 +948,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)
+
 -- 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/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 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;
 
 -- \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 bf1016489d..3a7e5d4582 100644
--- a/src/test/regress/sql/transactions.sql
+++ b/src/test/regress/sql/transactions.sql
@@ -493,8 +493,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;
@@ -512,9 +512,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.
@@ -527,17 +527,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;
 
 
 -- Tests for AND CHAIN in implicit transaction blocks
