diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 20ba105160..734b3e94e0 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -2108,6 +2108,16 @@ CREATE INDEX
+
+ \gf format [ filename ]
+ \gf format [ |command ]
+
+
+ \gf 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 e111cee556..fe93313964 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, "gf") == 0)
status = exec_command_g(scan_state, active_branch, cmd);
else if (strcmp(cmd, "gdesc") == 0)
status = exec_command_gdesc(scan_state, active_branch);
@@ -1259,7 +1262,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, "gf") == 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.gf_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)
@@ -3746,6 +3784,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
*
@@ -3763,55 +3863,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 67df0cd2c7..0260e26559 100644
--- a/src/bin/psql/common.c
+++ b/src/bin/psql/common.c
@@ -713,6 +713,9 @@ PrintQueryTuples(const PGresult *results)
if (pset.g_expanded)
my_popt.topt.expanded = 1;
+ if (pset.gf_format != PRINT_NOTHING)
+ my_popt.topt.format = pset.gf_format;
+
/* write output to \g argument, if any */
if (pset.gfname)
{
@@ -1408,6 +1411,9 @@ sendquery_cleanup:
/* reset \gx's expanded-mode flag */
pset.g_expanded = false;
+ /* reset \gf's format */
+ pset.gf_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 1f1f778426..c315a0c09a 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, _(" \\gf 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..0f1f469292 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 gf_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 b6b08d0ccb..eb90028f9b 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -1471,7 +1471,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", "\\gf", "\\gset", "\\gx",
"\\h", "\\help", "\\H",
"\\i", "\\if", "\\ir",
"\\l", "\\lo_import", "\\lo_export", "\\lo_list", "\\lo_unlink",
@@ -3767,6 +3767,12 @@ psql_completion(const char *text, int start, int end)
else if (TailMatchesCS("\\encoding"))
COMPLETE_WITH_QUERY(Query_for_list_of_encodings);
+
+ else if (TailMatchesCS("\\gf"))
+ COMPLETE_WITH_CS("aligned", "asciidoc", "csv", "html", "latex",
+ "latex-longtable", "troff-ms", "unaligned",
+ "wrapped");
+
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 242f817163..1118ff0091 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;
@@ -4809,3 +4810,27 @@ Owning table: "pg_catalog.pg_statistic"
Indexes:
"pg_toast_2619_index" PRIMARY KEY, btree (chunk_id, chunk_seq)
+select 1,2;
+ ?column? | ?column?
+----------+----------
+ 1 | 2
+(1 row)
+
+select 1,2 \gf csv
+?column?,?column?
+1,2
+select 1,2;
+ ?column? | ?column?
+----------+----------
+ 1 | 2
+(1 row)
+
+select 1,2 \gf 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 26a0bcf718..d4d609aeaf 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -1182,3 +1182,9 @@ drop role regress_partitioning_role;
-- \d on toast table (use pg_statistic's toast table, which has a known name)
\d pg_toast.pg_toast_2619
+
+
+select 1,2;
+select 1,2 \gf csv
+select 1,2;
+select 1,2 \gf latex