Commit 3a513067 authored by Peter Eisentraut's avatar Peter Eisentraut

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.

Author: Fabien COELHO <coelho@cri.ensmp.fr>
Reviewed-by: default avatar"Iwata, Aya" <iwata.aya@jp.fujitsu.com>
Reviewed-by: default avatarDaniel Verite <daniel@manitou-mail.org>
Reviewed-by: default avatarPeter Eisentraut <peter.eisentraut@2ndquadrant.com>
Reviewed-by: default avatarKyotaro Horiguchi <horikyota.ntt@gmail.com>
Reviewed-by: default avatarvignesh C <vignesh21@gmail.com>
Discussion: https://www.postgresql.org/message-id/flat/alpine.DEB.2.21.1904132231510.8961@lancre
parent 518442c7
...@@ -50,8 +50,28 @@ BEGIN \; ...@@ -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; ...@@ -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
......
...@@ -127,18 +127,11 @@ echo '\x \\ SELECT * FROM foo;' | psql ...@@ -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>
...@@ -3527,10 +3520,6 @@ select 1\; select 2\; select 3; ...@@ -3527,10 +3520,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>
...@@ -4117,6 +4106,18 @@ bar ...@@ -4117,6 +4106,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>
......
...@@ -33,6 +33,7 @@ static bool DescribeQuery(const char *query, double *elapsed_msec); ...@@ -33,6 +33,7 @@ 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 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);
static int SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_watch);
/* /*
...@@ -353,7 +354,7 @@ CheckConnection(void) ...@@ -353,7 +354,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;
...@@ -384,7 +385,7 @@ AcceptResult(const PGresult *result) ...@@ -384,7 +385,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);
...@@ -472,6 +473,18 @@ ClearOrSaveResult(PGresult *result) ...@@ -472,6 +473,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 * Print microtiming output. Always print raw milliseconds; if the interval
...@@ -572,7 +585,7 @@ PSQLexec(const char *query) ...@@ -572,7 +585,7 @@ PSQLexec(const char *query)
ResetCancelConn(); ResetCancelConn();
if (!AcceptResult(res)) if (!AcceptResult(res, true))
{ {
ClearOrSaveResult(res); ClearOrSaveResult(res);
res = NULL; res = NULL;
...@@ -594,10 +607,8 @@ PSQLexec(const char *query) ...@@ -594,10 +607,8 @@ PSQLexec(const char *query)
int int
PSQLexecWatch(const char *query, const printQueryOpt *opt) PSQLexecWatch(const char *query, const printQueryOpt *opt)
{ {
PGresult *res;
double elapsed_msec = 0; double elapsed_msec = 0;
instr_time before; int res;
instr_time after;
if (!pset.db) if (!pset.db)
{ {
...@@ -606,75 +617,16 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt) ...@@ -606,75 +617,16 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
} }
SetCancelConn(pset.db); SetCancelConn(pset.db);
res = SendQueryAndProcessResults(query, &elapsed_msec, true);
if (pset.timing)
INSTR_TIME_SET_CURRENT(before);
res = PQexec(pset.db, query);
ResetCancelConn(); ResetCancelConn();
if (!AcceptResult(res))
{
ClearOrSaveResult(res);
return 0;
}
if (pset.timing)
{
INSTR_TIME_SET_CURRENT(after);
INSTR_TIME_SUBTRACT(after, before);
elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
}
/*
* If SIGINT is sent while the query is processing, the interrupt will be
* consumed. The user's intention, though, is to cancel the entire watch
* process, so detect a sent cancellation request and exit in this case.
*/
if (cancel_pressed)
{
PQclear(res);
return 0;
}
switch (PQresultStatus(res))
{
case PGRES_TUPLES_OK:
printQuery(res, opt, pset.queryFout, false, pset.logfile);
break;
case PGRES_COMMAND_OK:
fprintf(pset.queryFout, "%s\n%s\n\n", opt->title, PQcmdStatus(res));
break;
case PGRES_EMPTY_QUERY:
pg_log_error("\\watch cannot be used with an empty query");
PQclear(res);
return -1;
case PGRES_COPY_OUT:
case PGRES_COPY_IN:
case PGRES_COPY_BOTH:
pg_log_error("\\watch cannot be used with COPY");
PQclear(res);
return -1;
default:
pg_log_error("unexpected result status for \\watch");
PQclear(res);
return -1;
}
PQclear(res);
fflush(pset.queryFout); fflush(pset.queryFout);
/* Possible microtiming output */ /* Possible microtiming output */
if (pset.timing) if (pset.timing)
PrintTiming(elapsed_msec); PrintTiming(elapsed_msec);
return 1; return res;
} }
...@@ -887,197 +839,114 @@ loop_exit: ...@@ -887,197 +839,114 @@ loop_exit:
/* /*
* ProcessResult: utility function for use by SendQuery() only * Marshal the COPY data. Either subroutine will get the
* * connection out of its COPY state, then call PQresultStatus()
* When our command string contained a COPY FROM STDIN or COPY TO STDOUT, * once and report any error. Return whether all was ok.
* 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 * For COPY OUT, direct the output to pset.copyStream if it's set,
* degenerates to an AcceptResult() call. * 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.
* *
* Changes its argument to point to the last PGresult of the command string, * Update result if further processing is necessary, or NULL otherwise.
* or NULL if that result was for a COPY TO STDOUT. (Returning NULL prevents * Return a result when queryFout can safely output a result status:
* the command status from being printed, which we want in that case so that * on COPY IN, or on COPY OUT if written to something other than pset.queryFout.
* the status line doesn't get taken as part of the COPY data.) * 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.
* 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 static bool
ProcessResult(PGresult **results) HandleCopyResult(PGresult **result)
{ {
bool success = true; bool success;
bool first_cycle = true; FILE *copystream;
PGresult *copy_result;
ExecStatusType result_status = PQresultStatus(*result);
for (;;) Assert(result_status == PGRES_COPY_OUT ||
result_status == PGRES_COPY_IN);
SetCancelConn(pset.db);
if (result_status == PGRES_COPY_OUT)
{ {
ExecStatusType result_status; bool need_close = false;
bool is_copy; bool is_pipe = false;
PGresult *next_result;
if (!AcceptResult(*results)) if (pset.copyStream)
{ {
/* /* invoked by \copy */
* Failure at this point is always a server-side failure or a copystream = pset.copyStream;
* failure to submit the command string. Either way, we're
* finished with this command string.
*/
success = false;
break;
} }
else if (pset.gfname)
result_status = PQresultStatus(*results);
switch (result_status)
{ {
case PGRES_EMPTY_QUERY: /* invoked by \g */
case PGRES_COMMAND_OK: if (openQueryOutputFile(pset.gfname,
case PGRES_TUPLES_OK: &copystream, &is_pipe))
is_copy = false; {
break; 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;
}
case PGRES_COPY_OUT: success = handleCopyOut(pset.db,
case PGRES_COPY_IN: copystream,
is_copy = true; &copy_result)
break; && (copystream != NULL);
default: /*
/* AcceptResult() should have caught anything else. */ * Suppress status printing if the report would go to the same
is_copy = false; * place as the COPY data just went. Note this doesn't
pg_log_error("unexpected PQresultStatus: %d", result_status); * prevent error reporting, since handleCopyOut did that.
break; */
if (copystream == pset.queryFout)
{
PQclear(copy_result);
copy_result = NULL;
} }
if (is_copy) if (need_close)
{ {
/* /* close \g argument file/pipe */
* Marshal the COPY data. Either subroutine will get the if (is_pipe)
* 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)
{ {
bool need_close = false; pclose(copystream);
bool is_pipe = false; restore_sigpipe_trap();
if (pset.copyStream)
{
/* invoked by \copy */
copystream = pset.copyStream;
}
else if (pset.gfname)
{
/* invoked by \g */
if (openQueryOutputFile(pset.gfname,
&copystream, &is_pipe))
{
need_close = true;
if (is_pipe)
disable_sigpipe_trap();
}
else
copystream = NULL; /* discard COPY data entirely */
}
else
{
/* fall back to the generic query output stream */
copystream = pset.queryFout;
}
success = handleCopyOut(pset.db,
copystream,
&copy_result)
&& success
&& (copystream != NULL);
/*
* Suppress status printing if the report would go to the same
* place as the COPY data just went. Note this doesn't
* prevent error reporting, since handleCopyOut did that.
*/
if (copystream == pset.queryFout)
{
PQclear(copy_result);
copy_result = NULL;
}
if (need_close)
{
/* close \g argument file/pipe */
if (is_pipe)
{
pclose(copystream);
restore_sigpipe_trap();
}
else
{
fclose(copystream);
}
}
} }
else else
{ {
/* COPY IN */ fclose(copystream);
copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
success = handleCopyIn(pset.db,
copystream,
PQbinaryTuples(*results),
&copy_result) && success;
} }
ResetCancelConn();
/*
* Replace the PGRES_COPY_OUT/IN result with COPY command's exit
* status, or with NULL if we want to suppress printing anything.
*/
PQclear(*results);
*results = copy_result;
}
else if (first_cycle)
{
/* fast path: no COPY commands; PQexec visited all results */
break;
} }
}
/* else
* Check PQgetResult() again. In the typical case of a single-command {
* string, it will return NULL. Otherwise, we'll have other results /* COPY IN */
* to process that may include other COPYs. We keep the last result. copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
*/ success = handleCopyIn(pset.db,
next_result = PQgetResult(pset.db); copystream,
if (!next_result) PQbinaryTuples(*result),
break; &copy_result);
PQclear(*results);
*results = next_result;
first_cycle = false;
} }
SetResultVariables(*results, success); ResetCancelConn();
PQclear(*result);
/* may need this to recover from conn loss during COPY */ *result = copy_result;
if (!first_cycle && !CheckConnection())
return false;
return success; return success;
} }
/* /*
* PrintQueryStatus: report command status as required * PrintQueryStatus: report command status as required
* *
* Note: Utility function for use by PrintQueryResults() only. * Note: Utility function for use by HandleQueryResult() only.
*/ */
static void static void
PrintQueryStatus(PGresult *results) PrintQueryStatus(PGresult *results)
...@@ -1105,43 +974,50 @@ PrintQueryStatus(PGresult *results) ...@@ -1105,43 +974,50 @@ PrintQueryStatus(PGresult *results)
/* /*
* PrintQueryResults: print out (or store or execute) query results as required * HandleQueryResult: print out, store or execute one query result
* * as required.
* Note: Utility function for use by SendQuery() only.
* *
* Returns true if the query executed successfully, false otherwise. * Returns true if the query executed successfully, false otherwise.
*/ */
static bool static bool
PrintQueryResults(PGresult *results) HandleQueryResult(PGresult *result, bool last)
{ {
bool success; bool success;
const char *cmdstatus; const char *cmdstatus;
if (!results) if (result == NULL)
return false; return false;
switch (PQresultStatus(results)) switch (PQresultStatus(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(results); success = StoreQueryTuple(result);
else if (pset.gexec_flag) else if (last && pset.gexec_flag)
success = ExecQueryTuples(results); success = ExecQueryTuples(result);
else if (pset.crosstab_flag) else if (last && pset.crosstab_flag)
success = PrintResultsInCrosstab(results); success = PrintResultsInCrosstab(result);
else if (last || pset.show_all_results)
success = PrintQueryTuples(result);
else else
success = PrintQueryTuples(results); success = true;
/* if it's INSERT/UPDATE/DELETE RETURNING, also print status */ /* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
cmdstatus = PQcmdStatus(results); 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(results); strncmp(cmdstatus, "UPDATE", 6) == 0 ||
strncmp(cmdstatus, "DELETE", 6) == 0)
PrintQueryStatus(result);
}
break; break;
case PGRES_COMMAND_OK: case PGRES_COMMAND_OK:
PrintQueryStatus(results); if (last || pset.show_all_results)
PrintQueryStatus(result);
success = true; success = true;
break; break;
...@@ -1151,7 +1027,7 @@ PrintQueryResults(PGresult *results) ...@@ -1151,7 +1027,7 @@ PrintQueryResults(PGresult *results)
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;
...@@ -1164,7 +1040,7 @@ PrintQueryResults(PGresult *results) ...@@ -1164,7 +1040,7 @@ PrintQueryResults(PGresult *results)
default: default:
success = false; success = false;
pg_log_error("unexpected PQresultStatus: %d", pg_log_error("unexpected PQresultStatus: %d",
PQresultStatus(results)); PQresultStatus(result));
break; break;
} }
...@@ -1173,6 +1049,217 @@ PrintQueryResults(PGresult *results) ...@@ -1173,6 +1049,217 @@ PrintQueryResults(PGresult *results)
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.
*/
typedef struct {
bool in_flip;
PQExpBufferData flip;
PQExpBufferData flop;
} t_notice_messages;
/*
* Store notices in appropriate buffer, for later display.
*/
static void
AppendNoticeMessage(void *arg, const char *msg)
{
t_notice_messages *notes = (t_notice_messages*) arg;
appendPQExpBufferStr(notes->in_flip ? &notes->flip : &notes->flop, msg);
}
/*
* Show notices stored in buffer, which is then reset.
*/
static void
ShowNoticeMessage(t_notice_messages *notes)
{
PQExpBufferData *current = notes->in_flip ? &notes->flip : &notes->flop;
if (current->data != NULL && *current->data != '\0')
pg_log_info("%s", current->data);
resetPQExpBuffer(current);
}
/*
* SendQueryAndProcessResults: utility function for use by SendQuery()
* and PSQLexecWatch().
*
* Sends query and cycles through PGresult objects.
*
* When not under \watch and if our command string contained a COPY FROM STDIN
* or COPY TO STDOUT, the PGresult associated with these commands must be
* processed by providing an input or output stream. In that event, we'll
* marshal data for the COPY.
*
* For other commands, the results are processed normally, depending on their
* status.
*
* Returns 1 on complete success, 0 on interrupt and -1 or errors. Possible
* failure modes include purely client-side problems; check the transaction
* status for the server-side opinion.
*
* Note that on a combined query, failure does not mean that nothing was
* committed.
*/
static int
SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_watch)
{
bool success;
instr_time before;
PGresult *result;
t_notice_messages notes;
if (pset.timing)
INSTR_TIME_SET_CURRENT(before);
success = PQsendQuery(pset.db, query);
ResetCancelConn();
if (!success)
{
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.in_flip = true;
initPQExpBuffer(&notes.flip);
initPQExpBuffer(&notes.flop);
PQsetNoticeProcessor(pset.db, AppendNoticeMessage, &notes);
/* first result */
result = PQgetResult(pset.db);
while (result != NULL)
{
ExecStatusType result_status;
PGresult *next_result;
bool last;
if (!AcceptResult(result, false))
{
/*
* Some error occured, either a server-side failure or
* a failure to submit the command string. Record that.
*/
const char *error = PQerrorMessage(pset.db);
ShowNoticeMessage(&notes);
if (strlen(error))
pg_log_info("%s", error);
CheckConnection();
if (!is_watch)
SetResultVariables(result, false);
ClearOrSaveResult(result);
success = false;
/* and switch to next result */
result = PQgetResult(pset.db);
continue;
}
/* must handle COPY before changing the current result */
result_status = PQresultStatus(result);
Assert(result_status != PGRES_COPY_BOTH);
if (result_status == PGRES_COPY_IN ||
result_status == PGRES_COPY_OUT)
{
ShowNoticeMessage(&notes);
if (is_watch)
{
ClearOrSaveAllResults();
pg_log_error("\\watch cannot be used with COPY");
return -1;
}
/* use normal notice processor during COPY */
PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
success &= HandleCopyResult(&result);
PQsetNoticeProcessor(pset.db, AppendNoticeMessage, &notes);
}
/*
* Check PQgetResult() again. In the typical case of a single-command
* string, it will return NULL. Otherwise, we'll have other results
* to process.
*/
notes.in_flip = !notes.in_flip;
next_result = PQgetResult(pset.db);
notes.in_flip = !notes.in_flip;
last = (next_result == NULL);
/*
* Get timing measure before printing the last result.
*
* It will include the display of previous results, if any.
* This cannot be helped because the server goes on processing
* further queries anyway while the previous ones are being displayed.
* The parallel execution of the client display hides the server time
* when it is shorter.
*
* With combined queries, timing must be understood as an upper bound
* of the time spent processing them.
*/
if (last && pset.timing)
{
instr_time now;
INSTR_TIME_SET_CURRENT(now);
INSTR_TIME_SUBTRACT(now, before);
*pelapsed_msec = INSTR_TIME_GET_MILLISEC(now);
}
/* notices already shown above for copy */
ShowNoticeMessage(&notes);
/* this may or may not print something depending on settings */
if (result != NULL)
success &= HandleQueryResult(result, last);
/* set variables on last result if all went well */
if (!is_watch && last && success)
SetResultVariables(result, true);
ClearOrSaveResult(result);
notes.in_flip = !notes.in_flip;
result = next_result;
}
/* reset notice hook */
PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
termPQExpBuffer(&notes.flip);
termPQExpBuffer(&notes.flop);
/* may need this to recover from conn loss during COPY */
if (!CheckConnection())
return -1;
return success ? 1 : -1;
}
/* /*
* SendQuery: send the query string to the backend * SendQuery: send the query string to the backend
...@@ -1294,28 +1381,9 @@ SendQuery(const char *query) ...@@ -1294,28 +1381,9 @@ SendQuery(const char *query)
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 */
instr_time before, int res = SendQueryAndProcessResults(query, &elapsed_msec, false);
after; OK = (res >= 0);
results = NULL;
if (pset.timing)
INSTR_TIME_SET_CURRENT(before);
results = PQexec(pset.db, query);
/* these operations are included in the timing result: */
ResetCancelConn();
OK = ProcessResult(&results);
if (pset.timing)
{
INSTR_TIME_SET_CURRENT(after);
INSTR_TIME_SUBTRACT(after, before);
elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
}
/* but printing results isn't: */
if (OK && results)
OK = PrintQueryResults(results);
} }
else else
{ {
...@@ -1497,7 +1565,7 @@ DescribeQuery(const char *query, double *elapsed_msec) ...@@ -1497,7 +1565,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
PQclear(results); PQclear(results);
results = PQdescribePrepared(pset.db, ""); results = PQdescribePrepared(pset.db, "");
OK = AcceptResult(results) && OK = AcceptResult(results, true) &&
(PQresultStatus(results) == PGRES_COMMAND_OK); (PQresultStatus(results) == PGRES_COMMAND_OK);
if (OK && results) if (OK && results)
{ {
...@@ -1545,7 +1613,7 @@ DescribeQuery(const char *query, double *elapsed_msec) ...@@ -1545,7 +1613,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
PQclear(results); PQclear(results);
results = PQexec(pset.db, buf.data); results = PQexec(pset.db, buf.data);
OK = AcceptResult(results); OK = AcceptResult(results, true);
if (pset.timing) if (pset.timing)
{ {
...@@ -1555,7 +1623,7 @@ DescribeQuery(const char *query, double *elapsed_msec) ...@@ -1555,7 +1623,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
} }
if (OK && results) if (OK && results)
OK = PrintQueryResults(results); OK = HandleQueryResult(results, true);
termPQExpBuffer(&buf); termPQExpBuffer(&buf);
} }
...@@ -1614,7 +1682,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) ...@@ -1614,7 +1682,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
if (PQtransactionStatus(pset.db) == PQTRANS_IDLE) if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
{ {
results = PQexec(pset.db, "BEGIN"); results = PQexec(pset.db, "BEGIN");
OK = AcceptResult(results) && OK = AcceptResult(results, true) &&
(PQresultStatus(results) == PGRES_COMMAND_OK); (PQresultStatus(results) == PGRES_COMMAND_OK);
ClearOrSaveResult(results); ClearOrSaveResult(results);
if (!OK) if (!OK)
...@@ -1628,7 +1696,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) ...@@ -1628,7 +1696,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
query); query);
results = PQexec(pset.db, buf.data); results = PQexec(pset.db, buf.data);
OK = AcceptResult(results) && OK = AcceptResult(results, true) &&
(PQresultStatus(results) == PGRES_COMMAND_OK); (PQresultStatus(results) == PGRES_COMMAND_OK);
if (!OK) if (!OK)
SetResultVariables(results, OK); SetResultVariables(results, OK);
...@@ -1701,7 +1769,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) ...@@ -1701,7 +1769,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
is_pager = false; is_pager = false;
} }
OK = AcceptResult(results); OK = AcceptResult(results, true);
Assert(!OK); Assert(!OK);
SetResultVariables(results, OK); SetResultVariables(results, OK);
ClearOrSaveResult(results); ClearOrSaveResult(results);
...@@ -1810,7 +1878,7 @@ cleanup: ...@@ -1810,7 +1878,7 @@ cleanup:
results = PQexec(pset.db, "CLOSE _psql_cursor"); results = PQexec(pset.db, "CLOSE _psql_cursor");
if (OK) if (OK)
{ {
OK = AcceptResult(results) && OK = AcceptResult(results, true) &&
(PQresultStatus(results) == PGRES_COMMAND_OK); (PQresultStatus(results) == PGRES_COMMAND_OK);
ClearOrSaveResult(results); ClearOrSaveResult(results);
} }
...@@ -1820,7 +1888,7 @@ cleanup: ...@@ -1820,7 +1888,7 @@ cleanup:
if (started_txn) if (started_txn)
{ {
results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK"); results = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
OK &= AcceptResult(results) && OK &= AcceptResult(results, true) &&
(PQresultStatus(results) == PGRES_COMMAND_OK); (PQresultStatus(results) == PGRES_COMMAND_OK);
ClearOrSaveResult(results); ClearOrSaveResult(results);
} }
......
...@@ -410,6 +410,8 @@ helpVariables(unsigned short int pager) ...@@ -410,6 +410,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"
......
...@@ -148,6 +148,7 @@ typedef struct _psqlSettings ...@@ -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;
......
...@@ -196,6 +196,7 @@ main(int argc, char *argv[]) ...@@ -196,6 +196,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);
...@@ -1130,6 +1131,12 @@ verbosity_hook(const char *newval) ...@@ -1130,6 +1131,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)
{ {
...@@ -1231,6 +1238,9 @@ EstablishVariableSpace(void) ...@@ -1231,6 +1238,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);
......
...@@ -4122,7 +4122,7 @@ psql_completion(const char *text, int start, int end) ...@@ -4122,7 +4122,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"))
......
...@@ -126,7 +126,7 @@ copy (select 1) to stdout\; select 1/0; -- row, then error ...@@ -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; -- ...@@ -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
......
...@@ -5078,3 +5078,96 @@ List of access methods ...@@ -5078,3 +5078,96 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows) (5 rows)
--
-- combined queries
--
CREATE FUNCTION warn(msg TEXT) RETURNS BOOLEAN LANGUAGE plpgsql
AS $$
BEGIN RAISE NOTICE 'warn %', msg ; RETURN TRUE ; END
$$;
-- show both
SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
one
-----
1
(1 row)
NOTICE: warn 1.5
CONTEXT: PL/pgSQL function warn(text) line 2 at RAISE
warn
------
t
(1 row)
two
-----
2
(1 row)
-- \gset applies to last query only
SELECT 3 AS three \; SELECT warn('3.5') \; SELECT 4 AS four \gset
three
-------
3
(1 row)
NOTICE: warn 3.5
CONTEXT: PL/pgSQL function warn(text) line 2 at RAISE
warn
------
t
(1 row)
\echo :three :four
:three 4
-- syntax error stops all processing
SELECT 5 \; SELECT 6 + \; SELECT warn('6.5') \; SELECT 7 ;
ERROR: syntax error at or near ";"
LINE 1: SELECT 5 ; SELECT 6 + ; SELECT warn('6.5') ; SELECT 7 ;
^
-- with aborted transaction, stop on first error
BEGIN \; SELECT 8 AS eight \; SELECT 9/0 AS nine \; ROLLBACK \; SELECT 10 AS ten ;
eight
-------
8
(1 row)
ERROR: division by zero
-- close previously aborted transaction
ROLLBACK;
-- misc SQL commands
-- (non SELECT output is sent to stderr, thus is not shown in expected results)
SELECT 'ok' AS "begin" \;
CREATE TABLE psql_comics(s TEXT) \;
INSERT INTO psql_comics VALUES ('Calvin'), ('hobbes') \;
COPY psql_comics FROM STDIN \;
UPDATE psql_comics SET s = 'Hobbes' WHERE s = 'hobbes' \;
DELETE FROM psql_comics WHERE s = 'Moe' \;
COPY psql_comics TO STDOUT \;
TRUNCATE psql_comics \;
DROP TABLE psql_comics \;
SELECT 'ok' AS "done" ;
begin
-------
ok
(1 row)
Calvin
Susie
Hobbes
done
------
ok
(1 row)
\set SHOW_ALL_RESULTS off
SELECT 1 AS one \; SELECT warn('1.5') \; SELECT 2 AS two ;
NOTICE: warn 1.5
CONTEXT: PL/pgSQL function warn(text) line 2 at RAISE
two
-----
2
(1 row)
\set SHOW_ALL_RESULTS on
DROP FUNCTION warn(TEXT);
...@@ -900,8 +900,18 @@ DROP TABLE abc; ...@@ -900,8 +900,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
...@@ -916,6 +926,12 @@ insert into i_table values(1)\; select * from i_table; ...@@ -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 -- 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
...@@ -935,8 +951,18 @@ WARNING: there is no transaction in progress ...@@ -935,8 +951,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;
...@@ -963,22 +989,52 @@ rollback; -- we are not in a transaction at this point ...@@ -963,22 +989,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
......
...@@ -84,10 +84,10 @@ drop table test1; ...@@ -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
......
...@@ -1228,3 +1228,41 @@ drop role regress_partitioning_role; ...@@ -1228,3 +1228,41 @@ drop role regress_partitioning_role;
\dAo * pg_catalog.jsonb_path_ops \dAo * pg_catalog.jsonb_path_ops
\dAp+ btree float_ops \dAp+ btree float_ops
\dAp * pg_catalog.uuid_ops \dAp * pg_catalog.uuid_ops
--
-- 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);
...@@ -504,7 +504,7 @@ DROP TABLE abc; ...@@ -504,7 +504,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:
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment