psql: Show all query results by default
authorPeter Eisentraut <[email protected]>
Mon, 4 Apr 2022 12:57:17 +0000 (14:57 +0200)
committerPeter Eisentraut <[email protected]>
Mon, 4 Apr 2022 18:00:33 +0000 (20:00 +0200)
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
3a5130672296ed4e682403a77a9a3ad3d21cef75 (that was later reverted).
There is also much more test coverage for various psql features now.

Author: Fabien COELHO <[email protected]>
Reviewed-by: Peter Eisentraut <[email protected]>
Reviewed-by: "Iwata, Aya" <[email protected]> (earlier version)
Reviewed-by: Daniel Verite <[email protected]> (earlier version)
Reviewed-by: Kyotaro Horiguchi <[email protected]> (earlier version)
Reviewed-by: vignesh C <[email protected]> (earlier version)
Discussion: https://www.postgresql.org/message-id/flat/alpine.DEB.2.21.1904132231510.8961@lancre

14 files changed:
contrib/pg_stat_statements/expected/pg_stat_statements.out
doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/common.c
src/bin/psql/help.c
src/bin/psql/settings.h
src/bin/psql/startup.c
src/bin/psql/t/001_basic.pl
src/bin/psql/tab-complete.c
src/test/regress/expected/copyselect.out
src/test/regress/expected/psql.out
src/test/regress/expected/transactions.out
src/test/regress/sql/copyselect.sql
src/test/regress/sql/psql.sql
src/test/regress/sql/transactions.sql

index e0abe34bb6aa04a5462c06c0de24c24cf02fd505..8f7f93172a250657199e76f87eedfde8ba0960cf 100644 (file)
@@ -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 
index caabb06c537d417819b8880cd82de01bd82ebda3..f01adb1fd27d644393d580d445d29187c2a2714b 100644 (file)
@@ -127,18 +127,11 @@ echo '\x \\ SELECT * FROM foo;' | psql
        commands included in the string to divide it into multiple
        transactions.  (See <xref linkend="protocol-flow-multi-statement"/>
        for more details about how the server handles multi-query strings.)
-       Also, <application>psql</application> only prints the
-       result of the last <acronym>SQL</acronym> command in the string.
-       This is different from the behavior when the same string is read from
-       a file or fed to <application>psql</application>'s standard input,
-       because then <application>psql</application> sends
-       each <acronym>SQL</acronym> command separately.
       </para>
       <para>
-       Because of this behavior, putting more than one SQL command in a
-       single <option>-c</option> string often has unexpected results.
-       It's better to use repeated <option>-c</option> commands or feed
-       multiple commands to <application>psql</application>'s standard input,
+       If having several commands executed in one transaction is not desired, 
+       use repeated <option>-c</option> commands or feed multiple commands to
+       <application>psql</application>'s standard input,
        either using <application>echo</application> as illustrated above, or
        via a shell here-document, for example:
 <programlisting>
@@ -3570,10 +3563,6 @@ select 1\; select 2\; select 3;
         commands included in the string to divide it into multiple
         transactions.  (See <xref linkend="protocol-flow-multi-statement"/>
         for more details about how the server handles multi-query strings.)
-        <application>psql</application> prints only the last query result
-        it receives for each request; in this example, although all
-        three <command>SELECT</command>s are indeed executed, <application>psql</application>
-        only prints the <literal>3</literal>.
         </para>
         </listitem>
       </varlistentry>
@@ -4160,6 +4149,18 @@ bar
       </varlistentry>
 
       <varlistentry>
+        <term><varname>SHOW_ALL_RESULTS</varname></term>
+        <listitem>
+        <para>
+        When this variable is set to <literal>off</literal>, only the last
+        result of a combined query (<literal>\;</literal>) is shown instead of
+        all of them.  The default is <literal>on</literal>.  The off behavior
+        is for compatibility with older versions of psql.
+        </para>
+        </listitem>
+      </varlistentry>
+
+       <varlistentry>
         <term><varname>SHOW_CONTEXT</varname></term>
         <listitem>
         <para>
