2018-03-26 14:44 GMT+02:00 Daniel Verite <dan...@manitou-mail.org>:

>         Pavel Stehule wrote:
>
> > implemented in attached patch
>
> Consider your initial output of \pset, when no option is specified
> =================
> $ ./psql
> psql (11devel)
> Type "help" for help.
>
> postgres=# \pset
> border                   1
> columns                  0
> expanded                 off
> fieldsep                 not used
> fieldsep_zero            ERROR
> footer                   on
> format                   aligned
> linestyle                ascii
> null                     ''
> numericlocale            off
> pager                    1
> pager_min_lines          0
> recordsep                '\n'
> recordsep_zero           off
> reset                    ERROR
> tableattr
> title
> tuples_only              off
> unicode_border_linestyle single
> unicode_column_linestyle single
> unicode_header_linestyle single
> ================
>
> These entries with ERROR correspond in fact to no error at all,
> or we have to pretend that the default state of psql is erroneous,
> which doesn't make sense.
>
> Also "reset" is not a variable, it seems to be a command,
> so it probably shouldn't be there in the first place.
>

fixed


>
> More generally, I'd think the point of reusing "fieldsep" was to
> reuse the concept, not reimplement it, let alone changing
> bits of behavior of the unaligned mode along the way.
>
> With this patch, again without specifying any option, just looking
> at what fieldsep is leads to this:
>
> postgres=# \pset fieldsep
> User didn't specified field separator.
> Current format doesn't specify default field separator.
>
> If this is the way to "solve" the fact that a user has to do
>  \pset fieldsep ','
> to get commas in csv mode, then IMV the proposed solution
> is clearly worse than the stated problem, and worse than
> simply adding fieldsep_csv to be independant from the
> unaligned mode.
>
>
I don't understand what is wrong there? There can be any message (short -
just "unset",...). I understand so default visual can look strange, because
aligned mode has not default field separator, but I don't see any other
possibility.

Can I do some recapitulation:

1. using CSV with default | as field separator is wrong - probably there is
a agreement

2. partial solution is fieldsep_X where X will be format name. It is better
than using | for csv, but it introduces new problems:

a) there are not new long, short options for this value

b) the list of pset options is bloating - every possible new format can
introduce fieldsep_X option

c) can be messy for people, because the setting fieldsep can has zero
effect on csv or some other formats that don't share default with unaligned
format.

So my position - I am very strong against to introduce CSV format with | as
field separator, and I am not happy if we introduce fieldsep_X like
options, because it is not too good too.

Regards

Pavel



>
> Best regards,
> --
> Daniel Vérité
> PostgreSQL-powered mailer: http://www.manitou-mail.org
> Twitter: @DanielVerite
>
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index 10b97950ec..c984a9cbaa 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -151,6 +151,16 @@ EOF
       </listitem>
     </varlistentry>
 
+    <varlistentry>
+      <term><option>--csv</option></term>
+      <listitem>
+      <para>
+      Switches to csv output mode. This is equivalent to <command>\pset format
+      csv</command> followed by <command>\pset fieldsep ','</command>.
+      </para>
+      </listitem>
+    </varlistentry>
+
     <varlistentry>
       <term><option>-d <replaceable class="parameter">dbname</replaceable></option></term>
       <term><option>--dbname=<replaceable class="parameter">dbname</replaceable></option></term>
@@ -246,7 +256,7 @@ EOF
       <listitem>
       <para>
       Use <replaceable class="parameter">separator</replaceable> as the
-      field separator for unaligned output. This is equivalent to
+      field separator for unaligned and csv outputs. This is equivalent to
       <command>\pset fieldsep</command> or <command>\f</command>.
       </para>
       </listitem>
@@ -382,7 +392,7 @@ EOF
       <listitem>
       <para>
       Use <replaceable class="parameter">separator</replaceable> as the
-      record separator for unaligned output. This is equivalent to
+      record separator for unaligned and csv outputs. This is equivalent to
       <command>\pset recordsep</command>.
       </para>
       </listitem>
