psql: Support zero byte field and record separators
authorPeter Eisentraut
Thu, 9 Feb 2012 18:15:48 +0000 (20:15 +0200)
committerPeter Eisentraut
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
       
     
 
+    
+      
+      
+      
+      
+      Set the field separator for unaligned output to a zero byte.
+      
+      
+    
+
+    
+      
+      
+      
+      
+      Set the record separator for unaligned output to a zero byte.  This is
+      useful for interfacing, for example, with xargs -0.
+      
+      
+    
+
      
       
       
@@ -1908,6 +1929,16 @@ lo_import 152801
           
           
 
+          
+          fieldsep_zero
+          
+          
+          Sets the field separator to use in unaligned output format to a zero
+          byte.
+          
+          
+          
+
           
           footer
           
@@ -2077,6 +2108,16 @@ lo_import 152801
           
           
 
+          
+          recordsep_zero
+          
+          
+          Sets the record separator to use in unaligned output format to a zero
+          byte.
+          
+          
+          
+
           
           tableattr (or T)
           
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 ."));
            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  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 
 */
    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  */
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;