1
0
mirror of https://github.com/postgres/postgres.git synced 2025-06-23 14:01:44 +03:00

psql: Show all query results by default

Previously, psql printed only the last result if a command string
returned multiple result sets.  Now it prints all of them.  The
previous behavior can be obtained by setting the psql variable
SHOW_ALL_RESULTS to off.

This is a significantly enhanced version of
3a51306722 (that was later reverted).
There is also much more test coverage for various psql features now.

Author: Fabien COELHO <coelho@cri.ensmp.fr>
Reviewed-by: Peter Eisentraut <peter.eisentraut@enterprisedb.com>
Reviewed-by: "Iwata, Aya" <iwata.aya@jp.fujitsu.com> (earlier version)
Reviewed-by: Daniel Verite <daniel@manitou-mail.org> (earlier version)
Reviewed-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com> (earlier version)
Reviewed-by: vignesh C <vignesh21@gmail.com> (earlier version)
Discussion: https://www.postgresql.org/message-id/flat/alpine.DEB.2.21.1904132231510.8961@lancre
This commit is contained in:
Peter Eisentraut
2022-04-04 14:57:17 +02:00
parent cbf4177f2c
commit 7844c9918a
14 changed files with 801 additions and 255 deletions

View File

@ -50,8 +50,28 @@ BEGIN \;
SELECT 2.0 AS "float" \; SELECT 2.0 AS "float" \;
SELECT 'world' AS "text" \; SELECT 'world' AS "text" \;
COMMIT; COMMIT;
float
-------
2.0
(1 row)
text
-------
world
(1 row)
-- compound with empty statements and spurious leading spacing -- compound with empty statements and spurious leading spacing
\;\; SELECT 3 + 3 \;\;\; SELECT ' ' || ' !' \;\; SELECT 1 + 4 \;; \;\; SELECT 3 + 3 \;\;\; SELECT ' ' || ' !' \;\; SELECT 1 + 4 \;;
?column?
----------
6
(1 row)
?column?
----------
!
(1 row)
?column? ?column?
---------- ----------
5 5
@ -61,6 +81,11 @@ COMMIT;
SELECT 1 + 1 + 1 AS "add" \gset SELECT 1 + 1 + 1 AS "add" \gset
SELECT :add + 1 + 1 AS "add" \; SELECT :add + 1 + 1 AS "add" \;
SELECT :add + 1 + 1 AS "add" \gset SELECT :add + 1 + 1 AS "add" \gset
add
-----
5
(1 row)
-- set operator -- set operator
SELECT 1 AS i UNION SELECT 2 ORDER BY i; SELECT 1 AS i UNION SELECT 2 ORDER BY i;
i i

View File

