]> granicus.if.org Git - postgresql/commitdiff
psql: Support zero byte field and record separators
authorPeter Eisentraut <peter_e@gmx.net>
Thu, 9 Feb 2012 18:15:48 +0000 (20:15 +0200)
committerPeter Eisentraut <peter_e@gmx.net>
Thu, 9 Feb 2012 18:20:15 +0000 (20:20 +0200)
Add new psql settings and command-line options to support setting the
field and record separators for unaligned output to a zero byte, for
easier interfacing with other shell tools.

reviewed by Abhijit Menon-Sen

doc/src/sgml/ref/psql-ref.sgml
src/bin/psql/command.c
src/bin/psql/help.c
src/bin/psql/print.c
src/bin/psql/print.h
src/bin/psql/startup.c

index a9b1ed2699d57ea51137964f5dba99b1df69b4b8..55aa5f2ac1dc7cde2d994c14733b147977af28f1 100644 (file)
@@ -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>
@@ -1908,6 +1929,16 @@ lo_import 152801
           </listitem>
           </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>
@@ -2077,6 +2108,16 @@ lo_import 152801
           </listitem>
           </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>
index ab809ec3a0282882a5b31cd688d096ff25551ff9..8421ad008602d097cdeef29da8d4dd1db75bedda 100644 (file)
@@ -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)
        {
index 172fd0c733b7fc53806a3a02b1fbe7bfb1557d30..eff0ea53b69052ddec01e25b8f6fcc1e18af7db1 100644 (file)
@@ -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"));
index e127edb1aed57fce9d2dcb7958abc6e3f4fa46c6..dec440c264e54258e2c6fffef0d4746a9bb2d091 100644 (file)
@@ -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);
        }
 }
 
index 86c6e754abdf07ffc1ea021e058f6c465001f8be..931535e478f2623e281cbcb0aae945cea24403f4 100644 (file)
@@ -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 ...> */
index 8b1864c10666ec57a807dbd54e5742a5b27e5a23..aff57728a2a67d669b00bc81b1b3efd78fff1020 100644 (file)
@@ -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;