On tor, 2012-01-26 at 19:00 +0530, Abhijit Menon-Sen wrote:
> At issue are (at least) these three lines from print_unaligned_text in
> src/bin/psql/print.c:
> 
>  358         /* the last record needs to be concluded with a newline
> */
>  359         if (need_recordsep)
>  360             fputc('\n', fout);
> 
> Perhaps the right thing to do would be to change this to output \0 if
> --record-separator-zero was used (but leave it at \n otherwise)? That
> is what my second attached patch does:
> 
> $ bin/psql --record-separator-zero --field-separator-zero -At -c
> 'select 1,2 union select 3,4'|xargs -0 echo
> 1 2 3 4
> 
> Thoughts?
> 
> > I think the most common use of this would be to set the record
> > separator from the command line, so we could use a short option
> > such as -0 or -z for that.
> 
> I agree. The current option names are very unwieldy to type.
> 
I have incorporated your two patches and added short options.  Updated
patch attached.

This made me wonder, however.  The existing -F and -R options set the
record *separator*.  The new options, however, set the record
*terminator*.  This is the small distinction that you had discovered.

Should we rename the options and/or add that to the documentation, or is
the new behavior obvious and any new terminology would be too confusing?
diff --git a/doc/src/sgml/ref/psql-ref.sgml b/doc/src/sgml/ref/psql-ref.sgml
index a9b1ed2..55aa5f2 100644
--- a/doc/src/sgml/ref/psql-ref.sgml
+++ b/doc/src/sgml/ref/psql-ref.sgml
@@ -482,6 +482,27 @@ PostgreSQL documentation
       </listitem>
     </varlistentry>
 
+    <varlistentry>
+      <term><option>-z</option></term>
+      <term><option>--field-separator-zero</option></term>
+      <listitem>
+      <para>
+      Set the field separator for unaligned output to a zero byte.
+      </para>
+      </listitem>
+    </varlistentry>
+
+    <varlistentry>
+      <term><option>-0</option></term>
+      <term><option>--record-separator-zero</option></term>
+      <listitem>
+      <para>
+      Set the record separator for unaligned output to a zero byte.  This is
+      useful for interfacing, for example, with <literal>xargs -0</literal>.
+      </para>
+      </listitem>
+    </varlistentry>
+
      <varlistentry>
       <term><option>-1</option></term>
       <term><option>--single-transaction</option></term>
@@ -1909,6 +1930,16 @@ lo_import 152801
           </varlistentry>
 
           <varlistentry>
+          <term><literal>fieldsep_zero</literal></term>
+          <listitem>
+          <para>
+          Sets the field separator to use in unaligned output format to a zero
+          byte.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry>
           <term><literal>footer</literal></term>
           <listitem>
           <para>
@@ -2078,6 +2109,16 @@ lo_import 152801
           </varlistentry>
 
           <varlistentry>
+          <term><literal>recordsep_zero</literal></term>
+          <listitem>
+          <para>
+          Sets the record separator to use in unaligned output format to a zero
+          byte.
+          </para>
+          </listitem>
+          </varlistentry>
+
+          <varlistentry>
           <term><literal>tableattr</literal> (or <literal>T</literal>)</term>
           <listitem>
           <para>
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index ab809ec..8421ad0 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -2272,11 +2272,26 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 	{
 		if (value)
 		{
-			free(popt->topt.fieldSep);
-			popt->topt.fieldSep = pg_strdup(value);
+			free(popt->topt.fieldSep.separator);
+			popt->topt.fieldSep.separator = pg_strdup(value);
+			popt->topt.fieldSep.separator_zero = false;
 		}
 		if (!quiet)
-			printf(_("Field separator is \"%s\".\n"), popt->topt.fieldSep);
+		{
+			if (popt->topt.fieldSep.separator_zero)
+				printf(_("Field separator is zero byte.\n"));
+			else
+				printf(_("Field separator is \"%s\".\n"), popt->topt.fieldSep.separator);
+		}
+	}
+
+	else if (strcmp(param, "fieldsep_zero") == 0)
+	{
+		free(popt->topt.fieldSep.separator);
+		popt->topt.fieldSep.separator = NULL;
+		popt->topt.fieldSep.separator_zero = true;
+		if (!quiet)
+			printf(_("Field separator is zero byte.\n"));
 	}
 
 	/* record separator for unaligned text */
