Hello Peter,
quite weird and confusing. Maybe this type of test should be done in
the TAP framework instead.
Attached v13 where the crash test is moved to tap.
--
Fabien.
diff --git a/contrib/pg_stat_statements/expected/pg_stat_statements.out b/contrib/pg_stat_statements/expected/pg_stat_statements.out
index e0abe34bb6..8f7f93172a 100644
--- a/contrib/pg_stat_statements/expected/pg_stat_statements.out
+++ b/contrib/pg_stat_statements/expected/pg_stat_statements.out
@@ -50,8 +50,28 @@ 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?
+----------
+ 6
+(1 row)
+
+ ?column?
+----------
+ !
+(1 row)
+
?column?
----------
5
@@ -61,6 +81,11 @@ COMMIT;
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
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 1ab200a4ad..0a22850912 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>
@@ -3570,10 +3563,6 @@ 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>.
</para>
</listitem>
</varlistentry>
@@ -4160,6 +4149,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 f210ccbde8..b8e8c2b245 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, bool *tx_ended);
/*
@@ -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)
{
const char *error = PQerrorMessage(pset.db);
@@ -472,6 +474,18 @@ 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
@@ -572,7 +586,7 @@ PSQLexec(const char *query)
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)
- INSTR_TIME_SET_CURRENT(before);
-
- res = PQexec(pset.db, query);
-
+ res = SendQueryAndProcessResults(query, &elapsed_msec, true, opt, printQueryFout, NULL);
ResetCancelConn();
- if (!AcceptResult(res))
- {
- ClearOrSaveResult(res);
- return 0;
- }
-
- if (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;
- }
-
- fout = printQueryFout ? printQueryFout : pset.queryFout;
-
- switch (PQresultStatus(res))
- {
- case PGRES_TUPLES_OK:
- printQuery(res, opt, fout, false, pset.logfile);
- break;
-
- case PGRES_COMMAND_OK:
- fprintf(fout, "%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(fout);
-
/* Possible microtiming output */
if (timing)
PrintTiming(elapsed_msec);
- 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:
/*
- * 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 than 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(pset.db);
+
+ 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(pset.db);
- if (result_status == PGRES_COPY_OUT)
+ /* invoked by \g */
+ if (openQueryOutputFile(pset.gfname,
+ ©stream, &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,
- ©stream, &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,
- ©_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),
- ©_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,
+ ©_result)
+ && (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),
+ ©_result);
}
- SetResultVariables(*results, success);
-
- /* may need this to recover from conn loss during COPY */
- if (!first_cycle && !CheckConnection())
- return false;
+ ResetCancelConn();
+ 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)
+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)
@@ -1109,43 +976,50 @@ 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 is_watch, const printQueryOpt *opt, FILE *printQueryFout)
{
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, opt, printQueryFout);
else
- success = PrintQueryTuples(results);
+ success = true;
+
/* 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);
+ 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(results);
+ if (last || pset.show_all_results)
+ PrintQueryStatus(result, printQueryFout);
success = true;
break;
@@ -1155,7 +1029,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;
@@ -1168,15 +1042,261 @@ 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;
}
+/*
+ * 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 {
+ PQExpBufferData messages[2];
+ int current;
+} 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(¬es->messages[notes->current], msg);
+}
+
+/*
+ * Show notices stored in buffer, which is then reset.
+ */
+static void
+ShowNoticeMessage(t_notice_messages *notes)
+{
+ PQExpBufferData *current = ¬es->messages[notes->current];
+ if (*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, const printQueryOpt *opt, FILE *printQueryFout, bool *tx_ended)
+{
+ bool timing = pset.timing;
+ bool success;
+ instr_time before;
+ PGresult *result;
+ t_notice_messages notes;
+
+ if (timing)
+ INSTR_TIME_SET_CURRENT(before);
+
+ success = PQsendQuery(pset.db, query);
+
+ if (!success)
+ {
+ const char *error = PQerrorMessage(pset.db);
+
+ if (strlen(error))
+ pg_log_info("%s", error);
+
+ CheckConnection();
+
+ 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.current = 0;
+ initPQExpBuffer(¬es.messages[0]);
+ initPQExpBuffer(¬es.messages[1]);
+ PQsetNoticeProcessor(pset.db, AppendNoticeMessage, ¬es);
+
+ /* 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.
+ */
+ const char *error = PQerrorMessage(pset.db);
+
+ ShowNoticeMessage(¬es);
+ if (strlen(error))
+ {
+ pg_log_info("%s", error);
+
+ /*
+ * On connection loss another result with a message will be
+ * generated, we do not want to see this error again.
+ */
+ PQclearErrorMessage(pset.db);
+ }
+ CheckConnection();
+ if (!is_watch)
+ SetResultVariables(result, false);
+ ClearOrSaveResult(result);
+ success = false;
+
+ /* and switch to next result */
+ result_status = PQresultStatus(result);
+ 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;
+ else
+ result = PQgetResult(pset.db);
+
+ continue;
+ }
+ 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;
+ }
+
+ result_status = PQresultStatus(result);
+
+ /* must handle COPY before changing the current result */
+ Assert(result_status != PGRES_COPY_BOTH);
+ if (result_status == PGRES_COPY_IN ||
+ result_status == PGRES_COPY_OUT)
+ {
+ ShowNoticeMessage(¬es);
+
+ if (is_watch)
+ {
+ ClearOrSaveAllResults();
+ pg_log_error("\\watch cannot be used with COPY");
+ return -1;
+ }
+
+ /* use normal notice processor during COPY */
+ PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
+
+ success &= HandleCopyResult(&result);
+
+ PQsetNoticeProcessor(pset.db, AppendNoticeMessage, ¬es);
+ }
+
+ /*
+ * Check PQgetResult() again. In the typical case of a single-command
+ * string, it will return NULL. Otherwise, we'll have other results
+ * to process. We need to do that to check whether this is the last.
+ */
+ notes.current ^= 1;
+ next_result = PQgetResult(pset.db);
+ notes.current ^= 1;
+ 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 && 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(¬es);
+
+ /* this may or may not print something depending on settings */
+ if (result != NULL)
+ success &= HandleQueryResult(result, last, false, opt, printQueryFout);
+
+ /* set variables on last result if all went well */
+ if (!is_watch && last && success)
+ SetResultVariables(result, true);
+
+ ClearOrSaveResult(result);
+ notes.current ^= 1;
+ result = next_result;
+
+ if (cancel_pressed)
+ {
+ ClearOrSaveAllResults();
+ break;
+ }
+ }
+
+ /* reset notice hook */
+ PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
+ termPQExpBuffer(¬es.messages[0]);
+ termPQExpBuffer(¬es.messages[1]);
+
+ /* may need this to recover from conn loss during COPY */
+ if (!CheckConnection())
+ return -1;
+
+ return cancel_pressed ? 0 : success ? 1 : -1;
+}
+
/*
* 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);
}
+ /* 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 ||
pset.on_error_rollback == PSQL_ERROR_ROLLBACK_ON))
{
- 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 (timing)
- INSTR_TIME_SET_CURRENT(before);
-
- results = PQexec(pset.db, query);
-
- /* these operations are included in the timing result: */
- ResetCancelConn();
- OK = ProcessResult(&results);
-
- if (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, NULL, NULL, &tx_ended);
+ OK = (res >= 0);
}
else
{
/* Fetch-in-segments mode */
OK = ExecQueryUsingCursor(query, &elapsed_msec);
- ResetCancelConn();
results = NULL; /* PQclear(NULL) does nothing */
}
@@ -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;
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);
@@ -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);
results = PQdescribePrepared(pset.db, "");
- OK = AcceptResult(results) &&
+ OK = AcceptResult(results, true) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
if (OK && results)
{
@@ -1538,7 +1647,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
PQclear(results);
results = PQexec(pset.db, buf.data);
- OK = AcceptResult(results);
+ OK = AcceptResult(results, true);
if (timing)
{
@@ -1548,7 +1657,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
}
if (OK && results)
- OK = PrintQueryResults(results);
+ OK = HandleQueryResult(results, true, false, NULL, NULL);
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);
results = PQexec(pset.db, buf.data);
- OK = AcceptResult(results) &&
+ OK = AcceptResult(results, true) &&
(PQresultStatus(results) == PGRES_COMMAND_OK);
if (!OK)
SetResultVariables(results, OK);
@@ -1695,7 +1804,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);
@@ -1804,7 +1913,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);
}
@@ -1814,7 +1923,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 937d6e9d49..82504258d0 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 f614b26e2c..57bddec5a5 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 be9dec749d..d08b15886a 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");
parse_psql_options(argc, argv, &options);
@@ -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 9e14dc71ff..77c60a0e7b 100644
--- a/src/bin/psql/t/001_basic.pl
+++ b/src/bin/psql/t/001_basic.pl
@@ -6,7 +6,8 @@ use warnings;
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
-use Test::More tests => 25;
+
+use Test::More tests => 29;
program_help_ok('psql');
program_version_ok('psql');
@@ -80,3 +81,19 @@ psql_like(
'handling of unexpected PQresultStatus',
'START_REPLICATION 0/0',
undef, qr/unexpected PQresultStatus: 8$/);
+
+# 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");
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 39be6f556a..fbe6b2b579 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -4347,7 +4347,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 72914116ee..099c2715c0 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -6782,6 +6782,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 20eb855abc..b73b44e817 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)
+
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 6428ebc507..428ee941b9 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -5290,3 +5290,129 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists;
^
STATEMENT: SELECT * FROM notexists;
+ 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)
+
+ three
+-------
+ 3
+(1 row)
+
+NOTICE: warn 3.5
+CONTEXT: PL/pgSQL function warn(text) line 2 at RAISE
+ warn
+------
+ t
+(1 row)
+
+:three 4
+ERROR: syntax error at or near ";"
+LINE 1: SELECT 5 ; SELECT 6 + ; SELECT warn('6.5') ; SELECT 7 ;
+ ^
+ eight
+-------
+ 8
+(1 row)
+
+ERROR: division by zero
+ begin
+-------
+ ok
+(1 row)
+
+Calvin
+Susie
+Hobbes
+ done
+------
+ ok
+(1 row)
+
+NOTICE: warn 1.5
+CONTEXT: PL/pgSQL function warn(text) line 2 at RAISE
+ two
+-----
+ 2
+(1 row)
+
+# initial AUTOCOMMIT: on
+# AUTOCOMMIT: off
+ s
+-------
+ hello
+ world
+(2 rows)
+
+# AUTOCOMMIT: on
+ s
+-------
+ hello
+ world
+(2 rows)
+
+# final AUTOCOMMIT: on
+# initial ON_ERROR_ROLLBACK: off
+# ON_ERROR_ROLLBACK: on
+# AUTOCOMMIT: on
+ERROR: type "no_such_type" does not exist
+LINE 1: CREATE TABLE bla(s NO_SUCH_TYPE);
+ ^
+ERROR: error oops!
+CONTEXT: PL/pgSQL function psql_error(text) line 3 at RAISE
+ s
+--------
+ Calvin
+ Hobbes
+(2 rows)
+
+ show
+--------------
+ before error
+(1 row)
+
+ERROR: error boum!
+CONTEXT: PL/pgSQL function psql_error(text) line 3 at RAISE
+ERROR: error bam!
+CONTEXT: PL/pgSQL function psql_error(text) line 3 at RAISE
+ s
+---------------
+ Calvin
+ Hobbes
+ Miss Wormwood
+ Susie
+(4 rows)
+
+# AUTOCOMMIT: off
+ERROR: error bad!
+CONTEXT: PL/pgSQL function psql_error(text) line 3 at RAISE
+ #mum
+------
+ 1
+(1 row)
+
+ERROR: error bad!
+CONTEXT: PL/pgSQL function psql_error(text) line 3 at RAISE
+ s
+---------------
+ Calvin
+ Dad
+ Hobbes
+ Miss Wormwood
+ Susie
+(5 rows)
+
+# final ON_ERROR_ROLLBACK: off
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 d4e4fdbbb7..69521dc915 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1316,3 +1316,123 @@ DROP TABLE oer_test;
\set ECHO errors
SELECT * FROM notexists;
\set ECHO none
+
+--
+-- 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;
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;
-- this implicitly commits: