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