diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/bin/psql/command.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-46651ce6fe013220ed397add242004d764fc0153.tar.xz postgresql-14-46651ce6fe013220ed397add242004d764fc0153.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/psql/command.c')
-rw-r--r-- | src/bin/psql/command.c | 5425 |
1 files changed, 5425 insertions, 0 deletions
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c new file mode 100644 index 0000000..062c79c --- /dev/null +++ b/src/bin/psql/command.c @@ -0,0 +1,5425 @@ +/* + * psql - the PostgreSQL interactive terminal + * + * Copyright (c) 2000-2021, 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 <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_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_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); +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, + const char *header_keyword); +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_info("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 results are 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, "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, "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) + status = exec_command_z(scan_state, active_branch); + 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; +} + +/* + * \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 + struct passwd *pw; + uid_t user_id = geteuid(); + + errno = 0; /* clear errno before call */ + pw = getpwuid(user_id); + if (!pw) + { + pg_log_error("could not get home directory for user ID %ld: %s", + (long) user_id, + errno ? strerror(errno) : _("user does not exist")); + exit(EXIT_FAILURE); + } + dir = pw->pw_dir; +#else /* WIN32 */ + + /* + * On Windows, 'cd' without arguments prints the current + * directory, so if someone wants to code this here instead... + */ + dir = "/"; +#endif /* WIN32 */ + } + + if (chdir(dir) == -1) + { + pg_log_error("\\%s: could not change directory to \"%s\": %m", + cmd, dir); + success = false; + } + + if (opt) + 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 results 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; + } + + if (pattern2) + free(pattern2); + } + break; + case 'a': + success = describeAggregates(pattern, show_verbose, show_system); + break; + case 'b': + success = describeTablespaces(pattern, show_verbose); + break; + case 'c': + 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 = do_lo_list(); + 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); + 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); + + if (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; + } + + if (pattern) + 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; + } + if (fname) + free(fname); + if (ln) + 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 (pset.sversion < (is_func ? 80400 : 70400)) + { + char sverbuf[32]; + + formatPGVersionNumber(pset.sversion, false, + sverbuf, sizeof(sverbuf)); + if (is_func) + pg_log_error("The server (version %s) does not support editing function source.", + sverbuf); + else + pg_log_error("The server (version %s) does not support editing view definitions.", + sverbuf); + status = PSQL_CMD_ERROR; + } + else 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 ", 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) + 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; + } + + if (obj_desc) + 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; +} + +/* + * \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); + + if (pattern) + 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 = do_lo_list(); + + 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; + char *pw2; + PQExpBufferData buf; + + 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); + } + + initPQExpBuffer(&buf); + printfPQExpBuffer(&buf, _("Enter new password for user \"%s\": "), user); + + pw1 = simple_prompt(buf.data, false); + pw2 = simple_prompt("Enter it again: ", false); + + 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; + + if (arg2) + { + prompt_text = arg1; + opt = arg2; + } + else + opt = arg1; + + if (!pset.inputfile) + { + result = simple_prompt(prompt_text, true); + } + 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 (result && + !SetVariable(pset.vars, opt, result)) + success = false; + + if (result) + free(result); + if (prompt_text) + 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", + 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 (pset.sversion < (is_func ? 80400 : 70400)) + { + char sverbuf[32]; + + formatPGVersionNumber(pset.sversion, false, + sverbuf, sizeof(sverbuf)); + if (is_func) + pg_log_error("The server (version %s) does not support showing function source.", + sverbuf); + else + pg_log_error("The server (version %s) does not support showing view definitions.", + sverbuf); + status = PSQL_CMD_ERROR; + } + else 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) + { + /* + * For functions, 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 ", and that there can be no such line before the + * real start of the function body. + */ + print_with_linenumbers(output, buf->data, + is_func ? "AS " : NULL); + } + else + { + /* just send the definition to output */ + fputs(buf->data, output); + } + + if (is_pager) + ClosePager(output); + } + + if (obj_desc) + 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; + 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); + 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) + { + sleep = strtod(opt, NULL); + if (sleep <= 0) + sleep = 1; + 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) +{ + bool success = true; + + if (active_branch) + { + char *pattern = psql_scan_slash_option(scan_state, + OT_NORMAL, NULL, true); + + success = permissionsList(pattern); + if (pattern) + 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); + + if (opt0) + 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); + + if (arg) + 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); + + if (arg) + 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. + */ +static char * +prompt_for_password(const char *username) +{ + char *result; + + if (username == NULL || username[0] == '\0') + result = simple_prompt("Password: ", false); + else + { + char *prompt_text; + + prompt_text = psprintf(_("Password for user %s: "), username); + result = simple_prompt(prompt_text, false); + free(prompt_text); + } + 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) + { + /* + * 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); + } + + /* + * 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) + { + /* + * 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)); + PQfinish(n_conn); + n_conn = NULL; + 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 */ + if (password) + pg_free(password); + if (cinfo) + 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 *host = PQhost(pset.db); + char *hostaddr = PQhostaddr(pset.db); + + if (is_unixsock_path(host)) + { + /* hostaddr overrides host */ + 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), host, PQport(pset.db)); + } + else + { + if (hostaddr && *hostaddr && strcmp(host, 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), host, 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), host, 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); + + if (pset.sversion / 100 > client_ver / 100) + 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 *bits; + const char *compression; + + if (!PQsslInUse(pset.db)) + return; /* no SSL */ + + protocol = PQsslAttribute(pset.db, "protocol"); + cipher = PQsslAttribute(pset.db, "cipher"); + bits = PQsslAttribute(pset.db, "key_bits"); + compression = PQsslAttribute(pset.db, "compression"); + + printf(_("SSL connection (protocol: %s, cipher: %s, bits: %s, compression: %s)\n"), + protocol ? protocol : _("unknown"), + cipher ? cipher : _("unknown"), + bits ? bits : _("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 + 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; + } + + /* 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 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 */ + if (popt->topt.fieldSep.separator) + free(popt->topt.fieldSep.separator); + if (popt->topt.recordSep.separator) + free(popt->topt.recordSep.separator); + if (popt->topt.tableAttr) + free(popt->topt.tableAttr); + if (popt->nullPrint) + free(popt->nullPrint); + if (popt->title) + free(popt->title); + + /* + * footers and translate_columns are never set in psql's print settings, + * so we needn't write code to duplicate them. + */ + Assert(popt->footers == NULL); + Assert(popt->translate_columns == NULL); + + /* Now we may flat-copy all the fields, including pointers. */ + memcpy(popt, save, sizeof(printQueryOpt)); + + /* Lastly, free "save" ... but its sub-structures now belong to popt. */ + free(save); +} + +static const char * +pset_bool_string(bool val) +{ + 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 psprintf("%s", _align2string(popt->topt.format)); + else if (strcmp(param, "linestyle") == 0) + return psprintf("%s", 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 + 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; + + 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; + int title_len; + int res = 0; + + if (!query_buf || query_buf->len <= 0) + { + pg_log_error("\\watch cannot be used with an empty query"); + return false; + } + + /* + * 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, because + * nobody wants to be prompted while watching the output of 'watch'. + */ + 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]; + long i; + + /* + * 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 results */ + res = PSQLexecWatch(query_buf->data, &myopt); + + /* + * PSQLexecWatch handles the case where we can no longer repeat the + * query, and returns 0 or -1. + */ + if (res <= 0) + break; + + /* + * 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) + * since pg_usleep isn't interruptible on some platforms. + */ + sigint_interrupt_enabled = true; + i = sleep_ms; + while (i > 0) + { + long s = Min(i, 1000L); + + pg_usleep(s * 1000L); + if (cancel_pressed) + break; + i -= s; + } + sigint_interrupt_enabled = false; + } + + /* + * 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. + */ + fprintf(stdout, "\n"); + fflush(stdout); + + 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 9.2, views may have reloptions (security_barrier) + * and from 9.4 onwards they may also 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 if (pset.sversion >= 90200) + { + 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); + } + else + { + printfPQExpBuffer(query, + "SELECT nspname, relname, relkind, " + "pg_catalog.pg_get_viewdef(c.oid, true), " + "NULL 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. + * + * If header_keyword isn't NULL, then line 1 should be the first line beginning + * with header_keyword; lines before that are unnumbered. + * + * Caution: this scribbles on *lines. + */ +static void +print_with_linenumbers(FILE *output, char *lines, + const char *header_keyword) +{ + bool in_header = (header_keyword != NULL); + size_t header_sz = in_header ? strlen(header_keyword) : 0; + int lineno = 0; + + while (*lines != '\0') + { + char *eol; + + if (in_header && strncmp(lines, header_keyword, header_sz) == 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); +} |