summaryrefslogtreecommitdiffstats
path: root/src/bin/psql/command.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-04 12:15:05 +0000
commit46651ce6fe013220ed397add242004d764fc0153 (patch)
tree6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/bin/psql/command.c
parentInitial commit. (diff)
downloadpostgresql-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.c5425
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, &quoted, 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, &quote, 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);
+}