aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/bin/psql/command.c218
-rw-r--r--src/bin/psql/command.h4
-rw-r--r--src/bin/psql/common.c21
-rw-r--r--src/bin/psql/help.c9
-rw-r--r--src/bin/psql/settings.h5
-rw-r--r--src/test/regress/expected/psql.out22
-rw-r--r--src/test/regress/sql/psql.sql7
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_