
A failure in parsing the interval value defined in the \watch command was silently switched to 1s of interval between two queries, which can be confusing. This commit improves the error handling, and a couple of tests are added to check after: - An incorrect value. - An out-of-range value. - A negative value. A value of zero is able to work now, meaning that there is no interval of time between two queries in a \watch loop. No backpatch is done, as it could break existing applications. Author: Andrey Borodin Reviewed-by: Kyotaro Horiguchi, Nathan Bossart, Michael Paquier Discussion: https://postgr.es/m/CAAhFRxiZ2-n_L1ErMm9AZjgmUK=qS6VHb+0SaMn8sqqbhF7How@mail.gmail.com
5701 lines
145 KiB
C
5701 lines
145 KiB
C
/*
|
|
* psql - the PostgreSQL interactive terminal
|
|
*
|
|
* Copyright (c) 2000-2023, PostgreSQL Global Development Group
|
|
*
|
|
* src/bin/psql/command.c
|
|
*/
|
|
#include "postgres_fe.h"
|
|
|
|
#include <ctype.h>
|
|
#include <time.h>
|
|
#include <pwd.h>
|
|
#include <utime.h>
|
|
#ifndef WIN32
|
|
#include <sys/stat.h> /* for stat() */
|
|
#include <sys/time.h> /* for setitimer() */
|
|
#include <fcntl.h> /* open() flags */
|
|
#include <unistd.h> /* for geteuid(), getpid(), stat() */
|
|
#else
|
|
#include <win32.h>
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#include <direct.h>
|
|
#include <sys/stat.h> /* for stat() */
|
|
#endif
|
|
|
|
#include "catalog/pg_class_d.h"
|
|
#include "command.h"
|
|
#include "common.h"
|
|
#include "common/logging.h"
|
|
#include "common/string.h"
|
|
#include "copy.h"
|
|
#include "crosstabview.h"
|
|
#include "describe.h"
|
|
#include "fe_utils/cancel.h"
|
|
#include "fe_utils/print.h"
|
|
#include "fe_utils/string_utils.h"
|
|
#include "help.h"
|
|
#include "input.h"
|
|
#include "large_obj.h"
|
|
#include "libpq-fe.h"
|
|
#include "libpq/pqcomm.h"
|
|
#include "mainloop.h"
|
|
#include "portability/instr_time.h"
|
|
#include "pqexpbuffer.h"
|
|
#include "psqlscanslash.h"
|
|
#include "settings.h"
|
|
#include "variables.h"
|
|
|
|
/*
|
|
* Editable database object types.
|
|
*/
|
|
typedef enum EditableObjectType
|
|
{
|
|
EditableFunction,
|
|
EditableView
|
|
} EditableObjectType;
|
|
|
|
/* local function declarations */
|
|
static backslashResult exec_command(const char *cmd,
|
|
PsqlScanState scan_state,
|
|
ConditionalStack cstack,
|
|
PQExpBuffer query_buf,
|
|
PQExpBuffer previous_buf);
|
|
static backslashResult exec_command_a(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_bind(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_C(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_connect(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_cd(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_conninfo(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_copy(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_copyright(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_crosstabview(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_d(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static bool exec_command_dfo(PsqlScanState scan_state, const char *cmd,
|
|
const char *pattern,
|
|
bool show_verbose, bool show_system);
|
|
static backslashResult exec_command_edit(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf, PQExpBuffer previous_buf);
|
|
static backslashResult exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf, bool is_func);
|
|
static backslashResult exec_command_echo(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf);
|
|
static backslashResult exec_command_else(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf);
|
|
static backslashResult exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf);
|
|
static backslashResult exec_command_encoding(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_errverbose(PsqlScanState scan_state, bool active_branch);
|
|
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_getenv(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_gexec(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_gset(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_help(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_html(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_include(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_if(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf);
|
|
static backslashResult exec_command_list(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_lo(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_out(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_print(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf, PQExpBuffer previous_buf);
|
|
static backslashResult exec_command_password(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_prompt(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_pset(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_quit(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_reset(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf);
|
|
static backslashResult exec_command_s(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_set(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_setenv(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_sf_sv(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd, bool is_func);
|
|
static backslashResult exec_command_t(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_T(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_timing(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_unset(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_write(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd,
|
|
PQExpBuffer query_buf, PQExpBuffer previous_buf);
|
|
static backslashResult exec_command_watch(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf, PQExpBuffer previous_buf);
|
|
static backslashResult exec_command_x(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_z(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd);
|
|
static backslashResult exec_command_shell_escape(PsqlScanState scan_state, bool active_branch);
|
|
static backslashResult exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch);
|
|
static char *read_connect_arg(PsqlScanState scan_state);
|
|
static PQExpBuffer gather_boolean_expression(PsqlScanState scan_state);
|
|
static bool is_true_boolean_expression(PsqlScanState scan_state, const char *name);
|
|
static void ignore_boolean_expression(PsqlScanState scan_state);
|
|
static void ignore_slash_options(PsqlScanState scan_state);
|
|
static void ignore_slash_filepipe(PsqlScanState scan_state);
|
|
static void ignore_slash_whole_line(PsqlScanState scan_state);
|
|
static bool is_branching_command(const char *cmd);
|
|
static void save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf);
|
|
static void discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf);
|
|
static bool copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf);
|
|
static bool do_connect(enum trivalue reuse_previous_specification,
|
|
char *dbname, char *user, char *host, char *port);
|
|
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
|
|
int lineno, bool discard_on_quit, bool *edited);
|
|
static bool do_shell(const char *command);
|
|
static bool do_watch(PQExpBuffer query_buf, double sleep);
|
|
static bool lookup_object_oid(EditableObjectType obj_type, const char *desc,
|
|
Oid *obj_oid);
|
|
static bool get_create_object_cmd(EditableObjectType obj_type, Oid oid,
|
|
PQExpBuffer buf);
|
|
static int strip_lineno_from_objdesc(char *obj);
|
|
static int count_lines_in_buf(PQExpBuffer buf);
|
|
static void print_with_linenumbers(FILE *output, char *lines, bool is_func);
|
|
static void minimal_error_message(PGresult *res);
|
|
|
|
static void printSSLInfo(void);
|
|
static void printGSSInfo(void);
|
|
static bool printPsetInfo(const char *param, printQueryOpt *popt);
|
|
static char *pset_value_string(const char *param, printQueryOpt *popt);
|
|
|
|
#ifdef WIN32
|
|
static void checkWin32Codepage(void);
|
|
#endif
|
|
|
|
|
|
|
|
/*----------
|
|
* HandleSlashCmds:
|
|
*
|
|
* Handles all the different commands that start with '\'.
|
|
* Ordinarily called by MainLoop().
|
|
*
|
|
* scan_state is a lexer working state that is set to continue scanning
|
|
* just after the '\'. The lexer is advanced past the command and all
|
|
* arguments on return.
|
|
*
|
|
* cstack is the current \if stack state. This will be examined, and
|
|
* possibly modified by conditional commands.
|
|
*
|
|
* query_buf contains the query-so-far, which may be modified by
|
|
* execution of the backslash command (for example, \r clears it).
|
|
*
|
|
* previous_buf contains the query most recently sent to the server
|
|
* (empty if none yet). This should not be modified here, but some
|
|
* commands copy its content into query_buf.
|
|
*
|
|
* query_buf and previous_buf will be NULL when executing a "-c"
|
|
* command-line option.
|
|
*
|
|
* Returns a status code indicating what action is desired, see command.h.
|
|
*----------
|
|
*/
|
|
|
|
backslashResult
|
|
HandleSlashCmds(PsqlScanState scan_state,
|
|
ConditionalStack cstack,
|
|
PQExpBuffer query_buf,
|
|
PQExpBuffer previous_buf)
|
|
{
|
|
backslashResult status;
|
|
char *cmd;
|
|
char *arg;
|
|
|
|
Assert(scan_state != NULL);
|
|
Assert(cstack != NULL);
|
|
|
|
/* Parse off the command name */
|
|
cmd = psql_scan_slash_command(scan_state);
|
|
|
|
/* And try to execute it */
|
|
status = exec_command(cmd, scan_state, cstack, query_buf, previous_buf);
|
|
|
|
if (status == PSQL_CMD_UNKNOWN)
|
|
{
|
|
pg_log_error("invalid command \\%s", cmd);
|
|
if (pset.cur_cmd_interactive)
|
|
pg_log_error_hint("Try \\? for help.");
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
|
|
if (status != PSQL_CMD_ERROR)
|
|
{
|
|
/*
|
|
* Eat any remaining arguments after a valid command. We want to
|
|
* suppress evaluation of backticks in this situation, so transiently
|
|
* push an inactive conditional-stack entry.
|
|
*/
|
|
bool active_branch = conditional_active(cstack);
|
|
|
|
conditional_stack_push(cstack, IFSTATE_IGNORED);
|
|
while ((arg = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false)))
|
|
{
|
|
if (active_branch)
|
|
pg_log_warning("\\%s: extra argument \"%s\" ignored", cmd, arg);
|
|
free(arg);
|
|
}
|
|
conditional_stack_pop(cstack);
|
|
}
|
|
else
|
|
{
|
|
/* silently throw away rest of line after an erroneous command */
|
|
while ((arg = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE, NULL, false)))
|
|
free(arg);
|
|
}
|
|
|
|
/* if there is a trailing \\, swallow it */
|
|
psql_scan_slash_command_end(scan_state);
|
|
|
|
free(cmd);
|
|
|
|
/* some commands write to queryFout, so make sure output is sent */
|
|
fflush(pset.queryFout);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
* Subroutine to actually try to execute a backslash command.
|
|
*
|
|
* The typical "success" result code is PSQL_CMD_SKIP_LINE, although some
|
|
* commands return something else. Failure result code is PSQL_CMD_ERROR,
|
|
* unless PSQL_CMD_UNKNOWN is more appropriate.
|
|
*/
|
|
static backslashResult
|
|
exec_command(const char *cmd,
|
|
PsqlScanState scan_state,
|
|
ConditionalStack cstack,
|
|
PQExpBuffer query_buf,
|
|
PQExpBuffer previous_buf)
|
|
{
|
|
backslashResult status;
|
|
bool active_branch = conditional_active(cstack);
|
|
|
|
/*
|
|
* In interactive mode, warn when we're ignoring a command within a false
|
|
* \if-branch. But we continue on, so as to parse and discard the right
|
|
* amount of parameter text. Each individual backslash command subroutine
|
|
* is responsible for doing nothing after discarding appropriate
|
|
* arguments, if !active_branch.
|
|
*/
|
|
if (pset.cur_cmd_interactive && !active_branch &&
|
|
!is_branching_command(cmd))
|
|
{
|
|
pg_log_warning("\\%s command ignored; use \\endif or Ctrl-C to exit current \\if block",
|
|
cmd);
|
|
}
|
|
|
|
if (strcmp(cmd, "a") == 0)
|
|
status = exec_command_a(scan_state, active_branch);
|
|
else if (strcmp(cmd, "bind") == 0)
|
|
status = exec_command_bind(scan_state, active_branch);
|
|
else if (strcmp(cmd, "C") == 0)
|
|
status = exec_command_C(scan_state, active_branch);
|
|
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
|
|
status = exec_command_connect(scan_state, active_branch);
|
|
else if (strcmp(cmd, "cd") == 0)
|
|
status = exec_command_cd(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "conninfo") == 0)
|
|
status = exec_command_conninfo(scan_state, active_branch);
|
|
else if (pg_strcasecmp(cmd, "copy") == 0)
|
|
status = exec_command_copy(scan_state, active_branch);
|
|
else if (strcmp(cmd, "copyright") == 0)
|
|
status = exec_command_copyright(scan_state, active_branch);
|
|
else if (strcmp(cmd, "crosstabview") == 0)
|
|
status = exec_command_crosstabview(scan_state, active_branch);
|
|
else if (cmd[0] == 'd')
|
|
status = exec_command_d(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
|
|
status = exec_command_edit(scan_state, active_branch,
|
|
query_buf, previous_buf);
|
|
else if (strcmp(cmd, "ef") == 0)
|
|
status = exec_command_ef_ev(scan_state, active_branch, query_buf, true);
|
|
else if (strcmp(cmd, "ev") == 0)
|
|
status = exec_command_ef_ev(scan_state, active_branch, query_buf, false);
|
|
else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0 ||
|
|
strcmp(cmd, "warn") == 0)
|
|
status = exec_command_echo(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "elif") == 0)
|
|
status = exec_command_elif(scan_state, cstack, query_buf);
|
|
else if (strcmp(cmd, "else") == 0)
|
|
status = exec_command_else(scan_state, cstack, query_buf);
|
|
else if (strcmp(cmd, "endif") == 0)
|
|
status = exec_command_endif(scan_state, cstack, query_buf);
|
|
else if (strcmp(cmd, "encoding") == 0)
|
|
status = exec_command_encoding(scan_state, active_branch);
|
|
else if (strcmp(cmd, "errverbose") == 0)
|
|
status = exec_command_errverbose(scan_state, active_branch);
|
|
else if (strcmp(cmd, "f") == 0)
|
|
status = exec_command_f(scan_state, active_branch);
|
|
else if (strcmp(cmd, "g") == 0 || strcmp(cmd, "gx") == 0)
|
|
status = exec_command_g(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "gdesc") == 0)
|
|
status = exec_command_gdesc(scan_state, active_branch);
|
|
else if (strcmp(cmd, "getenv") == 0)
|
|
status = exec_command_getenv(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "gexec") == 0)
|
|
status = exec_command_gexec(scan_state, active_branch);
|
|
else if (strcmp(cmd, "gset") == 0)
|
|
status = exec_command_gset(scan_state, active_branch);
|
|
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
|
|
status = exec_command_help(scan_state, active_branch);
|
|
else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
|
|
status = exec_command_html(scan_state, active_branch);
|
|
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0 ||
|
|
strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
|
|
status = exec_command_include(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "if") == 0)
|
|
status = exec_command_if(scan_state, cstack, query_buf);
|
|
else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
|
|
strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
|
|
status = exec_command_list(scan_state, active_branch, cmd);
|
|
else if (strncmp(cmd, "lo_", 3) == 0)
|
|
status = exec_command_lo(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
|
|
status = exec_command_out(scan_state, active_branch);
|
|
else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
|
|
status = exec_command_print(scan_state, active_branch,
|
|
query_buf, previous_buf);
|
|
else if (strcmp(cmd, "password") == 0)
|
|
status = exec_command_password(scan_state, active_branch);
|
|
else if (strcmp(cmd, "prompt") == 0)
|
|
status = exec_command_prompt(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "pset") == 0)
|
|
status = exec_command_pset(scan_state, active_branch);
|
|
else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
|
|
status = exec_command_quit(scan_state, active_branch);
|
|
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
|
|
status = exec_command_reset(scan_state, active_branch, query_buf);
|
|
else if (strcmp(cmd, "s") == 0)
|
|
status = exec_command_s(scan_state, active_branch);
|
|
else if (strcmp(cmd, "set") == 0)
|
|
status = exec_command_set(scan_state, active_branch);
|
|
else if (strcmp(cmd, "setenv") == 0)
|
|
status = exec_command_setenv(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
|
|
status = exec_command_sf_sv(scan_state, active_branch, cmd, true);
|
|
else if (strcmp(cmd, "sv") == 0 || strcmp(cmd, "sv+") == 0)
|
|
status = exec_command_sf_sv(scan_state, active_branch, cmd, false);
|
|
else if (strcmp(cmd, "t") == 0)
|
|
status = exec_command_t(scan_state, active_branch);
|
|
else if (strcmp(cmd, "T") == 0)
|
|
status = exec_command_T(scan_state, active_branch);
|
|
else if (strcmp(cmd, "timing") == 0)
|
|
status = exec_command_timing(scan_state, active_branch);
|
|
else if (strcmp(cmd, "unset") == 0)
|
|
status = exec_command_unset(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
|
|
status = exec_command_write(scan_state, active_branch, cmd,
|
|
query_buf, previous_buf);
|
|
else if (strcmp(cmd, "watch") == 0)
|
|
status = exec_command_watch(scan_state, active_branch,
|
|
query_buf, previous_buf);
|
|
else if (strcmp(cmd, "x") == 0)
|
|
status = exec_command_x(scan_state, active_branch);
|
|
else if (strcmp(cmd, "z") == 0 || strcmp(cmd, "zS") == 0)
|
|
status = exec_command_z(scan_state, active_branch, cmd);
|
|
else if (strcmp(cmd, "!") == 0)
|
|
status = exec_command_shell_escape(scan_state, active_branch);
|
|
else if (strcmp(cmd, "?") == 0)
|
|
status = exec_command_slash_command_help(scan_state, active_branch);
|
|
else
|
|
status = PSQL_CMD_UNKNOWN;
|
|
|
|
/*
|
|
* All the commands that return PSQL_CMD_SEND want to execute previous_buf
|
|
* if query_buf is empty. For convenience we implement that here, not in
|
|
* the individual command subroutines.
|
|
*/
|
|
if (status == PSQL_CMD_SEND)
|
|
(void) copy_previous_query(query_buf, previous_buf);
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
/*
|
|
* \a -- toggle field alignment
|
|
*
|
|
* This makes little sense but we keep it around.
|
|
*/
|
|
static backslashResult
|
|
exec_command_a(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
if (pset.popt.topt.format != PRINT_ALIGNED)
|
|
success = do_pset("format", "aligned", &pset.popt, pset.quiet);
|
|
else
|
|
success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
|
|
}
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \bind -- set query parameters
|
|
*/
|
|
static backslashResult
|
|
exec_command_bind(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt;
|
|
int nparams = 0;
|
|
int nalloc = 0;
|
|
|
|
pset.bind_params = NULL;
|
|
|
|
while ((opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false)))
|
|
{
|
|
nparams++;
|
|
if (nparams > nalloc)
|
|
{
|
|
nalloc = nalloc ? nalloc * 2 : 1;
|
|
pset.bind_params = pg_realloc_array(pset.bind_params, char *, nalloc);
|
|
}
|
|
pset.bind_params[nparams - 1] = opt;
|
|
}
|
|
|
|
pset.bind_nparams = nparams;
|
|
pset.bind_flag = true;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \C -- override table title (formerly change HTML caption)
|
|
*/
|
|
static backslashResult
|
|
exec_command_C(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
success = do_pset("title", opt, &pset.popt, pset.quiet);
|
|
free(opt);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \c or \connect -- connect to database using the specified parameters.
|
|
*
|
|
* \c [-reuse-previous=BOOL] dbname user host port
|
|
*
|
|
* Specifying a parameter as '-' is equivalent to omitting it. Examples:
|
|
*
|
|
* \c - - hst Connect to current database on current port of
|
|
* host "hst" as current user.
|
|
* \c - usr - prt Connect to current database on port "prt" of current host
|
|
* as user "usr".
|
|
* \c dbs Connect to database "dbs" on current port of current host
|
|
* as current user.
|
|
*/
|
|
static backslashResult
|
|
exec_command_connect(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
static const char prefix[] = "-reuse-previous=";
|
|
char *opt1,
|
|
*opt2,
|
|
*opt3,
|
|
*opt4;
|
|
enum trivalue reuse_previous = TRI_DEFAULT;
|
|
|
|
opt1 = read_connect_arg(scan_state);
|
|
if (opt1 != NULL && strncmp(opt1, prefix, sizeof(prefix) - 1) == 0)
|
|
{
|
|
bool on_off;
|
|
|
|
success = ParseVariableBool(opt1 + sizeof(prefix) - 1,
|
|
"-reuse-previous",
|
|
&on_off);
|
|
if (success)
|
|
{
|
|
reuse_previous = on_off ? TRI_YES : TRI_NO;
|
|
free(opt1);
|
|
opt1 = read_connect_arg(scan_state);
|
|
}
|
|
}
|
|
|
|
if (success) /* give up if reuse_previous was invalid */
|
|
{
|
|
opt2 = read_connect_arg(scan_state);
|
|
opt3 = read_connect_arg(scan_state);
|
|
opt4 = read_connect_arg(scan_state);
|
|
|
|
success = do_connect(reuse_previous, opt1, opt2, opt3, opt4);
|
|
|
|
free(opt2);
|
|
free(opt3);
|
|
free(opt4);
|
|
}
|
|
free(opt1);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \cd -- change directory
|
|
*/
|
|
static backslashResult
|
|
exec_command_cd(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
char *dir;
|
|
|
|
if (opt)
|
|
dir = opt;
|
|
else
|
|
{
|
|
#ifndef WIN32
|
|
/* This should match get_home_path() */
|
|
dir = getenv("HOME");
|
|
if (dir == NULL || dir[0] == '\0')
|
|
{
|
|
uid_t user_id = geteuid();
|
|
struct passwd *pw;
|
|
|
|
errno = 0; /* clear errno before call */
|
|
pw = getpwuid(user_id);
|
|
if (pw)
|
|
dir = pw->pw_dir;
|
|
else
|
|
{
|
|
pg_log_error("could not get home directory for user ID %ld: %s",
|
|
(long) user_id,
|
|
errno ? strerror(errno) : _("user does not exist"));
|
|
success = false;
|
|
}
|
|
}
|
|
#else /* WIN32 */
|
|
|
|
/*
|
|
* On Windows, 'cd' without arguments prints the current
|
|
* directory, so if someone wants to code this here instead...
|
|
*/
|
|
dir = "/";
|
|
#endif /* WIN32 */
|
|
}
|
|
|
|
if (success &&
|
|
chdir(dir) < 0)
|
|
{
|
|
pg_log_error("\\%s: could not change directory to \"%s\": %m",
|
|
cmd, dir);
|
|
success = false;
|
|
}
|
|
|
|
free(opt);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \conninfo -- display information about the current connection
|
|
*/
|
|
static backslashResult
|
|
exec_command_conninfo(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
if (active_branch)
|
|
{
|
|
char *db = PQdb(pset.db);
|
|
|
|
if (db == NULL)
|
|
printf(_("You are currently not connected to a database.\n"));
|
|
else
|
|
{
|
|
char *host = PQhost(pset.db);
|
|
char *hostaddr = PQhostaddr(pset.db);
|
|
|
|
if (is_unixsock_path(host))
|
|
{
|
|
/* hostaddr overrides host */
|
|
if (hostaddr && *hostaddr)
|
|
printf(_("You are connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"),
|
|
db, PQuser(pset.db), hostaddr, PQport(pset.db));
|
|
else
|
|
printf(_("You are connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
|
|
db, PQuser(pset.db), host, PQport(pset.db));
|
|
}
|
|
else
|
|
{
|
|
if (hostaddr && *hostaddr && strcmp(host, hostaddr) != 0)
|
|
printf(_("You are connected to database \"%s\" as user \"%s\" on host \"%s\" (address \"%s\") at port \"%s\".\n"),
|
|
db, PQuser(pset.db), host, hostaddr, PQport(pset.db));
|
|
else
|
|
printf(_("You are connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
|
|
db, PQuser(pset.db), host, PQport(pset.db));
|
|
}
|
|
printSSLInfo();
|
|
printGSSInfo();
|
|
}
|
|
}
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
/*
|
|
* \copy -- run a COPY command
|
|
*/
|
|
static backslashResult
|
|
exec_command_copy(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE, NULL, false);
|
|
|
|
success = do_copy(opt);
|
|
free(opt);
|
|
}
|
|
else
|
|
ignore_slash_whole_line(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \copyright -- print copyright notice
|
|
*/
|
|
static backslashResult
|
|
exec_command_copyright(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
if (active_branch)
|
|
print_copyright();
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
/*
|
|
* \crosstabview -- execute a query and display result in crosstab
|
|
*/
|
|
static backslashResult
|
|
exec_command_crosstabview(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < lengthof(pset.ctv_args); i++)
|
|
pset.ctv_args[i] = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
pset.crosstab_flag = true;
|
|
status = PSQL_CMD_SEND;
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \d* commands
|
|
*/
|
|
static backslashResult
|
|
exec_command_d(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *pattern;
|
|
bool show_verbose,
|
|
show_system;
|
|
|
|
/* We don't do SQLID reduction on the pattern yet */
|
|
pattern = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
show_verbose = strchr(cmd, '+') ? true : false;
|
|
show_system = strchr(cmd, 'S') ? true : false;
|
|
|
|
switch (cmd[1])
|
|
{
|
|
case '\0':
|
|
case '+':
|
|
case 'S':
|
|
if (pattern)
|
|
success = describeTableDetails(pattern, show_verbose, show_system);
|
|
else
|
|
/* standard listing of interesting things */
|
|
success = listTables("tvmsE", NULL, show_verbose, show_system);
|
|
break;
|
|
case 'A':
|
|
{
|
|
char *pattern2 = NULL;
|
|
|
|
if (pattern && cmd[2] != '\0' && cmd[2] != '+')
|
|
pattern2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true);
|
|
|
|
switch (cmd[2])
|
|
{
|
|
case '\0':
|
|
case '+':
|
|
success = describeAccessMethods(pattern, show_verbose);
|
|
break;
|
|
case 'c':
|
|
success = listOperatorClasses(pattern, pattern2, show_verbose);
|
|
break;
|
|
case 'f':
|
|
success = listOperatorFamilies(pattern, pattern2, show_verbose);
|
|
break;
|
|
case 'o':
|
|
success = listOpFamilyOperators(pattern, pattern2, show_verbose);
|
|
break;
|
|
case 'p':
|
|
success = listOpFamilyFunctions(pattern, pattern2, show_verbose);
|
|
break;
|
|
default:
|
|
status = PSQL_CMD_UNKNOWN;
|
|
break;
|
|
}
|
|
|
|
free(pattern2);
|
|
}
|
|
break;
|
|
case 'a':
|
|
success = describeAggregates(pattern, show_verbose, show_system);
|
|
break;
|
|
case 'b':
|
|
success = describeTablespaces(pattern, show_verbose);
|
|
break;
|
|
case 'c':
|
|
if (strncmp(cmd, "dconfig", 7) == 0)
|
|
success = describeConfigurationParameters(pattern,
|
|
show_verbose,
|
|
show_system);
|
|
else
|
|
success = listConversions(pattern,
|
|
show_verbose,
|
|
show_system);
|
|
break;
|
|
case 'C':
|
|
success = listCasts(pattern, show_verbose);
|
|
break;
|
|
case 'd':
|
|
if (strncmp(cmd, "ddp", 3) == 0)
|
|
success = listDefaultACLs(pattern);
|
|
else
|
|
success = objectDescription(pattern, show_system);
|
|
break;
|
|
case 'D':
|
|
success = listDomains(pattern, show_verbose, show_system);
|
|
break;
|
|
case 'f': /* function subsystem */
|
|
switch (cmd[2])
|
|
{
|
|
case '\0':
|
|
case '+':
|
|
case 'S':
|
|
case 'a':
|
|
case 'n':
|
|
case 'p':
|
|
case 't':
|
|
case 'w':
|
|
success = exec_command_dfo(scan_state, cmd, pattern,
|
|
show_verbose, show_system);
|
|
break;
|
|
default:
|
|
status = PSQL_CMD_UNKNOWN;
|
|
break;
|
|
}
|
|
break;
|
|
case 'g':
|
|
/* no longer distinct from \du */
|
|
success = describeRoles(pattern, show_verbose, show_system);
|
|
break;
|
|
case 'l':
|
|
success = listLargeObjects(show_verbose);
|
|
break;
|
|
case 'L':
|
|
success = listLanguages(pattern, show_verbose, show_system);
|
|
break;
|
|
case 'n':
|
|
success = listSchemas(pattern, show_verbose, show_system);
|
|
break;
|
|
case 'o':
|
|
success = exec_command_dfo(scan_state, cmd, pattern,
|
|
show_verbose, show_system);
|
|
break;
|
|
case 'O':
|
|
success = listCollations(pattern, show_verbose, show_system);
|
|
break;
|
|
case 'p':
|
|
success = permissionsList(pattern, show_system);
|
|
break;
|
|
case 'P':
|
|
{
|
|
switch (cmd[2])
|
|
{
|
|
case '\0':
|
|
case '+':
|
|
case 't':
|
|
case 'i':
|
|
case 'n':
|
|
success = listPartitionedTables(&cmd[2], pattern, show_verbose);
|
|
break;
|
|
default:
|
|
status = PSQL_CMD_UNKNOWN;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case 'T':
|
|
success = describeTypes(pattern, show_verbose, show_system);
|
|
break;
|
|
case 't':
|
|
case 'v':
|
|
case 'm':
|
|
case 'i':
|
|
case 's':
|
|
case 'E':
|
|
success = listTables(&cmd[1], pattern, show_verbose, show_system);
|
|
break;
|
|
case 'r':
|
|
if (cmd[2] == 'd' && cmd[3] == 's')
|
|
{
|
|
char *pattern2 = NULL;
|
|
|
|
if (pattern)
|
|
pattern2 = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
success = listDbRoleSettings(pattern, pattern2);
|
|
|
|
free(pattern2);
|
|
}
|
|
else
|
|
status = PSQL_CMD_UNKNOWN;
|
|
break;
|
|
case 'R':
|
|
switch (cmd[2])
|
|
{
|
|
case 'p':
|
|
if (show_verbose)
|
|
success = describePublications(pattern);
|
|
else
|
|
success = listPublications(pattern);
|
|
break;
|
|
case 's':
|
|
success = describeSubscriptions(pattern, show_verbose);
|
|
break;
|
|
default:
|
|
status = PSQL_CMD_UNKNOWN;
|
|
}
|
|
break;
|
|
case 'u':
|
|
success = describeRoles(pattern, show_verbose, show_system);
|
|
break;
|
|
case 'F': /* text search subsystem */
|
|
switch (cmd[2])
|
|
{
|
|
case '\0':
|
|
case '+':
|
|
success = listTSConfigs(pattern, show_verbose);
|
|
break;
|
|
case 'p':
|
|
success = listTSParsers(pattern, show_verbose);
|
|
break;
|
|
case 'd':
|
|
success = listTSDictionaries(pattern, show_verbose);
|
|
break;
|
|
case 't':
|
|
success = listTSTemplates(pattern, show_verbose);
|
|
break;
|
|
default:
|
|
status = PSQL_CMD_UNKNOWN;
|
|
break;
|
|
}
|
|
break;
|
|
case 'e': /* SQL/MED subsystem */
|
|
switch (cmd[2])
|
|
{
|
|
case 's':
|
|
success = listForeignServers(pattern, show_verbose);
|
|
break;
|
|
case 'u':
|
|
success = listUserMappings(pattern, show_verbose);
|
|
break;
|
|
case 'w':
|
|
success = listForeignDataWrappers(pattern, show_verbose);
|
|
break;
|
|
case 't':
|
|
success = listForeignTables(pattern, show_verbose);
|
|
break;
|
|
default:
|
|
status = PSQL_CMD_UNKNOWN;
|
|
break;
|
|
}
|
|
break;
|
|
case 'x': /* Extensions */
|
|
if (show_verbose)
|
|
success = listExtensionContents(pattern);
|
|
else
|
|
success = listExtensions(pattern);
|
|
break;
|
|
case 'X': /* Extended Statistics */
|
|
success = listExtendedStats(pattern);
|
|
break;
|
|
case 'y': /* Event Triggers */
|
|
success = listEventTriggers(pattern, show_verbose);
|
|
break;
|
|
default:
|
|
status = PSQL_CMD_UNKNOWN;
|
|
}
|
|
|
|
free(pattern);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
if (!success)
|
|
status = PSQL_CMD_ERROR;
|
|
|
|
return status;
|
|
}
|
|
|
|
/* \df and \do; messy enough to split out of exec_command_d */
|
|
static bool
|
|
exec_command_dfo(PsqlScanState scan_state, const char *cmd,
|
|
const char *pattern,
|
|
bool show_verbose, bool show_system)
|
|
{
|
|
bool success;
|
|
char *arg_patterns[FUNC_MAX_ARGS];
|
|
int num_arg_patterns = 0;
|
|
|
|
/* Collect argument-type patterns too */
|
|
if (pattern) /* otherwise it was just \df or \do */
|
|
{
|
|
char *ap;
|
|
|
|
while ((ap = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true)) != NULL)
|
|
{
|
|
arg_patterns[num_arg_patterns++] = ap;
|
|
if (num_arg_patterns >= FUNC_MAX_ARGS)
|
|
break; /* protect limited-size array */
|
|
}
|
|
}
|
|
|
|
if (cmd[1] == 'f')
|
|
success = describeFunctions(&cmd[2], pattern,
|
|
arg_patterns, num_arg_patterns,
|
|
show_verbose, show_system);
|
|
else
|
|
success = describeOperators(pattern,
|
|
arg_patterns, num_arg_patterns,
|
|
show_verbose, show_system);
|
|
|
|
while (--num_arg_patterns >= 0)
|
|
free(arg_patterns[num_arg_patterns]);
|
|
|
|
return success;
|
|
}
|
|
|
|
/*
|
|
* \e or \edit -- edit the current query buffer, or edit a file and
|
|
* make it the query buffer
|
|
*/
|
|
static backslashResult
|
|
exec_command_edit(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf, PQExpBuffer previous_buf)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
{
|
|
if (!query_buf)
|
|
{
|
|
pg_log_error("no query buffer");
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else
|
|
{
|
|
char *fname;
|
|
char *ln = NULL;
|
|
int lineno = -1;
|
|
|
|
fname = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
if (fname)
|
|
{
|
|
/* try to get separate lineno arg */
|
|
ln = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
if (ln == NULL)
|
|
{
|
|
/* only one arg; maybe it is lineno not fname */
|
|
if (fname[0] &&
|
|
strspn(fname, "0123456789") == strlen(fname))
|
|
{
|
|
/* all digits, so assume it is lineno */
|
|
ln = fname;
|
|
fname = NULL;
|
|
}
|
|
}
|
|
}
|
|
if (ln)
|
|
{
|
|
lineno = atoi(ln);
|
|
if (lineno < 1)
|
|
{
|
|
pg_log_error("invalid line number: %s", ln);
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
}
|
|
if (status != PSQL_CMD_ERROR)
|
|
{
|
|
bool discard_on_quit;
|
|
|
|
expand_tilde(&fname);
|
|
if (fname)
|
|
{
|
|
canonicalize_path(fname);
|
|
/* Always clear buffer if the file isn't modified */
|
|
discard_on_quit = true;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* If query_buf is empty, recall previous query for
|
|
* editing. But in that case, the query buffer should be
|
|
* emptied if editing doesn't modify the file.
|
|
*/
|
|
discard_on_quit = copy_previous_query(query_buf,
|
|
previous_buf);
|
|
}
|
|
|
|
if (do_edit(fname, query_buf, lineno, discard_on_quit, NULL))
|
|
status = PSQL_CMD_NEWEDIT;
|
|
else
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
free(fname);
|
|
free(ln);
|
|
}
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \ef/\ev -- edit the named function/view, or
|
|
* present a blank CREATE FUNCTION/VIEW template if no argument is given
|
|
*/
|
|
static backslashResult
|
|
exec_command_ef_ev(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf, bool is_func)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *obj_desc = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE,
|
|
NULL, true);
|
|
int lineno = -1;
|
|
|
|
if (!query_buf)
|
|
{
|
|
pg_log_error("no query buffer");
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else
|
|
{
|
|
Oid obj_oid = InvalidOid;
|
|
EditableObjectType eot = is_func ? EditableFunction : EditableView;
|
|
|
|
lineno = strip_lineno_from_objdesc(obj_desc);
|
|
if (lineno == 0)
|
|
{
|
|
/* error already reported */
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else if (!obj_desc)
|
|
{
|
|
/* set up an empty command to fill in */
|
|
resetPQExpBuffer(query_buf);
|
|
if (is_func)
|
|
appendPQExpBufferStr(query_buf,
|
|
"CREATE FUNCTION ( )\n"
|
|
" RETURNS \n"
|
|
" LANGUAGE \n"
|
|
" -- common options: IMMUTABLE STABLE STRICT SECURITY DEFINER\n"
|
|
"AS $function$\n"
|
|
"\n$function$\n");
|
|
else
|
|
appendPQExpBufferStr(query_buf,
|
|
"CREATE VIEW AS\n"
|
|
" SELECT \n"
|
|
" -- something...\n");
|
|
}
|
|
else if (!lookup_object_oid(eot, obj_desc, &obj_oid))
|
|
{
|
|
/* error already reported */
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else if (!get_create_object_cmd(eot, obj_oid, query_buf))
|
|
{
|
|
/* error already reported */
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else if (is_func && lineno > 0)
|
|
{
|
|
/*
|
|
* lineno "1" should correspond to the first line of the
|
|
* function body. We expect that pg_get_functiondef() will
|
|
* emit that on a line beginning with "AS ", "BEGIN ", or
|
|
* "RETURN ", and that there can be no such line before the
|
|
* real start of the function body. Increment lineno by the
|
|
* number of lines before that line, so that it becomes
|
|
* relative to the first line of the function definition.
|
|
*/
|
|
const char *lines = query_buf->data;
|
|
|
|
while (*lines != '\0')
|
|
{
|
|
if (strncmp(lines, "AS ", 3) == 0 ||
|
|
strncmp(lines, "BEGIN ", 6) == 0 ||
|
|
strncmp(lines, "RETURN ", 7) == 0)
|
|
break;
|
|
lineno++;
|
|
/* find start of next line */
|
|
lines = strchr(lines, '\n');
|
|
if (!lines)
|
|
break;
|
|
lines++;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (status != PSQL_CMD_ERROR)
|
|
{
|
|
bool edited = false;
|
|
|
|
if (!do_edit(NULL, query_buf, lineno, true, &edited))
|
|
status = PSQL_CMD_ERROR;
|
|
else if (!edited)
|
|
puts(_("No changes"));
|
|
else
|
|
status = PSQL_CMD_NEWEDIT;
|
|
}
|
|
|
|
free(obj_desc);
|
|
}
|
|
else
|
|
ignore_slash_whole_line(scan_state);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \echo, \qecho, and \warn -- echo arguments to stdout, query output, or stderr
|
|
*/
|
|
static backslashResult
|
|
exec_command_echo(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
|
{
|
|
if (active_branch)
|
|
{
|
|
char *value;
|
|
char quoted;
|
|
bool no_newline = false;
|
|
bool first = true;
|
|
FILE *fout;
|
|
|
|
if (strcmp(cmd, "qecho") == 0)
|
|
fout = pset.queryFout;
|
|
else if (strcmp(cmd, "warn") == 0)
|
|
fout = stderr;
|
|
else
|
|
fout = stdout;
|
|
|
|
while ((value = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, "ed, false)))
|
|
{
|
|
if (first && !no_newline && !quoted && strcmp(value, "-n") == 0)
|
|
no_newline = true;
|
|
else
|
|
{
|
|
if (first)
|
|
first = false;
|
|
else
|
|
fputc(' ', fout);
|
|
fputs(value, fout);
|
|
}
|
|
free(value);
|
|
}
|
|
if (!no_newline)
|
|
fputs("\n", fout);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
/*
|
|
* \encoding -- set/show client side encoding
|
|
*/
|
|
static backslashResult
|
|
exec_command_encoding(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
if (active_branch)
|
|
{
|
|
char *encoding = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!encoding)
|
|
{
|
|
/* show encoding */
|
|
puts(pg_encoding_to_char(pset.encoding));
|
|
}
|
|
else
|
|
{
|
|
/* set encoding */
|
|
if (PQsetClientEncoding(pset.db, encoding) == -1)
|
|
pg_log_error("%s: invalid encoding name or conversion procedure not found", encoding);
|
|
else
|
|
{
|
|
/* save encoding info into psql internal data */
|
|
pset.encoding = PQclientEncoding(pset.db);
|
|
pset.popt.topt.encoding = pset.encoding;
|
|
SetVariable(pset.vars, "ENCODING",
|
|
pg_encoding_to_char(pset.encoding));
|
|
}
|
|
free(encoding);
|
|
}
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
/*
|
|
* \errverbose -- display verbose message from last failed query
|
|
*/
|
|
static backslashResult
|
|
exec_command_errverbose(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
if (active_branch)
|
|
{
|
|
if (pset.last_error_result)
|
|
{
|
|
char *msg;
|
|
|
|
msg = PQresultVerboseErrorMessage(pset.last_error_result,
|
|
PQERRORS_VERBOSE,
|
|
PQSHOW_CONTEXT_ALWAYS);
|
|
if (msg)
|
|
{
|
|
pg_log_error("%s", msg);
|
|
PQfreemem(msg);
|
|
}
|
|
else
|
|
puts(_("out of memory"));
|
|
}
|
|
else
|
|
puts(_("There is no previous error."));
|
|
}
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
/*
|
|
* \f -- change field separator
|
|
*/
|
|
static backslashResult
|
|
exec_command_f(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
|
|
free(fname);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \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;
|
|
|
|
/*
|
|
* 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] == '(')
|
|
{
|
|
/* 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
|
|
{
|
|
expand_tilde(&fname);
|
|
pset.gfname = pg_strdup(fname);
|
|
}
|
|
if (strcmp(cmd, "gx") == 0)
|
|
{
|
|
/* 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;
|
|
}
|
|
|
|
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
|
|
*/
|
|
static backslashResult
|
|
exec_command_gdesc(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
{
|
|
pset.gdesc_flag = true;
|
|
status = PSQL_CMD_SEND;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \getenv -- set variable from environment variable
|
|
*/
|
|
static backslashResult
|
|
exec_command_getenv(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *myvar = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
char *envvar = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!myvar || !envvar)
|
|
{
|
|
pg_log_error("\\%s: missing required argument", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
char *envval = getenv(envvar);
|
|
|
|
if (envval && !SetVariable(pset.vars, myvar, envval))
|
|
success = false;
|
|
}
|
|
free(myvar);
|
|
free(envvar);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \gexec -- send query and execute each field of result
|
|
*/
|
|
static backslashResult
|
|
exec_command_gexec(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
{
|
|
pset.gexec_flag = true;
|
|
status = PSQL_CMD_SEND;
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \gset [prefix] -- send query and store result into variables
|
|
*/
|
|
static backslashResult
|
|
exec_command_gset(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *prefix = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (prefix)
|
|
pset.gset_prefix = prefix;
|
|
else
|
|
{
|
|
/* we must set a non-NULL prefix to trigger storing */
|
|
pset.gset_prefix = pg_strdup("");
|
|
}
|
|
/* gset_prefix is freed later */
|
|
status = PSQL_CMD_SEND;
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \help [topic] -- print help about SQL commands
|
|
*/
|
|
static backslashResult
|
|
exec_command_help(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE, NULL, false);
|
|
size_t len;
|
|
|
|
/* strip any trailing spaces and semicolons */
|
|
if (opt)
|
|
{
|
|
len = strlen(opt);
|
|
while (len > 0 &&
|
|
(isspace((unsigned char) opt[len - 1])
|
|
|| opt[len - 1] == ';'))
|
|
opt[--len] = '\0';
|
|
}
|
|
|
|
helpSQL(opt, pset.popt.topt.pager);
|
|
free(opt);
|
|
}
|
|
else
|
|
ignore_slash_whole_line(scan_state);
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
/*
|
|
* \H and \html -- toggle HTML formatting
|
|
*/
|
|
static backslashResult
|
|
exec_command_html(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
if (pset.popt.topt.format != PRINT_HTML)
|
|
success = do_pset("format", "html", &pset.popt, pset.quiet);
|
|
else
|
|
success = do_pset("format", "aligned", &pset.popt, pset.quiet);
|
|
}
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \i and \ir -- include a file
|
|
*/
|
|
static backslashResult
|
|
exec_command_include(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
if (!fname)
|
|
{
|
|
pg_log_error("\\%s: missing required argument", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
bool include_relative;
|
|
|
|
include_relative = (strcmp(cmd, "ir") == 0
|
|
|| strcmp(cmd, "include_relative") == 0);
|
|
expand_tilde(&fname);
|
|
success = (process_file(fname, include_relative) == EXIT_SUCCESS);
|
|
free(fname);
|
|
}
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \if <expr> -- beginning of an \if..\endif block
|
|
*
|
|
* <expr> is parsed as a boolean expression. Invalid expressions will emit a
|
|
* warning and be treated as false. Statements that follow a false expression
|
|
* will be parsed but ignored. Note that in the case where an \if statement
|
|
* is itself within an inactive section of a block, then the entire inner
|
|
* \if..\endif block will be parsed but ignored.
|
|
*/
|
|
static backslashResult
|
|
exec_command_if(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf)
|
|
{
|
|
if (conditional_active(cstack))
|
|
{
|
|
/*
|
|
* First, push a new active stack entry; this ensures that the lexer
|
|
* will perform variable substitution and backtick evaluation while
|
|
* scanning the expression. (That should happen anyway, since we know
|
|
* we're in an active outer branch, but let's be sure.)
|
|
*/
|
|
conditional_stack_push(cstack, IFSTATE_TRUE);
|
|
|
|
/* Remember current query state in case we need to restore later */
|
|
save_query_text_state(scan_state, cstack, query_buf);
|
|
|
|
/*
|
|
* Evaluate the expression; if it's false, change to inactive state.
|
|
*/
|
|
if (!is_true_boolean_expression(scan_state, "\\if expression"))
|
|
conditional_stack_poke(cstack, IFSTATE_FALSE);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* We're within an inactive outer branch, so this entire \if block
|
|
* will be ignored. We don't want to evaluate the expression, so push
|
|
* the "ignored" stack state before scanning it.
|
|
*/
|
|
conditional_stack_push(cstack, IFSTATE_IGNORED);
|
|
|
|
/* Remember current query state in case we need to restore later */
|
|
save_query_text_state(scan_state, cstack, query_buf);
|
|
|
|
ignore_boolean_expression(scan_state);
|
|
}
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
/*
|
|
* \elif <expr> -- alternative branch in an \if..\endif block
|
|
*
|
|
* <expr> is evaluated the same as in \if <expr>.
|
|
*/
|
|
static backslashResult
|
|
exec_command_elif(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf)
|
|
{
|
|
bool success = true;
|
|
|
|
switch (conditional_stack_peek(cstack))
|
|
{
|
|
case IFSTATE_TRUE:
|
|
|
|
/*
|
|
* Just finished active branch of this \if block. Update saved
|
|
* state so we will keep whatever data was put in query_buf by the
|
|
* active branch.
|
|
*/
|
|
save_query_text_state(scan_state, cstack, query_buf);
|
|
|
|
/*
|
|
* Discard \elif expression and ignore the rest until \endif.
|
|
* Switch state before reading expression to ensure proper lexer
|
|
* behavior.
|
|
*/
|
|
conditional_stack_poke(cstack, IFSTATE_IGNORED);
|
|
ignore_boolean_expression(scan_state);
|
|
break;
|
|
case IFSTATE_FALSE:
|
|
|
|
/*
|
|
* Discard any query text added by the just-skipped branch.
|
|
*/
|
|
discard_query_text(scan_state, cstack, query_buf);
|
|
|
|
/*
|
|
* Have not yet found a true expression in this \if block, so this
|
|
* might be the first. We have to change state before examining
|
|
* the expression, or the lexer won't do the right thing.
|
|
*/
|
|
conditional_stack_poke(cstack, IFSTATE_TRUE);
|
|
if (!is_true_boolean_expression(scan_state, "\\elif expression"))
|
|
conditional_stack_poke(cstack, IFSTATE_FALSE);
|
|
break;
|
|
case IFSTATE_IGNORED:
|
|
|
|
/*
|
|
* Discard any query text added by the just-skipped branch.
|
|
*/
|
|
discard_query_text(scan_state, cstack, query_buf);
|
|
|
|
/*
|
|
* Skip expression and move on. Either the \if block already had
|
|
* an active section, or whole block is being skipped.
|
|
*/
|
|
ignore_boolean_expression(scan_state);
|
|
break;
|
|
case IFSTATE_ELSE_TRUE:
|
|
case IFSTATE_ELSE_FALSE:
|
|
pg_log_error("\\elif: cannot occur after \\else");
|
|
success = false;
|
|
break;
|
|
case IFSTATE_NONE:
|
|
/* no \if to elif from */
|
|
pg_log_error("\\elif: no matching \\if");
|
|
success = false;
|
|
break;
|
|
}
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \else -- final alternative in an \if..\endif block
|
|
*
|
|
* Statements within an \else branch will only be executed if
|
|
* all previous \if and \elif expressions evaluated to false
|
|
* and the block was not itself being ignored.
|
|
*/
|
|
static backslashResult
|
|
exec_command_else(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf)
|
|
{
|
|
bool success = true;
|
|
|
|
switch (conditional_stack_peek(cstack))
|
|
{
|
|
case IFSTATE_TRUE:
|
|
|
|
/*
|
|
* Just finished active branch of this \if block. Update saved
|
|
* state so we will keep whatever data was put in query_buf by the
|
|
* active branch.
|
|
*/
|
|
save_query_text_state(scan_state, cstack, query_buf);
|
|
|
|
/* Now skip the \else branch */
|
|
conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE);
|
|
break;
|
|
case IFSTATE_FALSE:
|
|
|
|
/*
|
|
* Discard any query text added by the just-skipped branch.
|
|
*/
|
|
discard_query_text(scan_state, cstack, query_buf);
|
|
|
|
/*
|
|
* We've not found any true \if or \elif expression, so execute
|
|
* the \else branch.
|
|
*/
|
|
conditional_stack_poke(cstack, IFSTATE_ELSE_TRUE);
|
|
break;
|
|
case IFSTATE_IGNORED:
|
|
|
|
/*
|
|
* Discard any query text added by the just-skipped branch.
|
|
*/
|
|
discard_query_text(scan_state, cstack, query_buf);
|
|
|
|
/*
|
|
* Either we previously processed the active branch of this \if,
|
|
* or the whole \if block is being skipped. Either way, skip the
|
|
* \else branch.
|
|
*/
|
|
conditional_stack_poke(cstack, IFSTATE_ELSE_FALSE);
|
|
break;
|
|
case IFSTATE_ELSE_TRUE:
|
|
case IFSTATE_ELSE_FALSE:
|
|
pg_log_error("\\else: cannot occur after \\else");
|
|
success = false;
|
|
break;
|
|
case IFSTATE_NONE:
|
|
/* no \if to else from */
|
|
pg_log_error("\\else: no matching \\if");
|
|
success = false;
|
|
break;
|
|
}
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \endif -- ends an \if...\endif block
|
|
*/
|
|
static backslashResult
|
|
exec_command_endif(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf)
|
|
{
|
|
bool success = true;
|
|
|
|
switch (conditional_stack_peek(cstack))
|
|
{
|
|
case IFSTATE_TRUE:
|
|
case IFSTATE_ELSE_TRUE:
|
|
/* Close the \if block, keeping the query text */
|
|
success = conditional_stack_pop(cstack);
|
|
Assert(success);
|
|
break;
|
|
case IFSTATE_FALSE:
|
|
case IFSTATE_IGNORED:
|
|
case IFSTATE_ELSE_FALSE:
|
|
|
|
/*
|
|
* Discard any query text added by the just-skipped branch.
|
|
*/
|
|
discard_query_text(scan_state, cstack, query_buf);
|
|
|
|
/* Close the \if block */
|
|
success = conditional_stack_pop(cstack);
|
|
Assert(success);
|
|
break;
|
|
case IFSTATE_NONE:
|
|
/* no \if to end */
|
|
pg_log_error("\\endif: no matching \\if");
|
|
success = false;
|
|
break;
|
|
}
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \l -- list databases
|
|
*/
|
|
static backslashResult
|
|
exec_command_list(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *pattern;
|
|
bool show_verbose;
|
|
|
|
pattern = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
show_verbose = strchr(cmd, '+') ? true : false;
|
|
|
|
success = listAllDbs(pattern, show_verbose);
|
|
|
|
free(pattern);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \lo_* -- large object operations
|
|
*/
|
|
static backslashResult
|
|
exec_command_lo(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt1,
|
|
*opt2;
|
|
|
|
opt1 = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
opt2 = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
if (strcmp(cmd + 3, "export") == 0)
|
|
{
|
|
if (!opt2)
|
|
{
|
|
pg_log_error("\\%s: missing required argument", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
expand_tilde(&opt2);
|
|
success = do_lo_export(opt1, opt2);
|
|
}
|
|
}
|
|
|
|
else if (strcmp(cmd + 3, "import") == 0)
|
|
{
|
|
if (!opt1)
|
|
{
|
|
pg_log_error("\\%s: missing required argument", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
expand_tilde(&opt1);
|
|
success = do_lo_import(opt1, opt2);
|
|
}
|
|
}
|
|
|
|
else if (strcmp(cmd + 3, "list") == 0)
|
|
success = listLargeObjects(false);
|
|
else if (strcmp(cmd + 3, "list+") == 0)
|
|
success = listLargeObjects(true);
|
|
|
|
else if (strcmp(cmd + 3, "unlink") == 0)
|
|
{
|
|
if (!opt1)
|
|
{
|
|
pg_log_error("\\%s: missing required argument", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
success = do_lo_unlink(opt1);
|
|
}
|
|
|
|
else
|
|
status = PSQL_CMD_UNKNOWN;
|
|
|
|
free(opt1);
|
|
free(opt2);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
if (!success)
|
|
status = PSQL_CMD_ERROR;
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \o -- set query output
|
|
*/
|
|
static backslashResult
|
|
exec_command_out(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_FILEPIPE, NULL, true);
|
|
|
|
expand_tilde(&fname);
|
|
success = setQFout(fname);
|
|
free(fname);
|
|
}
|
|
else
|
|
ignore_slash_filepipe(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \p -- print the current query buffer
|
|
*/
|
|
static backslashResult
|
|
exec_command_print(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf, PQExpBuffer previous_buf)
|
|
{
|
|
if (active_branch)
|
|
{
|
|
/*
|
|
* We want to print the same thing \g would execute, but not to change
|
|
* the query buffer state; so we can't use copy_previous_query().
|
|
* Also, beware of possibility that buffer pointers are NULL.
|
|
*/
|
|
if (query_buf && query_buf->len > 0)
|
|
puts(query_buf->data);
|
|
else if (previous_buf && previous_buf->len > 0)
|
|
puts(previous_buf->data);
|
|
else if (!pset.quiet)
|
|
puts(_("Query buffer is empty."));
|
|
fflush(stdout);
|
|
}
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
/*
|
|
* \password -- set user password
|
|
*/
|
|
static backslashResult
|
|
exec_command_password(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *user = psql_scan_slash_option(scan_state,
|
|
OT_SQLID, NULL, true);
|
|
char *pw1 = NULL;
|
|
char *pw2 = NULL;
|
|
PQExpBufferData buf;
|
|
PromptInterruptContext prompt_ctx;
|
|
|
|
if (user == NULL)
|
|
{
|
|
/* By default, the command applies to CURRENT_USER */
|
|
PGresult *res;
|
|
|
|
res = PSQLexec("SELECT CURRENT_USER");
|
|
if (!res)
|
|
return PSQL_CMD_ERROR;
|
|
|
|
user = pg_strdup(PQgetvalue(res, 0, 0));
|
|
PQclear(res);
|
|
}
|
|
|
|
/* Set up to let SIGINT cancel simple_prompt_extended() */
|
|
prompt_ctx.jmpbuf = sigint_interrupt_jmp;
|
|
prompt_ctx.enabled = &sigint_interrupt_enabled;
|
|
prompt_ctx.canceled = false;
|
|
|
|
initPQExpBuffer(&buf);
|
|
printfPQExpBuffer(&buf, _("Enter new password for user \"%s\": "), user);
|
|
|
|
pw1 = simple_prompt_extended(buf.data, false, &prompt_ctx);
|
|
if (!prompt_ctx.canceled)
|
|
pw2 = simple_prompt_extended("Enter it again: ", false, &prompt_ctx);
|
|
|
|
if (prompt_ctx.canceled)
|
|
{
|
|
/* fail silently */
|
|
success = false;
|
|
}
|
|
else if (strcmp(pw1, pw2) != 0)
|
|
{
|
|
pg_log_error("Passwords didn't match.");
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
char *encrypted_password;
|
|
|
|
encrypted_password = PQencryptPasswordConn(pset.db, pw1, user, NULL);
|
|
|
|
if (!encrypted_password)
|
|
{
|
|
pg_log_info("%s", PQerrorMessage(pset.db));
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
PGresult *res;
|
|
|
|
printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ",
|
|
fmtId(user));
|
|
appendStringLiteralConn(&buf, encrypted_password, pset.db);
|
|
res = PSQLexec(buf.data);
|
|
if (!res)
|
|
success = false;
|
|
else
|
|
PQclear(res);
|
|
PQfreemem(encrypted_password);
|
|
}
|
|
}
|
|
|
|
free(user);
|
|
free(pw1);
|
|
free(pw2);
|
|
termPQExpBuffer(&buf);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \prompt -- prompt and set variable
|
|
*/
|
|
static backslashResult
|
|
exec_command_prompt(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt,
|
|
*prompt_text = NULL;
|
|
char *arg1,
|
|
*arg2;
|
|
|
|
arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
|
|
arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
|
|
|
|
if (!arg1)
|
|
{
|
|
pg_log_error("\\%s: missing required argument", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
char *result;
|
|
PromptInterruptContext prompt_ctx;
|
|
|
|
/* Set up to let SIGINT cancel simple_prompt_extended() */
|
|
prompt_ctx.jmpbuf = sigint_interrupt_jmp;
|
|
prompt_ctx.enabled = &sigint_interrupt_enabled;
|
|
prompt_ctx.canceled = false;
|
|
|
|
if (arg2)
|
|
{
|
|
prompt_text = arg1;
|
|
opt = arg2;
|
|
}
|
|
else
|
|
opt = arg1;
|
|
|
|
if (!pset.inputfile)
|
|
{
|
|
result = simple_prompt_extended(prompt_text, true, &prompt_ctx);
|
|
}
|
|
else
|
|
{
|
|
if (prompt_text)
|
|
{
|
|
fputs(prompt_text, stdout);
|
|
fflush(stdout);
|
|
}
|
|
result = gets_fromFile(stdin);
|
|
if (!result)
|
|
{
|
|
pg_log_error("\\%s: could not read value for variable",
|
|
cmd);
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
if (prompt_ctx.canceled ||
|
|
(result && !SetVariable(pset.vars, opt, result)))
|
|
success = false;
|
|
|
|
free(result);
|
|
free(prompt_text);
|
|
free(opt);
|
|
}
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \pset -- set printing parameters
|
|
*/
|
|
static backslashResult
|
|
exec_command_pset(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt0 = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
char *opt1 = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!opt0)
|
|
{
|
|
/* list all variables */
|
|
|
|
int i;
|
|
static const char *const my_list[] = {
|
|
"border", "columns", "csv_fieldsep", "expanded", "fieldsep",
|
|
"fieldsep_zero", "footer", "format", "linestyle", "null",
|
|
"numericlocale", "pager", "pager_min_lines",
|
|
"recordsep", "recordsep_zero",
|
|
"tableattr", "title", "tuples_only",
|
|
"unicode_border_linestyle",
|
|
"unicode_column_linestyle",
|
|
"unicode_header_linestyle",
|
|
"xheader_width",
|
|
NULL
|
|
};
|
|
|
|
for (i = 0; my_list[i] != NULL; i++)
|
|
{
|
|
char *val = pset_value_string(my_list[i], &pset.popt);
|
|
|
|
printf("%-24s %s\n", my_list[i], val);
|
|
free(val);
|
|
}
|
|
|
|
success = true;
|
|
}
|
|
else
|
|
success = do_pset(opt0, opt1, &pset.popt, pset.quiet);
|
|
|
|
free(opt0);
|
|
free(opt1);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \q or \quit -- exit psql
|
|
*/
|
|
static backslashResult
|
|
exec_command_quit(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
status = PSQL_CMD_TERMINATE;
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \r -- reset (clear) the query buffer
|
|
*/
|
|
static backslashResult
|
|
exec_command_reset(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf)
|
|
{
|
|
if (active_branch)
|
|
{
|
|
resetPQExpBuffer(query_buf);
|
|
psql_scan_reset(scan_state);
|
|
if (!pset.quiet)
|
|
puts(_("Query buffer reset (cleared)."));
|
|
}
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
/*
|
|
* \s -- save history in a file or show it on the screen
|
|
*/
|
|
static backslashResult
|
|
exec_command_s(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
expand_tilde(&fname);
|
|
success = printHistory(fname, pset.popt.topt.pager);
|
|
if (success && !pset.quiet && fname)
|
|
printf(_("Wrote history to file \"%s\".\n"), fname);
|
|
if (!fname)
|
|
putchar('\n');
|
|
free(fname);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \set -- set variable
|
|
*/
|
|
static backslashResult
|
|
exec_command_set(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt0 = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!opt0)
|
|
{
|
|
/* list all variables */
|
|
PrintVariables(pset.vars);
|
|
success = true;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Set variable to the concatenation of the arguments.
|
|
*/
|
|
char *newval;
|
|
char *opt;
|
|
|
|
opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
newval = pg_strdup(opt ? opt : "");
|
|
free(opt);
|
|
|
|
while ((opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false)))
|
|
{
|
|
newval = pg_realloc(newval, strlen(newval) + strlen(opt) + 1);
|
|
strcat(newval, opt);
|
|
free(opt);
|
|
}
|
|
|
|
if (!SetVariable(pset.vars, opt0, newval))
|
|
success = false;
|
|
|
|
free(newval);
|
|
}
|
|
free(opt0);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \setenv -- set environment variable
|
|
*/
|
|
static backslashResult
|
|
exec_command_setenv(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *envvar = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
char *envval = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!envvar)
|
|
{
|
|
pg_log_error("\\%s: missing required argument", cmd);
|
|
success = false;
|
|
}
|
|
else if (strchr(envvar, '=') != NULL)
|
|
{
|
|
pg_log_error("\\%s: environment variable name must not contain \"=\"",
|
|
cmd);
|
|
success = false;
|
|
}
|
|
else if (!envval)
|
|
{
|
|
/* No argument - unset the environment variable */
|
|
unsetenv(envvar);
|
|
success = true;
|
|
}
|
|
else
|
|
{
|
|
/* Set variable to the value of the next argument */
|
|
setenv(envvar, envval, 1);
|
|
success = true;
|
|
}
|
|
free(envvar);
|
|
free(envval);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \sf/\sv -- show a function/view's source code
|
|
*/
|
|
static backslashResult
|
|
exec_command_sf_sv(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd, bool is_func)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
{
|
|
bool show_linenumbers = (strchr(cmd, '+') != NULL);
|
|
PQExpBuffer buf;
|
|
char *obj_desc;
|
|
Oid obj_oid = InvalidOid;
|
|
EditableObjectType eot = is_func ? EditableFunction : EditableView;
|
|
|
|
buf = createPQExpBuffer();
|
|
obj_desc = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE, NULL, true);
|
|
if (!obj_desc)
|
|
{
|
|
if (is_func)
|
|
pg_log_error("function name is required");
|
|
else
|
|
pg_log_error("view name is required");
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else if (!lookup_object_oid(eot, obj_desc, &obj_oid))
|
|
{
|
|
/* error already reported */
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else if (!get_create_object_cmd(eot, obj_oid, buf))
|
|
{
|
|
/* error already reported */
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else
|
|
{
|
|
FILE *output;
|
|
bool is_pager;
|
|
|
|
/* Select output stream: stdout, pager, or file */
|
|
if (pset.queryFout == stdout)
|
|
{
|
|
/* count lines in function to see if pager is needed */
|
|
int lineno = count_lines_in_buf(buf);
|
|
|
|
output = PageOutput(lineno, &(pset.popt.topt));
|
|
is_pager = true;
|
|
}
|
|
else
|
|
{
|
|
/* use previously set output file, without pager */
|
|
output = pset.queryFout;
|
|
is_pager = false;
|
|
}
|
|
|
|
if (show_linenumbers)
|
|
{
|
|
/* add line numbers */
|
|
print_with_linenumbers(output, buf->data, is_func);
|
|
}
|
|
else
|
|
{
|
|
/* just send the definition to output */
|
|
fputs(buf->data, output);
|
|
}
|
|
|
|
if (is_pager)
|
|
ClosePager(output);
|
|
}
|
|
|
|
free(obj_desc);
|
|
destroyPQExpBuffer(buf);
|
|
}
|
|
else
|
|
ignore_slash_whole_line(scan_state);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \t -- turn off table headers and row count
|
|
*/
|
|
static backslashResult
|
|
exec_command_t(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
|
|
free(opt);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \T -- define html <table ...> attributes
|
|
*/
|
|
static backslashResult
|
|
exec_command_T(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *value = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
success = do_pset("tableattr", value, &pset.popt, pset.quiet);
|
|
free(value);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \timing -- enable/disable timing of queries
|
|
*/
|
|
static backslashResult
|
|
exec_command_timing(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (opt)
|
|
success = ParseVariableBool(opt, "\\timing", &pset.timing);
|
|
else
|
|
pset.timing = !pset.timing;
|
|
if (!pset.quiet)
|
|
{
|
|
if (pset.timing)
|
|
puts(_("Timing is on."));
|
|
else
|
|
puts(_("Timing is off."));
|
|
}
|
|
free(opt);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \unset -- unset variable
|
|
*/
|
|
static backslashResult
|
|
exec_command_unset(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!opt)
|
|
{
|
|
pg_log_error("\\%s: missing required argument", cmd);
|
|
success = false;
|
|
}
|
|
else if (!SetVariable(pset.vars, opt, NULL))
|
|
success = false;
|
|
|
|
free(opt);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \w -- write query buffer to file
|
|
*/
|
|
static backslashResult
|
|
exec_command_write(PsqlScanState scan_state, bool active_branch,
|
|
const char *cmd,
|
|
PQExpBuffer query_buf, PQExpBuffer previous_buf)
|
|
{
|
|
backslashResult status = PSQL_CMD_SKIP_LINE;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_FILEPIPE, NULL, true);
|
|
FILE *fd = NULL;
|
|
bool is_pipe = false;
|
|
|
|
if (!query_buf)
|
|
{
|
|
pg_log_error("no query buffer");
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else
|
|
{
|
|
if (!fname)
|
|
{
|
|
pg_log_error("\\%s: missing required argument", cmd);
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
else
|
|
{
|
|
expand_tilde(&fname);
|
|
if (fname[0] == '|')
|
|
{
|
|
is_pipe = true;
|
|
fflush(NULL);
|
|
disable_sigpipe_trap();
|
|
fd = popen(&fname[1], "w");
|
|
}
|
|
else
|
|
{
|
|
canonicalize_path(fname);
|
|
fd = fopen(fname, "w");
|
|
}
|
|
if (!fd)
|
|
{
|
|
pg_log_error("%s: %m", fname);
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fd)
|
|
{
|
|
int result;
|
|
|
|
/*
|
|
* We want to print the same thing \g would execute, but not to
|
|
* change the query buffer state; so we can't use
|
|
* copy_previous_query(). Also, beware of possibility that buffer
|
|
* pointers are NULL.
|
|
*/
|
|
if (query_buf && query_buf->len > 0)
|
|
fprintf(fd, "%s\n", query_buf->data);
|
|
else if (previous_buf && previous_buf->len > 0)
|
|
fprintf(fd, "%s\n", previous_buf->data);
|
|
|
|
if (is_pipe)
|
|
{
|
|
result = pclose(fd);
|
|
|
|
if (result != 0)
|
|
{
|
|
pg_log_error("%s: %s", fname, wait_result_to_str(result));
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
result = fclose(fd);
|
|
|
|
if (result == EOF)
|
|
{
|
|
pg_log_error("%s: %m", fname);
|
|
status = PSQL_CMD_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (is_pipe)
|
|
restore_sigpipe_trap();
|
|
|
|
free(fname);
|
|
}
|
|
else
|
|
ignore_slash_filepipe(scan_state);
|
|
|
|
return status;
|
|
}
|
|
|
|
/*
|
|
* \watch -- execute a query every N seconds
|
|
*/
|
|
static backslashResult
|
|
exec_command_watch(PsqlScanState scan_state, bool active_branch,
|
|
PQExpBuffer query_buf, PQExpBuffer previous_buf)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
double sleep = 2;
|
|
|
|
/* Convert optional sleep-length argument */
|
|
if (opt)
|
|
{
|
|
char *opt_end;
|
|
|
|
errno = 0;
|
|
sleep = strtod(opt, &opt_end);
|
|
if (sleep < 0 || *opt_end || errno == ERANGE)
|
|
{
|
|
pg_log_error("\\watch: incorrect interval value '%s'", opt);
|
|
free(opt);
|
|
resetPQExpBuffer(query_buf);
|
|
psql_scan_reset(scan_state);
|
|
return PSQL_CMD_ERROR;
|
|
}
|
|
free(opt);
|
|
}
|
|
|
|
/* If query_buf is empty, recall and execute previous query */
|
|
(void) copy_previous_query(query_buf, previous_buf);
|
|
|
|
success = do_watch(query_buf, sleep);
|
|
|
|
/* Reset the query buffer as though for \r */
|
|
resetPQExpBuffer(query_buf);
|
|
psql_scan_reset(scan_state);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \x -- set or toggle expanded table representation
|
|
*/
|
|
static backslashResult
|
|
exec_command_x(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
success = do_pset("expanded", opt, &pset.popt, pset.quiet);
|
|
free(opt);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \z -- list table privileges (equivalent to \dp)
|
|
*/
|
|
static backslashResult
|
|
exec_command_z(PsqlScanState scan_state, bool active_branch, const char *cmd)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *pattern;
|
|
bool show_system;
|
|
|
|
pattern = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
show_system = strchr(cmd, 'S') ? true : false;
|
|
|
|
success = permissionsList(pattern, show_system);
|
|
|
|
free(pattern);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \! -- execute shell command
|
|
*/
|
|
static backslashResult
|
|
exec_command_shell_escape(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
bool success = true;
|
|
|
|
if (active_branch)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE, NULL, false);
|
|
|
|
success = do_shell(opt);
|
|
free(opt);
|
|
}
|
|
else
|
|
ignore_slash_whole_line(scan_state);
|
|
|
|
return success ? PSQL_CMD_SKIP_LINE : PSQL_CMD_ERROR;
|
|
}
|
|
|
|
/*
|
|
* \? -- print help about backslash commands
|
|
*/
|
|
static backslashResult
|
|
exec_command_slash_command_help(PsqlScanState scan_state, bool active_branch)
|
|
{
|
|
if (active_branch)
|
|
{
|
|
char *opt0 = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!opt0 || strcmp(opt0, "commands") == 0)
|
|
slashUsage(pset.popt.topt.pager);
|
|
else if (strcmp(opt0, "options") == 0)
|
|
usage(pset.popt.topt.pager);
|
|
else if (strcmp(opt0, "variables") == 0)
|
|
helpVariables(pset.popt.topt.pager);
|
|
else
|
|
slashUsage(pset.popt.topt.pager);
|
|
|
|
free(opt0);
|
|
}
|
|
else
|
|
ignore_slash_options(scan_state);
|
|
|
|
return PSQL_CMD_SKIP_LINE;
|
|
}
|
|
|
|
|
|
/*
|
|
* Read and interpret an argument to the \connect slash command.
|
|
*
|
|
* Returns a malloc'd string, or NULL if no/empty argument.
|
|
*/
|
|
static char *
|
|
read_connect_arg(PsqlScanState scan_state)
|
|
{
|
|
char *result;
|
|
char quote;
|
|
|
|
/*
|
|
* Ideally we should treat the arguments as SQL identifiers. But for
|
|
* backwards compatibility with 7.2 and older pg_dump files, we have to
|
|
* take unquoted arguments verbatim (don't downcase them). For now,
|
|
* double-quoted arguments may be stripped of double quotes (as if SQL
|
|
* identifiers). By 7.4 or so, pg_dump files can be expected to
|
|
* double-quote all mixed-case \connect arguments, and then we can get rid
|
|
* of OT_SQLIDHACK.
|
|
*/
|
|
result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, "e, true);
|
|
|
|
if (!result)
|
|
return NULL;
|
|
|
|
if (quote)
|
|
return result;
|
|
|
|
if (*result == '\0' || strcmp(result, "-") == 0)
|
|
{
|
|
free(result);
|
|
return NULL;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Read a boolean expression, return it as a PQExpBuffer string.
|
|
*
|
|
* Note: anything more or less than one token will certainly fail to be
|
|
* parsed by ParseVariableBool, so we don't worry about complaining here.
|
|
* This routine's return data structure will need to be rethought anyway
|
|
* to support likely future extensions such as "\if defined VARNAME".
|
|
*/
|
|
static PQExpBuffer
|
|
gather_boolean_expression(PsqlScanState scan_state)
|
|
{
|
|
PQExpBuffer exp_buf = createPQExpBuffer();
|
|
int num_options = 0;
|
|
char *value;
|
|
|
|
/* collect all arguments for the conditional command into exp_buf */
|
|
while ((value = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false)) != NULL)
|
|
{
|
|
/* add spaces between tokens */
|
|
if (num_options > 0)
|
|
appendPQExpBufferChar(exp_buf, ' ');
|
|
appendPQExpBufferStr(exp_buf, value);
|
|
num_options++;
|
|
free(value);
|
|
}
|
|
|
|
return exp_buf;
|
|
}
|
|
|
|
/*
|
|
* Read a boolean expression, return true if the expression
|
|
* was a valid boolean expression that evaluated to true.
|
|
* Otherwise return false.
|
|
*
|
|
* Note: conditional stack's top state must be active, else lexer will
|
|
* fail to expand variables and backticks.
|
|
*/
|
|
static bool
|
|
is_true_boolean_expression(PsqlScanState scan_state, const char *name)
|
|
{
|
|
PQExpBuffer buf = gather_boolean_expression(scan_state);
|
|
bool value = false;
|
|
bool success = ParseVariableBool(buf->data, name, &value);
|
|
|
|
destroyPQExpBuffer(buf);
|
|
return success && value;
|
|
}
|
|
|
|
/*
|
|
* Read a boolean expression, but do nothing with it.
|
|
*
|
|
* Note: conditional stack's top state must be INACTIVE, else lexer will
|
|
* expand variables and backticks, which we do not want here.
|
|
*/
|
|
static void
|
|
ignore_boolean_expression(PsqlScanState scan_state)
|
|
{
|
|
PQExpBuffer buf = gather_boolean_expression(scan_state);
|
|
|
|
destroyPQExpBuffer(buf);
|
|
}
|
|
|
|
/*
|
|
* Read and discard "normal" slash command options.
|
|
*
|
|
* This should be used for inactive-branch processing of any slash command
|
|
* that eats one or more OT_NORMAL, OT_SQLID, or OT_SQLIDHACK parameters.
|
|
* We don't need to worry about exactly how many it would eat, since the
|
|
* cleanup logic in HandleSlashCmds would silently discard any extras anyway.
|
|
*/
|
|
static void
|
|
ignore_slash_options(PsqlScanState scan_state)
|
|
{
|
|
char *arg;
|
|
|
|
while ((arg = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false)) != NULL)
|
|
free(arg);
|
|
}
|
|
|
|
/*
|
|
* Read and discard FILEPIPE slash command argument.
|
|
*
|
|
* This *MUST* be used for inactive-branch processing of any slash command
|
|
* that takes an OT_FILEPIPE option. Otherwise we might consume a different
|
|
* amount of option text in active and inactive cases.
|
|
*/
|
|
static void
|
|
ignore_slash_filepipe(PsqlScanState scan_state)
|
|
{
|
|
char *arg = psql_scan_slash_option(scan_state,
|
|
OT_FILEPIPE, NULL, false);
|
|
|
|
free(arg);
|
|
}
|
|
|
|
/*
|
|
* Read and discard whole-line slash command argument.
|
|
*
|
|
* This *MUST* be used for inactive-branch processing of any slash command
|
|
* that takes an OT_WHOLE_LINE option. Otherwise we might consume a different
|
|
* amount of option text in active and inactive cases.
|
|
*/
|
|
static void
|
|
ignore_slash_whole_line(PsqlScanState scan_state)
|
|
{
|
|
char *arg = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE, NULL, false);
|
|
|
|
free(arg);
|
|
}
|
|
|
|
/*
|
|
* Return true if the command given is a branching command.
|
|
*/
|
|
static bool
|
|
is_branching_command(const char *cmd)
|
|
{
|
|
return (strcmp(cmd, "if") == 0 ||
|
|
strcmp(cmd, "elif") == 0 ||
|
|
strcmp(cmd, "else") == 0 ||
|
|
strcmp(cmd, "endif") == 0);
|
|
}
|
|
|
|
/*
|
|
* Prepare to possibly restore query buffer to its current state
|
|
* (cf. discard_query_text).
|
|
*
|
|
* We need to remember the length of the query buffer, and the lexer's
|
|
* notion of the parenthesis nesting depth.
|
|
*/
|
|
static void
|
|
save_query_text_state(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf)
|
|
{
|
|
if (query_buf)
|
|
conditional_stack_set_query_len(cstack, query_buf->len);
|
|
conditional_stack_set_paren_depth(cstack,
|
|
psql_scan_get_paren_depth(scan_state));
|
|
}
|
|
|
|
/*
|
|
* Discard any query text absorbed during an inactive conditional branch.
|
|
*
|
|
* We must discard data that was appended to query_buf during an inactive
|
|
* \if branch. We don't have to do anything there if there's no query_buf.
|
|
*
|
|
* Also, reset the lexer state to the same paren depth there was before.
|
|
* (The rest of its state doesn't need attention, since we could not be
|
|
* inside a comment or literal or partial token.)
|
|
*/
|
|
static void
|
|
discard_query_text(PsqlScanState scan_state, ConditionalStack cstack,
|
|
PQExpBuffer query_buf)
|
|
{
|
|
if (query_buf)
|
|
{
|
|
int new_len = conditional_stack_get_query_len(cstack);
|
|
|
|
Assert(new_len >= 0 && new_len <= query_buf->len);
|
|
query_buf->len = new_len;
|
|
query_buf->data[new_len] = '\0';
|
|
}
|
|
psql_scan_set_paren_depth(scan_state,
|
|
conditional_stack_get_paren_depth(cstack));
|
|
}
|
|
|
|
/*
|
|
* If query_buf is empty, copy previous_buf into it.
|
|
*
|
|
* This is used by various slash commands for which re-execution of a
|
|
* previous query is a common usage. For convenience, we allow the
|
|
* case of query_buf == NULL (and do nothing).
|
|
*
|
|
* Returns "true" if the previous query was copied into the query
|
|
* buffer, else "false".
|
|
*/
|
|
static bool
|
|
copy_previous_query(PQExpBuffer query_buf, PQExpBuffer previous_buf)
|
|
{
|
|
if (query_buf && query_buf->len == 0)
|
|
{
|
|
appendPQExpBufferStr(query_buf, previous_buf->data);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Ask the user for a password; 'username' is the username the
|
|
* password is for, if one has been explicitly specified.
|
|
* Returns a malloc'd string.
|
|
* If 'canceled' is provided, *canceled will be set to true if the prompt
|
|
* is canceled via SIGINT, and to false otherwise.
|
|
*/
|
|
static char *
|
|
prompt_for_password(const char *username, bool *canceled)
|
|
{
|
|
char *result;
|
|
PromptInterruptContext prompt_ctx;
|
|
|
|
/* Set up to let SIGINT cancel simple_prompt_extended() */
|
|
prompt_ctx.jmpbuf = sigint_interrupt_jmp;
|
|
prompt_ctx.enabled = &sigint_interrupt_enabled;
|
|
prompt_ctx.canceled = false;
|
|
|
|
if (username == NULL || username[0] == '\0')
|
|
result = simple_prompt_extended("Password: ", false, &prompt_ctx);
|
|
else
|
|
{
|
|
char *prompt_text;
|
|
|
|
prompt_text = psprintf(_("Password for user %s: "), username);
|
|
result = simple_prompt_extended(prompt_text, false, &prompt_ctx);
|
|
free(prompt_text);
|
|
}
|
|
|
|
if (canceled)
|
|
*canceled = prompt_ctx.canceled;
|
|
|
|
return result;
|
|
}
|
|
|
|
static bool
|
|
param_is_newly_set(const char *old_val, const char *new_val)
|
|
{
|
|
if (new_val == NULL)
|
|
return false;
|
|
|
|
if (old_val == NULL || strcmp(old_val, new_val) != 0)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* do_connect -- handler for \connect
|
|
*
|
|
* Connects to a database with given parameters. If we are told to re-use
|
|
* parameters, parameters from the previous connection are used where the
|
|
* command's own options do not supply a value. Otherwise, libpq defaults
|
|
* are used.
|
|
*
|
|
* In interactive mode, if connection fails with the given parameters,
|
|
* the old connection will be kept.
|
|
*/
|
|
static bool
|
|
do_connect(enum trivalue reuse_previous_specification,
|
|
char *dbname, char *user, char *host, char *port)
|
|
{
|
|
PGconn *o_conn = pset.db,
|
|
*n_conn = NULL;
|
|
PQconninfoOption *cinfo;
|
|
int nconnopts = 0;
|
|
bool same_host = false;
|
|
char *password = NULL;
|
|
char *client_encoding;
|
|
bool success = true;
|
|
bool keep_password = true;
|
|
bool has_connection_string;
|
|
bool reuse_previous;
|
|
|
|
has_connection_string = dbname ?
|
|
recognized_connection_string(dbname) : false;
|
|
|
|
/* Complain if we have additional arguments after a connection string. */
|
|
if (has_connection_string && (user || host || port))
|
|
{
|
|
pg_log_error("Do not give user, host, or port separately when using a connection string");
|
|
return false;
|
|
}
|
|
|
|
switch (reuse_previous_specification)
|
|
{
|
|
case TRI_YES:
|
|
reuse_previous = true;
|
|
break;
|
|
case TRI_NO:
|
|
reuse_previous = false;
|
|
break;
|
|
default:
|
|
reuse_previous = !has_connection_string;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* If we intend to re-use connection parameters, collect them out of the
|
|
* old connection, then replace individual values as necessary. (We may
|
|
* need to resort to looking at pset.dead_conn, if the connection died
|
|
* previously.) Otherwise, obtain a PQconninfoOption array containing
|
|
* libpq's defaults, and modify that. Note this function assumes that
|
|
* PQconninfo, PQconndefaults, and PQconninfoParse will all produce arrays
|
|
* containing the same options in the same order.
|
|
*/
|
|
if (reuse_previous)
|
|
{
|
|
if (o_conn)
|
|
cinfo = PQconninfo(o_conn);
|
|
else if (pset.dead_conn)
|
|
cinfo = PQconninfo(pset.dead_conn);
|
|
else
|
|
{
|
|
/* This is reachable after a non-interactive \connect failure */
|
|
pg_log_error("No database connection exists to re-use parameters from");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
cinfo = PQconndefaults();
|
|
|
|
if (cinfo)
|
|
{
|
|
if (has_connection_string)
|
|
{
|
|
/* Parse the connstring and insert values into cinfo */
|
|
PQconninfoOption *replcinfo;
|
|
char *errmsg;
|
|
|
|
replcinfo = PQconninfoParse(dbname, &errmsg);
|
|
if (replcinfo)
|
|
{
|
|
PQconninfoOption *ci;
|
|
PQconninfoOption *replci;
|
|
bool have_password = false;
|
|
|
|
for (ci = cinfo, replci = replcinfo;
|
|
ci->keyword && replci->keyword;
|
|
ci++, replci++)
|
|
{
|
|
Assert(strcmp(ci->keyword, replci->keyword) == 0);
|
|
/* Insert value from connstring if one was provided */
|
|
if (replci->val)
|
|
{
|
|
/*
|
|
* We know that both val strings were allocated by
|
|
* libpq, so the least messy way to avoid memory leaks
|
|
* is to swap them.
|
|
*/
|
|
char *swap = replci->val;
|
|
|
|
replci->val = ci->val;
|
|
ci->val = swap;
|
|
|
|
/*
|
|
* Check whether connstring provides options affecting
|
|
* password re-use. While any change in user, host,
|
|
* hostaddr, or port causes us to ignore the old
|
|
* connection's password, we don't force that for
|
|
* dbname, since passwords aren't database-specific.
|
|
*/
|
|
if (replci->val == NULL ||
|
|
strcmp(ci->val, replci->val) != 0)
|
|
{
|
|
if (strcmp(replci->keyword, "user") == 0 ||
|
|
strcmp(replci->keyword, "host") == 0 ||
|
|
strcmp(replci->keyword, "hostaddr") == 0 ||
|
|
strcmp(replci->keyword, "port") == 0)
|
|
keep_password = false;
|
|
}
|
|
/* Also note whether connstring contains a password. */
|
|
if (strcmp(replci->keyword, "password") == 0)
|
|
have_password = true;
|
|
}
|
|
else if (!reuse_previous)
|
|
{
|
|
/*
|
|
* When we have a connstring and are not re-using
|
|
* parameters, swap *all* entries, even those not set
|
|
* by the connstring. This avoids absorbing
|
|
* environment-dependent defaults from the result of
|
|
* PQconndefaults(). We don't want to do that because
|
|
* they'd override service-file entries if the
|
|
* connstring specifies a service parameter, whereas
|
|
* the priority should be the other way around. libpq
|
|
* can certainly recompute any defaults we don't pass
|
|
* here. (In this situation, it's a bit wasteful to
|
|
* have called PQconndefaults() at all, but not doing
|
|
* so would require yet another major code path here.)
|
|
*/
|
|
replci->val = ci->val;
|
|
ci->val = NULL;
|
|
}
|
|
}
|
|
Assert(ci->keyword == NULL && replci->keyword == NULL);
|
|
|
|
/* While here, determine how many option slots there are */
|
|
nconnopts = ci - cinfo;
|
|
|
|
PQconninfoFree(replcinfo);
|
|
|
|
/*
|
|
* If the connstring contains a password, tell the loop below
|
|
* that we may use it, regardless of other settings (i.e.,
|
|
* cinfo's password is no longer an "old" password).
|
|
*/
|
|
if (have_password)
|
|
keep_password = true;
|
|
|
|
/* Don't let code below try to inject dbname into params. */
|
|
dbname = NULL;
|
|
}
|
|
else
|
|
{
|
|
/* PQconninfoParse failed */
|
|
if (errmsg)
|
|
{
|
|
pg_log_error("%s", errmsg);
|
|
PQfreemem(errmsg);
|
|
}
|
|
else
|
|
pg_log_error("out of memory");
|
|
success = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* If dbname isn't a connection string, then we'll inject it and
|
|
* the other parameters into the keyword array below. (We can't
|
|
* easily insert them into the cinfo array because of memory
|
|
* management issues: PQconninfoFree would misbehave on Windows.)
|
|
* However, to avoid dependencies on the order in which parameters
|
|
* appear in the array, make a preliminary scan to set
|
|
* keep_password and same_host correctly.
|
|
*
|
|
* While any change in user, host, or port causes us to ignore the
|
|
* old connection's password, we don't force that for dbname,
|
|
* since passwords aren't database-specific.
|
|
*/
|
|
PQconninfoOption *ci;
|
|
|
|
for (ci = cinfo; ci->keyword; ci++)
|
|
{
|
|
if (user && strcmp(ci->keyword, "user") == 0)
|
|
{
|
|
if (!(ci->val && strcmp(user, ci->val) == 0))
|
|
keep_password = false;
|
|
}
|
|
else if (host && strcmp(ci->keyword, "host") == 0)
|
|
{
|
|
if (ci->val && strcmp(host, ci->val) == 0)
|
|
same_host = true;
|
|
else
|
|
keep_password = false;
|
|
}
|
|
else if (port && strcmp(ci->keyword, "port") == 0)
|
|
{
|
|
if (!(ci->val && strcmp(port, ci->val) == 0))
|
|
keep_password = false;
|
|
}
|
|
}
|
|
|
|
/* While here, determine how many option slots there are */
|
|
nconnopts = ci - cinfo;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* We failed to create the cinfo structure */
|
|
pg_log_error("out of memory");
|
|
success = false;
|
|
}
|
|
|
|
/*
|
|
* If the user asked to be prompted for a password, ask for one now. If
|
|
* not, use the password from the old connection, provided the username
|
|
* etc have not changed. Otherwise, try to connect without a password
|
|
* first, and then ask for a password if needed.
|
|
*
|
|
* XXX: this behavior leads to spurious connection attempts recorded in
|
|
* the postmaster's log. But libpq offers no API that would let us obtain
|
|
* a password and then continue with the first connection attempt.
|
|
*/
|
|
if (pset.getPassword == TRI_YES && success)
|
|
{
|
|
bool canceled = false;
|
|
|
|
/*
|
|
* If a connstring or URI is provided, we don't know which username
|
|
* will be used, since we haven't dug that out of the connstring.
|
|
* Don't risk issuing a misleading prompt. As in startup.c, it does
|
|
* not seem worth working harder, since this getPassword setting is
|
|
* normally only used in noninteractive cases.
|
|
*/
|
|
password = prompt_for_password(has_connection_string ? NULL : user,
|
|
&canceled);
|
|
success = !canceled;
|
|
}
|
|
|
|
/*
|
|
* Consider whether to force client_encoding to "auto" (overriding
|
|
* anything in the connection string). We do so if we have a terminal
|
|
* connection and there is no PGCLIENTENCODING environment setting.
|
|
*/
|
|
if (pset.notty || getenv("PGCLIENTENCODING"))
|
|
client_encoding = NULL;
|
|
else
|
|
client_encoding = "auto";
|
|
|
|
/* Loop till we have a connection or fail, which we might've already */
|
|
while (success)
|
|
{
|
|
const char **keywords = pg_malloc((nconnopts + 1) * sizeof(*keywords));
|
|
const char **values = pg_malloc((nconnopts + 1) * sizeof(*values));
|
|
int paramnum = 0;
|
|
PQconninfoOption *ci;
|
|
|
|
/*
|
|
* Copy non-default settings into the PQconnectdbParams parameter
|
|
* arrays; but inject any values specified old-style, as well as any
|
|
* interactively-obtained password, and a couple of fields we want to
|
|
* set forcibly.
|
|
*
|
|
* If you change this code, see also the initial-connection code in
|
|
* main().
|
|
*/
|
|
for (ci = cinfo; ci->keyword; ci++)
|
|
{
|
|
keywords[paramnum] = ci->keyword;
|
|
|
|
if (dbname && strcmp(ci->keyword, "dbname") == 0)
|
|
values[paramnum++] = dbname;
|
|
else if (user && strcmp(ci->keyword, "user") == 0)
|
|
values[paramnum++] = user;
|
|
else if (host && strcmp(ci->keyword, "host") == 0)
|
|
values[paramnum++] = host;
|
|
else if (host && !same_host && strcmp(ci->keyword, "hostaddr") == 0)
|
|
{
|
|
/* If we're changing the host value, drop any old hostaddr */
|
|
values[paramnum++] = NULL;
|
|
}
|
|
else if (port && strcmp(ci->keyword, "port") == 0)
|
|
values[paramnum++] = port;
|
|
/* If !keep_password, we unconditionally drop old password */
|
|
else if ((password || !keep_password) &&
|
|
strcmp(ci->keyword, "password") == 0)
|
|
values[paramnum++] = password;
|
|
else if (strcmp(ci->keyword, "fallback_application_name") == 0)
|
|
values[paramnum++] = pset.progname;
|
|
else if (client_encoding &&
|
|
strcmp(ci->keyword, "client_encoding") == 0)
|
|
values[paramnum++] = client_encoding;
|
|
else if (ci->val)
|
|
values[paramnum++] = ci->val;
|
|
/* else, don't bother making libpq parse this keyword */
|
|
}
|
|
/* add array terminator */
|
|
keywords[paramnum] = NULL;
|
|
values[paramnum] = NULL;
|
|
|
|
/* Note we do not want libpq to re-expand the dbname parameter */
|
|
n_conn = PQconnectdbParams(keywords, values, false);
|
|
|
|
pg_free(keywords);
|
|
pg_free(values);
|
|
|
|
if (PQstatus(n_conn) == CONNECTION_OK)
|
|
break;
|
|
|
|
/*
|
|
* Connection attempt failed; either retry the connection attempt with
|
|
* a new password, or give up.
|
|
*/
|
|
if (!password && PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO)
|
|
{
|
|
bool canceled = false;
|
|
|
|
/*
|
|
* Prompt for password using the username we actually connected
|
|
* with --- it might've come out of "dbname" rather than "user".
|
|
*/
|
|
password = prompt_for_password(PQuser(n_conn), &canceled);
|
|
PQfinish(n_conn);
|
|
n_conn = NULL;
|
|
success = !canceled;
|
|
continue;
|
|
}
|
|
|
|
/*
|
|
* We'll report the error below ... unless n_conn is NULL, indicating
|
|
* that libpq didn't have enough memory to make a PGconn.
|
|
*/
|
|
if (n_conn == NULL)
|
|
pg_log_error("out of memory");
|
|
|
|
success = false;
|
|
} /* end retry loop */
|
|
|
|
/* Release locally allocated data, whether we succeeded or not */
|
|
pg_free(password);
|
|
PQconninfoFree(cinfo);
|
|
|
|
if (!success)
|
|
{
|
|
/*
|
|
* Failed to connect to the database. In interactive mode, keep the
|
|
* previous connection to the DB; in scripting mode, close our
|
|
* previous connection as well.
|
|
*/
|
|
if (pset.cur_cmd_interactive)
|
|
{
|
|
if (n_conn)
|
|
{
|
|
pg_log_info("%s", PQerrorMessage(n_conn));
|
|
PQfinish(n_conn);
|
|
}
|
|
|
|
/* pset.db is left unmodified */
|
|
if (o_conn)
|
|
pg_log_info("Previous connection kept");
|
|
}
|
|
else
|
|
{
|
|
if (n_conn)
|
|
{
|
|
pg_log_error("\\connect: %s", PQerrorMessage(n_conn));
|
|
PQfinish(n_conn);
|
|
}
|
|
|
|
if (o_conn)
|
|
{
|
|
/*
|
|
* Transition to having no connection.
|
|
*
|
|
* Unlike CheckConnection(), we close the old connection
|
|
* immediately to prevent its parameters from being re-used.
|
|
* This is so that a script cannot accidentally reuse
|
|
* parameters it did not expect to. Otherwise, the state
|
|
* cleanup should be the same as in CheckConnection().
|
|
*/
|
|
PQfinish(o_conn);
|
|
pset.db = NULL;
|
|
ResetCancelConn();
|
|
UnsyncVariables();
|
|
}
|
|
|
|
/* On the same reasoning, release any dead_conn to prevent reuse */
|
|
if (pset.dead_conn)
|
|
{
|
|
PQfinish(pset.dead_conn);
|
|
pset.dead_conn = NULL;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Replace the old connection with the new one, and update
|
|
* connection-dependent variables. Keep the resynchronization logic in
|
|
* sync with CheckConnection().
|
|
*/
|
|
PQsetNoticeProcessor(n_conn, NoticeProcessor, NULL);
|
|
pset.db = n_conn;
|
|
SyncVariables();
|
|
connection_warnings(false); /* Must be after SyncVariables */
|
|
|
|
/* Tell the user about the new connection */
|
|
if (!pset.quiet)
|
|
{
|
|
if (!o_conn ||
|
|
param_is_newly_set(PQhost(o_conn), PQhost(pset.db)) ||
|
|
param_is_newly_set(PQport(o_conn), PQport(pset.db)))
|
|
{
|
|
char *connhost = PQhost(pset.db);
|
|
char *hostaddr = PQhostaddr(pset.db);
|
|
|
|
if (is_unixsock_path(connhost))
|
|
{
|
|
/* hostaddr overrides connhost */
|
|
if (hostaddr && *hostaddr)
|
|
printf(_("You are now connected to database \"%s\" as user \"%s\" on address \"%s\" at port \"%s\".\n"),
|
|
PQdb(pset.db), PQuser(pset.db), hostaddr, PQport(pset.db));
|
|
else
|
|
printf(_("You are now connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
|
|
PQdb(pset.db), PQuser(pset.db), connhost, PQport(pset.db));
|
|
}
|
|
else
|
|
{
|
|
if (hostaddr && *hostaddr && strcmp(connhost, hostaddr) != 0)
|
|
printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" (address \"%s\") at port \"%s\".\n"),
|
|
PQdb(pset.db), PQuser(pset.db), connhost, hostaddr, PQport(pset.db));
|
|
else
|
|
printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
|
|
PQdb(pset.db), PQuser(pset.db), connhost, PQport(pset.db));
|
|
}
|
|
}
|
|
else
|
|
printf(_("You are now connected to database \"%s\" as user \"%s\".\n"),
|
|
PQdb(pset.db), PQuser(pset.db));
|
|
}
|
|
|
|
/* Drop no-longer-needed connection(s) */
|
|
if (o_conn)
|
|
PQfinish(o_conn);
|
|
if (pset.dead_conn)
|
|
{
|
|
PQfinish(pset.dead_conn);
|
|
pset.dead_conn = NULL;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void
|
|
connection_warnings(bool in_startup)
|
|
{
|
|
if (!pset.quiet && !pset.notty)
|
|
{
|
|
int client_ver = PG_VERSION_NUM;
|
|
char cverbuf[32];
|
|
char sverbuf[32];
|
|
|
|
if (pset.sversion != client_ver)
|
|
{
|
|
const char *server_version;
|
|
|
|
/* Try to get full text form, might include "devel" etc */
|
|
server_version = PQparameterStatus(pset.db, "server_version");
|
|
/* Otherwise fall back on pset.sversion */
|
|
if (!server_version)
|
|
{
|
|
formatPGVersionNumber(pset.sversion, true,
|
|
sverbuf, sizeof(sverbuf));
|
|
server_version = sverbuf;
|
|
}
|
|
|
|
printf(_("%s (%s, server %s)\n"),
|
|
pset.progname, PG_VERSION, server_version);
|
|
}
|
|
/* For version match, only print psql banner on startup. */
|
|
else if (in_startup)
|
|
printf("%s (%s)\n", pset.progname, PG_VERSION);
|
|
|
|
/*
|
|
* Warn if server's major version is newer than ours, or if server
|
|
* predates our support cutoff (currently 9.2).
|
|
*/
|
|
if (pset.sversion / 100 > client_ver / 100 ||
|
|
pset.sversion < 90200)
|
|
printf(_("WARNING: %s major version %s, server major version %s.\n"
|
|
" Some psql features might not work.\n"),
|
|
pset.progname,
|
|
formatPGVersionNumber(client_ver, false,
|
|
cverbuf, sizeof(cverbuf)),
|
|
formatPGVersionNumber(pset.sversion, false,
|
|
sverbuf, sizeof(sverbuf)));
|
|
|
|
#ifdef WIN32
|
|
if (in_startup)
|
|
checkWin32Codepage();
|
|
#endif
|
|
printSSLInfo();
|
|
printGSSInfo();
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* printSSLInfo
|
|
*
|
|
* Prints information about the current SSL connection, if SSL is in use
|
|
*/
|
|
static void
|
|
printSSLInfo(void)
|
|
{
|
|
const char *protocol;
|
|
const char *cipher;
|
|
const char *compression;
|
|
|
|
if (!PQsslInUse(pset.db))
|
|
return; /* no SSL */
|
|
|
|
protocol = PQsslAttribute(pset.db, "protocol");
|
|
cipher = PQsslAttribute(pset.db, "cipher");
|
|
compression = PQsslAttribute(pset.db, "compression");
|
|
|
|
printf(_("SSL connection (protocol: %s, cipher: %s, compression: %s)\n"),
|
|
protocol ? protocol : _("unknown"),
|
|
cipher ? cipher : _("unknown"),
|
|
(compression && strcmp(compression, "off") != 0) ? _("on") : _("off"));
|
|
}
|
|
|
|
/*
|
|
* printGSSInfo
|
|
*
|
|
* Prints information about the current GSSAPI connection, if GSSAPI encryption is in use
|
|
*/
|
|
static void
|
|
printGSSInfo(void)
|
|
{
|
|
if (!PQgssEncInUse(pset.db))
|
|
return; /* no GSSAPI encryption in use */
|
|
|
|
printf(_("GSSAPI-encrypted connection\n"));
|
|
}
|
|
|
|
|
|
/*
|
|
* checkWin32Codepage
|
|
*
|
|
* Prints a warning when win32 console codepage differs from Windows codepage
|
|
*/
|
|
#ifdef WIN32
|
|
static void
|
|
checkWin32Codepage(void)
|
|
{
|
|
unsigned int wincp,
|
|
concp;
|
|
|
|
wincp = GetACP();
|
|
concp = GetConsoleCP();
|
|
if (wincp != concp)
|
|
{
|
|
printf(_("WARNING: Console code page (%u) differs from Windows code page (%u)\n"
|
|
" 8-bit characters might not work correctly. See psql reference\n"
|
|
" page \"Notes for Windows users\" for details.\n"),
|
|
concp, wincp);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/*
|
|
* SyncVariables
|
|
*
|
|
* Make psql's internal variables agree with connection state upon
|
|
* establishing a new connection.
|
|
*/
|
|
void
|
|
SyncVariables(void)
|
|
{
|
|
char vbuf[32];
|
|
const char *server_version;
|
|
|
|
/* get stuff from connection */
|
|
pset.encoding = PQclientEncoding(pset.db);
|
|
pset.popt.topt.encoding = pset.encoding;
|
|
pset.sversion = PQserverVersion(pset.db);
|
|
|
|
SetVariable(pset.vars, "DBNAME", PQdb(pset.db));
|
|
SetVariable(pset.vars, "USER", PQuser(pset.db));
|
|
SetVariable(pset.vars, "HOST", PQhost(pset.db));
|
|
SetVariable(pset.vars, "PORT", PQport(pset.db));
|
|
SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding));
|
|
|
|
/* this bit should match connection_warnings(): */
|
|
/* Try to get full text form of version, might include "devel" etc */
|
|
server_version = PQparameterStatus(pset.db, "server_version");
|
|
/* Otherwise fall back on pset.sversion */
|
|
if (!server_version)
|
|
{
|
|
formatPGVersionNumber(pset.sversion, true, vbuf, sizeof(vbuf));
|
|
server_version = vbuf;
|
|
}
|
|
SetVariable(pset.vars, "SERVER_VERSION_NAME", server_version);
|
|
|
|
snprintf(vbuf, sizeof(vbuf), "%d", pset.sversion);
|
|
SetVariable(pset.vars, "SERVER_VERSION_NUM", vbuf);
|
|
|
|
/* send stuff to it, too */
|
|
PQsetErrorVerbosity(pset.db, pset.verbosity);
|
|
PQsetErrorContextVisibility(pset.db, pset.show_context);
|
|
}
|
|
|
|
/*
|
|
* UnsyncVariables
|
|
*
|
|
* Clear variables that should be not be set when there is no connection.
|
|
*/
|
|
void
|
|
UnsyncVariables(void)
|
|
{
|
|
SetVariable(pset.vars, "DBNAME", NULL);
|
|
SetVariable(pset.vars, "USER", NULL);
|
|
SetVariable(pset.vars, "HOST", NULL);
|
|
SetVariable(pset.vars, "PORT", NULL);
|
|
SetVariable(pset.vars, "ENCODING", NULL);
|
|
SetVariable(pset.vars, "SERVER_VERSION_NAME", NULL);
|
|
SetVariable(pset.vars, "SERVER_VERSION_NUM", NULL);
|
|
}
|
|
|
|
|
|
/*
|
|
* helper for do_edit(): actually invoke the editor
|
|
*
|
|
* Returns true on success, false if we failed to invoke the editor or
|
|
* it returned nonzero status. (An error message is printed for failed-
|
|
* to-invoke cases, but not if the editor returns nonzero status.)
|
|
*/
|
|
static bool
|
|
editFile(const char *fname, int lineno)
|
|
{
|
|
const char *editorName;
|
|
const char *editor_lineno_arg = NULL;
|
|
char *sys;
|
|
int result;
|
|
|
|
Assert(fname != NULL);
|
|
|
|
/* Find an editor to use */
|
|
editorName = getenv("PSQL_EDITOR");
|
|
if (!editorName)
|
|
editorName = getenv("EDITOR");
|
|
if (!editorName)
|
|
editorName = getenv("VISUAL");
|
|
if (!editorName)
|
|
editorName = DEFAULT_EDITOR;
|
|
|
|
/* Get line number argument, if we need it. */
|
|
if (lineno > 0)
|
|
{
|
|
editor_lineno_arg = getenv("PSQL_EDITOR_LINENUMBER_ARG");
|
|
#ifdef DEFAULT_EDITOR_LINENUMBER_ARG
|
|
if (!editor_lineno_arg)
|
|
editor_lineno_arg = DEFAULT_EDITOR_LINENUMBER_ARG;
|
|
#endif
|
|
if (!editor_lineno_arg)
|
|
{
|
|
pg_log_error("environment variable PSQL_EDITOR_LINENUMBER_ARG must be set to specify a line number");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* On Unix the EDITOR value should *not* be quoted, since it might include
|
|
* switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
|
|
* if necessary. But this policy is not very workable on Windows, due to
|
|
* severe brain damage in their command shell plus the fact that standard
|
|
* program paths include spaces.
|
|
*/
|
|
#ifndef WIN32
|
|
if (lineno > 0)
|
|
sys = psprintf("exec %s %s%d '%s'",
|
|
editorName, editor_lineno_arg, lineno, fname);
|
|
else
|
|
sys = psprintf("exec %s '%s'",
|
|
editorName, fname);
|
|
#else
|
|
if (lineno > 0)
|
|
sys = psprintf("\"%s\" %s%d \"%s\"",
|
|
editorName, editor_lineno_arg, lineno, fname);
|
|
else
|
|
sys = psprintf("\"%s\" \"%s\"",
|
|
editorName, fname);
|
|
#endif
|
|
fflush(NULL);
|
|
result = system(sys);
|
|
if (result == -1)
|
|
pg_log_error("could not start editor \"%s\"", editorName);
|
|
else if (result == 127)
|
|
pg_log_error("could not start /bin/sh");
|
|
free(sys);
|
|
|
|
return result == 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* do_edit -- handler for \e
|
|
*
|
|
* If you do not specify a filename, the current query buffer will be copied
|
|
* into a temporary file.
|
|
*
|
|
* After this function is done, the resulting file will be copied back into the
|
|
* query buffer. As an exception to this, the query buffer will be emptied
|
|
* if the file was not modified (or the editor failed) and the caller passes
|
|
* "discard_on_quit" = true.
|
|
*
|
|
* If "edited" isn't NULL, *edited will be set to true if the query buffer
|
|
* is successfully replaced.
|
|
*/
|
|
static bool
|
|
do_edit(const char *filename_arg, PQExpBuffer query_buf,
|
|
int lineno, bool discard_on_quit, bool *edited)
|
|
{
|
|
char fnametmp[MAXPGPATH];
|
|
FILE *stream = NULL;
|
|
const char *fname;
|
|
bool error = false;
|
|
int fd;
|
|
struct stat before,
|
|
after;
|
|
|
|
if (filename_arg)
|
|
fname = filename_arg;
|
|
else
|
|
{
|
|
/* make a temp file to edit */
|
|
#ifndef WIN32
|
|
const char *tmpdir = getenv("TMPDIR");
|
|
|
|
if (!tmpdir)
|
|
tmpdir = "/tmp";
|
|
#else
|
|
char tmpdir[MAXPGPATH];
|
|
int ret;
|
|
|
|
ret = GetTempPath(MAXPGPATH, tmpdir);
|
|
if (ret == 0 || ret > MAXPGPATH)
|
|
{
|
|
pg_log_error("could not locate temporary directory: %s",
|
|
!ret ? strerror(errno) : "");
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* No canonicalize_path() here. EDIT.EXE run from CMD.EXE prepends the
|
|
* current directory to the supplied path unless we use only
|
|
* backslashes, so we do that.
|
|
*/
|
|
#ifndef WIN32
|
|
snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d.sql", tmpdir,
|
|
"/", (int) getpid());
|
|
#else
|
|
snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d.sql", tmpdir,
|
|
"" /* trailing separator already present */ , (int) getpid());
|
|
#endif
|
|
|
|
fname = (const char *) fnametmp;
|
|
|
|
fd = open(fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
|
if (fd != -1)
|
|
stream = fdopen(fd, "w");
|
|
|
|
if (fd == -1 || !stream)
|
|
{
|
|
pg_log_error("could not open temporary file \"%s\": %m", fname);
|
|
error = true;
|
|
}
|
|
else
|
|
{
|
|
unsigned int ql = query_buf->len;
|
|
|
|
/* force newline-termination of what we send to editor */
|
|
if (ql > 0 && query_buf->data[ql - 1] != '\n')
|
|
{
|
|
appendPQExpBufferChar(query_buf, '\n');
|
|
ql++;
|
|
}
|
|
|
|
if (fwrite(query_buf->data, 1, ql, stream) != ql)
|
|
{
|
|
pg_log_error("%s: %m", fname);
|
|
|
|
if (fclose(stream) != 0)
|
|
pg_log_error("%s: %m", fname);
|
|
|
|
if (remove(fname) != 0)
|
|
pg_log_error("%s: %m", fname);
|
|
|
|
error = true;
|
|
}
|
|
else if (fclose(stream) != 0)
|
|
{
|
|
pg_log_error("%s: %m", fname);
|
|
if (remove(fname) != 0)
|
|
pg_log_error("%s: %m", fname);
|
|
error = true;
|
|
}
|
|
else
|
|
{
|
|
struct utimbuf ut;
|
|
|
|
/*
|
|
* Try to set the file modification time of the temporary file
|
|
* a few seconds in the past. Otherwise, the low granularity
|
|
* (one second, or even worse on some filesystems) that we can
|
|
* portably measure with stat(2) could lead us to not
|
|
* recognize a modification, if the user typed very quickly.
|
|
*
|
|
* This is a rather unlikely race condition, so don't error
|
|
* out if the utime(2) call fails --- that would make the cure
|
|
* worse than the disease.
|
|
*/
|
|
ut.modtime = ut.actime = time(NULL) - 2;
|
|
(void) utime(fname, &ut);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!error && stat(fname, &before) != 0)
|
|
{
|
|
pg_log_error("%s: %m", fname);
|
|
error = true;
|
|
}
|
|
|
|
/* call editor */
|
|
if (!error)
|
|
error = !editFile(fname, lineno);
|
|
|
|
if (!error && stat(fname, &after) != 0)
|
|
{
|
|
pg_log_error("%s: %m", fname);
|
|
error = true;
|
|
}
|
|
|
|
/* file was edited if the size or modification time has changed */
|
|
if (!error &&
|
|
(before.st_size != after.st_size ||
|
|
before.st_mtime != after.st_mtime))
|
|
{
|
|
stream = fopen(fname, PG_BINARY_R);
|
|
if (!stream)
|
|
{
|
|
pg_log_error("%s: %m", fname);
|
|
error = true;
|
|
}
|
|
else
|
|
{
|
|
/* read file back into query_buf */
|
|
char line[1024];
|
|
|
|
resetPQExpBuffer(query_buf);
|
|
while (fgets(line, sizeof(line), stream) != NULL)
|
|
appendPQExpBufferStr(query_buf, line);
|
|
|
|
if (ferror(stream))
|
|
{
|
|
pg_log_error("%s: %m", fname);
|
|
error = true;
|
|
resetPQExpBuffer(query_buf);
|
|
}
|
|
else if (edited)
|
|
{
|
|
*edited = true;
|
|
}
|
|
|
|
fclose(stream);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* If the file was not modified, and the caller requested it, discard
|
|
* the query buffer.
|
|
*/
|
|
if (discard_on_quit)
|
|
resetPQExpBuffer(query_buf);
|
|
}
|
|
|
|
/* remove temp file */
|
|
if (!filename_arg)
|
|
{
|
|
if (remove(fname) == -1)
|
|
{
|
|
pg_log_error("%s: %m", fname);
|
|
error = true;
|
|
}
|
|
}
|
|
|
|
return !error;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* process_file
|
|
*
|
|
* Reads commands from filename and passes them to the main processing loop.
|
|
* Handler for \i and \ir, but can be used for other things as well. Returns
|
|
* MainLoop() error code.
|
|
*
|
|
* If use_relative_path is true and filename is not an absolute path, then open
|
|
* the file from where the currently processed file (if any) is located.
|
|
*/
|
|
int
|
|
process_file(char *filename, bool use_relative_path)
|
|
{
|
|
FILE *fd;
|
|
int result;
|
|
char *oldfilename;
|
|
char relpath[MAXPGPATH];
|
|
|
|
if (!filename)
|
|
{
|
|
fd = stdin;
|
|
filename = NULL;
|
|
}
|
|
else if (strcmp(filename, "-") != 0)
|
|
{
|
|
canonicalize_path(filename);
|
|
|
|
/*
|
|
* If we were asked to resolve the pathname relative to the location
|
|
* of the currently executing script, and there is one, and this is a
|
|
* relative pathname, then prepend all but the last pathname component
|
|
* of the current script to this pathname.
|
|
*/
|
|
if (use_relative_path && pset.inputfile &&
|
|
!is_absolute_path(filename) && !has_drive_prefix(filename))
|
|
{
|
|
strlcpy(relpath, pset.inputfile, sizeof(relpath));
|
|
get_parent_directory(relpath);
|
|
join_path_components(relpath, relpath, filename);
|
|
canonicalize_path(relpath);
|
|
|
|
filename = relpath;
|
|
}
|
|
|
|
fd = fopen(filename, PG_BINARY_R);
|
|
|
|
if (!fd)
|
|
{
|
|
pg_log_error("%s: %m", filename);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
fd = stdin;
|
|
filename = "<stdin>"; /* for future error messages */
|
|
}
|
|
|
|
oldfilename = pset.inputfile;
|
|
pset.inputfile = filename;
|
|
|
|
pg_logging_config(pset.inputfile ? 0 : PG_LOG_FLAG_TERSE);
|
|
|
|
result = MainLoop(fd);
|
|
|
|
if (fd != stdin)
|
|
fclose(fd);
|
|
|
|
pset.inputfile = oldfilename;
|
|
|
|
pg_logging_config(pset.inputfile ? 0 : PG_LOG_FLAG_TERSE);
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
static const char *
|
|
_align2string(enum printFormat in)
|
|
{
|
|
switch (in)
|
|
{
|
|
case PRINT_NOTHING:
|
|
return "nothing";
|
|
break;
|
|
case PRINT_ALIGNED:
|
|
return "aligned";
|
|
break;
|
|
case PRINT_ASCIIDOC:
|
|
return "asciidoc";
|
|
break;
|
|
case PRINT_CSV:
|
|
return "csv";
|
|
break;
|
|
case PRINT_HTML:
|
|
return "html";
|
|
break;
|
|
case PRINT_LATEX:
|
|
return "latex";
|
|
break;
|
|
case PRINT_LATEX_LONGTABLE:
|
|
return "latex-longtable";
|
|
break;
|
|
case PRINT_TROFF_MS:
|
|
return "troff-ms";
|
|
break;
|
|
case PRINT_UNALIGNED:
|
|
return "unaligned";
|
|
break;
|
|
case PRINT_WRAPPED:
|
|
return "wrapped";
|
|
break;
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
/*
|
|
* Parse entered Unicode linestyle. If ok, update *linestyle and return
|
|
* true, else return false.
|
|
*/
|
|
static bool
|
|
set_unicode_line_style(const char *value, size_t vallen,
|
|
unicode_linestyle *linestyle)
|
|
{
|
|
if (pg_strncasecmp("single", value, vallen) == 0)
|
|
*linestyle = UNICODE_LINESTYLE_SINGLE;
|
|
else if (pg_strncasecmp("double", value, vallen) == 0)
|
|
*linestyle = UNICODE_LINESTYLE_DOUBLE;
|
|
else
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
static const char *
|
|
_unicode_linestyle2string(int linestyle)
|
|
{
|
|
switch (linestyle)
|
|
{
|
|
case UNICODE_LINESTYLE_SINGLE:
|
|
return "single";
|
|
break;
|
|
case UNICODE_LINESTYLE_DOUBLE:
|
|
return "double";
|
|
break;
|
|
}
|
|
return "unknown";
|
|
}
|
|
|
|
/*
|
|
* 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)
|
|
{
|
|
size_t vallen = 0;
|
|
|
|
Assert(param != NULL);
|
|
|
|
if (value)
|
|
vallen = strlen(value);
|
|
|
|
/* set format */
|
|
if (strcmp(param, "format") == 0)
|
|
{
|
|
static const struct fmt
|
|
{
|
|
const char *name;
|
|
enum printFormat number;
|
|
} formats[] =
|
|
{
|
|
/* remember to update error message below when adding more */
|
|
{"aligned", PRINT_ALIGNED},
|
|
{"asciidoc", PRINT_ASCIIDOC},
|
|
{"csv", PRINT_CSV},
|
|
{"html", PRINT_HTML},
|
|
{"latex", PRINT_LATEX},
|
|
{"troff-ms", PRINT_TROFF_MS},
|
|
{"unaligned", PRINT_UNALIGNED},
|
|
{"wrapped", PRINT_WRAPPED}
|
|
};
|
|
|
|
if (!value)
|
|
;
|
|
else
|
|
{
|
|
int match_pos = -1;
|
|
|
|
for (int i = 0; i < lengthof(formats); i++)
|
|
{
|
|
if (pg_strncasecmp(formats[i].name, value, vallen) == 0)
|
|
{
|
|
if (match_pos < 0)
|
|
match_pos = i;
|
|
else
|
|
{
|
|
pg_log_error("\\pset: ambiguous abbreviation \"%s\" matches both \"%s\" and \"%s\"",
|
|
value,
|
|
formats[match_pos].name, formats[i].name);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
if (match_pos >= 0)
|
|
popt->topt.format = formats[match_pos].number;
|
|
else if (pg_strncasecmp("latex-longtable", value, vallen) == 0)
|
|
{
|
|
/*
|
|
* We must treat latex-longtable specially because latex is a
|
|
* prefix of it; if both were in the table above, we'd think
|
|
* "latex" is ambiguous.
|
|
*/
|
|
popt->topt.format = PRINT_LATEX_LONGTABLE;
|
|
}
|
|
else
|
|
{
|
|
pg_log_error("\\pset: allowed formats are aligned, asciidoc, csv, html, latex, latex-longtable, troff-ms, unaligned, wrapped");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* set table line style */
|
|
else if (strcmp(param, "linestyle") == 0)
|
|
{
|
|
if (!value)
|
|
;
|
|
else if (pg_strncasecmp("ascii", value, vallen) == 0)
|
|
popt->topt.line_style = &pg_asciiformat;
|
|
else if (pg_strncasecmp("old-ascii", value, vallen) == 0)
|
|
popt->topt.line_style = &pg_asciiformat_old;
|
|
else if (pg_strncasecmp("unicode", value, vallen) == 0)
|
|
popt->topt.line_style = &pg_utf8format;
|
|
else
|
|
{
|
|
pg_log_error("\\pset: allowed line styles are ascii, old-ascii, unicode");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* set unicode border line style */
|
|
else if (strcmp(param, "unicode_border_linestyle") == 0)
|
|
{
|
|
if (!value)
|
|
;
|
|
else if (set_unicode_line_style(value, vallen,
|
|
&popt->topt.unicode_border_linestyle))
|
|
refresh_utf8format(&(popt->topt));
|
|
else
|
|
{
|
|
pg_log_error("\\pset: allowed Unicode border line styles are single, double");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* set unicode column line style */
|
|
else if (strcmp(param, "unicode_column_linestyle") == 0)
|
|
{
|
|
if (!value)
|
|
;
|
|
else if (set_unicode_line_style(value, vallen,
|
|
&popt->topt.unicode_column_linestyle))
|
|
refresh_utf8format(&(popt->topt));
|
|
else
|
|
{
|
|
pg_log_error("\\pset: allowed Unicode column line styles are single, double");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* set unicode header line style */
|
|
else if (strcmp(param, "unicode_header_linestyle") == 0)
|
|
{
|
|
if (!value)
|
|
;
|
|
else if (set_unicode_line_style(value, vallen,
|
|
&popt->topt.unicode_header_linestyle))
|
|
refresh_utf8format(&(popt->topt));
|
|
else
|
|
{
|
|
pg_log_error("\\pset: allowed Unicode header line styles are single, double");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* set border style/width */
|
|
else if (strcmp(param, "border") == 0)
|
|
{
|
|
if (value)
|
|
popt->topt.border = atoi(value);
|
|
}
|
|
|
|
/* set expanded/vertical mode */
|
|
else if (strcmp(param, "x") == 0 ||
|
|
strcmp(param, "expanded") == 0 ||
|
|
strcmp(param, "vertical") == 0)
|
|
{
|
|
if (value && pg_strcasecmp(value, "auto") == 0)
|
|
popt->topt.expanded = 2;
|
|
else if (value)
|
|
{
|
|
bool on_off;
|
|
|
|
if (ParseVariableBool(value, NULL, &on_off))
|
|
popt->topt.expanded = on_off ? 1 : 0;
|
|
else
|
|
{
|
|
PsqlVarEnumError(param, value, "on, off, auto");
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
popt->topt.expanded = !popt->topt.expanded;
|
|
}
|
|
|
|
/* header line width in expanded mode */
|
|
else if (strcmp(param, "xheader_width") == 0)
|
|
{
|
|
if (! value)
|
|
;
|
|
else if (pg_strcasecmp(value, "full") == 0)
|
|
popt->topt.expanded_header_width_type = PRINT_XHEADER_FULL;
|
|
else if (pg_strcasecmp(value, "column") == 0)
|
|
popt->topt.expanded_header_width_type = PRINT_XHEADER_COLUMN;
|
|
else if (pg_strcasecmp(value, "page") == 0)
|
|
popt->topt.expanded_header_width_type = PRINT_XHEADER_PAGE;
|
|
else
|
|
{
|
|
popt->topt.expanded_header_width_type = PRINT_XHEADER_EXACT_WIDTH;
|
|
popt->topt.expanded_header_exact_width = atoi(value);
|
|
if (popt->topt.expanded_header_exact_width == 0)
|
|
{
|
|
pg_log_error("\\pset: allowed xheader_width values are full (default), column, page, or a number specifying the exact width.");
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* field separator for CSV format */
|
|
else if (strcmp(param, "csv_fieldsep") == 0)
|
|
{
|
|
if (value)
|
|
{
|
|
/* CSV separator has to be a one-byte character */
|
|
if (strlen(value) != 1)
|
|
{
|
|
pg_log_error("\\pset: csv_fieldsep must be a single one-byte character");
|
|
return false;
|
|
}
|
|
if (value[0] == '"' || value[0] == '\n' || value[0] == '\r')
|
|
{
|
|
pg_log_error("\\pset: csv_fieldsep cannot be a double quote, a newline, or a carriage return");
|
|
return false;
|
|
}
|
|
popt->topt.csvFieldSep[0] = value[0];
|
|
}
|
|
}
|
|
|
|
/* locale-aware numeric output */
|
|
else if (strcmp(param, "numericlocale") == 0)
|
|
{
|
|
if (value)
|
|
return ParseVariableBool(value, param, &popt->topt.numericLocale);
|
|
else
|
|
popt->topt.numericLocale = !popt->topt.numericLocale;
|
|
}
|
|
|
|
/* null display */
|
|
else if (strcmp(param, "null") == 0)
|
|
{
|
|
if (value)
|
|
{
|
|
free(popt->nullPrint);
|
|
popt->nullPrint = pg_strdup(value);
|
|
}
|
|
}
|
|
|
|
/* field separator for unaligned text */
|
|
else if (strcmp(param, "fieldsep") == 0)
|
|
{
|
|
if (value)
|
|
{
|
|
free(popt->topt.fieldSep.separator);
|
|
popt->topt.fieldSep.separator = pg_strdup(value);
|
|
popt->topt.fieldSep.separator_zero = false;
|
|
}
|
|
}
|
|
|
|
else if (strcmp(param, "fieldsep_zero") == 0)
|
|
{
|
|
free(popt->topt.fieldSep.separator);
|
|
popt->topt.fieldSep.separator = NULL;
|
|
popt->topt.fieldSep.separator_zero = true;
|
|
}
|
|
|
|
/* record separator for unaligned text */
|
|
else if (strcmp(param, "recordsep") == 0)
|
|
{
|
|
if (value)
|
|
{
|
|
free(popt->topt.recordSep.separator);
|
|
popt->topt.recordSep.separator = pg_strdup(value);
|
|
popt->topt.recordSep.separator_zero = false;
|
|
}
|
|
}
|
|
|
|
else if (strcmp(param, "recordsep_zero") == 0)
|
|
{
|
|
free(popt->topt.recordSep.separator);
|
|
popt->topt.recordSep.separator = NULL;
|
|
popt->topt.recordSep.separator_zero = true;
|
|
}
|
|
|
|
/* toggle between full and tuples-only format */
|
|
else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
|
|
{
|
|
if (value)
|
|
return ParseVariableBool(value, param, &popt->topt.tuples_only);
|
|
else
|
|
popt->topt.tuples_only = !popt->topt.tuples_only;
|
|
}
|
|
|
|
/* set title override */
|
|
else if (strcmp(param, "C") == 0 || strcmp(param, "title") == 0)
|
|
{
|
|
free(popt->title);
|
|
if (!value)
|
|
popt->title = NULL;
|
|
else
|
|
popt->title = pg_strdup(value);
|
|
}
|
|
|
|
/* set HTML table tag options */
|
|
else if (strcmp(param, "T") == 0 || strcmp(param, "tableattr") == 0)
|
|
{
|
|
free(popt->topt.tableAttr);
|
|
if (!value)
|
|
popt->topt.tableAttr = NULL;
|
|
else
|
|
popt->topt.tableAttr = pg_strdup(value);
|
|
}
|
|
|
|
/* toggle use of pager */
|
|
else if (strcmp(param, "pager") == 0)
|
|
{
|
|
if (value && pg_strcasecmp(value, "always") == 0)
|
|
popt->topt.pager = 2;
|
|
else if (value)
|
|
{
|
|
bool on_off;
|
|
|
|
if (!ParseVariableBool(value, NULL, &on_off))
|
|
{
|
|
PsqlVarEnumError(param, value, "on, off, always");
|
|
return false;
|
|
}
|
|
popt->topt.pager = on_off ? 1 : 0;
|
|
}
|
|
else if (popt->topt.pager == 1)
|
|
popt->topt.pager = 0;
|
|
else
|
|
popt->topt.pager = 1;
|
|
}
|
|
|
|
/* set minimum lines for pager use */
|
|
else if (strcmp(param, "pager_min_lines") == 0)
|
|
{
|
|
if (value)
|
|
popt->topt.pager_min_lines = atoi(value);
|
|
}
|
|
|
|
/* disable "(x rows)" footer */
|
|
else if (strcmp(param, "footer") == 0)
|
|
{
|
|
if (value)
|
|
return ParseVariableBool(value, param, &popt->topt.default_footer);
|
|
else
|
|
popt->topt.default_footer = !popt->topt.default_footer;
|
|
}
|
|
|
|
/* set border style/width */
|
|
else if (strcmp(param, "columns") == 0)
|
|
{
|
|
if (value)
|
|
popt->topt.columns = atoi(value);
|
|
}
|
|
else
|
|
{
|
|
pg_log_error("\\pset: unknown option: %s", param);
|
|
return false;
|
|
}
|
|
|
|
if (!quiet)
|
|
printPsetInfo(param, &pset.popt);
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* printPsetInfo: print the state of the "param" formatting parameter in popt.
|
|
*/
|
|
static bool
|
|
printPsetInfo(const char *param, printQueryOpt *popt)
|
|
{
|
|
Assert(param != NULL);
|
|
|
|
/* show border style/width */
|
|
if (strcmp(param, "border") == 0)
|
|
printf(_("Border style is %d.\n"), popt->topt.border);
|
|
|
|
/* show the target width for the wrapped format */
|
|
else if (strcmp(param, "columns") == 0)
|
|
{
|
|
if (!popt->topt.columns)
|
|
printf(_("Target width is unset.\n"));
|
|
else
|
|
printf(_("Target width is %d.\n"), popt->topt.columns);
|
|
}
|
|
|
|
/* show expanded/vertical mode */
|
|
else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0)
|
|
{
|
|
if (popt->topt.expanded == 1)
|
|
printf(_("Expanded display is on.\n"));
|
|
else if (popt->topt.expanded == 2)
|
|
printf(_("Expanded display is used automatically.\n"));
|
|
else
|
|
printf(_("Expanded display is off.\n"));
|
|
}
|
|
|
|
/* show xheader width value */
|
|
else if (strcmp(param, "xheader_width") == 0)
|
|
{
|
|
if (popt->topt.expanded_header_width_type == PRINT_XHEADER_FULL)
|
|
printf(_("Expanded header width is 'full'.\n"));
|
|
else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_COLUMN)
|
|
printf(_("Expanded header width is 'column'.\n"));
|
|
else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_PAGE)
|
|
printf(_("Expanded header width is 'page'.\n"));
|
|
else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_EXACT_WIDTH)
|
|
printf(_("Expanded header width is %d.\n"), popt->topt.expanded_header_exact_width);
|
|
}
|
|
|
|
/* show field separator for CSV format */
|
|
else if (strcmp(param, "csv_fieldsep") == 0)
|
|
{
|
|
printf(_("Field separator for CSV is \"%s\".\n"),
|
|
popt->topt.csvFieldSep);
|
|
}
|
|
|
|
/* show field separator for unaligned text */
|
|
else if (strcmp(param, "fieldsep") == 0)
|
|
{
|
|
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)
|
|
{
|
|
printf(_("Field separator is zero byte.\n"));
|
|
}
|
|
|
|
/* show disable "(x rows)" footer */
|
|
else if (strcmp(param, "footer") == 0)
|
|
{
|
|
if (popt->topt.default_footer)
|
|
printf(_("Default footer is on.\n"));
|
|
else
|
|
printf(_("Default footer is off.\n"));
|
|
}
|
|
|
|
/* show format */
|
|
else if (strcmp(param, "format") == 0)
|
|
{
|
|
printf(_("Output format is %s.\n"), _align2string(popt->topt.format));
|
|
}
|
|
|
|
/* show table line style */
|
|
else if (strcmp(param, "linestyle") == 0)
|
|
{
|
|
printf(_("Line style is %s.\n"),
|
|
get_line_style(&popt->topt)->name);
|
|
}
|
|
|
|
/* show null display */
|
|
else if (strcmp(param, "null") == 0)
|
|
{
|
|
printf(_("Null display is \"%s\".\n"),
|
|
popt->nullPrint ? popt->nullPrint : "");
|
|
}
|
|
|
|
/* show locale-aware numeric output */
|
|
else if (strcmp(param, "numericlocale") == 0)
|
|
{
|
|
if (popt->topt.numericLocale)
|
|
printf(_("Locale-adjusted numeric output is on.\n"));
|
|
else
|
|
printf(_("Locale-adjusted numeric output is off.\n"));
|
|
}
|
|
|
|
/* show toggle use of pager */
|
|
else if (strcmp(param, "pager") == 0)
|
|
{
|
|
if (popt->topt.pager == 1)
|
|
printf(_("Pager is used for long output.\n"));
|
|
else if (popt->topt.pager == 2)
|
|
printf(_("Pager is always used.\n"));
|
|
else
|
|
printf(_("Pager usage is off.\n"));
|
|
}
|
|
|
|
/* show minimum lines for pager use */
|
|
else if (strcmp(param, "pager_min_lines") == 0)
|
|
{
|
|
printf(ngettext("Pager won't be used for less than %d line.\n",
|
|
"Pager won't be used for less than %d lines.\n",
|
|
popt->topt.pager_min_lines),
|
|
popt->topt.pager_min_lines);
|
|
}
|
|
|
|
/* show record separator for unaligned text */
|
|
else if (strcmp(param, "recordsep") == 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>.\n"));
|
|
else
|
|
printf(_("Record separator is \"%s\".\n"),
|
|
popt->topt.recordSep.separator);
|
|
}
|
|
|
|
else if (strcmp(param, "recordsep_zero") == 0)
|
|
{
|
|
printf(_("Record separator is zero byte.\n"));
|
|
}
|
|
|
|
/* show HTML table tag options */
|
|
else if (strcmp(param, "T") == 0 || strcmp(param, "tableattr") == 0)
|
|
{
|
|
if (popt->topt.tableAttr)
|
|
printf(_("Table attributes are \"%s\".\n"),
|
|
popt->topt.tableAttr);
|
|
else
|
|
printf(_("Table attributes unset.\n"));
|
|
}
|
|
|
|
/* show title override */
|
|
else if (strcmp(param, "C") == 0 || strcmp(param, "title") == 0)
|
|
{
|
|
if (popt->title)
|
|
printf(_("Title is \"%s\".\n"), popt->title);
|
|
else
|
|
printf(_("Title is unset.\n"));
|
|
}
|
|
|
|
/* show toggle between full and tuples-only format */
|
|
else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
|
|
{
|
|
if (popt->topt.tuples_only)
|
|
printf(_("Tuples only is on.\n"));
|
|
else
|
|
printf(_("Tuples only is off.\n"));
|
|
}
|
|
|
|
/* Unicode style formatting */
|
|
else if (strcmp(param, "unicode_border_linestyle") == 0)
|
|
{
|
|
printf(_("Unicode border line style is \"%s\".\n"),
|
|
_unicode_linestyle2string(popt->topt.unicode_border_linestyle));
|
|
}
|
|
|
|
else if (strcmp(param, "unicode_column_linestyle") == 0)
|
|
{
|
|
printf(_("Unicode column line style is \"%s\".\n"),
|
|
_unicode_linestyle2string(popt->topt.unicode_column_linestyle));
|
|
}
|
|
|
|
else if (strcmp(param, "unicode_header_linestyle") == 0)
|
|
{
|
|
printf(_("Unicode header line style is \"%s\".\n"),
|
|
_unicode_linestyle2string(popt->topt.unicode_header_linestyle));
|
|
}
|
|
|
|
else
|
|
{
|
|
pg_log_error("\\pset: unknown option: %s", param);
|
|
return false;
|
|
}
|
|
|
|
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 */
|
|
free(popt->topt.fieldSep.separator);
|
|
free(popt->topt.recordSep.separator);
|
|
free(popt->topt.tableAttr);
|
|
free(popt->nullPrint);
|
|
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)
|
|
{
|
|
return val ? "on" : "off";
|
|
}
|
|
|
|
|
|
static char *
|
|
pset_quoted_string(const char *str)
|
|
{
|
|
char *ret = pg_malloc(strlen(str) * 2 + 3);
|
|
char *r = ret;
|
|
|
|
*r++ = '\'';
|
|
|
|
for (; *str; str++)
|
|
{
|
|
if (*str == '\n')
|
|
{
|
|
*r++ = '\\';
|
|
*r++ = 'n';
|
|
}
|
|
else if (*str == '\'')
|
|
{
|
|
*r++ = '\\';
|
|
*r++ = '\'';
|
|
}
|
|
else
|
|
*r++ = *str;
|
|
}
|
|
|
|
*r++ = '\'';
|
|
*r = '\0';
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
/*
|
|
* Return a malloc'ed string for the \pset value.
|
|
*
|
|
* Note that for some string parameters, print.c distinguishes between unset
|
|
* and empty string, but for others it doesn't. This function should produce
|
|
* output that produces the correct setting when fed back into \pset.
|
|
*/
|
|
static char *
|
|
pset_value_string(const char *param, printQueryOpt *popt)
|
|
{
|
|
Assert(param != NULL);
|
|
|
|
if (strcmp(param, "border") == 0)
|
|
return psprintf("%d", popt->topt.border);
|
|
else if (strcmp(param, "columns") == 0)
|
|
return psprintf("%d", popt->topt.columns);
|
|
else if (strcmp(param, "csv_fieldsep") == 0)
|
|
return pset_quoted_string(popt->topt.csvFieldSep);
|
|
else if (strcmp(param, "expanded") == 0)
|
|
return pstrdup(popt->topt.expanded == 2
|
|
? "auto"
|
|
: pset_bool_string(popt->topt.expanded));
|
|
else if (strcmp(param, "fieldsep") == 0)
|
|
return pset_quoted_string(popt->topt.fieldSep.separator
|
|
? popt->topt.fieldSep.separator
|
|
: "");
|
|
else if (strcmp(param, "fieldsep_zero") == 0)
|
|
return pstrdup(pset_bool_string(popt->topt.fieldSep.separator_zero));
|
|
else if (strcmp(param, "footer") == 0)
|
|
return pstrdup(pset_bool_string(popt->topt.default_footer));
|
|
else if (strcmp(param, "format") == 0)
|
|
return pstrdup(_align2string(popt->topt.format));
|
|
else if (strcmp(param, "linestyle") == 0)
|
|
return pstrdup(get_line_style(&popt->topt)->name);
|
|
else if (strcmp(param, "null") == 0)
|
|
return pset_quoted_string(popt->nullPrint
|
|
? popt->nullPrint
|
|
: "");
|
|
else if (strcmp(param, "numericlocale") == 0)
|
|
return pstrdup(pset_bool_string(popt->topt.numericLocale));
|
|
else if (strcmp(param, "pager") == 0)
|
|
return psprintf("%d", popt->topt.pager);
|
|
else if (strcmp(param, "pager_min_lines") == 0)
|
|
return psprintf("%d", popt->topt.pager_min_lines);
|
|
else if (strcmp(param, "recordsep") == 0)
|
|
return pset_quoted_string(popt->topt.recordSep.separator
|
|
? popt->topt.recordSep.separator
|
|
: "");
|
|
else if (strcmp(param, "recordsep_zero") == 0)
|
|
return pstrdup(pset_bool_string(popt->topt.recordSep.separator_zero));
|
|
else if (strcmp(param, "tableattr") == 0)
|
|
return popt->topt.tableAttr ? pset_quoted_string(popt->topt.tableAttr) : pstrdup("");
|
|
else if (strcmp(param, "title") == 0)
|
|
return popt->title ? pset_quoted_string(popt->title) : pstrdup("");
|
|
else if (strcmp(param, "tuples_only") == 0)
|
|
return pstrdup(pset_bool_string(popt->topt.tuples_only));
|
|
else if (strcmp(param, "unicode_border_linestyle") == 0)
|
|
return pstrdup(_unicode_linestyle2string(popt->topt.unicode_border_linestyle));
|
|
else if (strcmp(param, "unicode_column_linestyle") == 0)
|
|
return pstrdup(_unicode_linestyle2string(popt->topt.unicode_column_linestyle));
|
|
else if (strcmp(param, "unicode_header_linestyle") == 0)
|
|
return pstrdup(_unicode_linestyle2string(popt->topt.unicode_header_linestyle));
|
|
else if (strcmp(param, "xheader_width") == 0)
|
|
{
|
|
if (popt->topt.expanded_header_width_type == PRINT_XHEADER_FULL)
|
|
return(pstrdup("full"));
|
|
else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_COLUMN)
|
|
return(pstrdup("column"));
|
|
else if (popt->topt.expanded_header_width_type == PRINT_XHEADER_PAGE)
|
|
return(pstrdup("page"));
|
|
else
|
|
{
|
|
/* must be PRINT_XHEADER_EXACT_WIDTH */
|
|
char wbuff[32];
|
|
snprintf(wbuff, sizeof(wbuff), "%d",
|
|
popt->topt.expanded_header_exact_width);
|
|
return pstrdup(wbuff);
|
|
}
|
|
}
|
|
else
|
|
return pstrdup("ERROR");
|
|
}
|
|
|
|
|
|
|
|
#ifndef WIN32
|
|
#define DEFAULT_SHELL "/bin/sh"
|
|
#else
|
|
/*
|
|
* CMD.EXE is in different places in different Win32 releases so we
|
|
* have to rely on the path to find it.
|
|
*/
|
|
#define DEFAULT_SHELL "cmd.exe"
|
|
#endif
|
|
|
|
static bool
|
|
do_shell(const char *command)
|
|
{
|
|
int result;
|
|
|
|
fflush(NULL);
|
|
if (!command)
|
|
{
|
|
char *sys;
|
|
const char *shellName;
|
|
|
|
shellName = getenv("SHELL");
|
|
#ifdef WIN32
|
|
if (shellName == NULL)
|
|
shellName = getenv("COMSPEC");
|
|
#endif
|
|
if (shellName == NULL)
|
|
shellName = DEFAULT_SHELL;
|
|
|
|
/* See EDITOR handling comment for an explanation */
|
|
#ifndef WIN32
|
|
sys = psprintf("exec %s", shellName);
|
|
#else
|
|
sys = psprintf("\"%s\"", shellName);
|
|
#endif
|
|
result = system(sys);
|
|
free(sys);
|
|
}
|
|
else
|
|
result = system(command);
|
|
|
|
if (result == 127 || result == -1)
|
|
{
|
|
pg_log_error("\\!: failed");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* do_watch -- handler for \watch
|
|
*
|
|
* We break this out of exec_command to avoid having to plaster "volatile"
|
|
* onto a bunch of exec_command's variables to silence stupider compilers.
|
|
*/
|
|
static bool
|
|
do_watch(PQExpBuffer query_buf, double sleep)
|
|
{
|
|
long sleep_ms = (long) (sleep * 1000);
|
|
printQueryOpt myopt = pset.popt;
|
|
const char *strftime_fmt;
|
|
const char *user_title;
|
|
char *title;
|
|
const char *pagerprog = NULL;
|
|
FILE *pagerpipe = NULL;
|
|
int title_len;
|
|
int res = 0;
|
|
#ifndef WIN32
|
|
sigset_t sigalrm_sigchld_sigint;
|
|
sigset_t sigalrm_sigchld;
|
|
sigset_t sigint;
|
|
struct itimerval interval;
|
|
bool done = false;
|
|
#endif
|
|
|
|
if (!query_buf || query_buf->len <= 0)
|
|
{
|
|
pg_log_error("\\watch cannot be used with an empty query");
|
|
return false;
|
|
}
|
|
|
|
#ifndef WIN32
|
|
sigemptyset(&sigalrm_sigchld_sigint);
|
|
sigaddset(&sigalrm_sigchld_sigint, SIGCHLD);
|
|
sigaddset(&sigalrm_sigchld_sigint, SIGALRM);
|
|
sigaddset(&sigalrm_sigchld_sigint, SIGINT);
|
|
|
|
sigemptyset(&sigalrm_sigchld);
|
|
sigaddset(&sigalrm_sigchld, SIGCHLD);
|
|
sigaddset(&sigalrm_sigchld, SIGALRM);
|
|
|
|
sigemptyset(&sigint);
|
|
sigaddset(&sigint, SIGINT);
|
|
|
|
/*
|
|
* Block SIGALRM and SIGCHLD before we start the timer and the pager (if
|
|
* configured), to avoid races. sigwait() will receive them.
|
|
*/
|
|
sigprocmask(SIG_BLOCK, &sigalrm_sigchld, NULL);
|
|
|
|
/*
|
|
* Set a timer to interrupt sigwait() so we can run the query at the
|
|
* requested intervals.
|
|
*/
|
|
interval.it_value.tv_sec = sleep_ms / 1000;
|
|
interval.it_value.tv_usec = (sleep_ms % 1000) * 1000;
|
|
interval.it_interval = interval.it_value;
|
|
if (setitimer(ITIMER_REAL, &interval, NULL) < 0)
|
|
{
|
|
pg_log_error("could not set timer: %m");
|
|
done = true;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* For \watch, we ignore the size of the result and always use the pager
|
|
* if PSQL_WATCH_PAGER is set. We also ignore the regular PSQL_PAGER or
|
|
* PAGER environment variables, because traditional pagers probably won't
|
|
* be very useful for showing a stream of results.
|
|
*/
|
|
#ifndef WIN32
|
|
pagerprog = getenv("PSQL_WATCH_PAGER");
|
|
#endif
|
|
if (pagerprog && myopt.topt.pager)
|
|
{
|
|
fflush(NULL);
|
|
disable_sigpipe_trap();
|
|
pagerpipe = popen(pagerprog, "w");
|
|
|
|
if (!pagerpipe)
|
|
/* silently proceed without pager */
|
|
restore_sigpipe_trap();
|
|
}
|
|
|
|
/*
|
|
* Choose format for timestamps. We might eventually make this a \pset
|
|
* option. In the meantime, using a variable for the format suppresses
|
|
* overly-anal-retentive gcc warnings about %c being Y2K sensitive.
|
|
*/
|
|
strftime_fmt = "%c";
|
|
|
|
/*
|
|
* Set up rendering options, in particular, disable the pager unless
|
|
* PSQL_WATCH_PAGER was successfully launched.
|
|
*/
|
|
if (!pagerpipe)
|
|
myopt.topt.pager = 0;
|
|
|
|
|
|
/*
|
|
* If there's a title in the user configuration, make sure we have room
|
|
* for it in the title buffer. Allow 128 bytes for the timestamp plus 128
|
|
* bytes for the rest.
|
|
*/
|
|
user_title = myopt.title;
|
|
title_len = (user_title ? strlen(user_title) : 0) + 256;
|
|
title = pg_malloc(title_len);
|
|
|
|
for (;;)
|
|
{
|
|
time_t timer;
|
|
char timebuf[128];
|
|
|
|
/*
|
|
* Prepare title for output. Note that we intentionally include a
|
|
* newline at the end of the title; this is somewhat historical but it
|
|
* makes for reasonably nicely formatted output in simple cases.
|
|
*/
|
|
timer = time(NULL);
|
|
strftime(timebuf, sizeof(timebuf), strftime_fmt, localtime(&timer));
|
|
|
|
if (user_title)
|
|
snprintf(title, title_len, _("%s\t%s (every %gs)\n"),
|
|
user_title, timebuf, sleep);
|
|
else
|
|
snprintf(title, title_len, _("%s (every %gs)\n"),
|
|
timebuf, sleep);
|
|
myopt.title = title;
|
|
|
|
/* Run the query and print out the result */
|
|
res = PSQLexecWatch(query_buf->data, &myopt, pagerpipe);
|
|
|
|
/*
|
|
* PSQLexecWatch handles the case where we can no longer repeat the
|
|
* query, and returns 0 or -1.
|
|
*/
|
|
if (res <= 0)
|
|
break;
|
|
|
|
if (pagerpipe && ferror(pagerpipe))
|
|
break;
|
|
|
|
if (sleep == 0)
|
|
continue;
|
|
|
|
#ifdef WIN32
|
|
|
|
/*
|
|
* Set up cancellation of 'watch' via SIGINT. We redo this each time
|
|
* through the loop since it's conceivable something inside
|
|
* PSQLexecWatch could change sigint_interrupt_jmp.
|
|
*/
|
|
if (sigsetjmp(sigint_interrupt_jmp, 1) != 0)
|
|
break;
|
|
|
|
/*
|
|
* Enable 'watch' cancellations and wait a while before running the
|
|
* query again. Break the sleep into short intervals (at most 1s).
|
|
*/
|
|
sigint_interrupt_enabled = true;
|
|
for (long i = sleep_ms; i > 0;)
|
|
{
|
|
long s = Min(i, 1000L);
|
|
|
|
pg_usleep(s * 1000L);
|
|
if (cancel_pressed)
|
|
break;
|
|
i -= s;
|
|
}
|
|
sigint_interrupt_enabled = false;
|
|
#else
|
|
/* sigwait() will handle SIGINT. */
|
|
sigprocmask(SIG_BLOCK, &sigint, NULL);
|
|
if (cancel_pressed)
|
|
done = true;
|
|
|
|
/* Wait for SIGINT, SIGCHLD or SIGALRM. */
|
|
while (!done)
|
|
{
|
|
int signal_received;
|
|
|
|
errno = sigwait(&sigalrm_sigchld_sigint, &signal_received);
|
|
if (errno != 0)
|
|
{
|
|
/* Some other signal arrived? */
|
|
if (errno == EINTR)
|
|
continue;
|
|
else
|
|
{
|
|
pg_log_error("could not wait for signals: %m");
|
|
done = true;
|
|
break;
|
|
}
|
|
}
|
|
/* On ^C or pager exit, it's time to stop running the query. */
|
|
if (signal_received == SIGINT || signal_received == SIGCHLD)
|
|
done = true;
|
|
/* Otherwise, we must have SIGALRM. Time to run the query again. */
|
|
break;
|
|
}
|
|
|
|
/* Unblock SIGINT so that slow queries can be interrupted. */
|
|
sigprocmask(SIG_UNBLOCK, &sigint, NULL);
|
|
if (done)
|
|
break;
|
|
#endif
|
|
}
|
|
|
|
if (pagerpipe)
|
|
{
|
|
pclose(pagerpipe);
|
|
restore_sigpipe_trap();
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* If the terminal driver echoed "^C", libedit/libreadline might be
|
|
* confused about the cursor position. Therefore, inject a newline
|
|
* before the next prompt is displayed. We only do this when not
|
|
* using a pager, because pagers are expected to restore the screen to
|
|
* a sane state on exit.
|
|
*/
|
|
fprintf(stdout, "\n");
|
|
fflush(stdout);
|
|
}
|
|
|
|
#ifndef WIN32
|
|
/* Disable the interval timer. */
|
|
memset(&interval, 0, sizeof(interval));
|
|
setitimer(ITIMER_REAL, &interval, NULL);
|
|
/* Unblock SIGINT, SIGCHLD and SIGALRM. */
|
|
sigprocmask(SIG_UNBLOCK, &sigalrm_sigchld_sigint, NULL);
|
|
#endif
|
|
|
|
pg_free(title);
|
|
return (res >= 0);
|
|
}
|
|
|
|
/*
|
|
* a little code borrowed from PSQLexec() to manage ECHO_HIDDEN output.
|
|
* returns true unless we have ECHO_HIDDEN_NOEXEC.
|
|
*/
|
|
static bool
|
|
echo_hidden_command(const char *query)
|
|
{
|
|
if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
|
|
{
|
|
printf(_("********* QUERY **********\n"
|
|
"%s\n"
|
|
"**************************\n\n"), query);
|
|
fflush(stdout);
|
|
if (pset.logfile)
|
|
{
|
|
fprintf(pset.logfile,
|
|
_("********* QUERY **********\n"
|
|
"%s\n"
|
|
"**************************\n\n"), query);
|
|
fflush(pset.logfile);
|
|
}
|
|
|
|
if (pset.echo_hidden == PSQL_ECHO_HIDDEN_NOEXEC)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Look up the object identified by obj_type and desc. If successful,
|
|
* store its OID in *obj_oid and return true, else return false.
|
|
*
|
|
* Note that we'll fail if the object doesn't exist OR if there are multiple
|
|
* matching candidates OR if there's something syntactically wrong with the
|
|
* object description; unfortunately it can be hard to tell the difference.
|
|
*/
|
|
static bool
|
|
lookup_object_oid(EditableObjectType obj_type, const char *desc,
|
|
Oid *obj_oid)
|
|
{
|
|
bool result = true;
|
|
PQExpBuffer query = createPQExpBuffer();
|
|
PGresult *res;
|
|
|
|
switch (obj_type)
|
|
{
|
|
case EditableFunction:
|
|
|
|
/*
|
|
* We have a function description, e.g. "x" or "x(int)". Issue a
|
|
* query to retrieve the function's OID using a cast to regproc or
|
|
* regprocedure (as appropriate).
|
|
*/
|
|
appendPQExpBufferStr(query, "SELECT ");
|
|
appendStringLiteralConn(query, desc, pset.db);
|
|
appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
|
|
strchr(desc, '(') ? "regprocedure" : "regproc");
|
|
break;
|
|
|
|
case EditableView:
|
|
|
|
/*
|
|
* Convert view name (possibly schema-qualified) to OID. Note:
|
|
* this code doesn't check if the relation is actually a view.
|
|
* We'll detect that in get_create_object_cmd().
|
|
*/
|
|
appendPQExpBufferStr(query, "SELECT ");
|
|
appendStringLiteralConn(query, desc, pset.db);
|
|
appendPQExpBufferStr(query, "::pg_catalog.regclass::pg_catalog.oid");
|
|
break;
|
|
}
|
|
|
|
if (!echo_hidden_command(query->data))
|
|
{
|
|
destroyPQExpBuffer(query);
|
|
return false;
|
|
}
|
|
res = PQexec(pset.db, query->data);
|
|
if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
|
|
*obj_oid = atooid(PQgetvalue(res, 0, 0));
|
|
else
|
|
{
|
|
minimal_error_message(res);
|
|
result = false;
|
|
}
|
|
|
|
PQclear(res);
|
|
destroyPQExpBuffer(query);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Construct a "CREATE OR REPLACE ..." command that describes the specified
|
|
* database object. If successful, the result is stored in buf.
|
|
*/
|
|
static bool
|
|
get_create_object_cmd(EditableObjectType obj_type, Oid oid,
|
|
PQExpBuffer buf)
|
|
{
|
|
bool result = true;
|
|
PQExpBuffer query = createPQExpBuffer();
|
|
PGresult *res;
|
|
|
|
switch (obj_type)
|
|
{
|
|
case EditableFunction:
|
|
printfPQExpBuffer(query,
|
|
"SELECT pg_catalog.pg_get_functiondef(%u)",
|
|
oid);
|
|
break;
|
|
|
|
case EditableView:
|
|
|
|
/*
|
|
* pg_get_viewdef() just prints the query, so we must prepend
|
|
* CREATE for ourselves. We must fully qualify the view name to
|
|
* ensure the right view gets replaced. Also, check relation kind
|
|
* to be sure it's a view.
|
|
*
|
|
* Starting with PG 9.4, views may have WITH [LOCAL|CASCADED]
|
|
* CHECK OPTION. These are not part of the view definition
|
|
* returned by pg_get_viewdef() and so need to be retrieved
|
|
* separately. Materialized views (introduced in 9.3) may have
|
|
* arbitrary storage parameter reloptions.
|
|
*/
|
|
if (pset.sversion >= 90400)
|
|
{
|
|
printfPQExpBuffer(query,
|
|
"SELECT nspname, relname, relkind, "
|
|
"pg_catalog.pg_get_viewdef(c.oid, true), "
|
|
"pg_catalog.array_remove(pg_catalog.array_remove(c.reloptions,'check_option=local'),'check_option=cascaded') AS reloptions, "
|
|
"CASE WHEN 'check_option=local' = ANY (c.reloptions) THEN 'LOCAL'::text "
|
|
"WHEN 'check_option=cascaded' = ANY (c.reloptions) THEN 'CASCADED'::text ELSE NULL END AS checkoption "
|
|
"FROM pg_catalog.pg_class c "
|
|
"LEFT JOIN pg_catalog.pg_namespace n "
|
|
"ON c.relnamespace = n.oid WHERE c.oid = %u",
|
|
oid);
|
|
}
|
|
else
|
|
{
|
|
printfPQExpBuffer(query,
|
|
"SELECT nspname, relname, relkind, "
|
|
"pg_catalog.pg_get_viewdef(c.oid, true), "
|
|
"c.reloptions AS reloptions, "
|
|
"NULL AS checkoption "
|
|
"FROM pg_catalog.pg_class c "
|
|
"LEFT JOIN pg_catalog.pg_namespace n "
|
|
"ON c.relnamespace = n.oid WHERE c.oid = %u",
|
|
oid);
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (!echo_hidden_command(query->data))
|
|
{
|
|
destroyPQExpBuffer(query);
|
|
return false;
|
|
}
|
|
res = PQexec(pset.db, query->data);
|
|
if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
|
|
{
|
|
resetPQExpBuffer(buf);
|
|
switch (obj_type)
|
|
{
|
|
case EditableFunction:
|
|
appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0));
|
|
break;
|
|
|
|
case EditableView:
|
|
{
|
|
char *nspname = PQgetvalue(res, 0, 0);
|
|
char *relname = PQgetvalue(res, 0, 1);
|
|
char *relkind = PQgetvalue(res, 0, 2);
|
|
char *viewdef = PQgetvalue(res, 0, 3);
|
|
char *reloptions = PQgetvalue(res, 0, 4);
|
|
char *checkoption = PQgetvalue(res, 0, 5);
|
|
|
|
/*
|
|
* If the backend ever supports CREATE OR REPLACE
|
|
* MATERIALIZED VIEW, allow that here; but as of today it
|
|
* does not, so editing a matview definition in this way
|
|
* is impossible.
|
|
*/
|
|
switch (relkind[0])
|
|
{
|
|
#ifdef NOT_USED
|
|
case RELKIND_MATVIEW:
|
|
appendPQExpBufferStr(buf, "CREATE OR REPLACE MATERIALIZED VIEW ");
|
|
break;
|
|
#endif
|
|
case RELKIND_VIEW:
|
|
appendPQExpBufferStr(buf, "CREATE OR REPLACE VIEW ");
|
|
break;
|
|
default:
|
|
pg_log_error("\"%s.%s\" is not a view",
|
|
nspname, relname);
|
|
result = false;
|
|
break;
|
|
}
|
|
appendPQExpBuffer(buf, "%s.", fmtId(nspname));
|
|
appendPQExpBufferStr(buf, fmtId(relname));
|
|
|
|
/* reloptions, if not an empty array "{}" */
|
|
if (reloptions != NULL && strlen(reloptions) > 2)
|
|
{
|
|
appendPQExpBufferStr(buf, "\n WITH (");
|
|
if (!appendReloptionsArray(buf, reloptions, "",
|
|
pset.encoding,
|
|
standard_strings()))
|
|
{
|
|
pg_log_error("could not parse reloptions array");
|
|
result = false;
|
|
}
|
|
appendPQExpBufferChar(buf, ')');
|
|
}
|
|
|
|
/* View definition from pg_get_viewdef (a SELECT query) */
|
|
appendPQExpBuffer(buf, " AS\n%s", viewdef);
|
|
|
|
/* Get rid of the semicolon that pg_get_viewdef appends */
|
|
if (buf->len > 0 && buf->data[buf->len - 1] == ';')
|
|
buf->data[--(buf->len)] = '\0';
|
|
|
|
/* WITH [LOCAL|CASCADED] CHECK OPTION */
|
|
if (checkoption && checkoption[0] != '\0')
|
|
appendPQExpBuffer(buf, "\n WITH %s CHECK OPTION",
|
|
checkoption);
|
|
}
|
|
break;
|
|
}
|
|
/* Make sure result ends with a newline */
|
|
if (buf->len > 0 && buf->data[buf->len - 1] != '\n')
|
|
appendPQExpBufferChar(buf, '\n');
|
|
}
|
|
else
|
|
{
|
|
minimal_error_message(res);
|
|
result = false;
|
|
}
|
|
|
|
PQclear(res);
|
|
destroyPQExpBuffer(query);
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* If the given argument of \ef or \ev ends with a line number, delete the line
|
|
* number from the argument string and return it as an integer. (We need
|
|
* this kluge because we're too lazy to parse \ef's function or \ev's view
|
|
* argument carefully --- we just slop it up in OT_WHOLE_LINE mode.)
|
|
*
|
|
* Returns -1 if no line number is present, 0 on error, or a positive value
|
|
* on success.
|
|
*/
|
|
static int
|
|
strip_lineno_from_objdesc(char *obj)
|
|
{
|
|
char *c;
|
|
int lineno;
|
|
|
|
if (!obj || obj[0] == '\0')
|
|
return -1;
|
|
|
|
c = obj + strlen(obj) - 1;
|
|
|
|
/*
|
|
* This business of parsing backwards is dangerous as can be in a
|
|
* multibyte environment: there is no reason to believe that we are
|
|
* looking at the first byte of a character, nor are we necessarily
|
|
* working in a "safe" encoding. Fortunately the bitpatterns we are
|
|
* looking for are unlikely to occur as non-first bytes, but beware of
|
|
* trying to expand the set of cases that can be recognized. We must
|
|
* guard the <ctype.h> macros by using isascii() first, too.
|
|
*/
|
|
|
|
/* skip trailing whitespace */
|
|
while (c > obj && isascii((unsigned char) *c) && isspace((unsigned char) *c))
|
|
c--;
|
|
|
|
/* must have a digit as last non-space char */
|
|
if (c == obj || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
|
|
return -1;
|
|
|
|
/* find start of digit string */
|
|
while (c > obj && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
|
|
c--;
|
|
|
|
/* digits must be separated from object name by space or closing paren */
|
|
/* notice also that we are not allowing an empty object name ... */
|
|
if (c == obj || !isascii((unsigned char) *c) ||
|
|
!(isspace((unsigned char) *c) || *c == ')'))
|
|
return -1;
|
|
|
|
/* parse digit string */
|
|
c++;
|
|
lineno = atoi(c);
|
|
if (lineno < 1)
|
|
{
|
|
pg_log_error("invalid line number: %s", c);
|
|
return 0;
|
|
}
|
|
|
|
/* strip digit string from object name */
|
|
*c = '\0';
|
|
|
|
return lineno;
|
|
}
|
|
|
|
/*
|
|
* Count number of lines in the buffer.
|
|
* This is used to test if pager is needed or not.
|
|
*/
|
|
static int
|
|
count_lines_in_buf(PQExpBuffer buf)
|
|
{
|
|
int lineno = 0;
|
|
const char *lines = buf->data;
|
|
|
|
while (*lines != '\0')
|
|
{
|
|
lineno++;
|
|
/* find start of next line */
|
|
lines = strchr(lines, '\n');
|
|
if (!lines)
|
|
break;
|
|
lines++;
|
|
}
|
|
|
|
return lineno;
|
|
}
|
|
|
|
/*
|
|
* Write text at *lines to output with line numbers.
|
|
*
|
|
* For functions, lineno "1" should correspond to the first line of the
|
|
* function body; lines before that are unnumbered. We expect that
|
|
* pg_get_functiondef() will emit that on a line beginning with "AS ",
|
|
* "BEGIN ", or "RETURN ", and that there can be no such line before
|
|
* the real start of the function body.
|
|
*
|
|
* Caution: this scribbles on *lines.
|
|
*/
|
|
static void
|
|
print_with_linenumbers(FILE *output, char *lines, bool is_func)
|
|
{
|
|
bool in_header = is_func;
|
|
int lineno = 0;
|
|
|
|
while (*lines != '\0')
|
|
{
|
|
char *eol;
|
|
|
|
if (in_header &&
|
|
(strncmp(lines, "AS ", 3) == 0 ||
|
|
strncmp(lines, "BEGIN ", 6) == 0 ||
|
|
strncmp(lines, "RETURN ", 7) == 0))
|
|
in_header = false;
|
|
|
|
/* increment lineno only for body's lines */
|
|
if (!in_header)
|
|
lineno++;
|
|
|
|
/* find and mark end of current line */
|
|
eol = strchr(lines, '\n');
|
|
if (eol != NULL)
|
|
*eol = '\0';
|
|
|
|
/* show current line as appropriate */
|
|
if (in_header)
|
|
fprintf(output, " %s\n", lines);
|
|
else
|
|
fprintf(output, "%-7d %s\n", lineno, lines);
|
|
|
|
/* advance to next line, if any */
|
|
if (eol == NULL)
|
|
break;
|
|
lines = ++eol;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Report just the primary error; this is to avoid cluttering the output
|
|
* with, for instance, a redisplay of the internally generated query
|
|
*/
|
|
static void
|
|
minimal_error_message(PGresult *res)
|
|
{
|
|
PQExpBuffer msg;
|
|
const char *fld;
|
|
|
|
msg = createPQExpBuffer();
|
|
|
|
fld = PQresultErrorField(res, PG_DIAG_SEVERITY);
|
|
if (fld)
|
|
printfPQExpBuffer(msg, "%s: ", fld);
|
|
else
|
|
printfPQExpBuffer(msg, "ERROR: ");
|
|
fld = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
|
|
if (fld)
|
|
appendPQExpBufferStr(msg, fld);
|
|
else
|
|
appendPQExpBufferStr(msg, "(not available)");
|
|
appendPQExpBufferChar(msg, '\n');
|
|
|
|
pg_log_error("%s", msg->data);
|
|
|
|
destroyPQExpBuffer(msg);
|
|
}
|