@@ -558,7 +568,7 @@ EOF
       <listitem>
       <para>
       Set the field separator for unaligned output to a zero byte.  This is
-      equvalent to <command>\pset fieldsep_zero</command>.
+      equivalent to <command>\pset fieldsep_zero</command>.
       </para>
       </listitem>
     </varlistentry>
@@ -1937,9 +1947,9 @@ Tue Oct 26 21:40:57 CEST 1999
 
         <listitem>
         <para>
-        Sets the field separator for unaligned query output. The default
-        is the vertical bar (<literal>|</literal>). It is equivalent to
-        <command>\pset fieldsep</command>.
+        Sets the field separator for unaligned and csv query outputs. The
+        default is the vertical bar (<literal>|</literal>). It is equivalent
+        to <command>\pset fieldsep</command>.
         </para>
         </listitem>
       </varlistentry>
@@ -2546,8 +2556,8 @@ lo_import 152801
           <term><literal>fieldsep</literal></term>
           <listitem>
           <para>
-          Specifies the field separator to be used in unaligned output
-          format. That way one can create, for example, tab- or
+          Specifies the field separator to be used in unaligned and csv output
+          formats. That way one can create, for example, tab- or
           comma-separated output, which other programs might prefer. To
           set a tab as field separator, type <literal>\pset fieldsep
           '\t'</literal>. The default field separator is
@@ -2584,9 +2594,13 @@ lo_import 152801
           <term><literal>format</literal></term>
           <listitem>
           <para>
-          Sets the output format to one of <literal>unaligned</literal>,
-          <literal>aligned</literal>, <literal>wrapped</literal>,
-          <literal>html</literal>, <literal>asciidoc</literal>,
+          Sets the output format to one of
+          <literal>unaligned</literal>,
+          <literal>aligned</literal>,
+          <literal>csv</literal>,
+          <literal>wrapped</literal>,
+          <literal>html</literal>,
+          <literal>asciidoc</literal>,
           <literal>latex</literal> (uses <literal>tabular</literal>),
           <literal>latex-longtable</literal>, or
           <literal>troff-ms</literal>.
@@ -2601,6 +2615,15 @@ lo_import 152801
           format).
           </para>
 
+          <para><literal>csv</literal> format writes columns separated
+          by <literal>fieldsep</literal>, applying the CSV quoting rules
+          described in RFC-4180 and compatible with the CSV format
+          of the <command>COPY</command> command.
+          The header with column names is output unless the
+          <literal>tuples_only</literal> parameter is <literal>on</literal>.
+          Title and footers are not printed.
+          </para>
+
           <para><literal>aligned</literal> format is the standard, human-readable,
           nicely formatted text output;  this is the default.
           </para>
@@ -2747,8 +2770,8 @@ lo_import 152801
           <term><literal>recordsep</literal></term>
           <listitem>
           <para>
-          Specifies the record (line) separator to use in unaligned
-          output format. The default is a newline character.
+          Specifies the record (line) separator to use in unaligned or
+          csv output formats. The default is a newline character.
           </para>
           </listitem>
           </varlistentry>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 3560318749..f1daf3a88b 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3603,6 +3603,9 @@ _align2string(enum printFormat in)
 		case PRINT_TROFF_MS:
 			return "troff-ms";
 			break;
+		case PRINT_CSV:
+			return "csv";
+			break;
 	}
 	return "unknown";
 }
@@ -3658,27 +3661,37 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 	{
 		if (!value)
 			;
-		else if (pg_strncasecmp("unaligned", value, vallen) == 0)
-			popt->topt.format = PRINT_UNALIGNED;
 		else if (pg_strncasecmp("aligned", value, vallen) == 0)
 			popt->topt.format = PRINT_ALIGNED;
-		else if (pg_strncasecmp("wrapped", value, vallen) == 0)
-			popt->topt.format = PRINT_WRAPPED;
-		else if (pg_strncasecmp("html", value, vallen) == 0)
-			popt->topt.format = PRINT_HTML;
 		else if (pg_strncasecmp("asciidoc", value, vallen) == 0)
 			popt->topt.format = PRINT_ASCIIDOC;
+		else if (pg_strncasecmp("csv", value, vallen) == 0)
+			popt->topt.format = PRINT_CSV;
+		else if (pg_strncasecmp("html", value, vallen) == 0)
+			popt->topt.format = PRINT_HTML;
 		else if (pg_strncasecmp("latex", value, vallen) == 0)
 			popt->topt.format = PRINT_LATEX;
 		else if (pg_strncasecmp("latex-longtable", value, vallen) == 0)
 			popt->topt.format = PRINT_LATEX_LONGTABLE;
 		else if (pg_strncasecmp("troff-ms", value, vallen) == 0)
 			popt->topt.format = PRINT_TROFF_MS;
+		else if (pg_strncasecmp("unaligned", value, vallen) == 0)
+			popt->topt.format = PRINT_UNALIGNED;
+		else if (pg_strncasecmp("wrapped", value, vallen) == 0)
+			popt->topt.format = PRINT_WRAPPED;
 		else
 		{
-			psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, asciidoc, latex, latex-longtable, troff-ms\n");
+			psql_error("\\pset: allowed formats are aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned, wrapped\n");
 			return false;
 		}
+
+		if (!popt->topt.fieldSep.is_custom)
+		{
+			if (popt->topt.fieldSep.separator)
+				free(popt->topt.fieldSep.separator);
+			popt->topt.fieldSep.separator =
+					pg_strdup(get_format_fieldsep(popt->topt.format));
+		}
 	}
 
 	/* set table line style */
@@ -3801,6 +3814,30 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 			free(popt->topt.fieldSep.separator);
 			popt->topt.fieldSep.separator = pg_strdup(value);
 			popt->topt.fieldSep.separator_zero = false;
+			popt->topt.fieldSep.is_custom = true;
+		}
+	}
+
+	else if (strcmp(param, "reset") == 0)
+	{
+		if (!value)
+		{
+			psql_error("\\pset: reset missing object\n");
+			return false;
+		}
+
+		if (strcmp(value, "fieldsep") == 0)
+		{
+			free(popt->topt.fieldSep.separator);
+			popt->topt.fieldSep.separator =
+						pg_strdup(get_format_fieldsep(popt->topt.format));
+			popt->topt.fieldSep.separator_zero = false;
+			popt->topt.fieldSep.is_custom = false;
+		}
+		else
+		{
+			psql_error("\\pset: only fieldsep option can be reseted\n");
+			return false;
 		}
 	}
 
@@ -3920,6 +3957,10 @@ printPsetInfo(const char *param, struct printQueryOpt *popt)
 {
 	Assert(param != NULL);
 
+	/* do nothing if option was reseted */
+	if (strcmp(param, "reset") == 0)
+		return true;
+
 	/* show border style/width */
 	if (strcmp(param, "border") == 0)
 		printf(_("Border style is %d.\n"), popt->topt.border);
@@ -3949,6 +3990,17 @@ printPsetInfo(const char *param, struct printQueryOpt *popt)
 	{
 		if (popt->topt.fieldSep.separator_zero)
 			printf(_("Field separator is zero byte.\n"));
+		else if (!popt->topt.fieldSep.is_custom)
+		{
+			if (strcmp(popt->topt.fieldSep.separator, "not used") == 0)
+			{
+				printf("User didn't specified field separator.\n");
+				printf("Current format doesn't specify default field separator.\n");
+			}
+			else
+				printf(_("Field separator is format default (\"%s\").\n"),
+					  popt->topt.fieldSep.separator);
+		}
 		else
 			printf(_("Field separator is \"%s\".\n"),
 				   popt->topt.fieldSep.separator);
@@ -4150,9 +4202,16 @@ pset_value_string(const char *param, struct printQueryOpt *popt)
 					   ? "auto"
 					   : pset_bool_string(popt->topt.expanded));
 	else if (strcmp(param, "fieldsep") == 0)
-		return pset_quoted_string(popt->topt.fieldSep.separator
+	{
+		if (!popt->topt.fieldSep.is_custom &&
+				  popt->topt.fieldSep.separator &&
+				  strcmp(popt->topt.fieldSep.separator, "not used") == 0)
+			return pstrdup("not used");
+		else
+			return pset_quoted_string(popt->topt.fieldSep.separator
 								  ? popt->topt.fieldSep.separator
 								  : "");
+	}
 	else if (strcmp(param, "fieldsep_zero") == 0)
 		return pstrdup(pset_bool_string(popt->topt.fieldSep.separator_zero));
 	else if (strcmp(param, "footer") == 0)
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 702e742af4..50a07deccd 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -108,13 +108,14 @@ usage(unsigned short int pager)
 
 	fprintf(output, _("\nOutput format options:\n"));
 	fprintf(output, _("  -A, --no-align           unaligned table output mode\n"));
+	fprintf(output, _("      --csv                Comma-Separated-Values output mode\n"));
 	fprintf(output, _("  -F, --field-separator=STRING\n"
-					  "                           field separator for unaligned output (default: \"%s\")\n"),
-			DEFAULT_FIELD_SEP);
+					  "                           field separator for unaligned (default: \"%s\") or csv (default \"%s\") output\n"),
+			DEFAULT_FIELD_SEP, DEFAULT_FIELD_SEP_CSV);
 	fprintf(output, _("  -H, --html               HTML table output mode\n"));
 	fprintf(output, _("  -P, --pset=VAR[=ARG]     set printing option VAR to ARG (see \\pset command)\n"));
 	fprintf(output, _("  -R, --record-separator=STRING\n"
-					  "                           record separator for unaligned output (default: newline)\n"));
+					  "                           record separator for unaligned or csv output (default: newline)\n"));
 	fprintf(output, _("  -t, --tuples-only        print rows only\n"));
 	fprintf(output, _("  -T, --table-attr=TEXT    set HTML table tag attributes (e.g., width, border)\n"));
 	fprintf(output, _("  -x, --expanded           turn on expanded table output\n"));
@@ -268,14 +269,14 @@ slashUsage(unsigned short int pager)
 	fprintf(output, _("Formatting\n"));
 	fprintf(output, _("  \\a                     toggle between unaligned and aligned output mode\n"));
 	fprintf(output, _("  \\C [STRING]            set table title, or unset if none\n"));
-	fprintf(output, _("  \\f [STRING]            show or set field separator for unaligned query output\n"));
+	fprintf(output, _("  \\f [STRING]            show or set field separator for unaligned or csv output\n"));
 	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"
+					  "                         (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"
+					  "                         tuples_only|unicode_border_linestyle|reset|\n"
 					  "                         unicode_column_linestyle|unicode_header_linestyle})\n"));
 	fprintf(output, _("  \\t [on|off]            show only rows (currently %s)\n"),
 			ON(pset.popt.topt.tuples_only));
