]> granicus.if.org Git - postgresql/commitdiff
Add a \gexec command to psql for evaluation of computed queries.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 4 Apr 2016 19:25:16 +0000 (15:25 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 4 Apr 2016 19:25:16 +0000 (15:25 -0400)
\gexec executes the just-entered query, like \g, but instead of printing
the results it takes each field as a SQL command to send to the server.
Computing a series of queries to be executed is a fairly common thing,
but up to now you always had to resort to kluges like writing the queries
to a file and then inputting the file.  Now it can be done with no
intermediate step.

The implementation is fairly straightforward except for its interaction
with FETCH_COUNT.  ExecQueryUsingCursor isn't capable of being called
recursively, and even if it were, its need to create a transaction
block interferes unpleasantly with the desired behavior of \gexec after
a failure of a generated query (i.e., that it can continue).  Therefore,
disable use of ExecQueryUsingCursor when doing the master \gexec query.
We can still apply it to individual generated queries, however, and there
might be some value in doing so.

While testing this feature's interaction with single-step mode, I (tgl) was
led to conclude that SendQuery needs to recognize SIGINT (cancel_pressed)
as a negative response to the single-step prompt.  Perhaps that's a
back-patchable bug fix, but for now I just included it here.

Corey Huinker, reviewed by Jim Nasby, Daniel Vérité, and myself

doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/command.c
src/bin/psql/common.c
src/bin/psql/help.c
src/bin/psql/settings.h
src/bin/psql/tab-complete.c
src/test/regress/expected/psql.out
src/test/regress/sql/psql.sql

index e8afc247afe04eaed53522ea2326e33733ffd6ba..d8b9a03ee0ee9f1d32e0bdbf7b9031f93661c469 100644 (file)
@@ -1766,6 +1766,49 @@ Tue Oct 26 21:40:57 CEST 1999
         </listitem>
       </varlistentry>
 
+
+      <varlistentry>
+        <term><literal>\gexec</literal></term>
+
+        <listitem>
+        <para>
+         Sends the current query input buffer to the server, then treats
+         each column of each row of the query's output (if any) as a SQL
+         statement to be executed.  For example, to create an index on each
+         column of <structname>my_table</>:
+<programlisting>
+=&gt; <userinput>SELECT format('create index on my_table(%I)', attname)</>
+-&gt; <userinput>FROM pg_attribute</>
+-&gt; <userinput>WHERE attrelid = 'my_table'::regclass AND attnum &gt; 0</>
+-&gt; <userinput>ORDER BY attnum</>
+-&gt; <userinput>\gexec</>
+CREATE INDEX
+CREATE INDEX
+CREATE INDEX
+CREATE INDEX
+</programlisting>
+        </para>
+
+        <para>
+         The generated queries are executed in the order in which the rows
+         are returned, and left-to-right within each row if there is more
+         than one column.  NULL fields are ignored.  The generated queries
+         are sent literally to the server for processing, so they cannot be
+         <application>psql</> meta-commands nor contain <application>psql</>
+         variable references.  If any individual query fails, execution of
+         the remaining queries continues
+         unless <varname>ON_ERROR_STOP</varname> is set.  Execution of each
+         query is subject to <varname>ECHO</varname> processing.
+         (Setting <varname>ECHO</varname> to <literal>all</literal>
+         or <literal>queries</literal> is often advisable when
+         using <command>\gexec</>.)  Query logging, single-step mode,
+         timing, and other query execution features apply to each generated
+         query as well.
+        </para>
+        </listitem>
+      </varlistentry>
+
+
       <varlistentry>
         <term><literal>\gset [ <replaceable class="parameter">prefix</replaceable> ]</literal></term>
 
index 3401b5183b056bc2b8439e729bb2b0c16a91a060..1d326a81afe8b1b3a10d04742e0694fb5969fe3b 100644 (file)
@@ -871,6 +871,13 @@ exec_command(const char *cmd,
                status = PSQL_CMD_SEND;
        }
 
+       /* \gexec -- send query and execute each field of result */
+       else if (strcmp(cmd, "gexec") == 0)
+       {
+               pset.gexec_flag = true;
+               status = PSQL_CMD_SEND;
+       }
+
        /* \gset [prefix] -- send query and store result into variables */
        else if (strcmp(cmd, "gset") == 0)
        {
index a2a07fb538e8f0acfe606a6c78400e16a6ced0f5..df3441cc750089e12235798b507103a43a7aa652 100644 (file)
@@ -797,6 +797,76 @@ StoreQueryTuple(const PGresult *result)
 }
 
 
+/*
+ * ExecQueryTuples: assuming query result is OK, execute each query
+ * result field as a SQL statement
+ *
+ * Returns true if successful, false otherwise.
+ */
+static bool
+ExecQueryTuples(const PGresult *result)
+{
+       bool            success = true;
+       int                     nrows = PQntuples(result);
+       int                     ncolumns = PQnfields(result);
+       int                     r,
+                               c;
+
+       /*
+        * We must turn off gexec_flag to avoid infinite recursion.  Note that
+        * this allows ExecQueryUsingCursor to be applied to the individual query
+        * results.  SendQuery prevents it from being applied when fetching the
+        * queries-to-execute, because it can't handle recursion either.
+        */
+       pset.gexec_flag = false;
+
+       for (r = 0; r < nrows; r++)
+       {
+               for (c = 0; c < ncolumns; c++)
+               {
+                       if (!PQgetisnull(result, r, c))
+                       {
+                               const char *query = PQgetvalue(result, r, c);
+
+                               /* Abandon execution if cancel_pressed */
+                               if (cancel_pressed)
+                                       goto loop_exit;
+
+                               /*
+                                * ECHO_ALL mode should echo these queries, but SendQuery
+                                * assumes that MainLoop did that, so we have to do it here.
+                                */
+                               if (pset.echo == PSQL_ECHO_ALL && !pset.singlestep)
+                               {
+                                       puts(query);
+                                       fflush(stdout);
+                               }
+
+                               if (!SendQuery(query))
+                               {
+                                       /* Error - abandon execution if ON_ERROR_STOP */
+                                       success = false;
+                                       if (pset.on_error_stop)
+                                               goto loop_exit;
+                               }
+                       }
+               }
+       }
+
+loop_exit:
+
+       /*
+        * Restore state.  We know gexec_flag was on, else we'd not be here. (We
+        * also know it'll get turned off at end of command, but that's not ours
+        * to do here.)
+        */
+       pset.gexec_flag = true;
+
+       /* Return true if all queries were successful */
+       return success;
+}
+
+
 /*
  * ProcessResult: utility function for use by SendQuery() only
  *
@@ -971,7 +1041,7 @@ PrintQueryStatus(PGresult *results)
 
 
 /*
- * PrintQueryResults: print out (or store) query results as required
+ * PrintQueryResults: print out (or store or execute) query results as required
  *
  * Note: Utility function for use by SendQuery() only.
  *
@@ -989,9 +1059,11 @@ PrintQueryResults(PGresult *results)
        switch (PQresultStatus(results))
        {
                case PGRES_TUPLES_OK:
-                       /* store or print the data ... */
+                       /* store or execute or print the data ... */
                        if (pset.gset_prefix)
                                success = StoreQueryTuple(results);
+                       else if (pset.gexec_flag)
+                               success = ExecQueryTuples(results);
                        else
                                success = PrintQueryTuples(results);
                        /* if it's INSERT/UPDATE/DELETE RETURNING, also print status */
@@ -1068,6 +1140,7 @@ SendQuery(const char *query)
        {
                char            buf[3];
 
+               fflush(stderr);
                printf(_("***(Single step mode: verify command)*******************************************\n"
                                 "%s\n"
                                 "***(press return to proceed or enter x and return to cancel)********************\n"),
@@ -1076,6 +1149,8 @@ SendQuery(const char *query)
                if (fgets(buf, sizeof(buf), stdin) != NULL)
                        if (buf[0] == 'x')
                                goto sendquery_cleanup;
+               if (cancel_pressed)
+                       goto sendquery_cleanup;
        }
        else if (pset.echo == PSQL_ECHO_QUERIES)
        {
@@ -1138,7 +1213,7 @@ SendQuery(const char *query)
                }
        }
 
-       if (pset.fetch_count <= 0 || !is_select_command(query))
+       if (pset.fetch_count <= 0 || pset.gexec_flag || !is_select_command(query))
        {
                /* Default fetch-it-all-and-print mode */
                instr_time      before,
@@ -1278,6 +1353,9 @@ sendquery_cleanup:
                pset.gset_prefix = NULL;
        }
 
+       /* reset \gexec trigger */
+       pset.gexec_flag = false;
+
        return OK;
 }
 
@@ -1423,6 +1501,8 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec)
                        break;
                }
 
+               /* Note we do not deal with \gexec mode here */
+
                ntuples = PQntuples(results);
 
                if (ntuples < fetch_count)
@@ -1499,8 +1579,10 @@ cleanup:
        {
                OK = AcceptResult(results) &&
                        (PQresultStatus(results) == PGRES_COMMAND_OK);
+               ClearOrSaveResult(results);
        }
-       ClearOrSaveResult(results);
+       else
+               PQclear(results);
 
        if (started_txn)
        {
index c6f0993018fa3e9c41a9eb15a788554b5f4dbb27..7549451d21691de9fef52a57f6eea088b7aa7511 100644 (file)
@@ -168,12 +168,13 @@ slashUsage(unsigned short int pager)
         * Use "psql --help=commands | wc" to count correctly.  It's okay to count
         * the USE_READLINE line even in builds without that.
         */
-       output = PageOutput(110, pager ? &(pset.popt.topt) : NULL);
+       output = PageOutput(111, pager ? &(pset.popt.topt) : NULL);
 
        fprintf(output, _("General\n"));
        fprintf(output, _("  \\copyright             show PostgreSQL usage and distribution terms\n"));
        fprintf(output, _("  \\errverbose            show most recent error message at maximum verbosity\n"));
        fprintf(output, _("  \\g [FILE] or ;         execute query (and send results to file or |pipe)\n"));
+       fprintf(output, _("  \\gexec                 execute query, then execute each value in its result\n"));
        fprintf(output, _("  \\gset [PREFIX]         execute query and store results in psql variables\n"));
        fprintf(output, _("  \\q                     quit psql\n"));
        fprintf(output, _("  \\watch [SEC]           execute query every SEC seconds\n"));
index ae30b2e60e3ba333f6994a9296eaf4b7fa1075ba..c69f6ba1ec48a8a1c4d567b43317cb79c2d75cbc 100644 (file)
@@ -92,6 +92,7 @@ typedef struct _psqlSettings
 
        char       *gfname;                     /* one-shot file output argument for \g */
        char       *gset_prefix;        /* one-shot prefix argument for \gset */
+       bool            gexec_flag;             /* one-shot flag to execute query's results */
 
        bool            notty;                  /* stdin or stdout is not a tty (as determined
                                                                 * on startup) */
index 688d92a4520772c471a64b0596affb3fa6d33787..cb8a06d15e44118a613c46c718b4b7a33e4cfd34 100644 (file)
@@ -1281,7 +1281,7 @@ psql_completion(const char *text, int start, int end)
                "\\dm", "\\dn", "\\do", "\\dO", "\\dp", "\\drds", "\\ds", "\\dS",
                "\\dt", "\\dT", "\\dv", "\\du", "\\dx", "\\dy",
                "\\e", "\\echo", "\\ef", "\\encoding", "\\errverbose", "\\ev",
-               "\\f", "\\g", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
+               "\\f", "\\g", "\\gexec", "\\gset", "\\h", "\\help", "\\H", "\\i", "\\ir", "\\l",
                "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
                "\\o", "\\p", "\\password", "\\prompt", "\\pset", "\\q", "\\qecho", "\\r",
                "\\s", "\\set", "\\setenv", "\\sf", "\\sv", "\\t", "\\T",
index 178a8093b7374ce4342a33708c0b0bec6a670653..edcc414630c137f063858f2fe0f5f67a521da106 100644 (file)
@@ -51,6 +51,51 @@ select 10 as test01, 20 as test02 from generate_series(1,3) \gset
 more than one row returned for \gset
 select 10 as test01, 20 as test02 from generate_series(1,0) \gset
 no rows returned for \gset
+\unset FETCH_COUNT
+-- \gexec
+create temporary table gexec_test(a int, b text, c date, d float);
+select format('create index on gexec_test(%I)', attname)
+from pg_attribute
+where attrelid = 'gexec_test'::regclass and attnum > 0
+order by attnum
+\gexec
+create index on gexec_test(a)
+create index on gexec_test(b)
+create index on gexec_test(c)
+create index on gexec_test(d)
+-- \gexec should work in FETCH_COUNT mode too
+-- (though the fetch limit applies to the executed queries not the meta query)
+\set FETCH_COUNT 1
+select 'select 1 as ones', 'select x.y, x.y*2 as double from generate_series(1,4) as x(y)'
+union all
+select 'drop table gexec_test', NULL
+union all
+select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
+\gexec
+select 1 as ones
+ ones 
+------
+    1
+(1 row)
+
+select x.y, x.y*2 as double from generate_series(1,4) as x(y)
+ y | double 
+---+--------
+ 1 |      2
+ 2 |      4
+ 3 |      6
+ 4 |      8
+(4 rows)
+
+drop table gexec_test
+drop table gexec_test
+ERROR:  table "gexec_test" does not exist
+select '2000-01-01'::date as party_over
+ party_over 
+------------
+ 01-01-2000
+(1 row)
+
 \unset FETCH_COUNT
 -- show all pset options
 \pset
index 2f81380e226d93f311ce892c5396fcfc4f758e39..c5b36649491a8134df7e14ca0a8b7098a3646cfb 100644 (file)
@@ -38,6 +38,28 @@ select 10 as test01, 20 as test02 from generate_series(1,0) \gset
 
 \unset FETCH_COUNT
 
+-- \gexec
+
+create temporary table gexec_test(a int, b text, c date, d float);
+select format('create index on gexec_test(%I)', attname)
+from pg_attribute
+where attrelid = 'gexec_test'::regclass and attnum > 0
+order by attnum
+\gexec
+
+-- \gexec should work in FETCH_COUNT mode too
+-- (though the fetch limit applies to the executed queries not the meta query)
+\set FETCH_COUNT 1
+
+select 'select 1 as ones', 'select x.y, x.y*2 as double from generate_series(1,4) as x(y)'
+union all
+select 'drop table gexec_test', NULL
+union all
+select 'drop table gexec_test', 'select ''2000-01-01''::date as party_over'
+\gexec
+
+\unset FETCH_COUNT
+
 -- show all pset options
 \pset