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: "Iwata, Aya" <iwata.aya@jp.fujitsu.com> Reviewed-by: Daniel Verite <daniel@manitou-mail.org> Reviewed-by: Peter Eisentraut <peter.eisentraut@2ndquadrant.com> Reviewed-by: Kyotaro Horiguchi <horikyota.ntt@gmail.com> Reviewed-by: vignesh C <vignesh21@gmail.com> Discussion: https://www.postgresql.org/message-id/flat/alpine.DEB.2.21.1904132231510.8961@lancre
This commit is contained in:
parent
518442c7f3
commit
3a51306722
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
@ -3527,10 +3520,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>
|
||||
|
@ -4117,6 +4106,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>
|
||||
|
|
|
@ -33,6 +33,7 @@ static bool DescribeQuery(const char *query, double *elapsed_msec);
|
|||
static bool ExecQueryUsingCursor(const char *query, double *elapsed_msec);
|
||||
static bool command_no_begin(const char *query);
|
||||
static bool is_select_command(const char *query);
|
||||
static int SendQueryAndProcessResults(const char *query, double *pelapsed_msec, bool is_watch);
|
||||
|
||||
|
||||
/*
|
||||
|
@ -353,7 +354,7 @@ CheckConnection(void)
|
|||
* Returns true for valid result, false for error state.
|
||||
*/
|
||||
static bool
|
||||
AcceptResult(const PGresult *result)
|
||||
AcceptResult(const PGresult *result, bool show_error)
|
||||
{
|
||||
bool OK;
|
||||
|
||||
|
@ -384,7 +385,7 @@ AcceptResult(const PGresult *result)
|
|||
break;
|
||||
}
|
||||
|
||||
if (!OK)
|
||||
if (!OK && show_error)
|
||||
{
|
||||
const char *error = PQerrorMessage(pset.db);
|
||||
|
||||
|
@ -472,6 +473,18 @@ ClearOrSaveResult(PGresult *result)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Consume all results
|
||||
*/
|
||||
static void
|
||||
ClearOrSaveAllResults()
|
||||
{
|
||||
PGresult *result;
|
||||
|
||||
while ((result = PQgetResult(pset.db)) != NULL)
|
||||
ClearOrSaveResult(result);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Print microtiming output. Always print raw milliseconds; if the interval
|
||||
|
@ -572,7 +585,7 @@ PSQLexec(const char *query)
|
|||
|
||||
ResetCancelConn();
|
||||
|
||||
if (!AcceptResult(res))
|
||||
if (!AcceptResult(res, true))
|
||||
{
|
||||
ClearOrSaveResult(res);
|
||||
res = NULL;
|
||||
|
@ -594,10 +607,8 @@ PSQLexec(const char *query)
|
|||
int
|
||||
PSQLexecWatch(const char *query, const printQueryOpt *opt)
|
||||
{
|
||||
PGresult *res;
|
||||
double elapsed_msec = 0;
|
||||
instr_time before;
|
||||
instr_time after;
|
||||
int res;
|
||||
|
||||
if (!pset.db)
|
||||
{
|
||||
|
@ -606,75 +617,16 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt)
|
|||
}
|
||||
|
||||
SetCancelConn(pset.db);
|
||||
|
||||
if (pset.timing)
|
||||
INSTR_TIME_SET_CURRENT(before);
|
||||
|
||||
res = PQexec(pset.db, query);
|
||||
|
||||
res = SendQueryAndProcessResults(query, &elapsed_msec, true);
|
||||
ResetCancelConn();
|
||||
|
||||
if (!AcceptResult(res))
|
||||
{
|
||||
ClearOrSaveResult(res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (pset.timing)
|
||||
{
|
||||
INSTR_TIME_SET_CURRENT(after);
|
||||
INSTR_TIME_SUBTRACT(after, before);
|
||||
elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
|
||||
}
|
||||
|
||||
/*
|
||||
* If SIGINT is sent while the query is processing, the interrupt will be
|
||||
* consumed. The user's intention, though, is to cancel the entire watch
|
||||
* process, so detect a sent cancellation request and exit in this case.
|
||||
*/
|
||||
if (cancel_pressed)
|
||||
{
|
||||
PQclear(res);
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (PQresultStatus(res))
|
||||
{
|
||||
case PGRES_TUPLES_OK:
|
||||
printQuery(res, opt, pset.queryFout, false, pset.logfile);
|
||||
break;
|
||||
|
||||
case PGRES_COMMAND_OK:
|
||||
fprintf(pset.queryFout, "%s\n%s\n\n", opt->title, PQcmdStatus(res));
|
||||
break;
|
||||
|
||||
case PGRES_EMPTY_QUERY:
|
||||
pg_log_error("\\watch cannot be used with an empty query");
|
||||
PQclear(res);
|
||||
return -1;
|
||||
|
||||
case PGRES_COPY_OUT:
|
||||
case PGRES_COPY_IN:
|
||||
case PGRES_COPY_BOTH:
|
||||
pg_log_error("\\watch cannot be used with COPY");
|
||||
PQclear(res);
|
||||
return -1;
|
||||
|
||||
default:
|
||||
pg_log_error("unexpected result status for \\watch");
|
||||
PQclear(res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
PQclear(res);
|
||||
|
||||
fflush(pset.queryFout);
|
||||
|
||||
/* Possible microtiming output */
|
||||
if (pset.timing)
|
||||
PrintTiming(elapsed_msec);
|
||||
|
||||
return 1;
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
|
@ -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()
|
||||
* once and report any error. Return whether all was ok.
|
||||
*
|
||||
* When our command string contained a COPY FROM STDIN or COPY TO STDOUT,
|
||||
* PQexec() has stopped at the PGresult associated with the first such
|
||||
* command. In that event, we'll marshal data for the COPY and then cycle
|
||||
* through any subsequent PGresult objects.
|
||||
* For COPY OUT, direct the output to pset.copyStream if it's set,
|
||||
* otherwise to pset.gfname if it's set, otherwise to queryFout.
|
||||
* For COPY IN, use pset.copyStream as data source if it's set,
|
||||
* otherwise cur_cmd_source.
|
||||
*
|
||||
* When the command string contained no such COPY command, this function
|
||||
* degenerates to an AcceptResult() call.
|
||||
*
|
||||
* Changes its argument to point to the last PGresult of the command string,
|
||||
* or NULL if that result was for a COPY TO STDOUT. (Returning NULL prevents
|
||||
* the command status from being printed, which we want in that case so that
|
||||
* the status line doesn't get taken as part of the COPY data.)
|
||||
*
|
||||
* Returns true on complete success, false otherwise. Possible failure modes
|
||||
* include purely client-side problems; check the transaction status for the
|
||||
* server-side opinion.
|
||||
* Update result if further processing is necessary, or NULL otherwise.
|
||||
* Return a result when queryFout can safely output a result status:
|
||||
* on COPY IN, or on COPY OUT if written to something other 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;
|
||||
need_close = true;
|
||||
if (is_pipe)
|
||||
disable_sigpipe_trap();
|
||||
}
|
||||
else
|
||||
copystream = NULL; /* discard COPY data entirely */
|
||||
}
|
||||
else
|
||||
{
|
||||
/* fall back to the generic query output stream */
|
||||
copystream = pset.queryFout;
|
||||
}
|
||||
|
||||
if (pset.copyStream)
|
||||
{
|
||||
/* invoked by \copy */
|
||||
copystream = pset.copyStream;
|
||||
}
|
||||
else if (pset.gfname)
|
||||
{
|
||||
/* invoked by \g */
|
||||
if (openQueryOutputFile(pset.gfname,
|
||||
©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)
|
||||
&& (copystream != NULL);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Suppress status printing if the report would go to the same
|
||||
* place as the COPY data just went. Note this doesn't
|
||||
* prevent error reporting, since handleCopyOut did that.
|
||||
*/
|
||||
if (copystream == pset.queryFout)
|
||||
{
|
||||
PQclear(copy_result);
|
||||
copy_result = NULL;
|
||||
}
|
||||
|
||||
if (need_close)
|
||||
{
|
||||
/* close \g argument file/pipe */
|
||||
if (is_pipe)
|
||||
{
|
||||
pclose(copystream);
|
||||
restore_sigpipe_trap();
|
||||
}
|
||||
else
|
||||
{
|
||||
fclose(copystream);
|
||||
}
|
||||
}
|
||||
if (need_close)
|
||||
{
|
||||
/* close \g argument file/pipe */
|
||||
if (is_pipe)
|
||||
{
|
||||
pclose(copystream);
|
||||
restore_sigpipe_trap();
|
||||
}
|
||||
else
|
||||
{
|
||||
/* COPY IN */
|
||||
copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
|
||||
success = handleCopyIn(pset.db,
|
||||
copystream,
|
||||
PQbinaryTuples(*results),
|
||||
©_result) && success;
|
||||
fclose(copystream);
|
||||
}
|
||||
ResetCancelConn();
|
||||
|
||||
/*
|
||||
* Replace the PGRES_COPY_OUT/IN result with COPY command's exit
|
||||
* status, or with NULL if we want to suppress printing anything.
|
||||
*/
|
||||
PQclear(*results);
|
||||
*results = copy_result;
|
||||
}
|
||||
else if (first_cycle)
|
||||
{
|
||||
/* fast path: no COPY commands; PQexec visited all results */
|
||||
break;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check PQgetResult() again. In the typical case of a single-command
|
||||
* string, it will return NULL. Otherwise, we'll have other results
|
||||
* to process that may include other COPYs. We keep the last result.
|
||||
*/
|
||||
next_result = PQgetResult(pset.db);
|
||||
if (!next_result)
|
||||
break;
|
||||
|
||||
PQclear(*results);
|
||||
*results = next_result;
|
||||
first_cycle = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
/* COPY IN */
|
||||
copystream = pset.copyStream ? pset.copyStream : pset.cur_cmd_source;
|
||||
success = handleCopyIn(pset.db,
|
||||
copystream,
|
||||
PQbinaryTuples(*result),
|
||||
©_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)
|
||||
|
@ -1105,43 +974,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 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);
|
||||
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);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case PGRES_COMMAND_OK:
|
||||
PrintQueryStatus(results);
|
||||
if (last || pset.show_all_results)
|
||||
PrintQueryStatus(result);
|
||||
success = true;
|
||||
break;
|
||||
|
||||
|
@ -1151,7 +1027,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;
|
||||
|
||||
|
@ -1164,7 +1040,7 @@ PrintQueryResults(PGresult *results)
|
|||
default:
|
||||
success = false;
|
||||
pg_log_error("unexpected PQresultStatus: %d",
|
||||
PQresultStatus(results));
|
||||
PQresultStatus(result));
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -1173,6 +1049,217 @@ PrintQueryResults(PGresult *results)
|
|||
return success;
|
||||
}
|
||||
|
||||
/*
|
||||
* Data structure and functions to record notices while they are
|
||||
* emitted, so that they can be shown later.
|
||||
*
|
||||
* We need to know which result is last, which requires to extract
|
||||
* one result in advance, hence two buffers are needed.
|
||||
*/
|
||||
typedef struct {
|
||||
bool in_flip;
|
||||
PQExpBufferData flip;
|
||||
PQExpBufferData flop;
|
||||
} t_notice_messages;
|
||||
|
||||
/*
|
||||
* Store notices in appropriate buffer, for later display.
|
||||
*/
|
||||
static void
|
||||
AppendNoticeMessage(void *arg, const char *msg)
|
||||
{
|
||||
t_notice_messages *notes = (t_notice_messages*) arg;
|
||||
appendPQExpBufferStr(notes->in_flip ? ¬es->flip : ¬es->flop, msg);
|
||||
}
|
||||
|
||||
/*
|
||||
* Show notices stored in buffer, which is then reset.
|
||||
*/
|
||||
static void
|
||||
ShowNoticeMessage(t_notice_messages *notes)
|
||||
{
|
||||
PQExpBufferData *current = notes->in_flip ? ¬es->flip : ¬es->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(¬es.flip);
|
||||
initPQExpBuffer(¬es.flop);
|
||||
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);
|
||||
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(¬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.
|
||||
*/
|
||||
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(¬es);
|
||||
|
||||
/* 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(¬es.flip);
|
||||
termPQExpBuffer(¬es.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
|
||||
|
@ -1294,28 +1381,9 @@ SendQuery(const char *query)
|
|||
pset.crosstab_flag || !is_select_command(query))
|
||||
{
|
||||
/* Default fetch-it-all-and-print mode */
|
||||
instr_time before,
|
||||
after;
|
||||
|
||||
if (pset.timing)
|
||||
INSTR_TIME_SET_CURRENT(before);
|
||||
|
||||
results = PQexec(pset.db, query);
|
||||
|
||||
/* these operations are included in the timing result: */
|
||||
ResetCancelConn();
|
||||
OK = ProcessResult(&results);
|
||||
|
||||
if (pset.timing)
|
||||
{
|
||||
INSTR_TIME_SET_CURRENT(after);
|
||||
INSTR_TIME_SUBTRACT(after, before);
|
||||
elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
|
||||
}
|
||||
|
||||
/* but printing results isn't: */
|
||||
if (OK && results)
|
||||
OK = PrintQueryResults(results);
|
||||
int res = SendQueryAndProcessResults(query, &elapsed_msec, false);
|
||||
OK = (res >= 0);
|
||||
results = NULL;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1497,7 +1565,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)
|
||||
{
|
||||
|
@ -1545,7 +1613,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
|
|||
PQclear(results);
|
||||
|
||||
results = PQexec(pset.db, buf.data);
|
||||
OK = AcceptResult(results);
|
||||
OK = AcceptResult(results, true);
|
||||
|
||||
if (pset.timing)
|
||||
{
|
||||
|
@ -1555,7 +1623,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
|
|||
}
|
||||
|
||||
if (OK && results)
|
||||
OK = PrintQueryResults(results);
|
||||
OK = HandleQueryResult(results, true);
|
||||
|
||||
termPQExpBuffer(&buf);
|
||||
}
|
||||
|
@ -1614,7 +1682,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
|
|||
if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
|
||||
{
|
||||
results = PQexec(pset.db, "BEGIN");
|
||||
OK = AcceptResult(results) &&
|
||||
OK = AcceptResult(results, true) &&
|
||||
(PQresultStatus(results) == PGRES_COMMAND_OK);
|
||||
ClearOrSaveResult(results);
|
||||
if (!OK)
|
||||
|
@ -1628,7 +1696,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);
|
||||
|
@ -1701,7 +1769,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
|
|||
is_pager = false;
|
||||
}
|
||||
|
||||
OK = AcceptResult(results);
|
||||
OK = AcceptResult(results, true);
|
||||
Assert(!OK);
|
||||
SetResultVariables(results, OK);
|
||||
ClearOrSaveResult(results);
|
||||
|
@ -1810,7 +1878,7 @@ cleanup:
|
|||
results = PQexec(pset.db, "CLOSE _psql_cursor");
|
||||
if (OK)
|
||||
{
|
||||
OK = AcceptResult(results) &&
|
||||
OK = AcceptResult(results, true) &&
|
||||
(PQresultStatus(results) == PGRES_COMMAND_OK);
|
||||
ClearOrSaveResult(results);
|
||||
}
|
||||
|
@ -1820,7 +1888,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);
|
||||
}
|
||||
|
|
|
@ -410,6 +410,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"
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -196,6 +196,7 @@ main(int argc, char *argv[])
|
|||
SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1);
|
||||
SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2);
|
||||
SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3);
|
||||
SetVariableBool(pset.vars, "SHOW_ALL_RESULTS");
|
||||
|
||||
parse_psql_options(argc, argv, &options);
|
||||
|
||||
|
@ -1130,6 +1131,12 @@ verbosity_hook(const char *newval)
|
|||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
show_all_results_hook(const char *newval)
|
||||
{
|
||||
return ParseVariableBool(newval, "SHOW_ALL_RESULTS", &pset.show_all_results);
|
||||
}
|
||||
|
||||
static char *
|
||||
show_context_substitute_hook(char *newval)
|
||||
{
|
||||
|
@ -1231,6 +1238,9 @@ EstablishVariableSpace(void)
|
|||
SetVariableHooks(pset.vars, "VERBOSITY",
|
||||
verbosity_substitute_hook,
|
||||
verbosity_hook);
|
||||
SetVariableHooks(pset.vars, "SHOW_ALL_RESULTS",
|
||||
bool_substitute_hook,
|
||||
show_all_results_hook);
|
||||
SetVariableHooks(pset.vars, "SHOW_CONTEXT",
|
||||
show_context_substitute_hook,
|
||||
show_context_hook);
|
||||
|
|
|
@ -4122,7 +4122,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"))
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -5078,3 +5078,96 @@ List of access methods
|
|||
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
|
||||
(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;
|
|||
-- 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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -1228,3 +1228,41 @@ drop role regress_partitioning_role;
|
|||
\dAo * pg_catalog.jsonb_path_ops
|
||||
\dAp+ btree float_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;
|
|||
|
||||
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:
|
||||
|
|
Loading…
Reference in New Issue