@@ -426,8 +427,8 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  expanded (or x)\n"
 					  "    expanded output [on, off, auto]\n"));
 	fprintf(output, _("  fieldsep\n"
-					  "    field separator for unaligned output (default \"%s\")\n"),
-			DEFAULT_FIELD_SEP);
+					  "    field separator for unaligned (default: \"%s\") and csv (default \"%s\") output\n"),
+			DEFAULT_FIELD_SEP, DEFAULT_FIELD_SEP_CSV);
 	fprintf(output, _("  fieldsep_zero\n"
 					  "    set field separator for unaligned output to a zero byte\n"));
 	fprintf(output, _("  footer\n"
@@ -443,7 +444,7 @@ helpVariables(unsigned short int pager)
 	fprintf(output, _("  pager\n"
 					  "    control when an external pager is used [yes, no, always]\n"));
 	fprintf(output, _("  recordsep\n"
-					  "    record (line) separator for unaligned output\n"));
+					  "    record (line) separator for unaligned and csv output\n"));
 	fprintf(output, _("  recordsep_zero\n"
 					  "    set record separator for unaligned output to a zero byte\n"));
 	fprintf(output, _("  tableattr (or T)\n"
diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h
index 69e617e6b5..73314dbca1 100644
--- a/src/bin/psql/settings.h
+++ b/src/bin/psql/settings.h
@@ -12,7 +12,6 @@
 #include "variables.h"
 #include "fe_utils/print.h"
 
-#define DEFAULT_FIELD_SEP "|"
 #define DEFAULT_RECORD_SEP "\n"
 
 #if defined(WIN32) || defined(__CYGWIN__)
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index be57574cd3..4dd8d8f45e 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -194,7 +194,8 @@ main(int argc, char *argv[])
 	if (!pset.popt.topt.fieldSep.separator &&
 		!pset.popt.topt.fieldSep.separator_zero)
 	{
-		pset.popt.topt.fieldSep.separator = pg_strdup(DEFAULT_FIELD_SEP);
+		pset.popt.topt.fieldSep.separator = pg_strdup(get_format_fieldsep(pset.popt.topt.format));
+		pset.popt.topt.fieldSep.is_custom = false;
 		pset.popt.topt.fieldSep.separator_zero = false;
 	}
 	if (!pset.popt.topt.recordSep.separator &&
@@ -436,6 +437,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts *options)
 		{"echo-all", no_argument, NULL, 'a'},
 		{"no-align", no_argument, NULL, 'A'},
 		{"command", required_argument, NULL, 'c'},
+		{"csv", no_argument, NULL, 2}, /* no single-letter (leave -C for future use) */
 		{"dbname", required_argument, NULL, 'd'},
 		{"echo-queries", no_argument, NULL, 'e'},
 		{"echo-errors", no_argument, NULL, 'b'},
@@ -485,7 +487,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts *options)
 				SetVariable(pset.vars, "ECHO", "all");
 				break;
 			case 'A':
-				pset.popt.topt.format = PRINT_UNALIGNED;
+				do_pset("format", "unaligned", &pset.popt, true);
 				break;
 			case 'b':
 				SetVariable(pset.vars, "ECHO", "errors");
@@ -515,14 +517,13 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts *options)
 										  optarg);
 				break;
 			case 'F':
-				pset.popt.topt.fieldSep.separator = pg_strdup(optarg);
-				pset.popt.topt.fieldSep.separator_zero = false;
+				do_pset("fieldsep", optarg, &pset.popt, true);
 				break;
 			case 'h':
 				options->host = pg_strdup(optarg);
 				break;
 			case 'H':
-				pset.popt.topt.format = PRINT_HTML;
+				do_pset("format", "html", &pset.popt, true);
 				break;
 			case 'l':
 				options->list_dbs = true;
@@ -569,8 +570,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts *options)
 				SetVariableBool(pset.vars, "QUIET");
 				break;
 			case 'R':
-				pset.popt.topt.recordSep.separator = pg_strdup(optarg);
-				pset.popt.topt.recordSep.separator_zero = false;
+				do_pset("recordsep", optarg, &pset.popt, true);
 				break;
 			case 's':
 				SetVariableBool(pset.vars, "SINGLESTEP");
@@ -625,10 +625,10 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts *options)
 				options->no_psqlrc = true;
 				break;
 			case 'z':
-				pset.popt.topt.fieldSep.separator_zero = true;
+				do_pset("fieldsep_zero", NULL, &pset.popt, true);
 				break;
 			case '0':
-				pset.popt.topt.recordSep.separator_zero = true;
+				do_pset("recordsep_zero", NULL, &pset.popt, true);
 				break;
 			case '1':
 				options->single_txn = true;
@@ -658,6 +658,10 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts *options)
 					exit(EXIT_SUCCESS);
 				}
 				break;