@@ -2284,18 +2299,30 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
 	{
 		if (value)
 		{
-			free(popt->topt.recordSep);
-			popt->topt.recordSep = pg_strdup(value);
+			free(popt->topt.recordSep.separator);
+			popt->topt.recordSep.separator = pg_strdup(value);
+			popt->topt.recordSep.separator_zero = false;
 		}
 		if (!quiet)
 		{
-			if (strcmp(popt->topt.recordSep, "\n") == 0)
+			if (popt->topt.recordSep.separator_zero)
+				printf(_("Record separator is zero byte.\n"));
+			else if (strcmp(popt->topt.recordSep.separator, "\n") == 0)
 				printf(_("Record separator is <newline>."));
 			else
-				printf(_("Record separator is \"%s\".\n"), popt->topt.recordSep);
+				printf(_("Record separator is \"%s\".\n"), popt->topt.recordSep.separator);
 		}
 	}
 
+	else if (strcmp(param, "recordsep_zero") == 0)
+	{
+		free(popt->topt.recordSep.separator);
+		popt->topt.recordSep.separator = NULL;
+		popt->topt.recordSep.separator_zero = true;
+		if (!quiet)
+			printf(_("Record separator is zero byte.\n"));
+	}
+
 	/* toggle between full and tuples-only format */
 	else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
 	{
diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c
index 172fd0c..eff0ea5 100644
--- a/src/bin/psql/help.c
+++ b/src/bin/psql/help.c
@@ -123,6 +123,10 @@ usage(void)
 	printf(_("  -t, --tuples-only        print rows only\n"));
 	printf(_("  -T, --table-attr=TEXT    set HTML table tag attributes (e.g., width, border)\n"));
 	printf(_("  -x, --expanded           turn on expanded table output\n"));
+	printf(_("  -z, --field-separator-zero\n"
+			 "                           set field separator to zero byte\n"));
+	printf(_("  -0, --record-separator-zero\n"
+			 "                           set record separator to zero byte\n"));
 
 	printf(_("\nConnection options:\n"));
 	/* Display default host */
@@ -237,8 +241,8 @@ 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 := {format|border|expanded|fieldsep|footer|null|\n"
-					  "                         numericlocale|recordsep|tuples_only|title|tableattr|pager})\n"));
+					  "                         (NAME := {format|border|expanded|fieldsep|fieldsep_zero|footer|null|\n"
+					  "                         numericlocale|recordsep|recordsep_zero|tuples_only|title|tableattr|pager})\n"));
 	fprintf(output, _("  \\t [on|off]            show only rows (currently %s)\n"),
 			ON(pset.popt.topt.tuples_only));
 	fprintf(output, _("  \\T [STRING]            set HTML <table> tag attributes, or unset if none\n"));
diff --git a/src/bin/psql/print.c b/src/bin/psql/print.c
index e127edb..dec440c 100644
--- a/src/bin/psql/print.c
+++ b/src/bin/psql/print.c
@@ -268,6 +268,16 @@ fputnbytes(FILE *f, const char *str, size_t n)
 }
 
 
+static void
+print_separator(struct separator sep, FILE *fout)
+{
+	if (sep.separator_zero)
+		fputc('\000', fout);
+	else if (sep.separator)
+		fputs(sep.separator, fout);
+}
+
+
 /*************************/
 /* Unaligned text		 */
 /*************************/
