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:
@ -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
|
||||||
|
@ -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>
|
||||||
|
@ -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(¬ices->messages[notices->current], msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Show notices stored in buffer, which is then reset.
|
||||||
|
*/
|
||||||
|
static void
|
||||||
|
ShowNoticeMessage(struct t_notice_messages *notices)
|
||||||
|
{
|
||||||
|
PQExpBufferData *current = ¬ices->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(¬ices.messages[0]);
|
||||||
|
initPQExpBuffer(¬ices.messages[1]);
|
||||||
|
PQsetNoticeProcessor(pset.db, AppendNoticeMessage, ¬ices);
|
||||||
|
|
||||||
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(¬ices);
|
||||||
|
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(¬ices);
|
||||||
|
|
||||||
|
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, ¬ices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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(¬ices);
|
||||||
|
|
||||||
|
/* 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(¬ices.messages[0]);
|
||||||
|
termPQExpBuffer(¬ices.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);
|
||||||
}
|
}
|
||||||
|
@ -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"
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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',
|
||||||
|
@ -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"))
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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:
|
||||||
|
Reference in New Issue
Block a user