mirror of
https://github.com/postgres/postgres.git
synced 2025-04-29 13:56:47 +03:00
Add CSV table output mode in psql.
"\pset format csv", or --csv, selects comma-separated values table format. This is compliant with RFC 4180, except that we aren't too picky about whether the record separator is LF or CRLF; also, the user may choose a field separator other than comma. This output format is directly compatible with the server's COPY CSV format, and will also be useful as input to other programs. It's considerably safer for that purpose than the old recommendation to use "unaligned" format, since the latter couldn't handle data containing the field separator character. Daniel Vérité, reviewed by Fabien Coelho and David Fetter, some tweaking by me Discussion: https://postgr.es/m/a8de371e-006f-4f92-ab72-2bbe3ee78f03@manitou-mail.org
This commit is contained in:
parent
9a98984f49
commit
aa2ba50c2c
@ -68,8 +68,8 @@ PostgreSQL documentation
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Switches to unaligned output mode. (The default output mode is
|
Switches to unaligned output mode. (The default output mode is
|
||||||
otherwise aligned.) This is equivalent to <command>\pset format
|
<literal>aligned</literal>.) This is equivalent to
|
||||||
unaligned</command>.
|
<command>\pset format unaligned</command>.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
@ -151,6 +151,16 @@ EOF
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><option>--csv</option></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Switches to <acronym>CSV</acronym> (Comma-Separated Values) output
|
||||||
|
mode. This is equivalent to <command>\pset format csv</command>.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><option>-d <replaceable class="parameter">dbname</replaceable></option></term>
|
<term><option>-d <replaceable class="parameter">dbname</replaceable></option></term>
|
||||||
<term><option>--dbname=<replaceable class="parameter">dbname</replaceable></option></term>
|
<term><option>--dbname=<replaceable class="parameter">dbname</replaceable></option></term>
|
||||||
@ -270,8 +280,8 @@ EOF
|
|||||||
<term><option>--html</option></term>
|
<term><option>--html</option></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Turn on <acronym>HTML</acronym> tabular output. This is
|
Switches to <acronym>HTML</acronym> output mode. This is
|
||||||
equivalent to <literal>\pset format html</literal> or the
|
equivalent to <command>\pset format html</command> or the
|
||||||
<command>\H</command> command.
|
<command>\H</command> command.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@ -2520,6 +2530,19 @@ lo_import 152801
|
|||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
|
|
||||||
|
<varlistentry>
|
||||||
|
<term><literal>csv_fieldsep</literal></term>
|
||||||
|
<listitem>
|
||||||
|
<para>
|
||||||
|
Specifies the field separator to be used in
|
||||||
|
<acronym>CSV</acronym> output format. If the separator character
|
||||||
|
appears in a field's value, that field is output within double
|
||||||
|
quotes, following standard <acronym>CSV</acronym> rules.
|
||||||
|
The default is a comma.
|
||||||
|
</para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
|
||||||
<varlistentry>
|
<varlistentry>
|
||||||
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
|
<term><literal>expanded</literal> (or <literal>x</literal>)</term>
|
||||||
<listitem>
|
<listitem>
|
||||||
@ -2547,8 +2570,8 @@ lo_import 152801
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Specifies the field separator to be used in unaligned output
|
Specifies the field separator to be used in unaligned output
|
||||||
format. That way one can create, for example, tab- or
|
format. That way one can create, for example, tab-separated
|
||||||
comma-separated output, which other programs might prefer. To
|
output, which other programs might prefer. To
|
||||||
set a tab as field separator, type <literal>\pset fieldsep
|
set a tab as field separator, type <literal>\pset fieldsep
|
||||||
'\t'</literal>. The default field separator is
|
'\t'</literal>. The default field separator is
|
||||||
<literal>'|'</literal> (a vertical bar).
|
<literal>'|'</literal> (a vertical bar).
|
||||||
@ -2585,22 +2608,48 @@ lo_import 152801
|
|||||||
<listitem>
|
<listitem>
|
||||||
<para>
|
<para>
|
||||||
Sets the output format to one of <literal>aligned</literal>,
|
Sets the output format to one of <literal>aligned</literal>,
|
||||||
<literal>asciidoc</literal>, <literal>html</literal>,
|
<literal>asciidoc</literal>,
|
||||||
<literal>latex</literal> (uses <literal>tabular</literal>),
|
<literal>csv</literal>,
|
||||||
|
<literal>html</literal>,
|
||||||
|
<literal>latex</literal>,
|
||||||
<literal>latex-longtable</literal>, <literal>troff-ms</literal>,
|
<literal>latex-longtable</literal>, <literal>troff-ms</literal>,
|
||||||
<literal>unaligned</literal>, or <literal>wrapped</literal>.
|
<literal>unaligned</literal>, or <literal>wrapped</literal>.
|
||||||
Unique abbreviations are allowed.
|
Unique abbreviations are allowed.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
|
<para><literal>aligned</literal> format is the standard,
|
||||||
|
human-readable, nicely formatted text output; this is the default.
|
||||||
|
</para>
|
||||||
|
|
||||||
<para><literal>unaligned</literal> format writes all columns of a row on one
|
<para><literal>unaligned</literal> format writes all columns of a row on one
|
||||||
line, separated by the currently active field separator. This
|
line, separated by the currently active field separator. This
|
||||||
is useful for creating output that might be intended to be read
|
is useful for creating output that might be intended to be read
|
||||||
in by other programs (for example, tab-separated or comma-separated
|
in by other programs, for example, tab-separated or comma-separated
|
||||||
format).
|
format. However, the field separator character is not treated
|
||||||
|
specially if it appears in a column's value;
|
||||||
|
so <acronym>CSV</acronym> format may be better suited for such
|
||||||
|
purposes.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para><literal>aligned</literal> format is the standard, human-readable,
|
<para><literal>csv</literal> format
|
||||||
nicely formatted text output; this is the default.
|
<indexterm>
|
||||||
|
<primary>CSV (Comma-Separated Values) format</primary>
|
||||||
|
<secondary>in psql</secondary>
|
||||||
|
</indexterm>
|
||||||
|
writes column values separated by commas, applying the quoting
|
||||||
|
rules described in
|
||||||
|
<ulink url="https://tools.ietf.org/html/rfc4180">RFC 4180</ulink>.
|
||||||
|
This output is compatible with the CSV format of the server's
|
||||||
|
<command>COPY</command> command.
|
||||||
|
A header line with column names is generated unless
|
||||||
|
the <literal>tuples_only</literal> parameter is
|
||||||
|
<literal>on</literal>. Titles and footers are not printed.
|
||||||
|
Each row is terminated by the system-dependent end-of-line character,
|
||||||
|
which is typically a single newline (<literal>\n</literal>) for
|
||||||
|
Unix-like systems or a carriage return and newline sequence
|
||||||
|
(<literal>\r\n</literal>) for Microsoft Windows.
|
||||||
|
Field separator characters other than comma can be selected with
|
||||||
|
<command>\pset csv_fieldsep</command>.
|
||||||
</para>
|
</para>
|
||||||
|
|
||||||
<para><literal>wrapped</literal> format is like <literal>aligned</literal> but wraps
|
<para><literal>wrapped</literal> format is like <literal>aligned</literal> but wraps
|
||||||
@ -2620,8 +2669,12 @@ lo_import 152801
|
|||||||
language. They are not complete documents! This might not be
|
language. They are not complete documents! This might not be
|
||||||
necessary in <acronym>HTML</acronym>, but in
|
necessary in <acronym>HTML</acronym>, but in
|
||||||
<application>LaTeX</application> you must have a complete
|
<application>LaTeX</application> you must have a complete
|
||||||
document wrapper. <literal>latex-longtable</literal>
|
document wrapper.
|
||||||
also requires the <application>LaTeX</application>
|
The <literal>latex</literal> format
|
||||||
|
uses <application>LaTeX</application>'s <literal>tabular</literal>
|
||||||
|
environment.
|
||||||
|
The <literal>latex-longtable</literal> format
|
||||||
|
requires the <application>LaTeX</application>
|
||||||
<literal>longtable</literal> and <literal>booktabs</literal> packages.
|
<literal>longtable</literal> and <literal>booktabs</literal> packages.
|
||||||
</para>
|
</para>
|
||||||
</listitem>
|
</listitem>
|
||||||
@ -4588,17 +4641,24 @@ first second
|
|||||||
|
|
||||||
peter@localhost testdb=> <userinput>\pset border 1</userinput>
|
peter@localhost testdb=> <userinput>\pset border 1</userinput>
|
||||||
Border style is 1.
|
Border style is 1.
|
||||||
peter@localhost testdb=> <userinput>\pset format unaligned</userinput>
|
peter@localhost testdb=> <userinput>\pset format csv</userinput>
|
||||||
Output format is unaligned.
|
Output format is csv.
|
||||||
peter@localhost testdb=> <userinput>\pset fieldsep ","</userinput>
|
|
||||||
Field separator is ",".
|
|
||||||
peter@localhost testdb=> <userinput>\pset tuples_only</userinput>
|
peter@localhost testdb=> <userinput>\pset tuples_only</userinput>
|
||||||
Showing only tuples.
|
Tuples only is on.
|
||||||
peter@localhost testdb=> <userinput>SELECT second, first FROM my_table;</userinput>
|
peter@localhost testdb=> <userinput>SELECT second, first FROM my_table;</userinput>
|
||||||
one,1
|
one,1
|
||||||
two,2
|
two,2
|
||||||
three,3
|
three,3
|
||||||
four,4
|
four,4
|
||||||
|
peter@localhost testdb=> <userinput>\pset format unaligned</userinput>
|
||||||
|
Output format is unaligned.
|
||||||
|
peter@localhost testdb=> <userinput>\pset fieldsep '\t'</userinput>
|
||||||
|
Field separator is " ".
|
||||||
|
peter@localhost testdb=> <userinput>SELECT second, first FROM my_table;</userinput>
|
||||||
|
one 1
|
||||||
|
two 2
|
||||||
|
three 3
|
||||||
|
four 4
|
||||||
</programlisting>
|
</programlisting>
|
||||||
Alternatively, use the short commands:
|
Alternatively, use the short commands:
|
||||||
<programlisting>
|
<programlisting>
|
||||||
|
@ -1957,8 +1957,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
|
|||||||
|
|
||||||
int i;
|
int i;
|
||||||
static const char *const my_list[] = {
|
static const char *const my_list[] = {
|
||||||
"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
|
"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
|
||||||
"footer", "format", "linestyle", "null",
|
"fieldsep_zero", "footer", "format", "linestyle", "null",
|
||||||
"numericlocale", "pager", "pager_min_lines",
|
"numericlocale", "pager", "pager_min_lines",
|
||||||
"recordsep", "recordsep_zero",
|
"recordsep", "recordsep_zero",
|
||||||
"tableattr", "title", "tuples_only",
|
"tableattr", "title", "tuples_only",
|
||||||
@ -3616,6 +3616,9 @@ _align2string(enum printFormat in)
|
|||||||
case PRINT_ASCIIDOC:
|
case PRINT_ASCIIDOC:
|
||||||
return "asciidoc";
|
return "asciidoc";
|
||||||
break;
|
break;
|
||||||
|
case PRINT_CSV:
|
||||||
|
return "csv";
|
||||||
|
break;
|
||||||
case PRINT_HTML:
|
case PRINT_HTML:
|
||||||
return "html";
|
return "html";
|
||||||
break;
|
break;
|
||||||
@ -3696,6 +3699,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
|
|||||||
/* remember to update error message below when adding more */
|
/* remember to update error message below when adding more */
|
||||||
{"aligned", PRINT_ALIGNED},
|
{"aligned", PRINT_ALIGNED},
|
||||||
{"asciidoc", PRINT_ASCIIDOC},
|
{"asciidoc", PRINT_ASCIIDOC},
|
||||||
|
{"csv", PRINT_CSV},
|
||||||
{"html", PRINT_HTML},
|
{"html", PRINT_HTML},
|
||||||
{"latex", PRINT_LATEX},
|
{"latex", PRINT_LATEX},
|
||||||
{"troff-ms", PRINT_TROFF_MS},
|
{"troff-ms", PRINT_TROFF_MS},
|
||||||
@ -3737,7 +3741,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
psql_error("\\pset: allowed formats are aligned, asciidoc, html, latex, latex-longtable, troff-ms, unaligned, wrapped\n");
|
psql_error("\\pset: allowed formats are aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned, wrapped\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3836,6 +3840,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
|
|||||||
popt->topt.expanded = !popt->topt.expanded;
|
popt->topt.expanded = !popt->topt.expanded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* field separator for CSV format */
|
||||||
|
else if (strcmp(param, "csv_fieldsep") == 0)
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
/* CSV separator has to be a one-byte character */
|
||||||
|
if (strlen(value) != 1)
|
||||||
|
{
|
||||||
|
psql_error("\\pset: csv_fieldsep must be a single one-byte character\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (value[0] == '"' || value[0] == '\n' || value[0] == '\r')
|
||||||
|
{
|
||||||
|
psql_error("\\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
popt->topt.csvFieldSep[0] = value[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* locale-aware numeric output */
|
/* locale-aware numeric output */
|
||||||
else if (strcmp(param, "numericlocale") == 0)
|
else if (strcmp(param, "numericlocale") == 0)
|
||||||
{
|
{
|
||||||
@ -4006,6 +4030,13 @@ printPsetInfo(const char *param, struct printQueryOpt *popt)
|
|||||||
printf(_("Expanded display is off.\n"));
|
printf(_("Expanded display is off.\n"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* show field separator for CSV format */
|
||||||
|
else if (strcmp(param, "csv_fieldsep") == 0)
|
||||||
|
{
|
||||||
|
printf(_("Field separator for CSV is \"%s\".\n"),
|
||||||
|
popt->topt.csvFieldSep);
|
||||||
|
}
|
||||||
|
|
||||||
/* show field separator for unaligned text */
|
/* show field separator for unaligned text */
|
||||||
else if (strcmp(param, "fieldsep") == 0)
|
else if (strcmp(param, "fieldsep") == 0)
|
||||||
{
|
{
|
||||||
@ -4207,6 +4238,8 @@ pset_value_string(const char *param, struct printQueryOpt *popt)
|
|||||||
return psprintf("%d", popt->topt.border);
|
return psprintf("%d", popt->topt.border);
|
||||||
else if (strcmp(param, "columns") == 0)
|
else if (strcmp(param, "columns") == 0)
|
||||||
return psprintf("%d", popt->topt.columns);
|
return psprintf("%d", popt->topt.columns);
|
||||||
|
else if (strcmp(param, "csv_fieldsep") == 0)
|
||||||
|
return pset_quoted_string(popt->topt.csvFieldSep);
|
||||||
else if (strcmp(param, "expanded") == 0)
|
else if (strcmp(param, "expanded") == 0)
|
||||||
return pstrdup(popt->topt.expanded == 2
|
return pstrdup(popt->topt.expanded == 2
|
||||||
? "auto"
|
? "auto"
|
||||||
|
@ -68,7 +68,7 @@ usage(unsigned short int pager)
|
|||||||
* Keep this line count in sync with the number of lines printed below!
|
* Keep this line count in sync with the number of lines printed below!
|
||||||
* Use "psql --help=options | wc" to count correctly.
|
* Use "psql --help=options | wc" to count correctly.
|
||||||
*/
|
*/
|
||||||
output = PageOutput(61, pager ? &(pset.popt.topt) : NULL);
|
output = PageOutput(62, pager ? &(pset.popt.topt) : NULL);
|
||||||
|
|
||||||
fprintf(output, _("psql is the PostgreSQL interactive terminal.\n\n"));
|
fprintf(output, _("psql is the PostgreSQL interactive terminal.\n\n"));
|
||||||
fprintf(output, _("Usage:\n"));
|
fprintf(output, _("Usage:\n"));
|
||||||
@ -108,6 +108,7 @@ usage(unsigned short int pager)
|
|||||||
|
|
||||||
fprintf(output, _("\nOutput format options:\n"));
|
fprintf(output, _("\nOutput format options:\n"));
|
||||||
fprintf(output, _(" -A, --no-align unaligned table output mode\n"));
|
fprintf(output, _(" -A, --no-align unaligned table output mode\n"));
|
||||||
|
fprintf(output, _(" --csv CSV (Comma-Separated Values) table output mode\n"));
|
||||||
fprintf(output, _(" -F, --field-separator=STRING\n"
|
fprintf(output, _(" -F, --field-separator=STRING\n"
|
||||||
" field separator for unaligned output (default: \"%s\")\n"),
|
" field separator for unaligned output (default: \"%s\")\n"),
|
||||||
DEFAULT_FIELD_SEP);
|
DEFAULT_FIELD_SEP);
|
||||||
@ -167,7 +168,7 @@ slashUsage(unsigned short int pager)
|
|||||||
* Use "psql --help=commands | wc" to count correctly. It's okay to count
|
* Use "psql --help=commands | wc" to count correctly. It's okay to count
|
||||||
* the USE_READLINE line even in builds without that.
|
* the USE_READLINE line even in builds without that.
|
||||||
*/
|
*/
|
||||||
output = PageOutput(125, pager ? &(pset.popt.topt) : NULL);
|
output = PageOutput(126, pager ? &(pset.popt.topt) : NULL);
|
||||||
|
|
||||||
fprintf(output, _("General\n"));
|
fprintf(output, _("General\n"));
|
||||||
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
|
fprintf(output, _(" \\copyright show PostgreSQL usage and distribution terms\n"));
|
||||||
@ -272,11 +273,12 @@ slashUsage(unsigned short int pager)
|
|||||||
fprintf(output, _(" \\H toggle HTML output mode (currently %s)\n"),
|
fprintf(output, _(" \\H toggle HTML output mode (currently %s)\n"),
|
||||||
ON(pset.popt.topt.format == PRINT_HTML));
|
ON(pset.popt.topt.format == PRINT_HTML));
|
||||||
fprintf(output, _(" \\pset [NAME [VALUE]] set table output option\n"
|
fprintf(output, _(" \\pset [NAME [VALUE]] set table output option\n"
|
||||||
" (NAME := {border|columns|expanded|fieldsep|fieldsep_zero|\n"
|
" (border|columns|csv_fieldsep|expanded|fieldsep|\n"
|
||||||
" footer|format|linestyle|null|numericlocale|pager|\n"
|
" fieldsep_zero|footer|format|linestyle|null|\n"
|
||||||
" pager_min_lines|recordsep|recordsep_zero|tableattr|title|\n"
|
" numericlocale|pager|pager_min_lines|recordsep|\n"
|
||||||
" tuples_only|unicode_border_linestyle|\n"
|
" recordsep_zero|tableattr|title|tuples_only|\n"
|
||||||
" unicode_column_linestyle|unicode_header_linestyle})\n"));
|
" unicode_border_linestyle|unicode_column_linestyle|\n"
|
||||||
|
" unicode_header_linestyle)\n"));
|
||||||
fprintf(output, _(" \\t [on|off] show only rows (currently %s)\n"),
|
fprintf(output, _(" \\t [on|off] show only rows (currently %s)\n"),
|
||||||
ON(pset.popt.topt.tuples_only));
|
ON(pset.popt.topt.tuples_only));
|
||||||
fprintf(output, _(" \\T [STRING] set HTML <table> tag attributes, or unset if none\n"));
|
fprintf(output, _(" \\T [STRING] set HTML <table> tag attributes, or unset if none\n"));
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "variables.h"
|
#include "variables.h"
|
||||||
#include "fe_utils/print.h"
|
#include "fe_utils/print.h"
|
||||||
|
|
||||||
|
#define DEFAULT_CSV_FIELD_SEP ','
|
||||||
#define DEFAULT_FIELD_SEP "|"
|
#define DEFAULT_FIELD_SEP "|"
|
||||||
#define DEFAULT_RECORD_SEP "\n"
|
#define DEFAULT_RECORD_SEP "\n"
|
||||||
|
|
||||||
|
@ -144,6 +144,9 @@ main(int argc, char *argv[])
|
|||||||
pset.popt.topt.stop_table = true;
|
pset.popt.topt.stop_table = true;
|
||||||
pset.popt.topt.default_footer = true;
|
pset.popt.topt.default_footer = true;
|
||||||
|
|
||||||
|
pset.popt.topt.csvFieldSep[0] = DEFAULT_CSV_FIELD_SEP;
|
||||||
|
pset.popt.topt.csvFieldSep[1] = '\0';
|
||||||
|
|
||||||
pset.popt.topt.unicode_border_linestyle = UNICODE_LINESTYLE_SINGLE;
|
pset.popt.topt.unicode_border_linestyle = UNICODE_LINESTYLE_SINGLE;
|
||||||
pset.popt.topt.unicode_column_linestyle = UNICODE_LINESTYLE_SINGLE;
|
pset.popt.topt.unicode_column_linestyle = UNICODE_LINESTYLE_SINGLE;
|
||||||
pset.popt.topt.unicode_header_linestyle = UNICODE_LINESTYLE_SINGLE;
|
pset.popt.topt.unicode_header_linestyle = UNICODE_LINESTYLE_SINGLE;
|
||||||
@ -468,6 +471,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts *options)
|
|||||||
{"expanded", no_argument, NULL, 'x'},
|
{"expanded", no_argument, NULL, 'x'},
|
||||||
{"no-psqlrc", no_argument, NULL, 'X'},
|
{"no-psqlrc", no_argument, NULL, 'X'},
|
||||||
{"help", optional_argument, NULL, 1},
|
{"help", optional_argument, NULL, 1},
|
||||||
|
{"csv", no_argument, NULL, 2},
|
||||||
{NULL, 0, NULL, 0}
|
{NULL, 0, NULL, 0}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -658,6 +662,9 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts *options)
|
|||||||
exit(EXIT_SUCCESS);
|
exit(EXIT_SUCCESS);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 2:
|
||||||
|
pset.popt.topt.format = PRINT_CSV;
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
unknown_option:
|
unknown_option:
|
||||||
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
|
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
|
||||||
|
@ -2605,6 +2605,7 @@ psql_completion(const char *text, int start, int end)
|
|||||||
/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
|
/* Complete CREATE EVENT TRIGGER <name> ON with event_type */
|
||||||
else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
|
else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
|
||||||
COMPLETE_WITH("ddl_command_start", "ddl_command_end", "sql_drop");
|
COMPLETE_WITH("ddl_command_start", "ddl_command_end", "sql_drop");
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Complete CREATE EVENT TRIGGER <name> ON <event_type>. EXECUTE FUNCTION
|
* Complete CREATE EVENT TRIGGER <name> ON <event_type>. EXECUTE FUNCTION
|
||||||
* is the recommended grammar instead of EXECUTE PROCEDURE in version 11
|
* is the recommended grammar instead of EXECUTE PROCEDURE in version 11
|
||||||
@ -3524,7 +3525,7 @@ psql_completion(const char *text, int start, int end)
|
|||||||
else if (TailMatchesCS("\\password"))
|
else if (TailMatchesCS("\\password"))
|
||||||
COMPLETE_WITH_QUERY(Query_for_list_of_roles);
|
COMPLETE_WITH_QUERY(Query_for_list_of_roles);
|
||||||
else if (TailMatchesCS("\\pset"))
|
else if (TailMatchesCS("\\pset"))
|
||||||
COMPLETE_WITH_CS("border", "columns", "expanded",
|
COMPLETE_WITH_CS("border", "columns", "csv_fieldsep", "expanded",
|
||||||
"fieldsep", "fieldsep_zero", "footer", "format",
|
"fieldsep", "fieldsep_zero", "footer", "format",
|
||||||
"linestyle", "null", "numericlocale",
|
"linestyle", "null", "numericlocale",
|
||||||
"pager", "pager_min_lines",
|
"pager", "pager_min_lines",
|
||||||
@ -3536,7 +3537,7 @@ psql_completion(const char *text, int start, int end)
|
|||||||
else if (TailMatchesCS("\\pset", MatchAny))
|
else if (TailMatchesCS("\\pset", MatchAny))
|
||||||
{
|
{
|
||||||
if (TailMatchesCS("format"))
|
if (TailMatchesCS("format"))
|
||||||
COMPLETE_WITH_CS("aligned", "asciidoc", "html", "latex",
|
COMPLETE_WITH_CS("aligned", "asciidoc", "csv", "html", "latex",
|
||||||
"latex-longtable", "troff-ms", "unaligned",
|
"latex-longtable", "troff-ms", "unaligned",
|
||||||
"wrapped");
|
"wrapped");
|
||||||
else if (TailMatchesCS("linestyle"))
|
else if (TailMatchesCS("linestyle"))
|
||||||
|
@ -1737,7 +1737,119 @@ print_aligned_vertical(const printTableContent *cont,
|
|||||||
|
|
||||||
|
|
||||||
/**********************/
|
/**********************/
|
||||||
/* HTML printing ******/
|
/* CSV format */
|
||||||
|
/**********************/
|
||||||
|
|
||||||
|
|
||||||
|
static void
|
||||||
|
csv_escaped_print(const char *str, FILE *fout)
|
||||||
|
{
|
||||||
|
const char *p;
|
||||||
|
|
||||||
|
fputc('"', fout);
|
||||||
|
for (p = str; *p; p++)
|
||||||
|
{
|
||||||
|
if (*p == '"')
|
||||||
|
fputc('"', fout); /* double quotes are doubled */
|
||||||
|
fputc(*p, fout);
|
||||||
|
}
|
||||||
|
fputc('"', fout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
csv_print_field(const char *str, FILE *fout, char sep)
|
||||||
|
{
|
||||||
|
/*----------------
|
||||||
|
* Enclose and escape field contents when one of these conditions is met:
|
||||||
|
* - the field separator is found in the contents.
|
||||||
|
* - the field contains a CR or LF.
|
||||||
|
* - the field contains a double quote.
|
||||||
|
* - the field is exactly "\.".
|
||||||
|
* - the field separator is either "\" or ".".
|
||||||
|
* The last two cases prevent producing a line that the server's COPY
|
||||||
|
* command would interpret as an end-of-data marker. We only really
|
||||||
|
* need to ensure that the complete line isn't exactly "\.", but for
|
||||||
|
* simplicity we apply stronger restrictions here.
|
||||||
|
*----------------
|
||||||
|
*/
|
||||||
|
if (strchr(str, sep) != NULL ||
|
||||||
|
strcspn(str, "\r\n\"") != strlen(str) ||
|
||||||
|
strcmp(str, "\\.") == 0 ||
|
||||||
|
sep == '\\' || sep == '.')
|
||||||
|
csv_escaped_print(str, fout);
|
||||||
|
else
|
||||||
|
fputs(str, fout);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_csv_text(const printTableContent *cont, FILE *fout)
|
||||||
|
{
|
||||||
|
const char *const *ptr;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (cancel_pressed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The title and footer are never printed in csv format. The header is
|
||||||
|
* printed if opt_tuples_only is false.
|
||||||
|
*
|
||||||
|
* Despite RFC 4180 saying that end of lines are CRLF, terminate lines
|
||||||
|
* with '\n', which prints out as the system-dependent EOL string in text
|
||||||
|
* mode (typically LF on Unix and CRLF on Windows).
|
||||||
|
*/
|
||||||
|
if (cont->opt->start_table && !cont->opt->tuples_only)
|
||||||
|
{
|
||||||
|
/* print headers */
|
||||||
|
for (ptr = cont->headers; *ptr; ptr++)
|
||||||
|
{
|
||||||
|
if (ptr != cont->headers)
|
||||||
|
fputc(cont->opt->csvFieldSep[0], fout);
|
||||||
|
csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
|
||||||
|
}
|
||||||
|
fputc('\n', fout);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* print cells */
|
||||||
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
||||||
|
{
|
||||||
|
csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
|
||||||
|
if ((i + 1) % cont->ncolumns)
|
||||||
|
fputc(cont->opt->csvFieldSep[0], fout);
|
||||||
|
else
|
||||||
|
fputc('\n', fout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
print_csv_vertical(const printTableContent *cont, FILE *fout)
|
||||||
|
{
|
||||||
|
const char *const *ptr;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
/* print records */
|
||||||
|
for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
|
||||||
|
{
|
||||||
|
if (cancel_pressed)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* print name of column */
|
||||||
|
csv_print_field(cont->headers[i % cont->ncolumns], fout,
|
||||||
|
cont->opt->csvFieldSep[0]);
|
||||||
|
|
||||||
|
/* print field separator */
|
||||||
|
fputc(cont->opt->csvFieldSep[0], fout);
|
||||||
|
|
||||||
|
/* print field value */
|
||||||
|
csv_print_field(*ptr, fout, cont->opt->csvFieldSep[0]);
|
||||||
|
|
||||||
|
fputc('\n', fout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**********************/
|
||||||
|
/* HTML */
|
||||||
/**********************/
|
/**********************/
|
||||||
|
|
||||||
|
|
||||||
@ -1956,6 +2068,7 @@ print_html_vertical(const printTableContent *cont, FILE *fout)
|
|||||||
/* ASCIIDOC */
|
/* ASCIIDOC */
|
||||||
/*************************/
|
/*************************/
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
asciidoc_escaped_print(const char *in, FILE *fout)
|
asciidoc_escaped_print(const char *in, FILE *fout)
|
||||||
{
|
{
|
||||||
@ -2174,6 +2287,7 @@ print_asciidoc_vertical(const printTableContent *cont, FILE *fout)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*************************/
|
/*************************/
|
||||||
/* LaTeX */
|
/* LaTeX */
|
||||||
/*************************/
|
/*************************/
|
||||||
@ -2319,6 +2433,11 @@ print_latex_text(const printTableContent *cont, FILE *fout)
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*************************/
|
||||||
|
/* LaTeX longtable */
|
||||||
|
/*************************/
|
||||||
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
print_latex_longtable_text(const printTableContent *cont, FILE *fout)
|
print_latex_longtable_text(const printTableContent *cont, FILE *fout)
|
||||||
{
|
{
|
||||||
@ -3234,6 +3353,12 @@ printTable(const printTableContent *cont,
|
|||||||
else
|
else
|
||||||
print_aligned_text(cont, fout, is_pager);
|
print_aligned_text(cont, fout, is_pager);
|
||||||
break;
|
break;
|
||||||
|
case PRINT_CSV:
|
||||||
|
if (cont->opt->expanded == 1)
|
||||||
|
print_csv_vertical(cont, fout);
|
||||||
|
else
|
||||||
|
print_csv_text(cont, fout);
|
||||||
|
break;
|
||||||
case PRINT_HTML:
|
case PRINT_HTML:
|
||||||
if (cont->opt->expanded == 1)
|
if (cont->opt->expanded == 1)
|
||||||
print_html_vertical(cont, fout);
|
print_html_vertical(cont, fout);
|
||||||
|
@ -28,6 +28,7 @@ enum printFormat
|
|||||||
PRINT_NOTHING = 0, /* to make sure someone initializes this */
|
PRINT_NOTHING = 0, /* to make sure someone initializes this */
|
||||||
PRINT_ALIGNED,
|
PRINT_ALIGNED,
|
||||||
PRINT_ASCIIDOC,
|
PRINT_ASCIIDOC,
|
||||||
|
PRINT_CSV,
|
||||||
PRINT_HTML,
|
PRINT_HTML,
|
||||||
PRINT_LATEX,
|
PRINT_LATEX,
|
||||||
PRINT_LATEX_LONGTABLE,
|
PRINT_LATEX_LONGTABLE,
|
||||||
@ -112,6 +113,7 @@ typedef struct printTableOpt
|
|||||||
const printTextFormat *line_style; /* line style (NULL for default) */
|
const printTextFormat *line_style; /* line style (NULL for default) */
|
||||||
struct separator fieldSep; /* field separator for unaligned text mode */
|
struct separator fieldSep; /* field separator for unaligned text mode */
|
||||||
struct separator recordSep; /* record separator for unaligned text mode */
|
struct separator recordSep; /* record separator for unaligned text mode */
|
||||||
|
char csvFieldSep[2]; /* field separator for csv format */
|
||||||
bool numericLocale; /* locale-aware numeric units separator and
|
bool numericLocale; /* locale-aware numeric units separator and
|
||||||
* decimal marker */
|
* decimal marker */
|
||||||
char *tableAttr; /* attributes for HTML <table ...> */
|
char *tableAttr; /* attributes for HTML <table ...> */
|
||||||
|
@ -260,6 +260,7 @@ select '2000-01-01'::date as party_over
|
|||||||
\pset
|
\pset
|
||||||
border 1
|
border 1
|
||||||
columns 0
|
columns 0
|
||||||
|
csv_fieldsep ','
|
||||||
expanded off
|
expanded off
|
||||||
fieldsep '|'
|
fieldsep '|'
|
||||||
fieldsep_zero off
|
fieldsep_zero off
|
||||||
@ -2937,6 +2938,94 @@ execute q;
|
|||||||
<l|int >l|2
|
<l|int >l|2
|
||||||
|====
|
|====
|
||||||
deallocate q;
|
deallocate q;
|
||||||
|
-- test csv output format
|
||||||
|
\pset format csv
|
||||||
|
\pset border 1
|
||||||
|
\pset expanded off
|
||||||
|
\d psql_serial_tab_id_seq
|
||||||
|
Type,Start,Minimum,Maximum,Increment,Cycles?,Cache
|
||||||
|
integer,1,1,2147483647,1,no,1
|
||||||
|
\pset tuples_only true
|
||||||
|
\df exp
|
||||||
|
pg_catalog,exp,double precision,double precision,func
|
||||||
|
pg_catalog,exp,numeric,numeric,func
|
||||||
|
\pset tuples_only false
|
||||||
|
\pset expanded on
|
||||||
|
\d psql_serial_tab_id_seq
|
||||||
|
Type,integer
|
||||||
|
Start,1
|
||||||
|
Minimum,1
|
||||||
|
Maximum,2147483647
|
||||||
|
Increment,1
|
||||||
|
Cycles?,no
|
||||||
|
Cache,1
|
||||||
|
\pset tuples_only true
|
||||||
|
\df exp
|
||||||
|
Schema,pg_catalog
|
||||||
|
Name,exp
|
||||||
|
Result data type,double precision
|
||||||
|
Argument data types,double precision
|
||||||
|
Type,func
|
||||||
|
Schema,pg_catalog
|
||||||
|
Name,exp
|
||||||
|
Result data type,numeric
|
||||||
|
Argument data types,numeric
|
||||||
|
Type,func
|
||||||
|
\pset tuples_only false
|
||||||
|
prepare q as
|
||||||
|
select 'some"text' as "a""title", E' <foo>\n<bar>' as "junk",
|
||||||
|
' ' as "empty", n as int
|
||||||
|
from generate_series(1,2) as n;
|
||||||
|
\pset expanded off
|
||||||
|
execute q;
|
||||||
|
"a""title",junk,empty,int
|
||||||
|
"some""text"," <foo>
|
||||||
|
<bar>", ,1
|
||||||
|
"some""text"," <foo>
|
||||||
|
<bar>", ,2
|
||||||
|
\pset expanded on
|
||||||
|
execute q;
|
||||||
|
"a""title","some""text"
|
||||||
|
junk," <foo>
|
||||||
|
<bar>"
|
||||||
|
empty,
|
||||||
|
int,1
|
||||||
|
"a""title","some""text"
|
||||||
|
junk," <foo>
|
||||||
|
<bar>"
|
||||||
|
empty,
|
||||||
|
int,2
|
||||||
|
deallocate q;
|
||||||
|
-- special cases
|
||||||
|
\pset expanded off
|
||||||
|
select 'comma,comma' as comma, 'semi;semi' as semi;
|
||||||
|
comma,semi
|
||||||
|
"comma,comma",semi;semi
|
||||||
|
\pset csv_fieldsep ';'
|
||||||
|
select 'comma,comma' as comma, 'semi;semi' as semi;
|
||||||
|
comma;semi
|
||||||
|
comma,comma;"semi;semi"
|
||||||
|
select '\.' as data;
|
||||||
|
data
|
||||||
|
"\."
|
||||||
|
\pset csv_fieldsep '.'
|
||||||
|
select '\' as d1, '' as d2;
|
||||||
|
"d1"."d2"
|
||||||
|
"\".""
|
||||||
|
-- illegal csv separators
|
||||||
|
\pset csv_fieldsep ''
|
||||||
|
\pset: csv_fieldsep must be a single one-byte character
|
||||||
|
\pset csv_fieldsep '\0'
|
||||||
|
\pset: csv_fieldsep must be a single one-byte character
|
||||||
|
\pset csv_fieldsep '\n'
|
||||||
|
\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return
|
||||||
|
\pset csv_fieldsep '\r'
|
||||||
|
\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return
|
||||||
|
\pset csv_fieldsep '"'
|
||||||
|
\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return
|
||||||
|
\pset csv_fieldsep ',,'
|
||||||
|
\pset: csv_fieldsep must be a single one-byte character
|
||||||
|
\pset csv_fieldsep ','
|
||||||
-- test html output format
|
-- test html output format
|
||||||
\pset format html
|
\pset format html
|
||||||
\pset border 1
|
\pset border 1
|
||||||
|
@ -501,6 +501,54 @@ execute q;
|
|||||||
|
|
||||||
deallocate q;
|
deallocate q;
|
||||||
|
|
||||||
|
-- test csv output format
|
||||||
|
|
||||||
|
\pset format csv
|
||||||
|
|
||||||
|
\pset border 1
|
||||||
|
\pset expanded off
|
||||||
|
\d psql_serial_tab_id_seq
|
||||||
|
\pset tuples_only true
|
||||||
|
\df exp
|
||||||
|
\pset tuples_only false
|
||||||
|
\pset expanded on
|
||||||
|
\d psql_serial_tab_id_seq
|
||||||
|
\pset tuples_only true
|
||||||
|
\df exp
|
||||||
|
\pset tuples_only false
|
||||||
|
|
||||||
|
prepare q as
|
||||||
|
select 'some"text' as "a""title", E' <foo>\n<bar>' as "junk",
|
||||||
|
' ' as "empty", n as int
|
||||||
|
from generate_series(1,2) as n;
|
||||||
|
|
||||||
|
\pset expanded off
|
||||||
|
execute q;
|
||||||
|
|
||||||
|
\pset expanded on
|
||||||
|
execute q;
|
||||||
|
|
||||||
|
deallocate q;
|
||||||
|
|
||||||
|
-- special cases
|
||||||
|
\pset expanded off
|
||||||
|
select 'comma,comma' as comma, 'semi;semi' as semi;
|
||||||
|
\pset csv_fieldsep ';'
|
||||||
|
select 'comma,comma' as comma, 'semi;semi' as semi;
|
||||||
|
select '\.' as data;
|
||||||
|
\pset csv_fieldsep '.'
|
||||||
|
select '\' as d1, '' as d2;
|
||||||
|
|
||||||
|
-- illegal csv separators
|
||||||
|
\pset csv_fieldsep ''
|
||||||
|
\pset csv_fieldsep '\0'
|
||||||
|
\pset csv_fieldsep '\n'
|
||||||
|
\pset csv_fieldsep '\r'
|
||||||
|
\pset csv_fieldsep '"'
|
||||||
|
\pset csv_fieldsep ',,'
|
||||||
|
|
||||||
|
\pset csv_fieldsep ','
|
||||||
|
|
||||||
-- test html output format
|
-- test html output format
|
||||||
|
|
||||||
\pset format html
|
\pset format html
|
||||||
|
Loading…
x
Reference in New Issue
Block a user