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