index f6777bbfc44a9469a3338178a0175f40f2103508..5a707dab648b620217e858b0d03347e93eb1a290 100644 (file)
@@ -32,7 +32,8 @@
 
 static bool DescribeQuery(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 is_select_command(const char *query);
 
@@ -355,7 +356,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;
 
@@ -386,7 +387,7 @@ AcceptResult(const PGresult *result)
                                break;
                }
 
-       if (!OK)
+       if (!OK && show_error)
        {
                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
  * is >= 1 second, also break it down into days/hours/minutes/seconds.
@@ -574,7 +588,7 @@ PSQLexec(const char *query)
 
        ResetCancelConn();
 
-       if (!AcceptResult(res))
+       if (!AcceptResult(res, true))
        {
                ClearOrSaveResult(res);
                res = NULL;
@@ -597,11 +611,8 @@ int
 PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
 {
        bool            timing = pset.timing;
-       PGresult   *res;
        double          elapsed_msec = 0;
-       instr_time      before;
-       instr_time      after;
-       FILE       *fout;
+       int                     res;
 
        if (!pset.db)
        {
@@ -611,76 +622,15 @@ PSQLexecWatch(const char *query, const printQueryOpt *opt, FILE *printQueryFout)
 
        SetCancelConn(pset.db);
 
-       if (timing)
-               INSTR_TIME_SET_CURRENT(before);
-
-       res = PQexec(pset.db, query);
+       res = ExecQueryAndProcessResults(query, &elapsed_msec, NULL, true, opt, printQueryFout);
 
        ResetCancelConn();
 
-       if (!AcceptResult(res))
-       {
-               ClearOrSaveResult(res);
-               return 0;
-       }
-
-       if (timing)
-       {
-               INSTR_TIME_SET_CURRENT(after);
-               INSTR_TIME_SUBTRACT(after, before);
-               elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
-       }
-
-       /*
-        * If SIGINT is sent while the query is processing, the interrupt will be
-        * consumed.  The user's intention, though, is to cancel the entire watch
-        * process, so detect a sent cancellation request and exit in this case.
-        */
-       if (cancel_pressed)
-       {
-               PQclear(res);
-               return 0;
-       }
-
-       fout = printQueryFout ? printQueryFout : pset.queryFout;
-
-       switch (PQresultStatus(res))
-       {
-               case PGRES_TUPLES_OK:
-                       printQuery(res, opt, fout, false, pset.logfile);
-                       break;
-
-               case PGRES_COMMAND_OK:
-                       fprintf(fout, "%s\n%s\n\n", opt->title, PQcmdStatus(res));
-                       break;
-
-               case PGRES_EMPTY_QUERY:
-                       pg_log_error("\\watch cannot be used with an empty query");
-                       PQclear(res);
-                       return -1;
-
-               case PGRES_COPY_OUT:
-               case PGRES_COPY_IN:
-               case PGRES_COPY_BOTH:
-                       pg_log_error("\\watch cannot be used with COPY");
-                       PQclear(res);
-                       return -1;
-
-               default:
-                       pg_log_error("unexpected result status for \\watch");
-                       PQclear(res);
-                       return -1;
-       }
-
-       PQclear(res);
-
-       fflush(fout);
-
        /* Possible microtiming output */
        if (timing)
                PrintTiming(elapsed_msec);
 
-       return 1;
+       return res;
 }
 
 
@@ -715,7 +665,7 @@ PrintNotifications(void)
  * Returns true if successful, false otherwise.
  */
 static bool
-PrintQueryTuples(const PGresult *result)
+PrintQueryTuples(const PGresult *result, const printQueryOpt *opt, FILE *printQueryFout)
 {
        bool            ok = true;
 
@@ -747,8 +697,9 @@ PrintQueryTuples(const PGresult *result)
        }
        else
        {
-               printQuery(result, &pset.popt, pset.queryFout, false, pset.logfile);
-               if (ferror(pset.queryFout))
+               FILE *fout = printQueryFout ? printQueryFout : pset.queryFout;
+               printQuery(result, opt ? opt : &pset.popt, fout, false, pset.logfile);
+               if (ferror(fout))
                {
                        pg_log_error("could not print result table: %m");
                        ok = false;
@@ -1001,123 +952,27 @@ HandleCopyResult(PGresult **resultp)
        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
  *
  * Note: Utility function for use by PrintQueryResult() only.
  */
 static void
-PrintQueryStatus(PGresult *result)
+PrintQueryStatus(PGresult *result, FILE *printQueryFout)
 {
        char            buf[16];
+       FILE       *fout = printQueryFout ? printQueryFout : pset.queryFout;
 
        if (!pset.quiet)
        {
                if (pset.popt.topt.format == PRINT_HTML)
                {
-                       fputs("<p>", pset.queryFout);
-                       html_escaped_print(PQcmdStatus(result), pset.queryFout);
-                       fputs("</p>\n", pset.queryFout);
+                       fputs("<p>", fout);
+                       html_escaped_print(PQcmdStatus(result), fout);
+                       fputs("</p>\n", fout);
                }
                else
-                       fprintf(pset.queryFout, "%s\n", PQcmdStatus(result));
+                       fprintf(fout, "%s\n", PQcmdStatus(result));
        }
 
        if (pset.logfile)
@@ -1136,7 +991,7 @@ PrintQueryStatus(PGresult *result)
  * Returns true if the query executed successfully, false otherwise.
  */
 static bool
-PrintQueryResult(PGresult *result)
+PrintQueryResult(PGresult *result, bool last, bool is_watch, const printQueryOpt *opt, FILE *printQueryFout)
 {
        bool            success;
        const char *cmdstatus;
@@ -1148,24 +1003,32 @@ PrintQueryResult(PGresult *result)
        {
                case PGRES_TUPLES_OK:
                        /* store or execute or print the data ... */
-                       if (pset.gset_prefix)
+                       if (last && pset.gset_prefix)
                                success = StoreQueryTuple(result);
-                       else if (pset.gexec_flag)
+                       else if (last && pset.gexec_flag)
                                success = ExecQueryTuples(result);
-                       else if (pset.crosstab_flag)
+                       else if (last && pset.crosstab_flag)
                                success = PrintResultInCrosstab(result);
+                       else if (last || pset.show_all_results)
+                               success = PrintQueryTuples(result, opt, printQueryFout);
                        else
-                               success = PrintQueryTuples(result);
+                               success = true;
+
                        /* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
-                       cmdstatus = PQcmdStatus(result);
-                       if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
-                               strncmp(cmdstatus, "UPDATE", 6) == 0 ||
-                               strncmp(cmdstatus, "DELETE", 6) == 0)
-                               PrintQueryStatus(result);
+                       if (last || pset.show_all_results)
+                       {
+                               cmdstatus = PQcmdStatus(result);
+                               if (strncmp(cmdstatus, "INSERT", 6) == 0 ||
+                                       strncmp(cmdstatus, "UPDATE", 6) == 0 ||
+                                       strncmp(cmdstatus, "DELETE", 6) == 0)
+                                       PrintQueryStatus(result, printQueryFout);
+                       }
+
                        break;
 
                case PGRES_COMMAND_OK:
-                       PrintQueryStatus(result);
+                       if (last || pset.show_all_results)
+                               PrintQueryStatus(result, printQueryFout);
                        success = true;
                        break;
 
@@ -1175,7 +1038,7 @@ PrintQueryResult(PGresult *result)
 
                case PGRES_COPY_OUT:
                case PGRES_COPY_IN:
-                       /* nothing to do here */
+                       /* nothing to do here: already processed */
                        success = true;
                        break;
 
@@ -1192,11 +1055,46 @@ PrintQueryResult(PGresult *result)
                        break;
        }
 
-       fflush(pset.queryFout);
+       fflush(printQueryFout ? printQueryFout : pset.queryFout);
 
        return success;
 }
 
+/*
+ * Data structure and functions to record notices while they are
+ * emitted, so that they can be shown later.
+ *
+ * We need to know which result is last, which requires to extract
+ * one result in advance, hence two buffers are needed.
+ */
+struct t_notice_messages
+{
+       PQExpBufferData messages[2];
+       int                     current;
+};
+
+/*
+ * Store notices in appropriate buffer, for later display.
+ */
+static void
+AppendNoticeMessage(void *arg, const char *msg)
+{
+       struct t_notice_messages *notices = arg;
+       appendPQExpBufferStr(&notices->messages[notices->current], msg);
+}
+
+/*
+ * Show notices stored in buffer, which is then reset.
+ */
+static void
+ShowNoticeMessage(struct t_notice_messages *notices)
+{
+       PQExpBufferData *current = &notices->messages[notices->current];
+       if (*current->data != '\0')
+               pg_log_info("%s", current->data);
+       resetPQExpBuffer(current);
+}
+
 
 /*
  * SendQuery: send the query string to the backend
@@ -1273,7 +1171,6 @@ SendQuery(const char *query)
                {
                        pg_log_info("%s", PQerrorMessage(pset.db));
                        ClearOrSaveResult(result);
-                       ResetCancelConn();
                        goto sendquery_cleanup;
                }
                ClearOrSaveResult(result);
@@ -1292,7 +1189,6 @@ SendQuery(const char *query)
                {
                        pg_log_info("%s", PQerrorMessage(pset.db));
                        ClearOrSaveResult(result);
-                       ResetCancelConn();
                        goto sendquery_cleanup;
                }
                ClearOrSaveResult(result);
@@ -1303,19 +1199,17 @@ SendQuery(const char *query)
        {
                /* Describe query's result columns, without executing it */
                OK = DescribeQuery(query, &elapsed_msec);
-               ResetCancelConn();
        }
        else if (pset.fetch_count <= 0 || pset.gexec_flag ||
                         pset.crosstab_flag || !is_select_command(query))
        {
                /* 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
        {
                /* Fetch-in-segments mode */
                OK = ExecQueryUsingCursor(query, &elapsed_msec);
-               ResetCancelConn();
        }
 
        if (!OK && pset.echo == PSQL_ECHO_ERRORS)
@@ -1370,7 +1264,6 @@ SendQuery(const char *query)
                                ClearOrSaveResult(svptres);
                                OK = false;
 
-                               ResetCancelConn();
                                goto sendquery_cleanup;
                        }
                        PQclear(svptres);
@@ -1399,6 +1292,9 @@ SendQuery(const char *query)
 
 sendquery_cleanup:
 
+       /* global cancellation reset */
+       ResetCancelConn();
+
        /* reset \g's output-to-filename trigger */
        if (pset.gfname)
        {
@@ -1479,7 +1375,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
        PQclear(result);
 
        result = PQdescribePrepared(pset.db, "");
-       OK = AcceptResult(result) &&
+       OK = AcceptResult(result, true) &&
                (PQresultStatus(result) == PGRES_COMMAND_OK);
        if (OK && result)
        {
@@ -1527,7 +1423,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
                        PQclear(result);
 
                        result = PQexec(pset.db, buf.data);
-                       OK = AcceptResult(result);
+                       OK = AcceptResult(result, true);
 
                        if (timing)
                        {
@@ -1537,7 +1433,7 @@ DescribeQuery(const char *query, double *elapsed_msec)
                        }
 
                        if (OK && result)
-                               OK = PrintQueryResult(result);
+                               OK = PrintQueryResult(result, true, false, NULL, NULL);
 
                        termPQExpBuffer(&buf);
                }
@@ -1554,56 +1450,218 @@ DescribeQuery(const char *query, double *elapsed_msec)
 
 
 /*
- * ExecQueryAndProcessResults: SendQuery() subroutine for the normal way to
- * send a query
+ * ExecQueryAndProcessResults: 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 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)
 {
        bool            timing = pset.timing;
-       bool            OK;
+       bool            success;
        instr_time      before,
                                after;
        PGresult   *result;
+       struct t_notice_messages notices;
 
        if (timing)
                INSTR_TIME_SET_CURRENT(before);
 
-       result = PQexec(pset.db, query);
-
-       /* these operations are included in the timing result: */
-       ResetCancelConn();
-       OK = ProcessResult(&result);
+       success = PQsendQuery(pset.db, query);
 
-       if (timing)
+       if (!success)
        {
-               INSTR_TIME_SET_CURRENT(after);
-               INSTR_TIME_SUBTRACT(after, before);
-               *elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
-       }
+               const char *error = PQerrorMessage(pset.db);
 
-       /* but printing result isn't: */
-       if (OK && result)
-               OK = PrintQueryResult(result);
+               if (strlen(error))
+                       pg_log_info("%s", error);
+
+               CheckConnection();
+
+               return -1;
+       }
 
        /*
-        * 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.
+        * 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 (result && svpt_gone_p)
+       if (is_watch && cancel_pressed)
        {
-               const char *cmd = PQcmdStatus(result);
-               *svpt_gone_p = (strcmp(cmd, "COMMIT") == 0 ||
-                                               strcmp(cmd, "SAVEPOINT") == 0 ||
-                                               strcmp(cmd, "RELEASE") == 0 ||
-                                               strcmp(cmd, "ROLLBACK") == 0);
+               ClearOrSaveAllResults();
+               return 0;
        }
 
-       ClearOrSaveResult(result);
+       /* intercept notices */
+       notices.current = 0;
+       initPQExpBuffer(&notices.messages[0]);
+       initPQExpBuffer(&notices.messages[1]);
+       PQsetNoticeProcessor(pset.db, AppendNoticeMessage, &notices);
 
-       return OK;
+       /* first result */
+       result = PQgetResult(pset.db);
+
+       while (result != NULL)
+       {
+               ExecStatusType result_status;
+               PGresult   *next_result;
+               bool            last;
+
+               if (!AcceptResult(result, false))
+               {
+                       /*
+                        * Some error occured, either a server-side failure or
+                        * a failure to submit the command string.  Record that.
+                        */
+                       const char *error = PQresultErrorMessage(result);
+
+                       ShowNoticeMessage(&notices);
+                       if (strlen(error))
+                               pg_log_info("%s", error);
+
+                       CheckConnection();
+                       if (!is_watch)
+                               SetResultVariables(result, false);
+
+                       /* keep the result status before clearing it */
+                       result_status = PQresultStatus(result);
+                       ClearOrSaveResult(result);
+                       success = false;
+
+                       /*
+                        * switch to next result
+                        */
+                       if (result_status == PGRES_COPY_BOTH ||
+                               result_status == PGRES_COPY_OUT ||
+                               result_status == PGRES_COPY_IN)
+                               /*
+                                * For some obscure reason PQgetResult does *not* return a NULL in copy
+                                * cases despite the result having been cleared, but keeps returning an
+                                * "empty" result that we have to ignore manually.
+                                */
+                               result = NULL;
+                       else
+                               result = PQgetResult(pset.db);
+
+                       continue;
+               }
+               else if (svpt_gone_p && !*svpt_gone_p)
+               {
+                       /*
+                        * Check if the user ran any command that would destroy our internal
+                        * savepoint: If the user did COMMIT AND CHAIN, RELEASE or ROLLBACK, our
+                        * savepoint is gone. If they issued a SAVEPOINT, releasing ours would
+                        * remove theirs.
+                        */
+                       const char *cmd = PQcmdStatus(result);
+                       *svpt_gone_p = (strcmp(cmd, "COMMIT") == 0 ||
+                                                       strcmp(cmd, "SAVEPOINT") == 0 ||
+                                                       strcmp(cmd, "RELEASE") == 0 ||
+                                                       strcmp(cmd, "ROLLBACK") == 0);
+               }
+
+               result_status = PQresultStatus(result);
+
+               /* must handle COPY before changing the current result */
+               Assert(result_status != PGRES_COPY_BOTH);
+               if (result_status == PGRES_COPY_IN ||
+                       result_status == PGRES_COPY_OUT)
+               {
+                       ShowNoticeMessage(&notices);
+
+                       if (is_watch)
+                       {
+                               ClearOrSaveAllResults();
+                               pg_log_error("\\watch cannot be used with COPY");
+                               return -1;
+                       }
+
+                       /* use normal notice processor during COPY */
+                       PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
+
+                       success &= HandleCopyResult(&result);
+
+                       PQsetNoticeProcessor(pset.db, AppendNoticeMessage, &notices);
+               }
+
+               /*
+                * Check PQgetResult() again.  In the typical case of a single-command
+                * string, it will return NULL.  Otherwise, we'll have other results
+                * to process.  We need to do that to check whether this is the last.
+                */
+               notices.current ^= 1;
+               next_result = PQgetResult(pset.db);
+               notices.current ^= 1;
+               last = (next_result == NULL);
+
+               /*
+                * Get timing measure before printing the last result.
+                *
+                * It will include the display of previous results, if any.
+                * This cannot be helped because the server goes on processing
+                * further queries anyway while the previous ones are being displayed.
+                * The parallel execution of the client display hides the server time
+                * when it is shorter.
+                *
+                * With combined queries, timing must be understood as an upper bound
+                * of the time spent processing them.
+                */
+               if (last && timing)
+               {
+                       INSTR_TIME_SET_CURRENT(after);
+                       INSTR_TIME_SUBTRACT(after, before);
+                       *elapsed_msec = INSTR_TIME_GET_MILLISEC(after);
+               }
+
+               /* notices already shown above for copy */
+               ShowNoticeMessage(&notices);
+
+               /* this may or may not print something depending on settings */
+               if (result != NULL)
+                       success &= PrintQueryResult(result, last, false, opt, printQueryFout);
+
+               /* set variables on last result if all went well */
+               if (!is_watch && last && success)
+                       SetResultVariables(result, true);
+
+               ClearOrSaveResult(result);
+               notices.current ^= 1;
+               result = next_result;
+
+               if (cancel_pressed)
+               {
+                       ClearOrSaveAllResults();
+                       break;
+               }
+       }
+
+       /* reset notice hook */
+       PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
+       termPQExpBuffer(&notices.messages[0]);
+       termPQExpBuffer(&notices.messages[1]);
+
+       /* may need this to recover from conn loss during COPY */
+       if (!CheckConnection())
+               return -1;
+
+       return cancel_pressed ? 0 : success ? 1 : -1;
 }
 
 
@@ -1651,7 +1709,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
        if (PQtransactionStatus(pset.db) == PQTRANS_IDLE)
        {
                result = PQexec(pset.db, "BEGIN");
-               OK = AcceptResult(result) &&
+               OK = AcceptResult(result, true) &&
                        (PQresultStatus(result) == PGRES_COMMAND_OK);
                ClearOrSaveResult(result);
                if (!OK)
@@ -1665,7 +1723,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
                                          query);
 
        result = PQexec(pset.db, buf.data);
-       OK = AcceptResult(result) &&
+       OK = AcceptResult(result, true) &&
                (PQresultStatus(result) == PGRES_COMMAND_OK);
        if (!OK)
                SetResultVariables(result, OK);
@@ -1738,7 +1796,7 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
                                is_pager = false;
                        }
 
-                       OK = AcceptResult(result);
+                       OK = AcceptResult(result, true);
                        Assert(!OK);
                        SetResultVariables(result, OK);
                        ClearOrSaveResult(result);
@@ -1847,7 +1905,7 @@ cleanup:
        result = PQexec(pset.db, "CLOSE _psql_cursor");
        if (OK)
        {
-               OK = AcceptResult(result) &&
+               OK = AcceptResult(result, true) &&
                        (PQresultStatus(result) == PGRES_COMMAND_OK);
                ClearOrSaveResult(result);
        }
@@ -1857,7 +1915,7 @@ cleanup:
        if (started_txn)
        {
                result = PQexec(pset.db, OK ? "COMMIT" : "ROLLBACK");
-               OK &= AcceptResult(result) &&
+               OK &= AcceptResult(result, true) &&
                        (PQresultStatus(result) == PGRES_COMMAND_OK);
                ClearOrSaveResult(result);
        }
index 56afa6817e6b46eabf8499d58c1689334ad094d1..b3971bce64d4a251e49ca0d62bef65fd5f5323ae 100644 (file)
@@ -413,6 +413,8 @@ helpVariables(unsigned short int pager)
        fprintf(output, _("  SERVER_VERSION_NAME\n"
                                          "  SERVER_VERSION_NUM\n"
                                          "    server's version (in short string or numeric format)\n"));
+       fprintf(output, _("  SHOW_ALL_RESULTS\n"
+                                         "    show all results of a combined query (\\;) instead of only the last\n"));
        fprintf(output, _("  SHOW_CONTEXT\n"
                                          "    controls display of message context fields [never, errors, always]\n"));
        fprintf(output, _("  SINGLELINE\n"
index 80dbea9efd153d2588f40a0bb969b3167c8fd6b0..2399cffa3fba86717697fdc69a4978a40c8381ff 100644 (file)
@@ -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;
 
index be9dec749d721e2f78a6533d7870003ddfb80e4e..d08b15886aa23ca53eaee082fb1c7ffb6f6f043f 100644 (file)
@@ -203,6 +203,7 @@ main(int argc, char *argv[])
        SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1);
        SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2);
        SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3);
+       SetVariableBool(pset.vars, "SHOW_ALL_RESULTS");
 
        parse_psql_options(argc, argv, &options);
 
@@ -1150,6 +1151,12 @@ verbosity_hook(const char *newval)
        return true;
 }
 
