]> granicus.if.org Git - postgresql/commitdiff
psql: Support multiple -c and -f options, and allow mixing them.
authorRobert Haas <rhaas@postgresql.org>
Tue, 8 Dec 2015 19:04:08 +0000 (14:04 -0500)
committerRobert Haas <rhaas@postgresql.org>
Tue, 8 Dec 2015 19:04:08 +0000 (14:04 -0500)
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.

doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/command.c
src/bin/psql/command.h
src/bin/psql/startup.c
src/test/perl/PostgresNode.pm

index e4f72a81b4c1bc047fdf65dcf3b914e44389bb6e..47e9da2c8a5778a9af752a83a7f3fd8692396808 100644 (file)
@@ -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 &lt;&lt;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>
 
index 6e8c62395c0735f434d961dfa02b9b673c38be8b..cf6876b1988e5e227a174f55be695ded845091ea 100644 (file)
@@ -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);
 
index 54385e8f875d4fd20c395fa3bbfc7eeee2e263bd..c817600e187e0a0bfb82ba12f9f6c868842392d8 100644 (file)
@@ -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,
index 4e5021a43df80fae16ab3b2a4cf34434e52dc6c0..7af38fbab0a294555ac7fac40c5fb87a78dd8aa1 100644 (file)
@@ -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)
 {
index 7b6ddf25de9c202ad30101fef45599314ad3ad46..0632be25ca6b25188a990abce4d99c75fa500b11 100644 (file)
@@ -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);