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