+static bool
+show_all_results_hook(const char *newval)
+{
+       return ParseVariableBool(newval, "SHOW_ALL_RESULTS", &pset.show_all_results);
+}
+
 static char *
 show_context_substitute_hook(char *newval)
 {
@@ -1251,6 +1258,9 @@ EstablishVariableSpace(void)
        SetVariableHooks(pset.vars, "VERBOSITY",
                                         verbosity_substitute_hook,
                                         verbosity_hook);
+       SetVariableHooks(pset.vars, "SHOW_ALL_RESULTS",
+                                        bool_substitute_hook,
+                                        show_all_results_hook);
        SetVariableHooks(pset.vars, "SHOW_CONTEXT",
                                         show_context_substitute_hook,
                                         show_context_hook);
index 66796f4978d9e0463ff9cb0ce3191145234269a0..8ecb9b2583b5b15ad69550219091039e8556163e 100644 (file)
@@ -126,7 +126,7 @@ is($ret, 2, 'server crash: psql exit code');
 like($out, qr/before/, 'server crash: output before crash');
 ok($out !~ qr/AFTER/, 'server crash: no output after crash');
 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
        before or while processing the request.
 psql:<stdin>:2: fatal: connection to server was lost',
index 6f040674a256d7af5b59ecbadc8145ba564f9321..645d46ab75af3a0a8f9da8a1ae0644ab29a25265 100644 (file)
@@ -4628,7 +4628,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"))
index 72865fe1ebeeac818eea2457828b3e24fb73668f..bb9e026f913aed4963f8d42ab289eaab0a73918e 100644 (file)
@@ -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
index 6428ebc507d7f9c0ec84cc90299910ddf0087805..185c5053121a40e6d57d14e7779d3ff1214b7eb8 100644 (file)
@@ -5290,3 +5290,245 @@ ERROR:  relation "notexists" does not exist
 LINE 1: 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;
index 599d511a67acdb2a25e58ddfa4f642225390538c..a46fa5d48ab6cc012ecc9a169f64c5557d5b7926 100644 (file)
@@ -904,8 +904,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
@@ -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
 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 
@@ -939,8 +955,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;
@@ -967,22 +993,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
index 1d98dad3c8c55517d241c147f6baa4cbd9e31e38..e32a4f8e38e52e6bb67e20cd0991a18a49cc8634 100644 (file)
@@ -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; -- 1
 1
 \.
 2
index 0f5287f77bfa91e338517ee944bfdadaf9abc35d..8f49a5f347a1a24c0c82dc0decbe7392fb568696 100644 (file)
@@ -1316,3 +1316,144 @@ DROP TABLE oer_test;
 \set ECHO errors
 SELECT * FROM notexists;
 \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;
index 0a716b506bed56e8b6ea2083653fb123566f2643..d71c3ce93ea952f832d372e7dc2bc1982e696a27 100644 (file)
@@ -506,7 +506,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: