diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index e8afc247af..d8b9a03ee0 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -1766,6 +1766,49 @@ Tue Oct 26 21:40:57 CEST 1999 + + + \gexec + + + + 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 my_table: + +=> SELECT format('create index on my_table(%I)', attname) +-> FROM pg_attribute +-> WHERE attrelid = 'my_table'::regclass AND attnum > 0 +-> ORDER BY attnum +-> \gexec +CREATE INDEX +CREATE INDEX +CREATE INDEX +CREATE INDEX + + + + + 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 + psql meta-commands nor contain psql + variable references. If any individual query fails, execution of + the remaining queries continues + unless ON_ERROR_STOP is set. Execution of each + query is subject to ECHO processing. + (Setting ECHO to all + or queries is often advisable when + using \gexec.) Query logging, single-step mode, + timing, and other query execution features apply to each generated + query as well. + + + + + \gset [ prefix ] diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 3401b5183b..1d326a81af 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -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) { diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index a2a07fb538..df3441cc75 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -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) { diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index c6f0993018..7549451d21 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -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")); diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h index ae30b2e60e..c69f6ba1ec 100644 --- a/src/bin/psql/settings.h +++ b/src/bin/psql/settings.h @@ -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) */ diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c index 688d92a452..cb8a06d15e 100644 --- a/src/bin/psql/tab-complete.c +++ b/src/bin/psql/tab-complete.c @@ -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", diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 178a8093b7..edcc414630 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -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 diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 2f81380e22..c5b3664949 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -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