+			case 2:
+				/*  --csv: set both format and field separator */
+				do_pset("format", "csv", &pset.popt, true);
+				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 08d8ef09a4..735670e0f5 100644
--- a/src/bin/psql/tab-complete.c
+++ b/src/bin/psql/tab-complete.c
@@ -3743,8 +3743,9 @@ psql_completion(const char *text, int start, int end)
 		{"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
 			"footer", "format", "linestyle", "null", "numericlocale",
 			"pager", "pager_min_lines", "recordsep", "recordsep_zero",
-			"tableattr", "title", "tuples_only", "unicode_border_linestyle",
-		"unicode_column_linestyle", "unicode_header_linestyle", NULL};
+			"reset", "tableattr", "title", "tuples_only",
+			"unicode_border_linestyle", "unicode_column_linestyle",
+			"unicode_header_linestyle", NULL};
 
 		COMPLETE_WITH_LIST_CS(my_list);
 	}
@@ -3753,8 +3754,8 @@ psql_completion(const char *text, int start, int end)
 		if (TailMatchesCS1("format"))
 		{
 			static const char *const my_list[] =
-			{"unaligned", "aligned", "wrapped", "html", "asciidoc",
-			"latex", "latex-longtable", "troff-ms", NULL};
+			{"unaligned", "aligned", "csv", "wrapped", "html", "asciidoc",
+			 "latex", "latex-longtable", "troff-ms", NULL};
 
 			COMPLETE_WITH_LIST_CS(my_list);
 		}
diff --git a/src/fe_utils/print.c b/src/fe_utils/print.c
index ec5ad45a30..954f448400 100644
--- a/src/fe_utils/print.c
+++ b/src/fe_utils/print.c
@@ -35,7 +35,6 @@
 #include "catalog/pg_type.h"
 #include "fe_utils/mbprint.h"
 
-
 /*
  * If the calling program doesn't have any mechanism for setting
  * cancel_pressed, it will have no effect.
@@ -2783,6 +2782,109 @@ print_troff_ms_vertical(const printTableContent *cont, FILE *fout)
 	}
 }
 
+/*************************/
+/* CSV  				 */
+/*************************/
+static void
+csv_escaped_print(const char *text, FILE *fout)
+{
+	const char *p;
+
+	fputc('"', fout);
+	for (p = text; *p; p++)
+	{
+		if (*p == '"')
+			fputc('"', fout);	/* double quotes are doubled */
+		fputc(*p, fout);
+	}
+	fputc('"', fout);
+}
+
+static void
+csv_print_field(const char *text, FILE *fout, const 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
+	 */
+	if ((sep != NULL && *sep != '\0' && strstr(text, sep) != NULL) ||
+		strcspn(text, "\r\n\"") != strlen(text))
+	{
+		csv_escaped_print(text, fout);
+	}
+	else
+		fputs(text, fout);
+}
+
+static void
+print_csv_text(const printTableContent *cont, FILE *fout)
+{
+	const char *const *ptr;
+	const char* fieldsep = cont->opt->fieldSep.separator;
+	const char* const recordsep = cont->opt->recordSep.separator;
+	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.
+	 */
+
+	if (cont->opt->start_table && !cont->opt->tuples_only)
+	{
+		/* print headers */
+		for (ptr = cont->headers; *ptr; ptr++)
+		{
+			if (ptr != cont->headers)
+				fputs(fieldsep, fout);
+			csv_print_field(*ptr, fout, fieldsep);
+		}
+		fputs(recordsep, fout);
+	}
+
+	/* print cells */
+	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
+	{
+		if (cancel_pressed)
+			break;
+
+		csv_print_field(*ptr, fout, fieldsep);
+
+		if ((i + 1) % cont->ncolumns)
+			fputs(fieldsep, fout);
+		else
+			fputs(recordsep, fout);
+	}
+}
+
+static void
+print_csv_vertical(const printTableContent *cont, FILE *fout)
+{
+	unsigned int i;
+	const char *const *ptr;
+
+	/* Print records */
+	for (i = 0, ptr = cont->cells; *ptr; i++, ptr++)
+	{
+		if (cancel_pressed)
+			break;
+
+		/* Field name */
+		csv_print_field(cont->headers[i % cont->ncolumns], fout,
+						cont->opt->fieldSep.separator);
+		fputs(cont->opt->fieldSep.separator, fout);
+
+		/* Field value followed by record separator */
+		csv_print_field(*ptr, fout, cont->opt->fieldSep.separator);
+		fputs(cont->opt->recordSep.separator, fout);
+	}
+}
+
 
 /********************************/
 /* Public functions				*/
