mirror of
https://github.com/postgres/postgres.git
synced 2025-04-27 22:56:53 +03:00
psql: Support zero byte field and record separators
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
This commit is contained in:
parent
dd7c84185c
commit
169c8a9112
@ -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>
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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"));
|
||||
|
@ -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,25 +348,32 @@ 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)
|
||||
{
|
||||
if (cont->opt->recordSep.separator_zero)
|
||||
print_separator(cont->opt->recordSep, fout);
|
||||
else
|
||||
fputc('\n', 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,14 +425,18 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/* see above in print_unaligned_text() */
|
||||
if (cont->opt->recordSep.separator_zero)
|
||||
print_separator(cont->opt->recordSep, fout);
|
||||
else
|
||||
fputc('\n', fout);
|
||||
}
|
||||
}
|
||||
|
@ -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 ...> */
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user