2015-11-30 15:17 GMT+01:00 Michael Paquier <michael.paqu...@gmail.com>:
> On Thu, Nov 26, 2015 at 4:21 AM, Pavel Stehule wrote: > > Attached patch per Tom Lane proposal. > > > > * multiple -c -f options are supported, the order of options is respected > > * the statements for one -c options are executed in transactions > > * Iacob's doc patch merged > > enum _actions > { > ACT_NOTHING = 0, > - ACT_SINGLE_SLASH, > ACT_LIST_DB, > - ACT_SINGLE_QUERY, > - ACT_FILE > + ACT_FILE_STDIN > }; > > Removing some items from the list of potential actions and creating a > new sublist listing action types is a bit weird. Why not grouping them > together and allow for example -l as well in the list of things that > is considered as a repeatable action? It seems to me that we could > simplify the code this way, and instead of ACT_NOTHING we could check > if the list of actions is empty or not. > fixed Regards Pavel > -- > Michael >
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml new file mode 100644 index 5899bb4..2928c92 *** a/doc/src/sgml/ref/psql-ref.sgml --- b/doc/src/sgml/ref/psql-ref.sgml *************** PostgreSQL documentation *** 38,46 **** <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. </para> </refsect1> --- 38,47 ---- <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 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> *************** PostgreSQL documentation *** 89,126 **** <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. </para> <para> ! <replaceable class="parameter">command</replaceable> must be either ! a command string that is completely parsable by the server (i.e., ! it contains no <application>psql</application>-specific features), ! 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: ! <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. </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: <programlisting> psql <<EOF \x --- 90,134 ---- <term><option>--command=<replaceable class="parameter">command</replaceable></></term> <listitem> <para> ! 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 a ! command string that is completely parsable by the server (i.e., it ! contains no <application>psql</application>-specific features), 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 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> ! 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 the execution as a single query, putting more than one ! command in the <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, ! either using <application>echo</application> as illustrated above, or ! via a shell here-document, for example: <programlisting> psql <<EOF \x *************** EOF *** 183,193 **** <term><option>--file=<replaceable class="parameter">filename</replaceable></></term> <listitem> <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>. </para> <para> --- 191,203 ---- <term><option>--file=<replaceable class="parameter">filename</replaceable></></term> <listitem> <para> ! Use the file <replaceable class="parameter">filename</replaceable> as ! the source of commands instead of reading commands interactively. 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> *************** EOF *** 539,558 **** <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. </para> <para> ! If the script itself uses <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. </para> </listitem> </varlistentry> --- 549,569 ---- <term><option>--single-transaction</option></term> <listitem> <para> ! 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 commands themselves ! contain <command>BEGIN</>, <command>COMMIT</>, or <command>ROLLBACK</>, this option will not have the desired ! 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> *************** PSQL_EDITOR_LINENUMBER_ARG='--line ' *** 3725,3731 **** <term><filename>psqlrc</filename> and <filename>~/.psqlrc</filename></term> <listitem> <para> ! Unless it is passed an <option>-X</option> or <option>-c</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 --- 3736,3742 ---- <term><filename>psqlrc</filename> and <filename>~/.psqlrc</filename></term> <listitem> <para> ! 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 *************** PSQL_EDITOR_LINENUMBER_ARG='--line ' *** 3819,3824 **** --- 3830,3842 ---- </para> </listitem> + <listitem> + <para> + Before <productname>PostgreSQL</productname> 9.6, <option>-c</option> + used to imply <option>-X</option> and therefore it wouldn't + read <filename>psqlrc</filename> files, that is no longer the case. + </para> + </listitem> </itemizedlist> </refsect1> diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c new file mode 100644 index 438a4ec..d642803 *** a/src/bin/psql/command.c --- b/src/bin/psql/command.c *************** exec_command(const char *cmd, *** 916,922 **** include_relative = (strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0); expand_tilde(&fname); ! success = (process_file(fname, false, include_relative) == EXIT_SUCCESS); free(fname); } } --- 916,922 ---- include_relative = (strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0); expand_tilde(&fname); ! success = (process_file(fname, include_relative) == EXIT_SUCCESS); free(fname); } } *************** do_edit(const char *filename_arg, PQExpB *** 2284,2296 **** * the file from where the currently processed file (if any) is located. */ int ! process_file(char *filename, bool single_txn, bool use_relative_path) { FILE *fd; int result; char *oldfilename; char relpath[MAXPGPATH]; - PGresult *res; if (!filename) { --- 2284,2295 ---- * the file from where the currently processed file (if any) is located. */ int ! process_file(char *filename, bool use_relative_path) { FILE *fd; int result; char *oldfilename; char relpath[MAXPGPATH]; if (!filename) { *************** process_file(char *filename, bool single *** 2335,2371 **** 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); --- 2334,2341 ---- diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h new file mode 100644 index 54385e8..c817600 *** a/src/bin/psql/command.h --- b/src/bin/psql/command.h *************** typedef enum _backslashResult *** 27,33 **** extern backslashResult HandleSlashCmds(PsqlScanState scan_state, PQExpBuffer query_buf); ! extern int process_file(char *filename, bool single_txn, bool use_relative_path); extern bool do_pset(const char *param, const char *value, --- 27,33 ---- extern backslashResult HandleSlashCmds(PsqlScanState scan_state, PQExpBuffer query_buf); ! 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/common.c b/src/bin/psql/common.c new file mode 100644 index 0e266a3..96e5225 *** a/src/bin/psql/common.c --- b/src/bin/psql/common.c *************** recognized_connection_string(const char *** 1886,1888 **** --- 1886,1911 ---- { return uri_prefix_length(connstr) != 0 || strchr(connstr, '=') != NULL; } + + /* + * Support for list of actions. The SimpleStringList cannot be used due possible + * combination different actions with the requirement to save the order. + */ + void + simple_action_list_append(SimpleActionList *list, int action, const char *val) + { + SimpleActionListCell *cell; + + cell = (SimpleActionListCell *) + pg_malloc(offsetof(SimpleActionListCell, val) + strlen(val) + 1); + + cell->next = NULL; + cell->action = action; + strcpy(cell->val, val); + + if (list->tail) + list->tail->next = cell; + else + list->head = cell; + list->tail = cell; + } diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h new file mode 100644 index caf31d1..6bdee98 *** a/src/bin/psql/common.h --- b/src/bin/psql/common.h *************** *** 14,21 **** --- 14,44 ---- #include "print.h" + enum _actions + { + ACT_LIST_DB, + ACT_SINGLE_QUERY, + ACT_SINGLE_SLASH, + ACT_FILE + }; + + typedef struct SimpleActionListCell + { + struct SimpleActionListCell *next; + int action; + char val[FLEXIBLE_ARRAY_MEMBER]; + } SimpleActionListCell; + + typedef struct SimpleActionList + { + SimpleActionListCell *head; + SimpleActionListCell *tail; + } SimpleActionList; + #define atooid(x) ((Oid) strtoul((x), NULL, 10)) + extern void simple_action_list_append(SimpleActionList *list, int action, const char *val); + extern bool setQFout(const char *fname); extern void psql_error(const char *fmt,...) pg_attribute_printf(1, 2); diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c new file mode 100644 index 7aa997d..3f909df *** a/src/bin/psql/startup.c --- b/src/bin/psql/startup.c *************** PsqlSettings pset; *** 44,62 **** #define PSQLRC "psqlrc.conf" #endif - /* - * Structures to pass information between the option parsing routine - * and the main function - */ - enum _actions - { - ACT_NOTHING = 0, - ACT_SINGLE_SLASH, - ACT_LIST_DB, - ACT_SINGLE_QUERY, - ACT_FILE - }; - struct adhoc_opts { char *dbname; --- 44,49 ---- *************** struct adhoc_opts *** 64,76 **** char *port; char *username; char *logfilename; - enum _actions action; char *action_string; bool no_readline; bool no_psqlrc; bool single_txn; }; static void parse_psql_options(int argc, char *argv[], struct adhoc_opts * options); static void process_psqlrc(char *argv0); --- 51,65 ---- char *port; char *username; char *logfilename; char *action_string; bool no_readline; bool no_psqlrc; bool single_txn; + bool use_stdin; + SimpleActionList actions; }; + static bool use_default_dbname(struct adhoc_opts *options); static void parse_psql_options(int argc, char *argv[], struct adhoc_opts * options); static void process_psqlrc(char *argv0); *************** main(int argc, char *argv[]) *** 159,164 **** --- 148,157 ---- SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2); SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3); + options.actions.head = NULL; + options.actions.tail = NULL; + options.use_stdin = false; + parse_psql_options(argc, argv, &options); /* *************** main(int argc, char *argv[]) *** 166,179 **** * 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; ! } /* Bail out if -1 was specified but will be ignored. */ ! if (options.single_txn && options.action != ACT_FILE && options.action == ACT_NOTHING) { fprintf(stderr, _("%s: -1 can only be used in non-interactive mode\n"), pset.progname); exit(EXIT_FAILURE); --- 159,168 ---- * as if the user had specified "-f -". This lets single-transaction mode * work in this case. */ ! options.use_stdin = options.actions.head == NULL && pset.notty; /* Bail out if -1 was specified but will be ignored. */ ! if (options.single_txn && !options.use_stdin && options.actions.head == NULL) { fprintf(stderr, _("%s: -1 can only be used in non-interactive mode\n"), pset.progname); exit(EXIT_FAILURE); *************** main(int argc, char *argv[]) *** 217,224 **** keywords[3] = "password"; values[3] = password; keywords[4] = "dbname"; ! values[4] = (options.action == ACT_LIST_DB && ! options.dbname == NULL) ? "postgres" : options.dbname; keywords[5] = "fallback_application_name"; values[5] = pset.progname; --- 206,212 ---- keywords[3] = "password"; values[3] = password; keywords[4] = "dbname"; ! values[4] = use_default_dbname(&options) ? "postgres" : options.dbname; keywords[5] = "fallback_application_name"; values[5] = pset.progname; *************** main(int argc, char *argv[]) *** 259,276 **** SyncVariables(); - if (options.action == ACT_LIST_DB) - { - int success; - - if (!options.no_psqlrc) - process_psqlrc(argv[0]); - - success = listAllDbs(NULL, false); - PQfinish(pset.db); - exit(success ? EXIT_SUCCESS : EXIT_FAILURE); - } - if (options.logfilename) { pset.logfile = fopen(options.logfilename, "a"); --- 247,252 ---- *************** main(int argc, char *argv[]) *** 279,332 **** pset.progname, options.logfilename, strerror(errno)); } ! /* ! * Now find something to do ! */ /* ! * process file given by -f */ ! if (options.action == ACT_FILE) { ! if (!options.no_psqlrc) ! process_psqlrc(argv[0]); ! successResult = process_file(options.action_string, options.single_txn, false); ! } ! /* ! * 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; ! psql_scan_destroy(scan_state); ! } ! /* ! * 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); ! successResult = SendQuery(options.action_string) ! ? EXIT_SUCCESS : EXIT_FAILURE; ! } /* * or otherwise enter interactive main loop */ --- 255,348 ---- pset.progname, options.logfilename, strerror(errno)); } ! if (!options.no_psqlrc) ! process_psqlrc(argv[0]); /* ! * Now find something to do */ ! if (options.actions.head) { ! PGresult *res; ! SimpleActionListCell *cell; ! successResult = EXIT_SUCCESS; /* be compiler quiete */ ! if (options.single_txn) ! { ! if ((res = PSQLexec("BEGIN")) == NULL) ! { ! if (pset.on_error_stop) ! { ! successResult = EXIT_USER; ! goto error; ! } ! } ! else ! PQclear(res); ! } ! for (cell = options.actions.head; cell; cell = cell->next) ! { ! if (cell->action == ACT_LIST_DB) ! { ! successResult = listAllDbs(NULL, false); ! } ! else 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 ! { ! /* ACT_FILE */ ! successResult = process_file(cell->val, false); ! } ! ! if (successResult != EXIT_SUCCESS && pset.on_error_stop) ! break; ! } ! ! if (options.use_stdin) ! successResult = process_file(NULL, false); ! ! if (options.single_txn) ! { ! if ((res = PSQLexec("COMMIT")) == NULL) ! { ! if (pset.on_error_stop) ! { ! successResult = EXIT_USER; ! goto error; ! } ! } ! else ! PQclear(res); ! } + error: + ; + } /* * or otherwise enter interactive main loop */ *************** main(int argc, char *argv[]) *** 351,356 **** --- 367,392 ---- return successResult; } + /* + * Default dbname "postgres" can be used when no other dbname + * is entered and when only action ACT_LIST_DB is used + */ + static bool + use_default_dbname(struct adhoc_opts *options) + { + SimpleActionListCell *cell; + + if (options->dbname != NULL) + return false; + + for (cell = options->actions.head; cell; cell = cell->next) + { + if (cell->action != ACT_LIST_DB) + return false; + } + + return true; + } /* * Parse command line options *************** parse_psql_options(int argc, char *argv[ *** 419,432 **** 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++; ! } else ! options->action = ACT_SINGLE_QUERY; break; case 'd': options->dbname = pg_strdup(optarg); --- 455,468 ---- SetVariable(pset.vars, "ECHO", "errors"); break; case 'c': if (optarg[0] == '\\') ! simple_action_list_append(&options->actions, ! ACT_SINGLE_SLASH, ! pstrdup(optarg + 1)); else ! simple_action_list_append(&options->actions, ! ACT_SINGLE_QUERY, ! pstrdup(optarg)); break; case 'd': options->dbname = pg_strdup(optarg); *************** parse_psql_options(int argc, char *argv[ *** 438,445 **** SetVariableBool(pset.vars, "ECHO_HIDDEN"); break; case 'f': ! options->action = ACT_FILE; ! options->action_string = pg_strdup(optarg); break; case 'F': pset.popt.topt.fieldSep.separator = pg_strdup(optarg); --- 474,482 ---- SetVariableBool(pset.vars, "ECHO_HIDDEN"); break; case 'f': ! simple_action_list_append(&options->actions, ! ACT_FILE, ! pg_strdup(optarg)); break; case 'F': pset.popt.topt.fieldSep.separator = pg_strdup(optarg); *************** parse_psql_options(int argc, char *argv[ *** 452,458 **** pset.popt.topt.format = PRINT_HTML; break; case 'l': ! options->action = ACT_LIST_DB; break; case 'L': options->logfilename = pg_strdup(optarg); --- 489,497 ---- pset.popt.topt.format = PRINT_HTML; break; case 'l': ! simple_action_list_append(&options->actions, ! ACT_LIST_DB, ! pg_strdup("")); break; case 'L': options->logfilename = pg_strdup(optarg); *************** process_psqlrc_file(char *filename) *** 674,684 **** /* check for minor version first, then major, then no version */ if (access(psqlrc_minor, R_OK) == 0) ! (void) process_file(psqlrc_minor, false, false); else if (access(psqlrc_major, R_OK) == 0) ! (void) process_file(psqlrc_major, false, false); else if (access(filename, R_OK) == 0) ! (void) process_file(filename, false, false); free(psqlrc_minor); free(psqlrc_major); --- 713,723 ---- /* check for minor version first, then major, then no version */ if (access(psqlrc_minor, R_OK) == 0) ! (void) process_file(psqlrc_minor, false); else if (access(psqlrc_major, R_OK) == 0) ! (void) process_file(psqlrc_major, false); else if (access(filename, R_OK) == 0) ! (void) process_file(filename, false); free(psqlrc_minor); free(psqlrc_major);
-- Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org) To make changes to your subscription: http://www.postgresql.org/mailpref/pgsql-hackers