@@ -276,8 +286,6 @@ fputnbytes(FILE *f, const char *str, size_t n)
 static void
 print_unaligned_text(const printTableContent *cont, FILE *fout)
 {
-	const char *opt_fieldsep = cont->opt->fieldSep;
-	const char *opt_recordsep = cont->opt->recordSep;
 	bool		opt_tuples_only = cont->opt->tuples_only;
 	unsigned int i;
 	const char *const * ptr;
@@ -286,16 +294,14 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
 	if (cancel_pressed)
 		return;
 
-	if (!opt_fieldsep)
-		opt_fieldsep = "";
-	if (!opt_recordsep)
-		opt_recordsep = "";
-
 	if (cont->opt->start_table)
 	{
 		/* print title */
 		if (!opt_tuples_only && cont->title)
-			fprintf(fout, "%s%s", cont->title, opt_recordsep);
+		{
+			fputs(cont->title, fout);
+			print_separator(cont->opt->recordSep, fout);
+		}
 
 		/* print headers */
 		if (!opt_tuples_only)
@@ -303,7 +309,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
 			for (ptr = cont->headers; *ptr; ptr++)
 			{
 				if (ptr != cont->headers)
-					fputs(opt_fieldsep, fout);
+					print_separator(cont->opt->fieldSep, fout);
 				fputs(*ptr, fout);
 			}
 			need_recordsep = true;
@@ -318,7 +324,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
 	{
 		if (need_recordsep)
 		{
-			fputs(opt_recordsep, fout);
+			print_separator(cont->opt->recordSep, fout);
 			need_recordsep = false;
 			if (cancel_pressed)
 				break;
@@ -326,7 +332,7 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
 		fputs(*ptr, fout);
 
 		if ((i + 1) % cont->ncolumns)
-			fputs(opt_fieldsep, fout);
+			print_separator(cont->opt->fieldSep, fout);
 		else
 			need_recordsep = true;
 	}
@@ -342,16 +348,25 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
 			{
 				if (need_recordsep)
 				{
-					fputs(opt_recordsep, fout);
+					print_separator(cont->opt->recordSep, fout);
 					need_recordsep = false;
 				}
 				fputs(f->data, fout);
 				need_recordsep = true;
 			}
 		}
-		/* the last record needs to be concluded with a newline */
+		/*
+		 * The last record is terminated by a newline, independent of the set
+		 * record separator.  But when the record separator is a zero byte, we
+		 * use that (compatible with find -print0 and xargs).
+		 */
 		if (need_recordsep)
-			fputc('\n', fout);
+		{
+			if (cont->opt->recordSep.separator_zero)
+				print_separator(cont->opt->recordSep, fout);
+			else
+				fputc('\n', fout);
+		}
 	}
 }
 
@@ -359,8 +374,6 @@ print_unaligned_text(const printTableContent *cont, FILE *fout)
 static void
 print_unaligned_vertical(const printTableContent *cont, FILE *fout)
 {
-	const char *opt_fieldsep = cont->opt->fieldSep;
-	const char *opt_recordsep = cont->opt->recordSep;
 	bool		opt_tuples_only = cont->opt->tuples_only;
 	unsigned int i;
 	const char *const * ptr;
@@ -369,11 +382,6 @@ print_unaligned_vertical(const printTableContent *cont, FILE *fout)
 	if (cancel_pressed)
 		return;
 
-	if (!opt_fieldsep)
-		opt_fieldsep = "";
-	if (!opt_recordsep)
-		opt_recordsep = "";
-
 	if (cont->opt->start_table)
 	{
 		/* print title */
@@ -393,19 +401,19 @@ print_unaligned_vertical(const printTableContent *cont, FILE *fout)
 		if (need_recordsep)
 		{
 			/* record separator is 2 occurrences of recordsep in this mode */
-			fputs(opt_recordsep, fout);
-			fputs(opt_recordsep, fout);
+			print_separator(cont->opt->recordSep, fout);
+			print_separator(cont->opt->recordSep, fout);
 			need_recordsep = false;
 			if (cancel_pressed)
 				break;
 		}
 
 		fputs(cont->headers[i % cont->ncolumns], fout);
-		fputs(opt_fieldsep, fout);
+		print_separator(cont->opt->fieldSep, fout);
 		fputs(*ptr, fout);
 
 		if ((i + 1) % cont->ncolumns)
-			fputs(opt_recordsep, fout);
+			print_separator(cont->opt->recordSep, fout);
 		else
 			need_recordsep = true;
 	}
@@ -417,15 +425,19 @@ print_unaligned_vertical(const printTableContent *cont, FILE *fout)
 		{
 			printTableFooter *f;
 
-			fputs(opt_recordsep, fout);
+			print_separator(cont->opt->recordSep, fout);
 			for (f = cont->footers; f; f = f->next)
 			{
-				fputs(opt_recordsep, fout);
+				print_separator(cont->opt->recordSep, fout);
 				fputs(f->data, fout);
 			}
 		}
 
-		fputc('\n', fout);
+		/* see above in print_unaligned_text() */
+		if (cont->opt->recordSep.separator_zero)
+			print_separator(cont->opt->recordSep, fout);
+		else
+			fputc('\n', fout);
 	}
 }
 
diff --git a/src/bin/psql/print.h b/src/bin/psql/print.h
index 86c6e75..931535e 100644
--- a/src/bin/psql/print.h
+++ b/src/bin/psql/print.h
@@ -67,6 +67,12 @@ typedef struct printTextFormat
 										 * marks when border=0? */
 } printTextFormat;
 
