mirror of
https://github.com/postgres/postgres.git
synced 2025-06-30 21:42:05 +03:00
Allow psql's \g and \gx commands to transiently change \pset options.
We invented \gx to allow the "\pset expanded" flag to be forced on for the duration of one command output, but that turns out to not be nearly enough to satisfy the demand for variant output formats. Hence, make it possible to change any pset option(s) for the duration of a single command output, by writing "option=value ..." inside parentheses, for example \g (format=csv csv_fieldsep='\t') somefile \gx can now be understood as a shorthand for including expanded=on inside the parentheses. Patch by me, expanding on a proposal by Pavel Stehule Discussion: https://postgr.es/m/CAFj8pRBx9OnBPRJVtfA5ycUpySge-XootAXAsv_4rrkHxJ8eRg@mail.gmail.com
This commit is contained in:
@ -86,6 +86,10 @@ static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool ac
|
||||
static backslashResult exec_command_f(PsqlScanState scan_state, bool active_branch);
|
||||
static backslashResult exec_command_g(PsqlScanState scan_state, bool active_branch,
|
||||
const char *cmd);
|
||||
static backslashResult process_command_g_options(char *first_option,
|
||||
PsqlScanState scan_state,
|
||||
bool active_branch,
|
||||
const char *cmd);
|
||||
static backslashResult exec_command_gdesc(PsqlScanState scan_state, bool active_branch);
|
||||
static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch);
|
||||
static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch);
|
||||
@ -160,8 +164,8 @@ static void minimal_error_message(PGresult *res);
|
||||
|
||||
static void printSSLInfo(void);
|
||||
static void printGSSInfo(void);
|
||||
static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
|
||||
static char *pset_value_string(const char *param, struct printQueryOpt *popt);
|
||||
static bool printPsetInfo(const char *param, printQueryOpt *popt);
|
||||
static char *pset_value_string(const char *param, printQueryOpt *popt);
|
||||
|
||||
#ifdef WIN32
|
||||
static void checkWin32Codepage(void);
|
||||
@ -1280,19 +1284,40 @@ exec_command_f(PsqlScanState scan_state, bool active_branch)
|
||||
}
|
||||
|
||||
/*
|
||||
* \g [filename] -- send query, optionally with output to file/pipe
|
||||
* \gx [filename] -- same as \g, with expanded mode forced
|
||||
* \g [(pset-option[=pset-value] ...)] [filename/shell-command]
|
||||
* \gx [(pset-option[=pset-value] ...)] [filename/shell-command]
|
||||
*
|
||||
* Send the current query. If pset options are specified, they are made
|
||||
* active just for this query. If a filename or pipe command is given,
|
||||
* the query output goes there. \gx implicitly sets "expanded=on" along
|
||||
* with any other pset options that are specified.
|
||||
*/
|
||||
static backslashResult
|
||||
exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
||||
{
|
||||
backslashResult status = PSQL_CMD_SKIP_LINE;
|
||||
char *fname;
|
||||
|
||||
if (active_branch)
|
||||
/*
|
||||
* Because the option processing for this is fairly complicated, we do it
|
||||
* and then decide whether the branch is active.
|
||||
*/
|
||||
fname = psql_scan_slash_option(scan_state,
|
||||
OT_FILEPIPE, NULL, false);
|
||||
|
||||
if (fname && fname[0] == '(')
|
||||
{
|
||||
char *fname = psql_scan_slash_option(scan_state,
|
||||
OT_FILEPIPE, NULL, false);
|
||||
/* Consume pset options through trailing ')' ... */
|
||||
status = process_command_g_options(fname + 1, scan_state,
|
||||
active_branch, cmd);
|
||||
free(fname);
|
||||
/* ... and again attempt to scan the filename. */
|
||||
fname = psql_scan_slash_option(scan_state,
|
||||
OT_FILEPIPE, NULL, false);
|
||||
}
|
||||
|
||||
if (status == PSQL_CMD_SKIP_LINE && active_branch)
|
||||
{
|
||||
if (!fname)
|
||||
pset.gfname = NULL;
|
||||
else
|
||||
@ -1300,17 +1325,98 @@ exec_command_g(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
||||
expand_tilde(&fname);
|
||||
pset.gfname = pg_strdup(fname);
|
||||
}
|
||||
free(fname);
|
||||
if (strcmp(cmd, "gx") == 0)
|
||||
pset.g_expanded = true;
|
||||
{
|
||||
/* save settings if not done already, then force expanded=on */
|
||||
if (pset.gsavepopt == NULL)
|
||||
pset.gsavepopt = savePsetInfo(&pset.popt);
|
||||
pset.popt.topt.expanded = 1;
|
||||
}
|
||||
status = PSQL_CMD_SEND;
|
||||
}
|
||||
else
|
||||
ignore_slash_filepipe(scan_state);
|
||||
|
||||
free(fname);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
/*
|
||||
* Process parenthesized pset options for \g
|
||||
*
|
||||
* Note: okay to modify first_option, but not to free it; caller does that
|
||||
*/
|
||||
static backslashResult
|
||||
process_command_g_options(char *first_option, PsqlScanState scan_state,
|
||||
bool active_branch, const char *cmd)
|
||||
{
|
||||
bool success = true;
|
||||
bool found_r_paren = false;
|
||||
|
||||
do
|
||||
{
|
||||
char *option;
|
||||
size_t optlen;
|
||||
|
||||
/* If not first time through, collect a new option */
|
||||
if (first_option)
|
||||
option = first_option;
|
||||
else
|
||||
{
|
||||
option = psql_scan_slash_option(scan_state,
|
||||
OT_NORMAL, NULL, false);
|
||||
if (!option)
|
||||
{
|
||||
if (active_branch)
|
||||
{
|
||||
pg_log_error("\\%s: missing right parenthesis", cmd);
|
||||
success = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for terminating right paren, and remove it from string */
|
||||
optlen = strlen(option);
|
||||
if (optlen > 0 && option[optlen - 1] == ')')
|
||||
{
|
||||
option[--optlen] = '\0';
|
||||
found_r_paren = true;
|
||||
}
|
||||
|
||||
/* If there was anything besides parentheses, parse/execute it */
|
||||
if (optlen > 0)
|
||||
{
|
||||
/* We can have either "name" or "name=value" */
|
||||
char *valptr = strchr(option, '=');
|
||||
|
||||
if (valptr)
|
||||
*valptr++ = '\0';
|
||||
if (active_branch)
|
||||
{
|
||||
/* save settings if not done already, then apply option */
|
||||
if (pset.gsavepopt == NULL)
|
||||
pset.gsavepopt = savePsetInfo(&pset.popt);
|
||||
success &= do_pset(option, valptr, &pset.popt, true);
|
||||
}
|
||||
}
|
||||
|
||||
/* Clean up after this option. We should not free first_option. */
|
||||
if (first_option)
|
||||
first_option = NULL;
|
||||
else
|
||||
free(option);
|
||||
} while (!found_r_paren);
|
||||
|
||||
/* If we failed after already changing some options, undo side-effects */
|
||||
if (!success && active_branch && pset.gsavepopt)
|
||||
{
|
||||
restorePsetInfo(&pset.popt, pset.gsavepopt);
|
||||
pset.gsavepopt = NULL;
|
||||
}
|
||||
|
||||
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* \gdesc -- describe query result
|
||||
*/
|
||||
@ -3785,6 +3891,17 @@ _unicode_linestyle2string(int linestyle)
|
||||
/*
|
||||
* do_pset
|
||||
*
|
||||
* Performs the assignment "param = value", where value could be NULL;
|
||||
* for some params that has an effect such as inversion, for others
|
||||
* it does nothing.
|
||||
*
|
||||
* Adjusts the state of the formatting options at *popt. (In practice that
|
||||
* is always pset.popt, but maybe someday it could be different.)
|
||||
*
|
||||
* If successful and quiet is false, then invokes printPsetInfo() to report
|
||||
* the change.
|
||||
*
|
||||
* Returns true if successful, else false (eg for invalid param or value).
|
||||
*/
|
||||
bool
|
||||
do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
|
||||
@ -4109,9 +4226,11 @@ do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* printPsetInfo: print the state of the "param" formatting parameter in popt.
|
||||
*/
|
||||
static bool
|
||||
printPsetInfo(const char *param, struct printQueryOpt *popt)
|
||||
printPsetInfo(const char *param, printQueryOpt *popt)
|
||||
{
|
||||
Assert(param != NULL);
|
||||
|
||||
@ -4292,6 +4411,77 @@ printPsetInfo(const char *param, struct printQueryOpt *popt)
|
||||
return true;
|
||||
}
|
||||
|
||||
/*
|
||||
* savePsetInfo: make a malloc'd copy of the data in *popt.
|
||||
*
|
||||
* Possibly this should be somewhere else, but it's a bit specific to psql.
|
||||
*/
|
||||
printQueryOpt *
|
||||
savePsetInfo(const printQueryOpt *popt)
|
||||
{
|
||||
printQueryOpt *save;
|
||||
|
||||
save = (printQueryOpt *) pg_malloc(sizeof(printQueryOpt));
|
||||
|
||||
/* Flat-copy all the scalar fields, then duplicate sub-structures. */
|
||||
memcpy(save, popt, sizeof(printQueryOpt));
|
||||
|
||||
/* topt.line_style points to const data that need not be duplicated */
|
||||
if (popt->topt.fieldSep.separator)
|
||||
save->topt.fieldSep.separator = pg_strdup(popt->topt.fieldSep.separator);
|
||||
if (popt->topt.recordSep.separator)
|
||||
save->topt.recordSep.separator = pg_strdup(popt->topt.recordSep.separator);
|
||||
if (popt->topt.tableAttr)
|
||||
save->topt.tableAttr = pg_strdup(popt->topt.tableAttr);
|
||||
if (popt->nullPrint)
|
||||
save->nullPrint = pg_strdup(popt->nullPrint);
|
||||
if (popt->title)
|
||||
save->title = pg_strdup(popt->title);
|
||||
|
||||
/*
|
||||
* footers and translate_columns are never set in psql's print settings,
|
||||
* so we needn't write code to duplicate them.
|
||||
*/
|
||||
Assert(popt->footers == NULL);
|
||||
Assert(popt->translate_columns == NULL);
|
||||
|
||||
return save;
|
||||
}
|
||||
|
||||
/*
|
||||
* restorePsetInfo: restore *popt from the previously-saved copy *save,
|
||||
* then free *save.
|
||||
*/
|
||||
void
|
||||
restorePsetInfo(printQueryOpt *popt, printQueryOpt *save)
|
||||
{
|
||||
/* Free all the old data we're about to overwrite the pointers to. */
|
||||
|
||||
/* topt.line_style points to const data that need not be duplicated */
|
||||
if (popt->topt.fieldSep.separator)
|
||||
free(popt->topt.fieldSep.separator);
|
||||
if (popt->topt.recordSep.separator)
|
||||
free(popt->topt.recordSep.separator);
|
||||
if (popt->topt.tableAttr)
|
||||
free(popt->topt.tableAttr);
|
||||
if (popt->nullPrint)
|
||||
free(popt->nullPrint);
|
||||
if (popt->title)
|
||||
free(popt->title);
|
||||
|
||||
/*
|
||||
* footers and translate_columns are never set in psql's print settings,
|
||||
* so we needn't write code to duplicate them.
|
||||
*/
|
||||
Assert(popt->footers == NULL);
|
||||
Assert(popt->translate_columns == NULL);
|
||||
|
||||
/* Now we may flat-copy all the fields, including pointers. */
|
||||
memcpy(popt, save, sizeof(printQueryOpt));
|
||||
|
||||
/* Lastly, free "save" ... but its sub-structures now belong to popt. */
|
||||
free(save);
|
||||
}
|
||||
|
||||
static const char *
|
||||
pset_bool_string(bool val)
|
||||
@ -4339,7 +4529,7 @@ pset_quoted_string(const char *str)
|
||||
* output that produces the correct setting when fed back into \pset.
|
||||
*/
|
||||
static char *
|
||||
pset_value_string(const char *param, struct printQueryOpt *popt)
|
||||
pset_value_string(const char *param, printQueryOpt *popt)
|
||||
{
|
||||
Assert(param != NULL);
|
||||
|
||||
|
Reference in New Issue
Block a user