@ -127,18 +127,11 @@ echo '\x \\ SELECT * FROM foo;' | psql
commands included in the string to divide it into multiple commands included in the string to divide it into multiple
transactions. (See <xref linkend="protocol-flow-multi-statement"/> transactions. (See <xref linkend="protocol-flow-multi-statement"/>
for more details about how the server handles multi-query strings.) 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>
<para> <para>
Because of this behavior, putting more than one SQL command in a If having several commands executed in one transaction is not desired,
single <option>-c</option> string often has unexpected results. use repeated <option>-c</option> commands or feed multiple commands to
It's better to use repeated <option>-c</option> commands or feed <application>psql</application>'s standard input,
multiple commands to <application>psql</application>'s standard input,
either using <application>echo</application> as illustrated above, or either using <application>echo</application> as illustrated above, or
via a shell here-document, for example: via a shell here-document, for example:
<programlisting> <programlisting>
@ -3570,10 +3563,6 @@ select 1\; select 2\; select 3;
commands included in the string to divide it into multiple commands included in the string to divide it into multiple
transactions. (See <xref linkend="protocol-flow-multi-statement"/> transactions. (See <xref linkend="protocol-flow-multi-statement"/>
for more details about how the server handles multi-query strings.) 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> </para>
</listitem> </listitem>
</varlistentry> </varlistentry>
@ -4160,6 +4149,18 @@ bar
</varlistentry> </varlistentry>
<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> <term><varname>SHOW_CONTEXT</varname></term>
<listitem> <listitem>
<para> <para>

View File

@ -32,7 +32,8 @@
static bool DescribeQuery(const char *query, double *elapsed_msec); static bool DescribeQuery(const char *query, double *elapsed_msec);
static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec); static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
static bool ExecQueryAndProcessResult(const char *query, double *elapsed_msec, bool *svpt_gone_p); static int ExecQueryAndProcessResults(const char *query, double *elapsed_msec, bool *svpt_gone_p,
bool is_watch, const printQueryOpt *opt, FILE *printQueryFout);
static bool command_no_begin(const char *query); static bool command_no_begin(const char *query);
static bool is_select_command(const char *query); static bool is_select_command(const char *query);
@ -355,7 +356,7 @@ CheckConnection(void)
* Returns true for valid result, false for error state. * Returns true for valid result, false for error state.
*/ */
static bool static bool
AcceptResult(const PGresult *result) AcceptResult(const PGresult *result, bool show_error)
{ {
bool OK; bool OK;
@ -386,7 +387,7 @@ AcceptResult(const PGresult *result)
break; break;
} }
if (!OK) if (!OK && show_error)
{ {
const char *error = PQerrorMessage(pset.db); const char *error = PQerrorMessage(pset.db);
@ -475,6 +476,19 @@ ClearOrSaveResult(PGresult *result)
} }
/*
* Consume all results
*/
static void
ClearOrSaveAllResults(void)
{
PGresult *result;
while ((result = PQgetResult(pset.db)) != NULL)
ClearOrSaveResult(result);
}
/* /*
* Print microtiming output. Always print raw milliseconds; if the interval * Print microtiming output. Always print raw milliseconds; if the interval
* is >= 1 second, also break it down into days/hours/minutes/seconds. * is >= 1 second, also break it down into days/hours/minutes/seconds.
@ -574,7 +588,7 @@ PSQLexec(const char *query)
ResetCancelConn(); ResetCancelConn();
if (!AcceptResult(res)) if (!AcceptResult(res, true))
{ {
ClearOrSaveResult(res); ClearOrSaveResult(res);
res = NULL; res = NULL;
@ -597,11 +611,8 @@ int
PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout) PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
{ {
bool timing = pset.timing; bool timing = pset.timing;
PGresult *res;
double elapsed_msec = 0; double elapsed_msec = 0;
instr_time before; int res;
instr_time after;
FILE *fout;
if (!pset.db) if (!pset.db)
{ {
@ -611,76 +622,15 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
SetCancelConn(pset.db); SetCancelConn(pset.db);
if (timing) res = ExecQueryAndProcessResults(query, &elapsed_msec, NULL, true, opt, printQueryFout);
INSTR_TIME_SET_CURRENT(before);
res = PQexec(pset.db, query);
ResetCancelConn(); 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 */ /* Possible microtiming output */
if (timing) if (timing)
PrintTiming(elapsed_msec); PrintTiming(elapsed_msec);
return 1; return res;
} }
@ -715,7 +665,7 @@ PrintNotifications(void)
* Returns true if successful, false otherwise. * Returns true if successful, false otherwise.
*/ */
static bool static bool
PrintQueryTuples(const PGresult *result) PrintQueryTuples(const PGresult *result, const printQueryOpt *opt, FILE *printQueryFout)
{ {
bool ok = true; bool ok = true;
@ -747,8 +697,9 @@ PrintQueryTuples(const PGresult *result)
} }
else else
{ {
printQuery(result, &pset.popt, pset.queryFout, false, pset.logfile); FILE *fout = printQueryFout ? printQueryFout : pset.queryFout;
if (ferror(pset.queryFout)) printQuery(result, opt ? opt : &pset.popt, fout, false, pset.logfile);
if (ferror(fout))
{ {
pg_log_error("could not print result table: %m"); pg_log_error("could not print result table: %m");
ok = false; ok = false;
@ -1001,123 +952,27 @@ HandleCopyResult(PGresult **resultp)
return success; 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.
*/
static bool
ProcessResult(PGresult **resultp)
{
bool success = true;
bool first_cycle = true;
for (;;)
{
ExecStatusType result_status;
bool is_copy;
PGresult *next_result;
if (!AcceptResult(*resultp))
{
/*
* 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;
}
result_status = PQresultStatus(*resultp);
switch (result_status)
{
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)
success = HandleCopyResult(resultp);
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(*resultp);
*resultp = next_result;
first_cycle = false;
}
SetResultVariables(*resultp, success);
/* may need this to recover from conn loss during COPY */
if (!first_cycle && !CheckConnection())
return false;
return success;
}
/* /*
* PrintQueryStatus: report command status as required * PrintQueryStatus: report command status as required
* *
* Note: Utility function for use by PrintQueryResult() only. * Note: Utility function for use by PrintQueryResult() only.
*/ */
static void static void
PrintQueryStatus(PGresult *result) PrintQueryStatus(PGresult *result, FILE *printQueryFout)
{ {
char buf[16]; char buf[16];
FILE *fout = printQueryFout ? printQueryFout : pset.queryFout;
if (!pset.quiet) if (!pset.quiet)
{ {
if (pset.popt.topt.format == PRINT_HTML) if (pset.popt.topt.format == PRINT_HTML)
{ {
fputs("<p>", pset.queryFout); fputs("<p>", fout);
html_escaped_print(PQcmdStatus(result), pset.queryFout); html_escaped_print(PQcmdStatus(result), fout);
fputs("</p>\n", pset.queryFout); fputs("</p>\n", fout);
} }
else else
fprintf(pset.queryFout, "%s\n", PQcmdStatus(result)); fprintf(fout, "%s\n", PQcmdStatus(result));
} }
if (pset.logfile) if (pset.logfile)
@ -1136,7 +991,7 @@ PrintQueryStatus(PGresult *result)
* Returns true if the query executed successfully, false otherwise. * Returns true if the query executed successfully, false otherwise.
*/ */
static bool static bool
PrintQueryResult(PGresult *result) PrintQueryResult(PGresult *result, bool last, bool is_watch, const printQueryOpt *opt, FILE *printQueryFout)
{ {
bool success; bool success;
const char *cmdstatus; const char *cmdstatus;
@ -1148,24 +1003,32 @@ PrintQueryResult(PGresult *result)
{ {
case PGRES_TUPLES_OK: case PGRES_TUPLES_OK:
/* store or execute or print the data ... */ /* store or execute or print the data ... */
if (pset.gset_prefix) if (last && pset.gset_prefix)
success = StoreQueryTuple(result); success = StoreQueryTuple(result);
else if (pset.gexec_flag) else if (last && pset.gexec_flag)
success = ExecQueryTuples(result); success = ExecQueryTuples(result);
else if (pset.crosstab_flag) else if (last && pset.crosstab_flag)
success = PrintResultInCrosstab(result); success = PrintResultInCrosstab(result);
else if (last || pset.show_all_results)
success = PrintQueryTuples(result, opt, printQueryFout);
else else
success = PrintQueryTuples(result); success = true;
/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */ /* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
cmdstatus = PQcmdStatus(result); if (last || pset.show_all_results)
if (strncmp(cmdstatus, "INSERT", 6) == 0 || {
strncmp(cmdstatus, "UPDATE", 6) == 0 || cmdstatus = PQcmdStatus(result);
strncmp(cmdstatus, "DELETE", 6) == 0) if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
PrintQueryStatus(result); strncmp(cmdstatus, "UPDATE", 6) == 0 ||
strncmp(cmdstatus, "DELETE", 6) == 0)
PrintQueryStatus(result, printQueryFout);
}
break; break;
case PGRES_COMMAND_OK: case PGRES_COMMAND_OK:
PrintQueryStatus(result); if (last || pset.show_all_results)
PrintQueryStatus(result, printQueryFout);
success = true; success = true;
break; break;
@ -1175,7 +1038,7 @@ PrintQueryResult(PGresult *result)
case PGRES_COPY_OUT: case PGRES_COPY_OUT:
case PGRES_COPY_IN: case PGRES_COPY_IN:
/* nothing to do here */ /* nothing to do here: already processed */
success = true; success = true;
break; break;
@ -1192,11 +1055,46 @@ PrintQueryResult(PGresult *result)
break; break;
} }
fflush(pset.queryFout); fflush(printQueryFout ? printQueryFout : pset.queryFout);
return success; 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.
*/
struct t_notice_messages
{
PQExpBufferData messages[2];
int current;
};
/*
* Store notices in appropriate buffer, for later display.
*/
static void
AppendNoticeMessage(void *arg, const char *msg)
{
struct t_notice_messages *notices = arg;
appendPQExpBufferStr(&notices->messages[notices->current], msg);
}
/*
* Show notices stored in buffer, which is then reset.
*/
static void
ShowNoticeMessage(struct t_notice_messages *notices)
{
PQExpBufferData *current = &notices->messages[notices->current];
if (*current->data != '\0')
pg_log_info("%s", current->data);
resetPQExpBuffer(current);
}
/* /*
* SendQuery: send the query string to the backend * SendQuery: send the query string to the backend
@ -1273,7 +1171,6 @@ SendQuery(const char *query)
{ {
pg_log_info("%s", PQerrorMessage(pset.db)); pg_log_info("%s", PQerrorMessage(pset.db));
ClearOrSaveResult(result); ClearOrSaveResult(result);
ResetCancelConn();
goto sendquery_cleanup; goto sendquery_cleanup;
} }
ClearOrSaveResult(result); ClearOrSaveResult(result);
@ -1292,7 +1189,6 @@ SendQuery(const char *query)
{ {
pg_log_info("%s", PQerrorMessage(pset.db)); pg_log_info("%s", PQerrorMessage(pset.db));
ClearOrSaveResult(result); ClearOrSaveResult(result);
ResetCancelConn();
goto sendquery_cleanup; goto sendquery_cleanup;
} }
ClearOrSaveResult(result); ClearOrSaveResult(result);
@ -1303,19 +1199,17 @@ SendQuery(const char *query)
{ {
/* Describe query's result columns, without executing it */ /* Describe query's result columns, without executing it */
OK = DescribeQuery(query, &elapsed_msec); OK = DescribeQuery(query, &elapsed_msec);
ResetCancelConn();
} }
else if (pset.fetch_count <= 0 || pset.gexec_flag || else if (pset.fetch_count <= 0 || pset.gexec_flag ||
pset.crosstab_flag || !is_select_command(query)) pset.crosstab_flag || !is_select_command(query))
{ {
/* Default fetch-it-all-and-print mode */ /* Default fetch-it-all-and-print mode */
OK = ExecQueryAndProcessResult(query, &elapsed_msec, &svpt_gone); OK = (ExecQueryAndProcessResults(query, &elapsed_msec, &svpt_gone, false, NULL, NULL) >= 0);
} }
else else
{ {
/* Fetch-in-segments mode */ /* Fetch-in-segments mode */
OK = ExecQueryUsingCursor(query, &elapsed_msec); OK = ExecQueryUsingCursor(query, &elapsed_msec);
ResetCancelConn();
} }
if (!OK && pset.echo == PSQL_ECHO_ERRORS) if (!OK && pset.echo == PSQL_ECHO_ERRORS)
@ -1370,7 +1264,6 @@ SendQuery(const char *query)
ClearOrSaveResult(svptres); ClearOrSaveResult(svptres);
OK = false; OK = false;
ResetCancelConn();
goto sendquery_cleanup; goto sendquery_cleanup;
} }
PQclear(svptres); PQclear(svptres);
@ -1399,6 +1292,9 @@ SendQuery(const char *query)
sendquery_cleanup: sendquery_cleanup:
/* global cancellation reset */
ResetCancelConn();
/* reset \g's output-to-filename trigger */ /* reset \g's output-to-filename trigger */
if (pset.gfname) if (pset.gfname)
{ {
@ -1479,7 +1375,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
PQclear(result); PQclear(result);
result = PQdescribePrepared(pset.db, ""); result = PQdescribePrepared(pset.db, "");
OK = AcceptResult(result) && OK = AcceptResult(result, true) &&
(PQresultStatus(result) == PGRES_COMMAND_OK); (PQresultStatus(result) == PGRES_COMMAND_OK);
if (OK && result) if (OK && result)
{ {
@ -1527,7 +1423,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
PQclear(result); PQclear(result);
result = PQexec(pset.db, buf.data); result = PQexec(pset.db, buf.data);
OK = AcceptResult(result); OK = AcceptResult(result, true);
if (timing) if (timing)
{ {
@ -1537,7 +1433,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
} }
if (OK && result) if (OK && result)
OK = PrintQueryResult(result); OK = PrintQueryResult(result, true, false, NULL, NULL);
termPQExpBuffer(&buf); termPQExpBuffer(&buf);
} }
@ -1554,56 +1450,218 @@ DescribeQuery(const char *query, double *elapsed_msec)
/* /*
* ExecQueryAndProcessResults: SendQuery() subroutine for the normal way to * ExecQueryAndProcessResults: utility function for use by SendQuery()
* send a query * 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 bool static int
ExecQueryAndProcessResult(const char *query, double *elapsed_msec, bool *svpt_gone_p) ExecQueryAndProcessResults(const char *query, double *elapsed_msec, bool *svpt_gone_p,
bool is_watch, const printQueryOpt *opt, FILE *printQueryFout)
{ {
bool timing = pset.timing; bool timing = pset.timing;
bool OK; bool success;
instr_time before, instr_time before,
after; after;
PGresult *result; PGresult *result;
struct t_notice_messages notices;
if (timing) if (timing)
INSTR_TIME_SET_CURRENT(before); INSTR_TIME_SET_CURRENT(before);
result = PQexec(pset.db, query); success = PQsendQuery(pset.db, query);
/* these operations are included in the timing result: */ if (!success)
ResetCancelConn();
OK = ProcessResult(&result);
if (timing)
{ {
INSTR_TIME_SET_CURRENT(after); const char *error = PQerrorMessage(pset.db);
INSTR_TIME_SUBTRACT(after, before);
*elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
}
/* but printing result isn't: */ if (strlen(error))
if (OK && result) pg_log_info("%s", error);
OK = PrintQueryResult(result);
CheckConnection();
return -1;
}
/* /*
* Check if the user ran any command that would destroy our internal * If SIGINT is sent while the query is processing, the interrupt will be
* savepoint: If the user did COMMIT AND CHAIN, RELEASE or ROLLBACK, our * consumed. The user's intention, though, is to cancel the entire watch
* savepoint is gone. If they issued a SAVEPOINT, releasing ours would * process, so detect a sent cancellation request and exit in this case.
* remove theirs.
*/ */
if (result && svpt_gone_p) if (is_watch && cancel_pressed)
{ {
const char *cmd = PQcmdStatus(result); ClearOrSaveAllResults();
*svpt_gone_p = (strcmp(cmd, "COMMIT") == 0 || return 0;
strcmp(cmd, "SAVEPOINT") == 0 ||
strcmp(cmd, "RELEASE") == 0 ||
strcmp(cmd, "ROLLBACK") == 0);
} }
ClearOrSaveResult(result); /* intercept notices */
notices.current = 0;
initPQExpBuffer(&notices.messages[0]);
initPQExpBuffer(&notices.messages[1]);
PQsetNoticeProcessor(pset.db, AppendNoticeMessage, &notices);
return OK; /* 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 = PQresultErrorMessage(result);
ShowNoticeMessage(&notices);
if (strlen(error))
pg_log_info("%s", error);
CheckConnection();
if (!is_watch)
SetResultVariables(result, false);
/* keep the result status before clearing it */
result_status = PQresultStatus(result);
ClearOrSaveResult(result);
success = false;
/*
* switch to next result
*/
if (result_status == PGRES_COPY_BOTH ||
result_status == PGRES_COPY_OUT ||
result_status == PGRES_COPY_IN)
/*
* 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.
*/
result = NULL;
else
result = PQgetResult(pset.db);
continue;
}
else if (svpt_gone_p && !*svpt_gone_p)
{
/*
* Check if the user ran any command that would destroy our internal
* savepoint: If the user did COMMIT AND CHAIN, RELEASE or ROLLBACK, our
* savepoint is gone. If they issued a SAVEPOINT, releasing ours would
* remove theirs.
*/
const char *cmd = PQcmdStatus(result);
*svpt_gone_p = (strcmp(cmd, "COMMIT") == 0 ||
strcmp(cmd, "SAVEPOINT") == 0 ||
strcmp(cmd, "RELEASE") == 0 ||
strcmp(cmd, "ROLLBACK") == 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(&notices);
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, &notices);
}
/*
* 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.
*/
notices.current ^= 1;
next_result = PQgetResult(pset.db);
notices.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_SET_CURRENT(after);
INSTR_TIME_SUBTRACT(after, before);
*elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
}
/* notices already shown above for copy */
ShowNoticeMessage(&notices);
/* this may or may not print something depending on settings */
if (result != NULL)
success &= PrintQueryResult(result, last, false, opt, printQueryFout);
/* set variables on last result if all went well */
if (!is_watch && last && success)
SetResultVariables(result, true);
ClearOrSaveResult(result);
notices.current ^= 1;
result = next_result;
if (cancel_pressed)
{
ClearOrSaveAllResults();
break;
}
}
/* reset notice hook */
PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
termPQExpBuffer(&notices.messages[0]);
termPQExpBuffer(&notices.messages[1]);
/* may need this to recover from conn loss during COPY */
if (!CheckConnection())
return -1;
return cancel_pressed ? 0 : success ? 1 : -1;
} }
@ -1651,7 +1709,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
if (PQtransactionStatus(pset.db) == PQTRANS_IDLE) if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
{ {
result = PQexec(pset.db, "BEGIN"); result = PQexec(pset.db, "BEGIN");
OK = AcceptResult(result) && OK = AcceptResult(result, true) &&
(PQresultStatus(result) == PGRES_COMMAND_OK); (PQresultStatus(result) == PGRES_COMMAND_OK);
ClearOrSaveResult(result); ClearOrSaveResult(result);
if (!OK) if (!OK)
@ -1665,7 +1723,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
query); query);
result = PQexec(pset.db, buf.data); result = PQexec(pset.db, buf.data);
OK = AcceptResult(result) && OK = AcceptResult(result, true) &&
(PQresultStatus(result) == PGRES_COMMAND_OK); (PQresultStatus(result) == PGRES_COMMAND_OK);
if (!OK) if (!OK)
SetResultVariables(result, OK); SetResultVariables(result, OK);
@ -1738,7 +1796,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
is_pager = false; is_pager = false;
} }
OK = AcceptResult(result); OK = AcceptResult(result, true);
Assert(!OK); Assert(!OK);
SetResultVariables(result, OK); SetResultVariables(result, OK);
ClearOrSaveResult(result); ClearOrSaveResult(result);
@ -1847,7 +1905,7 @@ cleanup:
result = PQexec(pset.db, "CLOSE _psql_cursor"); result = PQexec(pset.db, "CLOSE _psql_cursor");
if (OK) if (OK)
{ {
OK = AcceptResult(result) && OK = AcceptResult(result, true) &&
(PQresultStatus(result) == PGRES_COMMAND_OK); (PQresultStatus(result) == PGRES_COMMAND_OK);
ClearOrSaveResult(result); ClearOrSaveResult(result);
} }
@ -1857,7 +1915,7 @@ cleanup:
if (started_txn) if (started_txn)
{ {
result = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK"); result = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
OK &= AcceptResult(result) && OK &= AcceptResult(result, true) &&
(PQresultStatus(result) == PGRES_COMMAND_OK); (PQresultStatus(result) == PGRES_COMMAND_OK);
ClearOrSaveResult(result); ClearOrSaveResult(result);
} }

View File

@ -413,6 +413,8 @@ helpVariables(unsigned short int pager)
fprintf(output, _(" SERVER_VERSION_NAME\n" fprintf(output, _(" SERVER_VERSION_NAME\n"
" SERVER_VERSION_NUM\n" " SERVER_VERSION_NUM\n"
" server's version (in short string or numeric format)\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" fprintf(output, _(" SHOW_CONTEXT\n"
" controls display of message context fields [never, errors, always]\n")); " controls display of message context fields [never, errors, always]\n"));
fprintf(output, _(" SINGLELINE\n" fprintf(output, _(" SINGLELINE\n"

View File

@ -148,6 +148,7 @@ typedef struct _psqlSettings
const char *prompt2; const char *prompt2;
const char *prompt3; const char *prompt3;
PGVerbosity verbosity; /* current error verbosity level */ PGVerbosity verbosity; /* current error verbosity level */
bool show_all_results;
PGContextVisibility show_context; /* current context display level */ PGContextVisibility show_context; /* current context display level */
} PsqlSettings; } PsqlSettings;

View File

@ -203,6 +203,7 @@ main(int argc, char *argv[])
SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1); SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1);
SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2); SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2);
SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3); SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3);
SetVariableBool(pset.vars, "SHOW_ALL_RESULTS");
parse_psql_options(argc, argv, &options); parse_psql_options(argc, argv, &options);
@ -1150,6 +1151,12 @@ verbosity_hook(const char *newval)
return true; return true;
} }
static bool
show_all_results_hook(const char *newval)
{
return ParseVariableBool(newval, "SHOW_ALL_RESULTS", &pset.show_all_results);
}
static char * static char *
show_context_substitute_hook(char *newval) show_context_substitute_hook(char *newval)
{ {
@ -1251,6 +1258,9 @@ EstablishVariableSpace(void)
SetVariableHooks(pset.vars, "VERBOSITY", SetVariableHooks(pset.vars, "VERBOSITY",
verbosity_substitute_hook, verbosity_substitute_hook,
verbosity_hook); verbosity_hook);
SetVariableHooks(pset.vars, "SHOW_ALL_RESULTS",
bool_substitute_hook,
show_all_results_hook);
SetVariableHooks(pset.vars, "SHOW_CONTEXT", SetVariableHooks(pset.vars, "SHOW_CONTEXT",
show_context_substitute_hook, show_context_substitute_hook,
show_context_hook); show_context_hook);

View File

@ -126,7 +126,7 @@ is($ret, 2, 'server crash: psql exit code');
like($out, qr/before/, 'server crash: output before crash'); like($out, qr/before/, 'server crash: output before crash');
ok($out !~ qr/AFTER/, 'server crash: no output after crash'); ok($out !~ qr/AFTER/, 'server crash: no output after crash');
is($err, 'psql:<stdin>:2: FATAL: terminating connection due to administrator command 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 This probably means the server terminated abnormally
before or while processing the request. before or while processing the request.
psql:<stdin>:2: fatal: connection to server was lost', psql:<stdin>:2: fatal: connection to server was lost',

View File

@ -4628,7 +4628,7 @@ psql_completion(const char *text, int start, int end)
matches = complete_from_variables(text, "", "", false); matches = complete_from_variables(text, "", "", false);
else if (TailMatchesCS("\\set", MatchAny)) else if (TailMatchesCS("\\set", MatchAny))
{ {
if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|" if (TailMatchesCS("AUTOCOMMIT|ON_ERROR_STOP|QUIET|SHOW_ALL_RESULTS|"
"SINGLELINE|SINGLESTEP")) "SINGLELINE|SINGLESTEP"))
COMPLETE_WITH_CS("on", "off"); COMPLETE_WITH_CS("on", "off");
else if (TailMatchesCS("COMP_KEYWORD_CASE")) else if (TailMatchesCS("COMP_KEYWORD_CASE"))

View File

@ -126,7 +126,7 @@ copy (select 1) to stdout\; select 1/0; -- row, then error
ERROR: division by zero ERROR: division by zero
select 1/0\; copy (select 1) to stdout; -- error only select 1/0\; copy (select 1) to stdout; -- error only
ERROR: division by zero 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 1
2 2
?column? ?column?
@ -134,8 +134,18 @@ copy (select 1) to stdout\; copy (select 2) to stdout\; select 0\; select 3; --
3 3
(1 row) (1 row)
?column?
----------
4
(1 row)
create table test3 (c int); 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? ?column?
---------- ----------
1 1

View File

@ -5290,3 +5290,245 @@ ERROR: relation "notexists" does not exist
LINE 1: SELECT * FROM notexists; LINE 1: SELECT * FROM notexists;
^ ^
STATEMENT: SELECT * FROM notexists; STATEMENT: SELECT * FROM notexists;
--
-- 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;
-- 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" ;
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 and combined queries
--
\set AUTOCOMMIT off
\echo '# AUTOCOMMIT:' :AUTOCOMMIT
# AUTOCOMMIT: off
-- 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;
s
-------
hello
world
(2 rows)
\set AUTOCOMMIT on
\echo '# AUTOCOMMIT:' :AUTOCOMMIT
# AUTOCOMMIT: on
-- 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;
s
-------
hello
world
(2 rows)
--
-- 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
# 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
-- 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
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 COUNT(*) AS "#mum"
FROM bla WHERE s = 'Mum' \; -- no mum here
SELECT * FROM bla ORDER BY 1;
#mum
------
0
(1 row)
s
---------------
Calvin
Dad
Hobbes
Miss Wormwood
Susie
(5 rows)
-- reset all
\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;

View File

@ -904,8 +904,18 @@ DROP TABLE abc;
-- tests rely on the fact that psql will not break SQL commands apart at a -- 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. -- backslash-quoted semicolon, but will send them as one Query.
create temp table i_table (f1 int); 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; SELECT 1\; SELECT 2\; SELECT 3;
?column?
----------
1
(1 row)
?column?
----------
2
(1 row)
?column? ?column?
---------- ----------
3 3
@ -920,6 +930,12 @@ insert into i_table values(1)\; select * from i_table;
-- 1/0 error will cause rolling back the whole implicit transaction -- 1/0 error will cause rolling back the whole implicit transaction
insert into i_table values(2)\; select * from i_table\; select 1/0; insert into i_table values(2)\; select * from i_table\; select 1/0;
f1
----
1
2
(2 rows)
ERROR: division by zero ERROR: division by zero
select * from i_table; select * from i_table;
f1 f1
@ -939,8 +955,18 @@ WARNING: there is no transaction in progress
-- begin converts implicit transaction into a regular one that -- begin converts implicit transaction into a regular one that
-- can extend past the end of the Query -- can extend past the end of the Query
select 1\; begin\; insert into i_table values(5); select 1\; begin\; insert into i_table values(5);
?column?
----------
1
(1 row)
commit; commit;
select 1\; begin\; insert into i_table values(6); select 1\; begin\; insert into i_table values(6);
?column?
----------
1
(1 row)
rollback; rollback;
-- commit in implicit-transaction state commits but issues a warning. -- 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; insert into i_table values(7)\; commit\; insert into i_table values(8)\; select 1/0;
@ -967,22 +993,52 @@ rollback; -- we are not in a transaction at this point
WARNING: there is no transaction in progress WARNING: there is no transaction in progress
-- implicit transaction block is still a transaction block, for e.g. VACUUM -- implicit transaction block is still a transaction block, for e.g. VACUUM
SELECT 1\; VACUUM; SELECT 1\; VACUUM;
?column?
----------
1
(1 row)
ERROR: VACUUM cannot run inside a transaction block ERROR: VACUUM cannot run inside a transaction block
SELECT 1\; COMMIT\; VACUUM; SELECT 1\; COMMIT\; VACUUM;
WARNING: there is no transaction in progress WARNING: there is no transaction in progress
?column?
----------
1
(1 row)
ERROR: VACUUM cannot run inside a transaction block ERROR: VACUUM cannot run inside a transaction block
-- we disallow savepoint-related commands in implicit-transaction state -- we disallow savepoint-related commands in implicit-transaction state
SELECT 1\; SAVEPOINT sp; SELECT 1\; SAVEPOINT sp;
?column?
----------
1
(1 row)
ERROR: SAVEPOINT can only be used in transaction blocks ERROR: SAVEPOINT can only be used in transaction blocks
SELECT 1\; COMMIT\; SAVEPOINT sp; SELECT 1\; COMMIT\; SAVEPOINT sp;
WARNING: there is no transaction in progress WARNING: there is no transaction in progress
?column?
----------
1
(1 row)
ERROR: SAVEPOINT can only be used in transaction blocks ERROR: SAVEPOINT can only be used in transaction blocks
ROLLBACK TO SAVEPOINT sp\; SELECT 2; ROLLBACK TO SAVEPOINT sp\; SELECT 2;
ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks ERROR: ROLLBACK TO SAVEPOINT can only be used in transaction blocks
SELECT 2\; RELEASE SAVEPOINT sp\; SELECT 3; SELECT 2\; RELEASE SAVEPOINT sp\; SELECT 3;
?column?
----------
2
(1 row)
ERROR: RELEASE SAVEPOINT can only be used in transaction blocks ERROR: RELEASE SAVEPOINT can only be used in transaction blocks
-- but this is OK, because the BEGIN converts it to a regular xact -- but this is OK, because the BEGIN converts it to a regular xact
SELECT 1\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT; SELECT 1\; BEGIN\; SAVEPOINT sp\; ROLLBACK TO SAVEPOINT sp\; COMMIT;
?column?
----------
1
(1 row)
-- Tests for AND CHAIN in implicit transaction blocks -- Tests for AND CHAIN in implicit transaction blocks
SET TRANSACTION READ ONLY\; COMMIT AND CHAIN; -- error SET TRANSACTION READ ONLY\; COMMIT AND CHAIN; -- error
ERROR: COMMIT AND CHAIN can only be used in transaction blocks ERROR: COMMIT AND CHAIN can only be used in transaction blocks

View File

@ -84,10 +84,10 @@ drop table test1;
-- psql handling of COPY in multi-command strings -- psql handling of COPY in multi-command strings
copy (select 1) to stdout\; select 1/0; -- row, then error copy (select 1) to stdout\; select 1/0; -- row, then error
select 1/0\; copy (select 1) to stdout; -- error only 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); 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 1
\. \.
2 2

View File

@ -1316,3 +1316,144 @@ DROP TABLE oer_test;
\set ECHO errors \set ECHO errors
SELECT * FROM notexists; SELECT * FROM notexists;
\set ECHO all \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;

View File

@ -506,7 +506,7 @@ DROP TABLE abc;
create temp table i_table (f1 int); 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; SELECT 1\; SELECT 2\; SELECT 3;
-- this implicitly commits: -- this implicitly commits: