diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 402c4b1b26..ba480c7bb6 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2199,6 +2199,16 @@ CREATE INDEX
+
+ \gfmt format [ filename ]
+ \gfmt format [ |command ]
+
+
+ \gfmt is equivalent to \g, but
+ forces specified format. See \pset format.
+
+
+
\gset [ prefix ]
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index abb18a19c2..d724a5eb16 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -163,6 +163,8 @@ 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 format_number(const char *value, int vallen, enum printFormat *numformat);
+
#ifdef WIN32
static void checkWin32Codepage(void);
#endif
@@ -333,7 +335,8 @@ exec_command(const char *cmd,
status = exec_command_errverbose(scan_state, active_branch);
else if (strcmp(cmd, "f") == 0)
status = exec_command_f(scan_state, active_branch);
- else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
+ else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0 ||
+ strcmp(cmd, "gfmt") == 0)
status = exec_command_g(scan_state, active_branch, cmd);
else if (strcmp(cmd, "gdesc") == 0)
status = exec_command_gdesc(scan_state, active_branch);
@@ -1282,6 +1285,8 @@ 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
+ * \gfmt format [filename] -- send result in specified format to
+ * file/pipe.
*/
static backslashResult
exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
@@ -1290,7 +1295,42 @@ exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
if (active_branch)
{
- char *fname = psql_scan_slash_option(scan_state,
+ char *fname;
+
+ if (strcmp(cmd, "gfmt") == 0)
+ {
+ char *fmtname = psql_scan_slash_option(scan_state,
+ OT_NORMAL, NULL, false);
+
+ if (!fmtname)
+ {
+ pg_log_error("no format name");
+
+ return PSQL_CMD_ERROR;
+ }
+ else
+ {
+ enum printFormat format;
+ bool result;
+
+ result = format_number(fmtname, strlen(fmtname), &format);
+
+ free(fmtname);
+
+ if (result)
+ {
+ pset.gfmt_format = format;
+ }
+ else
+ {
+ pg_log_error("\\pset: allowed formats are aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned, wrapped");
+
+ return PSQL_CMD_ERROR;
+ }
+ }
+ }
+
+ fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, false);
if (!fname)
@@ -3777,6 +3817,68 @@ _unicode_linestyle2string(int linestyle)
return "unknown";
}
+/*
+ * Returns true if format name was recognized.
+ */
+static bool
+format_number(const char *value, int vallen, enum printFormat *numformat)
+{
+ static const struct fmt
+ {
+ const char *name;
+ enum printFormat number;
+ } formats[] =
+ {
+ /* remember to update error message below when adding more */
+ {"aligned", PRINT_ALIGNED},
+ {"asciidoc", PRINT_ASCIIDOC},
+ {"csv", PRINT_CSV},
+ {"html", PRINT_HTML},
+ {"latex", PRINT_LATEX},
+ {"troff-ms", PRINT_TROFF_MS},
+ {"unaligned", PRINT_UNALIGNED},
+ {"wrapped", PRINT_WRAPPED}
+ };
+
+ int match_pos = -1;
+
+ for (int i = 0; i < lengthof(formats); i++)
+ {
+ if (pg_strncasecmp(formats[i].name, value, vallen) == 0)
+ {
+ if (match_pos < 0)
+ match_pos = i;
+ else
+ {
+ pg_log_error("\\pset: ambiguous abbreviation \"%s\" matches both \"%s\" and \"%s\"",
+ value,
+ formats[match_pos].name, formats[i].name);
+ return false;
+ }
+ }
+ }
+ if (match_pos >= 0)
+ {
+ *numformat = formats[match_pos].number;
+
+ return true;
+ }
+ else if (pg_strncasecmp("latex-longtable", value, vallen) == 0)
+ {
+ /*
+ * We must treat latex-longtable specially because latex is a
+ * prefix of it; if both were in the table above, we'd think
+ * "latex" is ambiguous.
+ */
+ *numformat = PRINT_LATEX_LONGTABLE;
+
+ return true;
+ }
+
+ return false;
+}
+
+
/*
* do_pset
*
@@ -3794,55 +3896,14 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
/* set format */
if (strcmp(param, "format") == 0)
{
- static const struct fmt
- {
- const char *name;
- enum printFormat number;
- } formats[] =
- {
- /* remember to update error message below when adding more */
- {"aligned", PRINT_ALIGNED},
- {"asciidoc", PRINT_ASCIIDOC},
- {"csv", PRINT_CSV},
- {"html", PRINT_HTML},
- {"latex", PRINT_LATEX},
- {"troff-ms", PRINT_TROFF_MS},
- {"unaligned", PRINT_UNALIGNED},
- {"wrapped", PRINT_WRAPPED}
- };
+ enum printFormat number;
if (!value)
;
else
{
- int match_pos = -1;
-
- for (int i = 0; i < lengthof(formats); i++)
- {
- if (pg_strncasecmp(formats[i].name, value, vallen) == 0)
- {
- if (match_pos < 0)
- match_pos = i;
- else
- {
- pg_log_error("\\pset: ambiguous abbreviation \"%s\" matches both \"%s\" and \"%s\"",
- value,
- formats[match_pos].name, formats[i].name);
- return false;
- }
- }
- }
- if (match_pos >= 0)
- popt->topt.format = formats[match_pos].number;
- else if (pg_strncasecmp("latex-longtable", value, vallen) == 0)
- {
- /*
- * We must treat latex-longtable specially because latex is a
- * prefix of it; if both were in the table above, we'd think
- * "latex" is ambiguous.
- */
- popt->topt.format = PRINT_LATEX_LONGTABLE;
- }
+ if (format_number(value, vallen, &number))
+ popt->topt.format = number;
else
{
pg_log_error("\\pset: allowed formats are aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned, wrapped");
diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c
index 396a40089c..834b3fb4e7 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -714,6 +714,9 @@ PrintQueryTuples(const PGresult *results)
if (pset.g_expanded)
my_popt.topt.expanded = 1;
+ if (pset.gfmt_format != PRINT_NOTHING)
+ my_popt.topt.format = pset.gfmt_format;
+
/* write output to \g argument, if any */
if (pset.gfname)
{
@@ -1421,6 +1424,9 @@ sendquery_cleanup:
/* reset \gx's expanded-mode flag */
pset.g_expanded = false;
+ /* reset \gfmt's format */
+ pset.gfmt_format = PRINT_NOTHING;
+
/* reset \gset trigger */
if (pset.gset_prefix)
{
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 9a18cb3059..40d4579204 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -169,7 +169,7 @@ 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(129, pager ? &(pset.popt.topt) : NULL);
fprintf(output, _("General\n"));
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
@@ -178,6 +178,7 @@ slashUsage(unsigned short int pager)
fprintf(output, _(" \\g [FILE] or ; execute query (and send results to file or |pipe)\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, _(" \\gfmt format [FILE] as \\g, but forces output format\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, _(" \\q quit psql\n"));
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 2b384a38a1..78e409848f 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -96,6 +96,7 @@ typedef struct _psqlSettings
bool gdesc_flag; /* one-shot request to describe query results */
bool gexec_flag; /* one-shot request to execute query results */
bool crosstab_flag; /* one-shot request to crosstab results */
+ enum printFormat gfmt_format; /* one-shot print format requested by \gf */
char *ctv_args[4]; /* \crosstabview arguments */
bool notty; /* stdin or stdout is not a tty (as determined
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index ae35fa4aa9..625315dedc 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1479,7 +1479,7 @@ psql_completion(const char *text, int start, int end)
"\\e", "\\echo", "\\ef", "\\elif", "\\else", "\\encoding",
"\\endif", "\\errverbose", "\\ev",
"\\f",
- "\\g", "\\gdesc", "\\gexec", "\\gset", "\\gx",
+ "\\g", "\\gdesc", "\\gexec", "\\gfmt", "\\gset", "\\gx",
"\\h", "\\help", "\\H",
"\\i", "\\if", "\\ir",
"\\l", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
@@ -3781,6 +3781,17 @@ psql_completion(const char *text, int start, int end)
else if (TailMatchesCS("\\encoding"))
COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+
+ else if (TailMatchesCS("\\gfmt"))
+ COMPLETE_WITH_CS("aligned", "asciidoc", "csv", "html", "latex",
+ "latex-longtable", "troff-ms", "unaligned",
+ "wrapped");
+ else if (TailMatchesCS("\\gfmt", MatchAny))
+ {
+ completion_charp = "\\";
+ completion_force_quote = false;
+ matches = rl_completion_matches(text, complete_from_files);
+ }
else if (TailMatchesCS("\\h|\\help"))
COMPLETE_WITH_LIST(sql_commands);
else if (TailMatchesCS("\\h|\\help", MatchAny))
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 2423ae2f37..8e8813a5ce 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -4174,6 +4174,7 @@ deallocate q;
-- check ambiguous format requests
\pset format a
\pset: ambiguous abbreviation "a" matches both "aligned" and "asciidoc"
+\pset: allowed formats are aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned, wrapped
\pset format l
-- clean up after output format tests
drop table psql_serial_tab;
@@ -4971,3 +4972,28 @@ List of access methods
hash | uuid_ops | uuid | uuid | 2 | uuid_hash_extended
(5 rows)
+-- execution with specified format
+select 1,2;
+ ?column? | ?column?
+----------+----------
+ 1 | 2
+(1 row)
+
+select 1,2 \gfmt csv
+?column?,?column?
+1,2
+select 1,2;
+ ?column? | ?column?
+----------+----------
+ 1 | 2
+(1 row)
+
+select 1,2 \gfmt latex
+\begin{tabular}{r | r}
+\textit{?column?} & \textit{?column?} \\
+\hline
+1 & 2 \\
+\end{tabular}
+
+\noindent (1 row) \\
+
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 3c876d2699..f9a5d2716c 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1200,3 +1200,9 @@ drop role regress_partitioning_role;
\dAo * pg_catalog.jsonb_path_ops
\dAp brin uuid_minmax_ops
\dAp * pg_catalog.uuid_ops
+
+-- execution with specified format
+select 1,2;
+select 1,2 \gfmt csv
+select 1,2;
+select 1,2 \gfmt latex