@@ -3234,6 +3336,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);
@@ -3424,6 +3532,21 @@ get_line_style(const printTableOpt *opt)
 		return &pg_asciiformat;
 }
 
+/* returns default fieldSep used by formats */
+const char *
+get_format_fieldsep(enum printFormat format)
+{
+	switch (format)
+	{
+		case PRINT_UNALIGNED:
+			return DEFAULT_FIELD_SEP;
+		case PRINT_CSV:
+			return DEFAULT_FIELD_SEP_CSV;
+		default:
+			return "not used";
+	}
+}
+
 void
 refresh_utf8format(const printTableOpt *opt)
 {
diff --git a/src/include/fe_utils/print.h b/src/include/fe_utils/print.h
index 83320d06bd..6ca04e7ea6 100644
--- a/src/include/fe_utils/print.h
+++ b/src/include/fe_utils/print.h
@@ -23,6 +23,9 @@
 #define DEFAULT_PAGER "less"
 #endif
 
+#define DEFAULT_FIELD_SEP "|"
+#define DEFAULT_FIELD_SEP_CSV ","
+
 enum printFormat
 {
 	PRINT_NOTHING = 0,			/* to make sure someone initializes this */
@@ -33,7 +36,8 @@ enum printFormat
 	PRINT_ASCIIDOC,
 	PRINT_LATEX,
 	PRINT_LATEX_LONGTABLE,
-	PRINT_TROFF_MS
+	PRINT_TROFF_MS,
+	PRINT_CSV
 	/* add your favourite output format here ... */
 };
 
@@ -91,6 +95,7 @@ struct separator
 {
 	char	   *separator;
 	bool		separator_zero;
+	bool		is_custom;			/* true, when user set this option */
 };
 
 typedef struct printTableOpt
@@ -158,8 +163,8 @@ typedef struct printTableContent
 	char	   *aligns;			/* Array of alignment specifiers; 'l' or 'r',
 								 * one per column */
 	char	   *align;			/* Pointer to the last added alignment */
-} printTableContent;
 
