diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 6e6d0f42d13..47714eb0c8c 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -68,8 +68,8 @@ PostgreSQL documentation
Switches to unaligned output mode. (The default output mode is
- otherwise aligned.) This is equivalent to \pset format
- unaligned.
+ aligned.) This is equivalent to
+ \pset format unaligned.
@@ -151,6 +151,16 @@ EOF
+
+
+
+
+ Switches to CSV (Comma-Separated Values) output
+ mode. This is equivalent to \pset format csv.
+
+
+
+
@@ -270,8 +280,8 @@ EOF
- Turn on HTML tabular output. This is
- equivalent to \pset format html or the
+ Switches to HTML output mode. This is
+ equivalent to \pset format html or the
\H command.
@@ -2520,6 +2530,19 @@ lo_import 152801
+
+ csv_fieldsep
+
+
+ Specifies the field separator to be used in
+ CSV output format. If the separator character
+ appears in a field's value, that field is output within double
+ quotes, following standard CSV rules.
+ The default is a comma.
+
+
+
+
expanded (or x)
@@ -2547,8 +2570,8 @@ lo_import 152801
Specifies the field separator to be used in unaligned output
- format. That way one can create, for example, tab- or
- comma-separated output, which other programs might prefer. To
+ format. That way one can create, for example, tab-separated
+ output, which other programs might prefer. To
set a tab as field separator, type \pset fieldsep
'\t'. The default field separator is
'|' (a vertical bar).
@@ -2585,22 +2608,48 @@ lo_import 152801
Sets the output format to one of aligned,
- asciidoc, html,
- latex (uses tabular),
+ asciidoc,
+ csv,
+ html,
+ latex,
latex-longtable, troff-ms,
unaligned, or wrapped.
Unique abbreviations are allowed.
+ aligned format is the standard,
+ human-readable, nicely formatted text output; this is the default.
+
+
unaligned format writes all columns of a row on one
line, separated by the currently active field separator. This
is useful for creating output that might be intended to be read
- in by other programs (for example, tab-separated or comma-separated
- format).
+ in by other programs, for example, tab-separated or comma-separated
+ format. However, the field separator character is not treated
+ specially if it appears in a column's value;
+ so CSV format may be better suited for such
+ purposes.
- aligned format is the standard, human-readable,
- nicely formatted text output; this is the default.
+ csv format
+
+ CSV (Comma-Separated Values) format
+ in psql
+
+ writes column values separated by commas, applying the quoting
+ rules described in
+ RFC 4180.
+ This output is compatible with the CSV format of the server's
+ COPY command.
+ A header line with column names is generated unless
+ the tuples_only parameter is
+ on. Titles and footers are not printed.
+ Each row is terminated by the system-dependent end-of-line character,
+ which is typically a single newline (\n) for
+ Unix-like systems or a carriage return and newline sequence
+ (\r\n) for Microsoft Windows.
+ Field separator characters other than comma can be selected with
+ \pset csv_fieldsep.
wrapped format is like aligned but wraps
@@ -2620,8 +2669,12 @@ lo_import 152801
language. They are not complete documents! This might not be
necessary in HTML, but in
LaTeX you must have a complete
- document wrapper. latex-longtable
- also requires the LaTeX
+ document wrapper.
+ The latex format
+ uses LaTeX's tabular
+ environment.
+ The latex-longtable format
+ requires the LaTeXlongtable and booktabs packages.
@@ -4588,17 +4641,24 @@ first second
peter@localhost testdb=> \pset border 1
Border style is 1.
-peter@localhost testdb=> \pset format unaligned
-Output format is unaligned.
-peter@localhost testdb=> \pset fieldsep ","
-Field separator is ",".
+peter@localhost testdb=> \pset format csv
+Output format is csv.
peter@localhost testdb=> \pset tuples_only
-Showing only tuples.
+Tuples only is on.
peter@localhost testdb=> SELECT second, first FROM my_table;
one,1
two,2
three,3
four,4
+peter@localhost testdb=> \pset format unaligned
+Output format is unaligned.
+peter@localhost testdb=> \pset fieldsep '\t'
+Field separator is " ".
+peter@localhost testdb=> SELECT second, first FROM my_table;
+one 1
+two 2
+three 3
+four 4
Alternatively, use the short commands:
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 13d4c5792f3..4a298ef0c5b 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -1957,8 +1957,8 @@ exec_command_pset(PsqlScanState scan_state, bool active_branch)
int i;
static const char *const my_list[] = {
- "border", "columns", "expanded", "fieldsep", "fieldsep_zero",
- "footer", "format", "linestyle", "null",
+ "border", "columns", "csv_fieldsep", "expanded", "fieldsep",
+ "fieldsep_zero", "footer", "format", "linestyle", "null",
"numericlocale", "pager", "pager_min_lines",
"recordsep", "recordsep_zero",
"tableattr", "title", "tuples_only",
@@ -3616,6 +3616,9 @@ _align2string(enum printFormat in)
case PRINT_ASCIIDOC:
return "asciidoc";
break;
+ case PRINT_CSV:
+ return "csv";
+ break;
case PRINT_HTML:
return "html";
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 */
{"aligned", PRINT_ALIGNED},
{"asciidoc", PRINT_ASCIIDOC},
+ {"csv", PRINT_CSV},
{"html", PRINT_HTML},
{"latex", PRINT_LATEX},
{"troff-ms", PRINT_TROFF_MS},
@@ -3737,7 +3741,7 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
}
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;
}
}
@@ -3836,6 +3840,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
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 */
else if (strcmp(param, "numericlocale") == 0)
{
@@ -4006,6 +4030,13 @@ printPsetInfo(const char *param, struct printQueryOpt *popt)
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 */
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);
else if (strcmp(param, "columns") == 0)
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)
return pstrdup(popt->topt.expanded == 2
? "auto"
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 586aebddd3d..2e9fe760eb3 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -68,7 +68,7 @@ usage(unsigned short int pager)
* Keep this line count in sync with the number of lines printed below!
* 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, _("Usage:\n"));
@@ -108,6 +108,7 @@ usage(unsigned short int pager)
fprintf(output, _("\nOutput format options:\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"
" field separator for unaligned output (default: \"%s\")\n"),
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
* 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, _(" \\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"),
ON(pset.popt.topt.format == PRINT_HTML));
fprintf(output, _(" \\pset [NAME [VALUE]] set table output option\n"
- " (NAME := {border|columns|expanded|fieldsep|fieldsep_zero|\n"
- " footer|format|linestyle|null|numericlocale|pager|\n"
- " pager_min_lines|recordsep|recordsep_zero|tableattr|title|\n"
- " tuples_only|unicode_border_linestyle|\n"
- " unicode_column_linestyle|unicode_header_linestyle})\n"));
+ " (border|columns|csv_fieldsep|expanded|fieldsep|\n"
+ " fieldsep_zero|footer|format|linestyle|null|\n"
+ " numericlocale|pager|pager_min_lines|recordsep|\n"
+ " recordsep_zero|tableattr|title|tuples_only|\n"
+ " unicode_border_linestyle|unicode_column_linestyle|\n"
+ " unicode_header_linestyle)\n"));
fprintf(output, _(" \\t [on|off] show only rows (currently %s)\n"),
ON(pset.popt.topt.tuples_only));
fprintf(output, _(" \\T [STRING] set HTML
tag attributes, or unset if none\n"));
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 69e617e6b5f..176c85afd0e 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -12,6 +12,7 @@
#include "variables.h"
#include "fe_utils/print.h"
+#define DEFAULT_CSV_FIELD_SEP ','
#define DEFAULT_FIELD_SEP "|"
#define DEFAULT_RECORD_SEP "\n"
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index be57574cd32..e7536a8a06f 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -144,6 +144,9 @@ main(int argc, char *argv[])
pset.popt.topt.stop_table = 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_column_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'},
{"no-psqlrc", no_argument, NULL, 'X'},
{"help", optional_argument, NULL, 1},
+ {"csv", no_argument, NULL, 2},
{NULL, 0, NULL, 0}
};
@@ -658,6 +662,9 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts *options)
exit(EXIT_SUCCESS);
}
break;
+ case 2:
+ pset.popt.topt.format = PRINT_CSV;
+ break;
default:
unknown_option:
fprintf(stderr, _("Try \"%s --help\" for more information.\n"),
diff --git a/src/bin/psql/tab-complete.c b/src/bin/psql/tab-complete.c
index 9dbd5551668..7993c05283d 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -2605,6 +2605,7 @@ psql_completion(const char *text, int start, int end)
/* Complete CREATE EVENT TRIGGER ON with event_type */
else if (Matches("CREATE", "EVENT", "TRIGGER", MatchAny, "ON"))
COMPLETE_WITH("ddl_command_start", "ddl_command_end", "sql_drop");
+
/*
* Complete CREATE EVENT TRIGGER ON . EXECUTE FUNCTION
* 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"))
COMPLETE_WITH_QUERY(Query_for_list_of_roles);
else if (TailMatchesCS("\\pset"))
- COMPLETE_WITH_CS("border", "columns", "expanded",
+ COMPLETE_WITH_CS("border", "columns", "csv_fieldsep", "expanded",
"fieldsep", "fieldsep_zero", "footer", "format",
"linestyle", "null", "numericlocale",
"pager", "pager_min_lines",
@@ -3536,7 +3537,7 @@ psql_completion(const char *text, int start, int end)
else if (TailMatchesCS("\\pset", MatchAny))
{
if (TailMatchesCS("format"))
- COMPLETE_WITH_CS("aligned", "asciidoc", "html", "latex",
+ COMPLETE_WITH_CS("aligned", "asciidoc", "csv", "html", "latex",
"latex-longtable", "troff-ms", "unaligned",
"wrapped");
else if (TailMatchesCS("linestyle"))
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index cb9a9a06131..6b78f0909cd 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -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 */
/**********************/
@@ -1953,9 +2065,10 @@ print_html_vertical(const printTableContent *cont, FILE *fout)
/*************************/
-/* ASCIIDOC */
+/* ASCIIDOC */
/*************************/
+
static void
asciidoc_escaped_print(const char *in, FILE *fout)
{
@@ -2174,6 +2287,7 @@ print_asciidoc_vertical(const printTableContent *cont, FILE *fout)
}
}
+
/*************************/
/* LaTeX */
/*************************/
@@ -2319,6 +2433,11 @@ print_latex_text(const printTableContent *cont, FILE *fout)
}
+/*************************/
+/* LaTeX longtable */
+/*************************/
+
+
static void
print_latex_longtable_text(const printTableContent *cont, FILE *fout)
{
@@ -2564,7 +2683,7 @@ print_latex_vertical(const printTableContent *cont, FILE *fout)
/*************************/
-/* Troff -ms */
+/* Troff -ms */
/*************************/
@@ -3234,6 +3353,12 @@ printTable(const printTableContent *cont,
else
print_aligned_text(cont, fout, is_pager);
break;
+ case PRINT_CSV:
+ if (cont->opt->expanded == 1)
+ print_csv_vertical(cont, fout);
+ else
+ print_csv_text(cont, fout);
+ break;
case PRINT_HTML:
if (cont->opt->expanded == 1)
print_html_vertical(cont, fout);
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index b761349bc72..4f7987e5cbb 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -28,6 +28,7 @@ enum printFormat
PRINT_NOTHING = 0, /* to make sure someone initializes this */
PRINT_ALIGNED,
PRINT_ASCIIDOC,
+ PRINT_CSV,
PRINT_HTML,
PRINT_LATEX,
PRINT_LATEX_LONGTABLE,
@@ -112,6 +113,7 @@ typedef struct printTableOpt
const printTextFormat *line_style; /* line style (NULL for default) */
struct separator fieldSep; /* field 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
* decimal marker */
char *tableAttr; /* attributes for HTML
*/
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 7f20ae2c609..b6face58e39 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -260,6 +260,7 @@ select '2000-01-01'::date as party_over
\pset
border 1
columns 0
+csv_fieldsep ','
expanded off
fieldsep '|'
fieldsep_zero off
@@ -2937,6 +2938,94 @@ execute q;
l|2
|====
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' \n' 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","
+", ,1
+"some""text","
+", ,2
+\pset expanded on
+execute q;
+"a""title","some""text"
+junk,"
+"
+empty,
+int,1
+"a""title","some""text"
+junk,"
+"
+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
\pset format html
\pset border 1
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 82db94756ec..bfe02d12be0 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -501,6 +501,54 @@ execute 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' \n' 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
\pset format html