diff options
author | Robert Haas <rhaas@postgresql.org> | 2015-12-08 14:04:08 -0500 |
---|---|---|
committer | Robert Haas <rhaas@postgresql.org> | 2015-12-08 14:04:08 -0500 |
commit | d5563d7df94488bf0ab52ac0678e8a07e5b8297e (patch) | |
tree | d43ce085b39e22a2e6ca8d621a8d0a165e5f78a7 | |
parent | 385f337c9f39b21dca96ca4770552a10a6d5af24 (diff) | |
download | postgresql-d5563d7df94488bf0ab52ac0678e8a07e5b8297e.tar.gz postgresql-d5563d7df94488bf0ab52ac0678e8a07e5b8297e.zip |
psql: Support multiple -c and -f options, and allow mixing them.
To support this, we must reconcile some historical anomalies in the
behavior of -c. In particular, as a backward-incompatibility, -c no
longer implies --no-psqlrc.
Pavel Stehule (code) and Catalin Iacob (documentation). Review by
Michael Paquier and myself. Proposed behavior per Tom Lane.
-rw-r--r-- | doc/src/sgml/ref/psql-ref.sgml | 88 | ||||
-rw-r--r-- | src/bin/psql/command.c | 34 | ||||
-rw-r--r-- | src/bin/psql/command.h | 2 | ||||
-rw-r--r-- | src/bin/psql/startup.c | 214 | ||||
-rw-r--r-- | src/test/perl/PostgresNode.pm | 2 |
5 files changed, 205 insertions, 135 deletions
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml index e4f72a81b4c..47e9da2c8a5 100644 --- a/doc/src/sgml/ref/psql-ref.sgml +++ b/doc/src/sgml/ref/psql-ref.sgml @@ -38,9 +38,10 @@ PostgreSQL documentation <productname>PostgreSQL</productname>. It enables you to type in queries interactively, issue them to <productname>PostgreSQL</productname>, and see the query results. - Alternatively, input can be from a file. In addition, it provides a - number of meta-commands and various shell-like features to - facilitate writing scripts and automating a wide variety of tasks. + Alternatively, input can be from a file or from command line + arguments. In addition, it provides a number of meta-commands and various + shell-like features to facilitate writing scripts and automating a wide + variety of tasks. </para> </refsect1> @@ -89,11 +90,10 @@ PostgreSQL documentation <term><option>--command=<replaceable class="parameter">command</replaceable></></term> <listitem> <para> - Specifies that <application>psql</application> is to execute one - command string, <replaceable class="parameter">command</replaceable>, - and then exit. This is useful in shell scripts. Start-up files - (<filename>psqlrc</filename> and <filename>~/.psqlrc</filename>) are - ignored with this option. + Specifies that <application>psql</application> is to execute the given + command string, <replaceable class="parameter">command</replaceable>. + This option can be repeated and combined in any order with + the <option>-f</option> option. </para> <para> <replaceable class="parameter">command</replaceable> must be either @@ -102,25 +102,34 @@ PostgreSQL documentation or a single backslash command. Thus you cannot mix <acronym>SQL</acronym> and <application>psql</application> meta-commands with this option. To achieve that, you could - pipe the string into <application>psql</application>, for example: + use repeated <option>-c</option> options or pipe the string + into <application>psql</application>, for example: + <literal>psql -c '\x' -c 'SELECT * FROM foo;'</literal> or <literal>echo '\x \\ SELECT * FROM foo;' | psql</literal>. (<literal>\\</> is the separator meta-command.) </para> <para> - If the command string contains multiple SQL commands, they are - processed in a single transaction, unless there are explicit - <command>BEGIN</>/<command>COMMIT</> commands included in the - string to divide it into multiple transactions. This is - different from the behavior when the same string is fed to - <application>psql</application>'s standard input. Also, only - the result of the last SQL command is returned. + Each command string passed to <option>-c</option> is sent to the server + as a single query. Because of this, the server executes it as a single + transaction, even if a command string contains + multiple <acronym>SQL</acronym> commands, unless there are + explicit <command>BEGIN</>/<command>COMMIT</> commands included in the + string to divide it into multiple transactions. Also, the server only + returns the result of the last <acronym>SQL</acronym> command to the + client. This is different from the behavior when the same string with + multiple <acronym>SQL</acronym> commands is fed + to <application>psql</application>'s standard input because + then <application>psql</application> sends each <acronym>SQL</acronym> + command separately. </para> <para> - Because of these legacy behaviors, putting more than one command in - the <option>-c</option> string often has unexpected results. It's - better to 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: + Putting more than one command in the <option>-c</option> string often + has unexpected results. This is a result of the fact that the whole + string is sent to the server as a single query. + It's better to 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> psql <<EOF \x @@ -185,9 +194,11 @@ EOF <para> Use the file <replaceable class="parameter">filename</replaceable> as the source of commands instead of reading commands interactively. - After the file is processed, <application>psql</application> - terminates. This is in many ways equivalent to the meta-command - <command>\i</command>. + This option can be repeated and combined in any order with + the <option>-c</option> option. After the commands in + every <option>-c</option> command string and <option>-f</option> file + are processed, <application>psql</application> terminates. This option + is in many ways equivalent to the meta-command <command>\i</command>. </para> <para> @@ -539,20 +550,21 @@ EOF <term><option>--single-transaction</option></term> <listitem> <para> - When <application>psql</application> executes a script, adding - this option wraps <command>BEGIN</>/<command>COMMIT</> around the - script to execute it as a single transaction. This ensures that - either all the commands complete successfully, or no changes are - applied. + When <application>psql</application> executes commands from a script + and/or a <option>-c</option> option, adding this option + wraps <command>BEGIN</>/<command>COMMIT</> around all of those + commands as a whole to execute them as a single transaction. This + ensures that either all the commands complete successfully, or no + changes are applied. </para> <para> - If the script itself uses <command>BEGIN</>, <command>COMMIT</>, + If the commands themselves + contain <command>BEGIN</>, <command>COMMIT</>, or <command>ROLLBACK</>, this option will not have the desired - effects. - Also, if the script contains any command that cannot be executed - inside a transaction block, specifying this option will cause that - command (and hence the whole transaction) to fail. + effects. Also, if an individual command cannot be executed inside a + transaction block, specifying this option will cause the whole + transaction to fail. </para> </listitem> </varlistentry> @@ -3725,7 +3737,7 @@ PSQL_EDITOR_LINENUMBER_ARG='--line ' <term><filename>psqlrc</filename> and <filename>~/.psqlrc</filename></term> <listitem> <para> - Unless it is passed an <option>-X</option> or <option>-c</option> option, + Unless it is passed an <option>-X</option> option, <application>psql</application> attempts to read and execute commands from the system-wide startup file (<filename>psqlrc</filename>) and then the user's personal startup file (<filename>~/.psqlrc</filename>), after @@ -3819,6 +3831,12 @@ PSQL_EDITOR_LINENUMBER_ARG='--line ' </para> </listitem> + <listitem> + <para> + Before <productname>PostgreSQL</productname> 9.6, <option>-c</option> + implied <option>-X</option>; this is no longer the case. + </para> + </listitem> </itemizedlist> </refsect1> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 6e8c62395c0..cf6876b1988 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -916,7 +916,7 @@ exec_command(const char *cmd, include_relative = (strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0); expand_tilde(&fname); - success = (process_file(fname, false, include_relative) == EXIT_SUCCESS); + success = (process_file(fname, include_relative) == EXIT_SUCCESS); free(fname); } } @@ -2288,13 +2288,12 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf, * the file from where the currently processed file (if any) is located. */ int -process_file(char *filename, bool single_txn, bool use_relative_path) +process_file(char *filename, bool use_relative_path) { FILE *fd; int result; char *oldfilename; char relpath[MAXPGPATH]; - PGresult *res; if (!filename) { @@ -2339,37 +2338,8 @@ process_file(char *filename, bool single_txn, bool use_relative_path) oldfilename = pset.inputfile; pset.inputfile = filename; - if (single_txn) - { - if ((res = PSQLexec("BEGIN")) == NULL) - { - if (pset.on_error_stop) - { - result = EXIT_USER; - goto error; - } - } - else - PQclear(res); - } - result = MainLoop(fd); - if (single_txn) - { - if ((res = PSQLexec("COMMIT")) == NULL) - { - if (pset.on_error_stop) - { - result = EXIT_USER; - goto error; - } - } - else - PQclear(res); - } - -error: if (fd != stdin) fclose(fd); diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h index 54385e8f875..c817600e187 100644 --- a/src/bin/psql/command.h +++ b/src/bin/psql/command.h @@ -27,7 +27,7 @@ typedef enum _backslashResult extern backslashResult HandleSlashCmds(PsqlScanState scan_state, PQExpBuffer query_buf); -extern int process_file(char *filename, bool single_txn, bool use_relative_path); +extern int process_file(char *filename, bool use_relative_path); extern bool do_pset(const char *param, const char *value, diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c index 4e5021a43df..7af38fbab0a 100644 --- a/src/bin/psql/startup.c +++ b/src/bin/psql/startup.c @@ -50,13 +50,24 @@ PsqlSettings pset; */ enum _actions { - ACT_NOTHING = 0, - ACT_SINGLE_SLASH, - ACT_LIST_DB, ACT_SINGLE_QUERY, + ACT_SINGLE_SLASH, ACT_FILE }; +typedef struct SimpleActionListCell +{ + struct SimpleActionListCell *next; + int action; + char *val; +} SimpleActionListCell; + +typedef struct SimpleActionList +{ + SimpleActionListCell *head; + SimpleActionListCell *tail; +} SimpleActionList; + struct adhoc_opts { char *dbname; @@ -64,11 +75,11 @@ struct adhoc_opts char *port; char *username; char *logfilename; - enum _actions action; - char *action_string; bool no_readline; bool no_psqlrc; bool single_txn; + bool list_dbs; + SimpleActionList actions; }; static void parse_psql_options(int argc, char *argv[], @@ -76,6 +87,8 @@ static void parse_psql_options(int argc, char *argv[], static void process_psqlrc(char *argv0); static void process_psqlrc_file(char *filename); static void showVersion(void); +static void simple_action_list_append(SimpleActionList *list, + int action, const char *val); static void EstablishVariableSpace(void); #define NOPAGER 0 @@ -159,6 +172,9 @@ main(int argc, char *argv[]) SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2); SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3); + options.actions.head = NULL; + options.actions.tail = NULL; + parse_psql_options(argc, argv, &options); /* @@ -166,14 +182,11 @@ main(int argc, char *argv[]) * as if the user had specified "-f -". This lets single-transaction mode * work in this case. */ - if (options.action == ACT_NOTHING && pset.notty) - { - options.action = ACT_FILE; - options.action_string = NULL; - } + if (options.actions.head == NULL && pset.notty) + simple_action_list_append(&options.actions, ACT_FILE, NULL); /* Bail out if -1 was specified but will be ignored. */ - if (options.single_txn && options.action != ACT_FILE && options.action == ACT_NOTHING) + if (options.single_txn && options.actions.head == NULL) { fprintf(stderr, _("%s: -1 can only be used in non-interactive mode\n"), pset.progname); exit(EXIT_FAILURE); @@ -217,8 +230,7 @@ main(int argc, char *argv[]) keywords[3] = "password"; values[3] = password; keywords[4] = "dbname"; - values[4] = (options.action == ACT_LIST_DB && - options.dbname == NULL) ? + values[4] = (options.list_dbs && options.dbname == NULL) ? "postgres" : options.dbname; keywords[5] = "fallback_application_name"; values[5] = pset.progname; @@ -259,7 +271,7 @@ main(int argc, char *argv[]) SyncVariables(); - if (options.action == ACT_LIST_DB) + if (options.list_dbs) { int success; @@ -279,52 +291,91 @@ main(int argc, char *argv[]) pset.progname, options.logfilename, strerror(errno)); } - /* - * Now find something to do - */ + if (!options.no_psqlrc) + process_psqlrc(argv[0]); /* - * process file given by -f + * If any actions were given by caller, process them in the order in + * which they were specified. */ - if (options.action == ACT_FILE) + if (options.actions.head != NULL) { - if (!options.no_psqlrc) - process_psqlrc(argv[0]); + PGresult *res; + SimpleActionListCell *cell; - successResult = process_file(options.action_string, options.single_txn, false); - } + successResult = EXIT_SUCCESS; /* silence compiler */ - /* - * process slash command if one was given to -c - */ - else if (options.action == ACT_SINGLE_SLASH) - { - PsqlScanState scan_state; - - if (pset.echo == PSQL_ECHO_ALL) - puts(options.action_string); - - scan_state = psql_scan_create(); - psql_scan_setup(scan_state, - options.action_string, - strlen(options.action_string)); - - successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR - ? EXIT_SUCCESS : EXIT_FAILURE; + if (options.single_txn) + { + if ((res = PSQLexec("BEGIN")) == NULL) + { + if (pset.on_error_stop) + { + successResult = EXIT_USER; + goto error; + } + } + else + PQclear(res); + } - psql_scan_destroy(scan_state); - } + for (cell = options.actions.head; cell; cell = cell->next) + { + if (cell->action == ACT_SINGLE_QUERY) + { + if (pset.echo == PSQL_ECHO_ALL) + puts(cell->val); + + successResult = SendQuery(cell->val) + ? EXIT_SUCCESS : EXIT_FAILURE; + } + else if (cell->action == ACT_SINGLE_SLASH) + { + PsqlScanState scan_state; + + if (pset.echo == PSQL_ECHO_ALL) + puts(cell->val); + + scan_state = psql_scan_create(); + psql_scan_setup(scan_state, + cell->val, + strlen(cell->val)); + + successResult = HandleSlashCmds(scan_state, NULL) != PSQL_CMD_ERROR + ? EXIT_SUCCESS : EXIT_FAILURE; + + psql_scan_destroy(scan_state); + } + else if (cell->action == ACT_FILE) + { + successResult = process_file(cell->val, false); + } + else + { + /* should never come here */ + Assert(0); + } + + if (successResult != EXIT_SUCCESS && pset.on_error_stop) + break; + } - /* - * If the query given to -c was a normal one, send it - */ - else if (options.action == ACT_SINGLE_QUERY) - { - if (pset.echo == PSQL_ECHO_ALL) - puts(options.action_string); + if (options.single_txn) + { + if ((res = PSQLexec("COMMIT")) == NULL) + { + if (pset.on_error_stop) + { + successResult = EXIT_USER; + goto error; + } + } + else + PQclear(res); + } - successResult = SendQuery(options.action_string) - ? EXIT_SUCCESS : EXIT_FAILURE; +error: + ; } /* @@ -332,9 +383,6 @@ main(int argc, char *argv[]) */ else { - if (!options.no_psqlrc) - process_psqlrc(argv[0]); - connection_warnings(true); if (!pset.quiet) printf(_("Type \"help\" for help.\n\n")); @@ -419,14 +467,14 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options) SetVariable(pset.vars, "ECHO", "errors"); break; case 'c': - options->action_string = pg_strdup(optarg); if (optarg[0] == '\\') - { - options->action = ACT_SINGLE_SLASH; - options->action_string++; - } + simple_action_list_append(&options->actions, + ACT_SINGLE_SLASH, + pstrdup(optarg + 1)); else - options->action = ACT_SINGLE_QUERY; + simple_action_list_append(&options->actions, + ACT_SINGLE_QUERY, + pstrdup(optarg)); break; case 'd': options->dbname = pg_strdup(optarg); @@ -438,8 +486,9 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options) SetVariableBool(pset.vars, "ECHO_HIDDEN"); break; case 'f': - options->action = ACT_FILE; - options->action_string = pg_strdup(optarg); + simple_action_list_append(&options->actions, + ACT_FILE, + pg_strdup(optarg)); break; case 'F': pset.popt.topt.fieldSep.separator = pg_strdup(optarg); @@ -452,7 +501,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options) pset.popt.topt.format = PRINT_HTML; break; case 'l': - options->action = ACT_LIST_DB; + options->list_dbs = true; break; case 'L': options->logfilename = pg_strdup(optarg); @@ -675,11 +724,11 @@ process_psqlrc_file(char *filename) /* check for minor version first, then major, then no version */ if (access(psqlrc_minor, R_OK) == 0) - (void) process_file(psqlrc_minor, false, false); + (void) process_file(psqlrc_minor, false); else if (access(psqlrc_major, R_OK) == 0) - (void) process_file(psqlrc_major, false, false); + (void) process_file(psqlrc_major, false); else if (access(filename, R_OK) == 0) - (void) process_file(filename, false, false); + (void) process_file(filename, false); free(psqlrc_minor); free(psqlrc_major); @@ -893,6 +942,39 @@ show_context_hook(const char *newval) } +/* + * Support for list of actions. SimpleStringList cannot be used due possible + * combination different actions with the requirement to save the order. + */ +static void +simple_action_list_append(SimpleActionList *list, int action, const char *val) +{ + SimpleActionListCell *cell; + size_t vallen = 0; + + if (val) + vallen = strlen(val); + + cell = (SimpleActionListCell *) + pg_malloc(offsetof(SimpleActionListCell, val) + vallen + 1); + + cell->next = NULL; + cell->action = action; + if (val) + { + cell->val = pg_malloc(vallen + 1); + strcpy(cell->val, val); + } + else + cell->val = NULL; + + if (list->tail) + list->tail->next = cell; + else + list->head = cell; + list->tail = cell; +} + static void EstablishVariableSpace(void) { diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm index 7b6ddf25de9..0632be25ca6 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -400,7 +400,7 @@ sub poll_query_until while ($attempts < $max_attempts) { my $cmd = - [ 'psql', '-At', '-c', $query, '-d', $self->connstr($dbname) ]; + [ 'psql', '-XAt', '-c', $query, '-d', $self->connstr($dbname) ]; my $result = IPC::Run::run $cmd, '>', \$stdout, '2>', \$stderr; chomp($stdout); |