+} printTableContent;
 typedef struct printQueryOpt
 {
 	printTableOpt topt;			/* the options above */
@@ -210,6 +215,7 @@ extern char column_type_alignment(Oid);
 
 extern void setDecimalLocale(void);
 extern const printTextFormat *get_line_style(const printTableOpt *opt);
+extern const char *get_format_fieldsep(enum printFormat format);
 extern void refresh_utf8format(const printTableOpt *opt);
 
 #endif							/* PRINT_H */
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 3818cfea7e..04f7ee11f7 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -261,7 +261,7 @@ select '2000-01-01'::date as party_over
 border                   1
 columns                  0
 expanded                 off
-fieldsep                 '|'
+fieldsep                 ''
 fieldsep_zero            off
 footer                   on
 format                   aligned
@@ -3243,3 +3243,106 @@ last error message: division by zero
 \echo 'last error code:' :LAST_ERROR_SQLSTATE
 last error code: 22012
 \unset FETCH_COUNT
+-- test csv format
+prepare q as select 'ab,cd' as col1, 'ab' as "col,2", E'a\tb' as col3, '"' as col4,
+  '""' as col5, 'a"b' as "col""6", E'a\nb' as col7, NULL as col8, 'ab' as "col
+  9",  array['ab', E'cd\nef'] as col10,
+  '{"a":"a,b", "a,b":null, "c":"a,\"b"}'::json as col11
+   from generate_series(1,2);
+\pset format csv
+\pset fieldsep ','
+\pset expanded off
+\t off
+execute q;
+col1,"col,2",col3,col4,col5,"col""6",col7,col8,"col
+  9",col10,col11
+"ab,cd",ab,a	b,"""","""""","a""b","a
+b",,ab,"{ab,""cd
+ef""}","{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+"ab,cd",ab,a	b,"""","""""","a""b","a
+b",,ab,"{ab,""cd
+ef""}","{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+\pset fieldsep '\t'
+execute q;
+col1	col,2	col3	col4	col5	"col""6"	col7	col8	"col
+  9"	col10	col11
+ab,cd	ab	"a	b"	""""	""""""	"a""b"	"a
+b"		ab	"{ab,""cd
+ef""}"	"{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+ab,cd	ab	"a	b"	""""	""""""	"a""b"	"a
+b"		ab	"{ab,""cd
+ef""}"	"{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+\t on
+execute q;
+ab,cd	ab	"a	b"	""""	""""""	"a""b"	"a
+b"		ab	"{ab,""cd
+ef""}"	"{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+ab,cd	ab	"a	b"	""""	""""""	"a""b"	"a
+b"		ab	"{ab,""cd
+ef""}"	"{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+\t off
+\pset expanded on
+execute q;
+col1	ab,cd
+col,2	ab
+col3	"a	b"
+col4	""""
+col5	""""""
+"col""6"	"a""b"
+col7	"a
+b"
+col8	
+"col
+  9"	ab
+col10	"{ab,""cd
+ef""}"
+col11	"{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+col1	ab,cd
+col,2	ab
+col3	"a	b"
+col4	""""
+col5	""""""
+"col""6"	"a""b"
+col7	"a
+b"
+col8	
+"col
+  9"	ab
+col10	"{ab,""cd
+ef""}"
+col11	"{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+\pset fieldsep ','
+execute q;
+col1,"ab,cd"
+"col,2",ab
+col3,a	b
+col4,""""
+col5,""""""
+"col""6","a""b"
+col7,"a
+b"
+col8,
+"col
+  9",ab
+col10,"{ab,""cd
+ef""}"
+col11,"{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+col1,"ab,cd"
+"col,2",ab
+col3,a	b
+col4,""""
+col5,""""""
+"col""6","a""b"
+col7,"a
+b"
+col8,
+"col
+  9",ab
+col10,"{ab,""cd
+ef""}"
+col11,"{""a"":""a,b"", ""a,b"":null, ""c"":""a,\""b""}"
+deallocate q;
+\pset format aligned
+\pset expanded off
+\pset fieldsep '|'
+\t off
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index b45da9bb8d..6891a9b54c 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -688,3 +688,31 @@ select 1/(15-unique2) from tenk1 order by unique2 limit 19;
 \echo 'last error code:' :LAST_ERROR_SQLSTATE
 
 \unset FETCH_COUNT
+
+-- test csv format
+prepare q as select 'ab,cd' as col1, 'ab' as "col,2", E'a\tb' as col3, '"' as col4,
+  '""' as col5, 'a"b' as "col""6", E'a\nb' as col7, NULL as col8, 'ab' as "col
+  9",  array['ab', E'cd\nef'] as col10,
+  '{"a":"a,b", "a,b":null, "c":"a,\"b"}'::json as col11
+   from generate_series(1,2);
+
+\pset format csv
+\pset fieldsep ','
+\pset expanded off
+\t off
+execute q;
+\pset fieldsep '\t'
+execute q;
+\t on
+execute q;
+\t off
+\pset expanded on
+execute q;
+\pset fieldsep ','
+execute q;
+
+deallocate q;
+\pset format aligned
+\pset expanded off
+\pset fieldsep '|'
+\t off

Reply via email to