diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bin/psql/command.c | 218 | ||||
-rw-r--r-- | src/bin/psql/command.h | 4 | ||||
-rw-r--r-- | src/bin/psql/common.c | 21 | ||||
-rw-r--r-- | src/bin/psql/help.c | 9 | ||||
-rw-r--r-- | src/bin/psql/settings.h | 5 | ||||
-rw-r--r-- | src/test/regress/expected/psql.out | 22 | ||||
-rw-r--r-- | src/test/regress/sql/psql.sql | 7 |
7 files changed, 253 insertions, 33 deletions
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index db31fa87536..a5160f91de6 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -86,6 +86,10 @@ static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool ac static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd); +static backslashResult process_command_g_options(char *first_option, + PsqlScanState scan_state, + bool active_branch, + const char *cmd); static backslashResult exec_command_gdesc(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch); static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch); @@ -160,8 +164,8 @@ static void minimal_error_message(PGresult *res); static void printSSLInfo(void); static void printGSSInfo(void); -static bool printPsetInfo(const char *param, struct printQueryOpt *popt); -static char *pset_value_string(const char *param, struct printQueryOpt *popt); +static bool printPsetInfo(const char *param, printQueryOpt *popt); +static char *pset_value_string(const char *param, printQueryOpt *popt); #ifdef WIN32 static void checkWin32Codepage(void); @@ -1280,19 +1284,40 @@ exec_command_f(PsqlScanState scan_state, bool active_branch) } /* - * \g [filename] -- send query, optionally with output to file/pipe - * \gx [filename] -- same as \g, with expanded mode forced + * \g [(pset-option[=pset-value] ...)] [filename/shell-command] + * \gx [(pset-option[=pset-value] ...)] [filename/shell-command] + * + * Send the current query. If pset options are specified, they are made + * active just for this query. If a filename or pipe command is given, + * the query output goes there. \gx implicitly sets "expanded=on" along + * with any other pset options that are specified. */ static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd) { backslashResult status = PSQL_CMD_SKIP_LINE; + char *fname; - if (active_branch) + /* + * Because the option processing for this is fairly complicated, we do it + * and then decide whether the branch is active. + */ + fname = psql_scan_slash_option(scan_state, + OT_FILEPIPE, NULL, false); + + if (fname && fname[0] == '(') { - char *fname = psql_scan_slash_option(scan_state, - OT_FILEPIPE, NULL, false); + /* Consume pset options through trailing ')' ... */ + status = process_command_g_options(fname + 1, scan_state, + active_branch, cmd); + free(fname); + /* ... and again attempt to scan the filename. */ + fname = psql_scan_slash_option(scan_state, + OT_FILEPIPE, NULL, false); + } + if (status == PSQL_CMD_SKIP_LINE && active_branch) + { if (!fname) pset.gfname = NULL; else @@ -1300,18 +1325,99 @@ exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd) expand_tilde(&fname); pset.gfname = pg_strdup(fname); } - free(fname); if (strcmp(cmd, "gx") == 0) - pset.g_expanded = true; + { + /* save settings if not done already, then force expanded=on */ + if (pset.gsavepopt == NULL) + pset.gsavepopt = savePsetInfo(&pset.popt); + pset.popt.topt.expanded = 1; + } status = PSQL_CMD_SEND; } - else - ignore_slash_filepipe(scan_state); + + free(fname); return status; } /* + * Process parenthesized pset options for \g + * + * Note: okay to modify first_option, but not to free it; caller does that + */ +static backslashResult +process_command_g_options(char *first_option, PsqlScanState scan_state, + bool active_branch, const char *cmd) +{ + bool success = true; + bool found_r_paren = false; + + do + { + char *option; + size_t optlen; + + /* If not first time through, collect a new option */ + if (first_option) + option = first_option; + else + { + option = psql_scan_slash_option(scan_state, + OT_NORMAL, NULL, false); + if (!option) + { + if (active_branch) + { + pg_log_error("\\%s: missing right parenthesis", cmd); + success = false; + } + break; + } + } + + /* Check for terminating right paren, and remove it from string */ + optlen = strlen(option); + if (optlen > 0 && option[optlen - 1] == ')') + { + option[--optlen] = '\0'; + found_r_paren = true; + } + + /* If there was anything besides parentheses, parse/execute it */ + if (optlen > 0) + { + /* We can have either "name" or "name=value" */ + char *valptr = strchr(option, '='); + + if (valptr) + *valptr++ = '\0'; + if (active_branch) + { + /* save settings if not done already, then apply option */ + if (pset.gsavepopt == NULL) + pset.gsavepopt = savePsetInfo(&pset.popt); + success &= do_pset(option, valptr, &pset.popt, true); + } + } + + /* Clean up after this option. We should not free first_option. */ + if (first_option) + first_option = NULL; + else + free(option); + } while (!found_r_paren); + + /* If we failed after already changing some options, undo side-effects */ + if (!success && active_branch && pset.gsavepopt) + { + restorePsetInfo(&pset.popt, pset.gsavepopt); + pset.gsavepopt = NULL; + } + + return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR; +} + +/* * \gdesc -- describe query result */ static backslashResult @@ -3785,6 +3891,17 @@ _unicode_linestyle2string(int linestyle) /* * do_pset * + * Performs the assignment "param = value", where value could be NULL; + * for some params that has an effect such as inversion, for others + * it does nothing. + * + * Adjusts the state of the formatting options at *popt. (In practice that + * is always pset.popt, but maybe someday it could be different.) + * + * If successful and quiet is false, then invokes printPsetInfo() to report + * the change. + * + * Returns true if successful, else false (eg for invalid param or value). */ bool do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) @@ -4109,9 +4226,11 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet) return true; } - +/* + * printPsetInfo: print the state of the "param" formatting parameter in popt. + */ static bool -printPsetInfo(const char *param, struct printQueryOpt *popt) +printPsetInfo(const char *param, printQueryOpt *popt) { Assert(param != NULL); @@ -4292,6 +4411,77 @@ printPsetInfo(const char *param, struct printQueryOpt *popt) return true; } +/* + * savePsetInfo: make a malloc'd copy of the data in *popt. + * + * Possibly this should be somewhere else, but it's a bit specific to psql. + */ +printQueryOpt * +savePsetInfo(const printQueryOpt *popt) +{ + printQueryOpt *save; + + save = (printQueryOpt *) pg_malloc(sizeof(printQueryOpt)); + + /* Flat-copy all the scalar fields, then duplicate sub-structures. */ + memcpy(save, popt, sizeof(printQueryOpt)); + + /* topt.line_style points to const data that need not be duplicated */ + if (popt->topt.fieldSep.separator) + save->topt.fieldSep.separator = pg_strdup(popt->topt.fieldSep.separator); + if (popt->topt.recordSep.separator) + save->topt.recordSep.separator = pg_strdup(popt->topt.recordSep.separator); + if (popt->topt.tableAttr) + save->topt.tableAttr = pg_strdup(popt->topt.tableAttr); + if (popt->nullPrint) + save->nullPrint = pg_strdup(popt->nullPrint); + if (popt->title) + save->title = pg_strdup(popt->title); + + /* + * footers and translate_columns are never set in psql's print settings, + * so we needn't write code to duplicate them. + */ + Assert(popt->footers == NULL); + Assert(popt->translate_columns == NULL); + + return save; +} + +/* + * restorePsetInfo: restore *popt from the previously-saved copy *save, + * then free *save. + */ +void +restorePsetInfo(printQueryOpt *popt, printQueryOpt *save) +{ + /* Free all the old data we're about to overwrite the pointers to. */ + + /* topt.line_style points to const data that need not be duplicated */ + if (popt->topt.fieldSep.separator) + free(popt->topt.fieldSep.separator); + if (popt->topt.recordSep.separator) + free(popt->topt.recordSep.separator); + if (popt->topt.tableAttr) + free(popt->topt.tableAttr); + if (popt->nullPrint) + free(popt->nullPrint); + if (popt->title) + free(popt->title); + + /* + * footers and translate_columns are never set in psql's print settings, + * so we needn't write code to duplicate them. + */ + Assert(popt->footers == NULL); + Assert(popt->translate_columns == NULL); + + /* Now we may flat-copy all the fields, including pointers. */ + memcpy(popt, save, sizeof(printQueryOpt)); + + /* Lastly, free "save" ... but its sub-structures now belong to popt. */ + free(save); +} static const char * pset_bool_string(bool val) @@ -4339,7 +4529,7 @@ pset_quoted_string(const char *str) * output that produces the correct setting when fed back into \pset. */ static char * -pset_value_string(const char *param, struct printQueryOpt *popt) +pset_value_string(const char *param, printQueryOpt *popt) { Assert(param != NULL); diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h index 61138381438..006832f1022 100644 --- a/src/bin/psql/command.h +++ b/src/bin/psql/command.h @@ -36,6 +36,10 @@ extern bool do_pset(const char *param, printQueryOpt *popt, bool quiet); +extern printQueryOpt *savePsetInfo(const printQueryOpt *popt); + +extern void restorePsetInfo(printQueryOpt *popt, printQueryOpt *save); + extern void connection_warnings(bool in_startup); extern void SyncVariables(void); diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c index 396a40089ce..621a33f7e83 100644 --- a/src/bin/psql/common.c +++ b/src/bin/psql/common.c @@ -707,13 +707,8 @@ PrintNotifications(void) static bool PrintQueryTuples(const PGresult *results) { - printQueryOpt my_popt = pset.popt; bool result = true; - /* one-shot expanded output requested via \gx */ - if (pset.g_expanded) - my_popt.topt.expanded = 1; - /* write output to \g argument, if any */ if (pset.gfname) { @@ -725,7 +720,7 @@ PrintQueryTuples(const PGresult *results) if (is_pipe) disable_sigpipe_trap(); - printQuery(results, &my_popt, fout, false, pset.logfile); + printQuery(results, &pset.popt, fout, false, pset.logfile); if (ferror(fout)) { pg_log_error("could not print result table: %m"); @@ -742,7 +737,7 @@ PrintQueryTuples(const PGresult *results) } else { - printQuery(results, &my_popt, pset.queryFout, false, pset.logfile); + printQuery(results, &pset.popt, pset.queryFout, false, pset.logfile); if (ferror(pset.queryFout)) { pg_log_error("could not print result table: %m"); @@ -1418,8 +1413,12 @@ sendquery_cleanup: pset.gfname = NULL; } - /* reset \gx's expanded-mode flag */ - pset.g_expanded = false; + /* restore print settings if \g changed them */ + if (pset.gsavepopt) + { + restorePsetInfo(&pset.popt, pset.gsavepopt); + pset.gsavepopt = NULL; + } /* reset \gset trigger */ if (pset.gset_prefix) @@ -1646,10 +1645,6 @@ ExecQueryUsingCursor(const char *query, double *elapsed_msec) "FETCH FORWARD %d FROM _psql_cursor", fetch_count); - /* one-shot expanded output requested via \gx */ - if (pset.g_expanded) - my_popt.topt.expanded = 1; - /* prepare to write output to \g argument, if any */ if (pset.gfname) { diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c index 9a18cb30594..e7509480429 100644 --- a/src/bin/psql/help.c +++ b/src/bin/psql/help.c @@ -68,7 +68,7 @@ usage(unsigned short int pager) * Keep this line count in sync with the number of lines printed below! * Use "psql --help=options | wc" to count correctly. */ - output = PageOutput(62, pager ? &(pset.popt.topt) : NULL); + output = PageOutput(63, pager ? &(pset.popt.topt) : NULL); fprintf(output, _("psql is the PostgreSQL interactive terminal.\n\n")); fprintf(output, _("Usage:\n")); @@ -169,17 +169,18 @@ 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(128, pager ? &(pset.popt.topt) : NULL); + output = PageOutput(133, pager ? &(pset.popt.topt) : NULL); fprintf(output, _("General\n")); fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n")); fprintf(output, _(" \\crosstabview [COLUMNS] execute query and display results in crosstab\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, _(" \\g [(OPTIONS)] [FILE] execute query (and send results to file or |pipe);\n")); + fprintf(output, _(" \\g with no arguments is equivalent to a semicolon\n")); fprintf(output, _(" \\gdesc describe result of query, without executing it\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, _(" \\gx [FILE] as \\g, but forces expanded output mode\n")); + fprintf(output, _(" \\gx [(OPTIONS)] [FILE] as \\g, but forces expanded output mode\n")); fprintf(output, _(" \\q quit psql\n")); fprintf(output, _(" \\watch [SEC] execute query every SEC seconds\n")); fprintf(output, "\n"); diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h index 2b384a38a1b..97941aa10c6 100644 --- a/src/bin/psql/settings.h +++ b/src/bin/psql/settings.h @@ -88,10 +88,11 @@ typedef struct _psqlSettings PGresult *last_error_result; /* most recent error result, if any */ - printQueryOpt popt; + printQueryOpt popt; /* The active print format settings */ char *gfname; /* one-shot file output argument for \g */ - bool g_expanded; /* one-shot expanded output requested via \gx */ + printQueryOpt *gsavepopt; /* if not null, saved print format settings */ + char *gset_prefix; /* one-shot prefix argument for \gset */ bool gdesc_flag; /* one-shot request to describe query results */ bool gexec_flag; /* one-shot request to execute query results */ diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out index 2423ae2f37f..0b990fd814a 100644 --- a/src/test/regress/expected/psql.out +++ b/src/test/regress/expected/psql.out @@ -76,6 +76,28 @@ four | 4 (1 row) \unset FETCH_COUNT +-- \g/\gx with pset options +SELECT 1 as one, 2 as two \g (format=csv csv_fieldsep='\t') +one two +1 2 +\g + one | two +-----+----- + 1 | 2 +(1 row) + +SELECT 1 as one, 2 as two \gx (title='foo bar') +foo bar +-[ RECORD 1 ] +one | 1 +two | 2 + +\g + one | two +-----+----- + 1 | 2 +(1 row) + -- \gset select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_ \echo :pref01_test01 :pref01_test02 :pref01_test03 diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql index 3c876d26992..d462c357bd5 100644 --- a/src/test/regress/sql/psql.sql +++ b/src/test/regress/sql/psql.sql @@ -38,6 +38,13 @@ SELECT 3 as three, 4 as four \gx \unset FETCH_COUNT +-- \g/\gx with pset options + +SELECT 1 as one, 2 as two \g (format=csv csv_fieldsep='\t') +\g +SELECT 1 as one, 2 as two \gx (title='foo bar') +\g + -- \gset select 10 as test01, 20 as test02, 'Hello' as test03 \gset pref01_ |