+struct separator
+{
+	char	   *separator;
+	bool		separator_zero;
+};
+
 typedef struct printTableOpt
 {
 	enum printFormat format;	/* see enum above */
@@ -81,8 +87,8 @@ typedef struct printTableOpt
 	bool		stop_table;		/* print stop decoration, eg </table> */
 	unsigned long prior_records;	/* start offset for record counters */
 	const printTextFormat *line_style;	/* line style (NULL for default) */
-	char	   *fieldSep;		/* field separator for unaligned text mode */
-	char	   *recordSep;		/* record separator for unaligned text mode */
+	struct separator fieldSep;	/* field separator for unaligned text mode */
+	struct separator recordSep;	/* record separator for unaligned text mode */
 	bool		numericLocale;	/* locale-aware numeric units separator and
 								 * decimal marker */
 	char	   *tableAttr;		/* attributes for HTML <table ...> */
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c
index 8b1864c..aff5772 100644
--- a/src/bin/psql/startup.c
+++ b/src/bin/psql/startup.c
@@ -150,10 +150,18 @@ main(int argc, char *argv[])
 
 	parse_psql_options(argc, argv, &options);
 
-	if (!pset.popt.topt.fieldSep)
-		pset.popt.topt.fieldSep = pg_strdup(DEFAULT_FIELD_SEP);
-	if (!pset.popt.topt.recordSep)
-		pset.popt.topt.recordSep = pg_strdup(DEFAULT_RECORD_SEP);
+	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_zero = false;
+	}
+	if (!pset.popt.topt.recordSep.separator &&
+		!pset.popt.topt.recordSep.separator_zero)
+	{
+		pset.popt.topt.recordSep.separator = pg_strdup(DEFAULT_RECORD_SEP);
+		pset.popt.topt.recordSep.separator_zero = false;
+	}
 
 	if (options.username == NULL)
 		password_prompt = pg_strdup(_("Password: "));
@@ -338,6 +346,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
 		{"echo-hidden", no_argument, NULL, 'E'},
 		{"file", required_argument, NULL, 'f'},
 		{"field-separator", required_argument, NULL, 'F'},
+		{"field-separator-zero", no_argument, NULL, 'z'},
 		{"host", required_argument, NULL, 'h'},
 		{"html", no_argument, NULL, 'H'},
 		{"list", no_argument, NULL, 'l'},
@@ -349,6 +358,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
 		{"pset", required_argument, NULL, 'P'},
 		{"quiet", no_argument, NULL, 'q'},
 		{"record-separator", required_argument, NULL, 'R'},
+		{"record-separator-zero", no_argument, NULL, '0'},
 		{"single-step", no_argument, NULL, 's'},
 		{"single-line", no_argument, NULL, 'S'},
 		{"tuples-only", no_argument, NULL, 't'},
@@ -372,7 +382,7 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
 
 	memset(options, 0, sizeof *options);
 
-	while ((c = getopt_long(argc, argv, "aAc:d:eEf:F:h:HlL:no:p:P:qR:sStT:U:v:VwWxX?1",
+	while ((c = getopt_long(argc, argv, "aAc:d:eEf:F:h:HlL:no:p:P:qR:sStT:U:v:VwWxXz?01",
 							long_options, &optindex)) != -1)
 	{
 		switch (c)
@@ -407,7 +417,8 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
 				options->action_string = optarg;
 				break;
 			case 'F':
-				pset.popt.topt.fieldSep = pg_strdup(optarg);
+				pset.popt.topt.fieldSep.separator = pg_strdup(optarg);
+				pset.popt.topt.fieldSep.separator_zero = false;
 				break;
 			case 'h':
 				options->host = optarg;
@@ -459,7 +470,8 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
 				SetVariableBool(pset.vars, "QUIET");
 				break;
 			case 'R':
-				pset.popt.topt.recordSep = pg_strdup(optarg);
+				pset.popt.topt.recordSep.separator = pg_strdup(optarg);
+				pset.popt.topt.recordSep.separator_zero = false;
 				break;
 			case 's':
 				SetVariableBool(pset.vars, "SINGLESTEP");
@@ -521,6 +533,12 @@ parse_psql_options(int argc, char *argv[], struct adhoc_opts * options)
 			case 'X':
 				options->no_psqlrc = true;
 				break;
+			case 'z':
+				pset.popt.topt.fieldSep.separator_zero = true;
+				break;
+			case '0':
+				pset.popt.topt.recordSep.separator_zero = true;
+				break;
 			case '1':
 				options->single_txn = true;
 				break;
-- 
Sent via pgsql-hackers mailing list (pgsql-hackers@postgresql.org)
To make changes to your subscription:
http://www.postgresql.org/mailpref/pgsql-hackers

Reply via email to