diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-04 12:15:05 +0000 |
commit | 46651ce6fe013220ed397add242004d764fc0153 (patch) | |
tree | 6e5299f990f88e60174a1d3ae6e48eedd2688b2b /src/bin/psql/startup.c | |
parent | Initial commit. (diff) | |
download | postgresql-14-46651ce6fe013220ed397add242004d764fc0153.tar.xz postgresql-14-46651ce6fe013220ed397add242004d764fc0153.zip |
Adding upstream version 14.5.upstream/14.5upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/bin/psql/startup.c')
-rw-r--r-- | src/bin/psql/startup.c | 1243 |
1 files changed, 1243 insertions, 0 deletions
diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c new file mode 100644 index 0000000..110906a --- /dev/null +++ b/src/bin/psql/startup.c @@ -0,0 +1,1243 @@ +/* + * psql - the PostgreSQL interactive terminal + * + * Copyright (c) 2000-2021, PostgreSQL Global Development Group + * + * src/bin/psql/startup.c + */ +#include "postgres_fe.h" + +#ifndef WIN32 +#include <unistd.h> +#else /* WIN32 */ +#include <io.h> +#include <win32.h> +#endif /* WIN32 */ + +#include "command.h" +#include "common.h" +#include "common/logging.h" +#include "common/string.h" +#include "describe.h" +#include "fe_utils/print.h" +#include "getopt_long.h" +#include "help.h" +#include "input.h" +#include "mainloop.h" +#include "settings.h" + +/* + * Global psql options + */ +PsqlSettings pset; + +#ifndef WIN32 +#define SYSPSQLRC "psqlrc" +#define PSQLRC ".psqlrc" +#else +#define SYSPSQLRC "psqlrc" +#define PSQLRC "psqlrc.conf" +#endif + +/* + * Structures to pass information between the option parsing routine + * and the main function + */ +enum _actions +{ + ACT_SINGLE_QUERY, + ACT_SINGLE_SLASH, + ACT_FILE +}; + +typedef struct SimpleActionListCell +{ + struct SimpleActionListCell *next; + enum _actions action; + char *val; +} SimpleActionListCell; + +typedef struct SimpleActionList +{ + SimpleActionListCell *head; + SimpleActionListCell *tail; +} SimpleActionList; + +struct adhoc_opts +{ + char *dbname; + char *host; + char *port; + char *username; + char *logfilename; + bool no_readline; + bool no_psqlrc; + bool single_txn; + bool list_dbs; + SimpleActionList actions; +}; + +static void parse_psql_options(int argc, char *argv[], + struct adhoc_opts *options); +static void simple_action_list_append(SimpleActionList *list, + enum _actions action, const char *val); +static void process_psqlrc(char *argv0); +static void process_psqlrc_file(char *filename); +static void showVersion(void); +static void EstablishVariableSpace(void); + +#define NOPAGER 0 + +static void +log_pre_callback(void) +{ + if (pset.queryFout && pset.queryFout != stdout) + fflush(pset.queryFout); +} + +static void +log_locus_callback(const char **filename, uint64 *lineno) +{ + if (pset.inputfile) + { + *filename = pset.inputfile; + *lineno = pset.lineno; + } + else + { + *filename = NULL; + *lineno = 0; + } +} + +/* + * + * main + * + */ +int +main(int argc, char *argv[]) +{ + struct adhoc_opts options; + int successResult; + char *password = NULL; + bool new_pass; + + pg_logging_init(argv[0]); + pg_logging_set_pre_callback(log_pre_callback); + pg_logging_set_locus_callback(log_locus_callback); + set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("psql")); + + if (argc > 1) + { + if ((strcmp(argv[1], "-?") == 0) || (argc == 2 && (strcmp(argv[1], "--help") == 0))) + { + usage(NOPAGER); + exit(EXIT_SUCCESS); + } + if (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0) + { + showVersion(); + exit(EXIT_SUCCESS); + } + } + + pset.progname = get_progname(argv[0]); + + pset.db = NULL; + pset.dead_conn = NULL; + setDecimalLocale(); + pset.encoding = PQenv2encoding(); + pset.queryFout = stdout; + pset.queryFoutPipe = false; + pset.copyStream = NULL; + pset.last_error_result = NULL; + pset.cur_cmd_source = stdin; + pset.cur_cmd_interactive = false; + + /* We rely on unmentioned fields of pset.popt to start out 0/false/NULL */ + pset.popt.topt.format = PRINT_ALIGNED; + pset.popt.topt.border = 1; + pset.popt.topt.pager = 1; + pset.popt.topt.pager_min_lines = 0; + pset.popt.topt.start_table = true; + pset.popt.topt.stop_table = true; + pset.popt.topt.default_footer = true; + + pset.popt.topt.csvFieldSep[0] = DEFAULT_CSV_FIELD_SEP; + pset.popt.topt.csvFieldSep[1] = '\0'; + + pset.popt.topt.unicode_border_linestyle = UNICODE_LINESTYLE_SINGLE; + pset.popt.topt.unicode_column_linestyle = UNICODE_LINESTYLE_SINGLE; + pset.popt.topt.unicode_header_linestyle = UNICODE_LINESTYLE_SINGLE; + + refresh_utf8format(&(pset.popt.topt)); + + /* We must get COLUMNS here before readline() sets it */ + pset.popt.topt.env_columns = getenv("COLUMNS") ? atoi(getenv("COLUMNS")) : 0; + + pset.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout))); + + pset.getPassword = TRI_DEFAULT; + + EstablishVariableSpace(); + + /* Create variables showing psql version number */ + SetVariable(pset.vars, "VERSION", PG_VERSION_STR); + SetVariable(pset.vars, "VERSION_NAME", PG_VERSION); + SetVariable(pset.vars, "VERSION_NUM", CppAsString2(PG_VERSION_NUM)); + + /* Initialize variables for last error */ + SetVariable(pset.vars, "LAST_ERROR_MESSAGE", ""); + SetVariable(pset.vars, "LAST_ERROR_SQLSTATE", "00000"); + + /* Default values for variables (that don't match the result of \unset) */ + SetVariableBool(pset.vars, "AUTOCOMMIT"); + SetVariable(pset.vars, "PROMPT1", DEFAULT_PROMPT1); + SetVariable(pset.vars, "PROMPT2", DEFAULT_PROMPT2); + SetVariable(pset.vars, "PROMPT3", DEFAULT_PROMPT3); + + parse_psql_options(argc, argv, &options); + + /* + * If no action was specified and we're in non-interactive mode, treat it + * as if the user had specified "-f -". This lets single-transaction mode + * work in this case. + */ + if (options.actions.head == NULL && pset.notty) + simple_action_list_append(&options.actions, ACT_FILE, NULL); + + /* Bail out if -1 was specified but will be ignored. */ + if (options.single_txn && options.actions.head == NULL) + { + pg_log_fatal("-1 can only be used in non-interactive mode"); + exit(EXIT_FAILURE); + } + + if (!pset.popt.topt.fieldSep.separator && + !pset.popt.topt.fieldSep.separator_zero) + { + pset.popt.topt.fieldSep.separator = pg_strdup(DEFAULT_FIELD_SEP); + pset.popt.topt.fieldSep.separator_zero = false; + } + if (!pset.popt.topt.recordSep.separator && + !pset.popt.topt.recordSep.separator_zero) + { + pset.popt.topt.recordSep.separator = pg_strdup(DEFAULT_RECORD_SEP); + pset.popt.topt.recordSep.separator_zero = false; + } + + if (pset.getPassword == TRI_YES) + { + /* + * We can't be sure yet of the username that will be used, so don't + * offer a potentially wrong one. Typical uses of this option are + * noninteractive anyway. + */ + password = simple_prompt("Password: ", false); + } + + /* loop until we have a password if requested by backend */ + do + { +#define PARAMS_ARRAY_SIZE 8 + const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords)); + const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values)); + + keywords[0] = "host"; + values[0] = options.host; + keywords[1] = "port"; + values[1] = options.port; + keywords[2] = "user"; + values[2] = options.username; + keywords[3] = "password"; + values[3] = password; + keywords[4] = "dbname"; /* see do_connect() */ + values[4] = (options.list_dbs && options.dbname == NULL) ? + "postgres" : options.dbname; + keywords[5] = "fallback_application_name"; + values[5] = pset.progname; + keywords[6] = "client_encoding"; + values[6] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto"; + keywords[7] = NULL; + values[7] = NULL; + + new_pass = false; + pset.db = PQconnectdbParams(keywords, values, true); + free(keywords); + free(values); + + if (PQstatus(pset.db) == CONNECTION_BAD && + PQconnectionNeedsPassword(pset.db) && + !password && + pset.getPassword != TRI_NO) + { + /* + * Before closing the old PGconn, extract the user name that was + * actually connected with --- it might've come out of a URI or + * connstring "database name" rather than options.username. + */ + const char *realusername = PQuser(pset.db); + char *password_prompt; + + if (realusername && realusername[0]) + password_prompt = psprintf(_("Password for user %s: "), + realusername); + else + password_prompt = pg_strdup(_("Password: ")); + PQfinish(pset.db); + + password = simple_prompt(password_prompt, false); + free(password_prompt); + new_pass = true; + } + } while (new_pass); + + if (PQstatus(pset.db) == CONNECTION_BAD) + { + pg_log_error("%s", PQerrorMessage(pset.db)); + PQfinish(pset.db); + exit(EXIT_BADCONN); + } + + psql_setup_cancel_handler(); + + PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL); + + SyncVariables(); + + if (options.list_dbs) + { + int success; + + if (!options.no_psqlrc) + process_psqlrc(argv[0]); + + success = listAllDbs(NULL, false); + PQfinish(pset.db); + exit(success ? EXIT_SUCCESS : EXIT_FAILURE); + } + + if (options.logfilename) + { + pset.logfile = fopen(options.logfilename, "a"); + if (!pset.logfile) + { + pg_log_fatal("could not open log file \"%s\": %m", + options.logfilename); + exit(EXIT_FAILURE); + } + } + + if (!options.no_psqlrc) + process_psqlrc(argv[0]); + + /* + * If any actions were given by user, process them in the order in which + * they were specified. Note single_txn is only effective in this mode. + */ + if (options.actions.head != NULL) + { + PGresult *res; + SimpleActionListCell *cell; + + successResult = EXIT_SUCCESS; /* silence compiler */ + + if (options.single_txn) + { + if ((res = PSQLexec("BEGIN")) == NULL) + { + if (pset.on_error_stop) + { + successResult = EXIT_USER; + goto error; + } + } + else + PQclear(res); + } + + for (cell = options.actions.head; cell; cell = cell->next) + { + if (cell->action == ACT_SINGLE_QUERY) + { + pg_logging_config(PG_LOG_FLAG_TERSE); + + if (pset.echo == PSQL_ECHO_ALL) + puts(cell->val); + + successResult = SendQuery(cell->val) + ? EXIT_SUCCESS : EXIT_FAILURE; + } + else if (cell->action == ACT_SINGLE_SLASH) + { + PsqlScanState scan_state; + ConditionalStack cond_stack; + + pg_logging_config(PG_LOG_FLAG_TERSE); + + if (pset.echo == PSQL_ECHO_ALL) + puts(cell->val); + + scan_state = psql_scan_create(&psqlscan_callbacks); + psql_scan_setup(scan_state, + cell->val, strlen(cell->val), + pset.encoding, standard_strings()); + cond_stack = conditional_stack_create(); + psql_scan_set_passthrough(scan_state, (void *) cond_stack); + + successResult = HandleSlashCmds(scan_state, + cond_stack, + NULL, + NULL) != PSQL_CMD_ERROR + ? EXIT_SUCCESS : EXIT_FAILURE; + + psql_scan_destroy(scan_state); + conditional_stack_destroy(cond_stack); + } + else if (cell->action == ACT_FILE) + { + successResult = process_file(cell->val, false); + } + else + { + /* should never come here */ + Assert(false); + } + + if (successResult != EXIT_SUCCESS && pset.on_error_stop) + break; + } + + if (options.single_txn) + { + if ((res = PSQLexec("COMMIT")) == NULL) + { + if (pset.on_error_stop) + { + successResult = EXIT_USER; + goto error; + } + } + else + PQclear(res); + } + +error: + ; + } + + /* + * or otherwise enter interactive main loop + */ + else + { + pg_logging_config(PG_LOG_FLAG_TERSE); + connection_warnings(true); + if (!pset.quiet) + printf(_("Type \"help\" for help.\n\n")); + initializeInput(options.no_readline ? 0 : 1); + successResult = MainLoop(stdin); + } + + /* clean up */ + if (pset.logfile) + fclose(pset.logfile); + if (pset.db) + PQfinish(pset.db); + if (pset.dead_conn) + PQfinish(pset.dead_conn); + setQFout(NULL); + + return successResult; +} + + +/* + * Parse command line options + */ + +static void +parse_psql_options(int argc, char *argv[], struct adhoc_opts *options) +{ + static struct option long_options[] = + { + {"echo-all", no_argument, NULL, 'a'}, + {"no-align", no_argument, NULL, 'A'}, + {"command", required_argument, NULL, 'c'}, + {"dbname", required_argument, NULL, 'd'}, + {"echo-queries", no_argument, NULL, 'e'}, + {"echo-errors", no_argument, NULL, 'b'}, + {"echo-hidden", no_argument, NULL, 'E'}, + {"file", required_argument, NULL, 'f'}, + {"field-separator", required_argument, NULL, 'F'}, + {"field-separator-zero", no_argument, NULL, 'z'}, + {"host", required_argument, NULL, 'h'}, + {"html", no_argument, NULL, 'H'}, + {"list", no_argument, NULL, 'l'}, + {"log-file", required_argument, NULL, 'L'}, + {"no-readline", no_argument, NULL, 'n'}, + {"single-transaction", no_argument, NULL, '1'}, + {"output", required_argument, NULL, 'o'}, + {"port", required_argument, NULL, 'p'}, + {"pset", required_argument, NULL, 'P'}, + {"quiet", no_argument, NULL, 'q'}, + {"record-separator", required_argument, NULL, 'R'}, + {"record-separator-zero", no_argument, NULL, '0'}, + {"single-step", no_argument, NULL, 's'}, + {"single-line", no_argument, NULL, 'S'}, + {"tuples-only", no_argument, NULL, 't'}, + {"table-attr", required_argument, NULL, 'T'}, + {"username", required_argument, NULL, 'U'}, + {"set", required_argument, NULL, 'v'}, + {"variable", required_argument, NULL, 'v'}, + {"version", no_argument, NULL, 'V'}, + {"no-password", no_argument, NULL, 'w'}, + {"password", no_argument, NULL, 'W'}, + {"expanded", no_argument, NULL, 'x'}, + {"no-psqlrc", no_argument, NULL, 'X'}, + {"help", optional_argument, NULL, 1}, + {"csv", no_argument, NULL, 2}, + {NULL, 0, NULL, 0} + }; + + int optindex; + int c; + + memset(options, 0, sizeof *options); + + while ((c = getopt_long(argc, argv, "aAbc:d:eEf:F:h:HlL:no:p:P:qR:sStT:U:v:VwWxXz?01", + long_options, &optindex)) != -1) + { + switch (c) + { + case 'a': + SetVariable(pset.vars, "ECHO", "all"); + break; + case 'A': + pset.popt.topt.format = PRINT_UNALIGNED; + break; + case 'b': + SetVariable(pset.vars, "ECHO", "errors"); + break; + case 'c': + if (optarg[0] == '\\') + simple_action_list_append(&options->actions, + ACT_SINGLE_SLASH, + optarg + 1); + else + simple_action_list_append(&options->actions, + ACT_SINGLE_QUERY, + optarg); + break; + case 'd': + options->dbname = pg_strdup(optarg); + break; + case 'e': + SetVariable(pset.vars, "ECHO", "queries"); + break; + case 'E': + SetVariableBool(pset.vars, "ECHO_HIDDEN"); + break; + case 'f': + simple_action_list_append(&options->actions, + ACT_FILE, + optarg); + break; + case 'F': + pset.popt.topt.fieldSep.separator = pg_strdup(optarg); + pset.popt.topt.fieldSep.separator_zero = false; + break; + case 'h': + options->host = pg_strdup(optarg); + break; + case 'H': + pset.popt.topt.format = PRINT_HTML; + break; + case 'l': + options->list_dbs = true; + break; + case 'L': + options->logfilename = pg_strdup(optarg); + break; + case 'n': + options->no_readline = true; + break; + case 'o': + if (!setQFout(optarg)) + exit(EXIT_FAILURE); + break; + case 'p': + options->port = pg_strdup(optarg); + break; + case 'P': + { + char *value; + char *equal_loc; + bool result; + + value = pg_strdup(optarg); + equal_loc = strchr(value, '='); + if (!equal_loc) + result = do_pset(value, NULL, &pset.popt, true); + else + { + *equal_loc = '\0'; + result = do_pset(value, equal_loc + 1, &pset.popt, true); + } + + if (!result) + { + pg_log_fatal("could not set printing parameter \"%s\"", value); + exit(EXIT_FAILURE); + } + + free(value); + break; + } + case 'q': + SetVariableBool(pset.vars, "QUIET"); + break; + case 'R': + pset.popt.topt.recordSep.separator = pg_strdup(optarg); + pset.popt.topt.recordSep.separator_zero = false; + break; + case 's': + SetVariableBool(pset.vars, "SINGLESTEP"); + break; + case 'S': + SetVariableBool(pset.vars, "SINGLELINE"); + break; + case 't': + pset.popt.topt.tuples_only = true; + break; + case 'T': + pset.popt.topt.tableAttr = pg_strdup(optarg); + break; + case 'U': + options->username = pg_strdup(optarg); + break; + case 'v': + { + char *value; + char *equal_loc; + + value = pg_strdup(optarg); + equal_loc = strchr(value, '='); + if (!equal_loc) + { + if (!DeleteVariable(pset.vars, value)) + exit(EXIT_FAILURE); /* error already printed */ + } + else + { + *equal_loc = '\0'; + if (!SetVariable(pset.vars, value, equal_loc + 1)) + exit(EXIT_FAILURE); /* error already printed */ + } + + free(value); + break; + } + case 'V': + showVersion(); + exit(EXIT_SUCCESS); + case 'w': + pset.getPassword = TRI_NO; + break; + case 'W': + pset.getPassword = TRI_YES; + break; + case 'x': + pset.popt.topt.expanded = true; + break; + case 'X': + options->no_psqlrc = true; + break; + case 'z': + pset.popt.topt.fieldSep.separator_zero = true; + break; + case '0': + pset.popt.topt.recordSep.separator_zero = true; + break; + case '1': + options->single_txn = true; + break; + case '?': + if (optind <= argc && + strcmp(argv[optind - 1], "-?") == 0) + { + /* actual help option given */ + usage(NOPAGER); + exit(EXIT_SUCCESS); + } + else + { + /* getopt error (unknown option or missing argument) */ + goto unknown_option; + } + break; + case 1: + { + if (!optarg || strcmp(optarg, "options") == 0) + usage(NOPAGER); + else if (optarg && strcmp(optarg, "commands") == 0) + slashUsage(NOPAGER); + else if (optarg && strcmp(optarg, "variables") == 0) + helpVariables(NOPAGER); + else + goto unknown_option; + + exit(EXIT_SUCCESS); + } + break; + case 2: + pset.popt.topt.format = PRINT_CSV; + break; + default: + unknown_option: + fprintf(stderr, _("Try \"%s --help\" for more information.\n"), + pset.progname); + exit(EXIT_FAILURE); + break; + } + } + + /* + * if we still have arguments, use it as the database name and username + */ + while (argc - optind >= 1) + { + if (!options->dbname) + options->dbname = argv[optind]; + else if (!options->username) + options->username = argv[optind]; + else if (!pset.quiet) + pg_log_warning("extra command-line argument \"%s\" ignored", + argv[optind]); + + optind++; + } +} + + +/* + * Append a new item to the end of the SimpleActionList. + * Note that "val" is copied if it's not NULL. + */ +static void +simple_action_list_append(SimpleActionList *list, + enum _actions action, const char *val) +{ + SimpleActionListCell *cell; + + cell = (SimpleActionListCell *) pg_malloc(sizeof(SimpleActionListCell)); + + cell->next = NULL; + cell->action = action; + if (val) + cell->val = pg_strdup(val); + else + cell->val = NULL; + + if (list->tail) + list->tail->next = cell; + else + list->head = cell; + list->tail = cell; +} + + +/* + * Load .psqlrc file, if found. + */ +static void +process_psqlrc(char *argv0) +{ + char home[MAXPGPATH]; + char rc_file[MAXPGPATH]; + char my_exec_path[MAXPGPATH]; + char etc_path[MAXPGPATH]; + char *envrc = getenv("PSQLRC"); + + if (find_my_exec(argv0, my_exec_path) < 0) + { + pg_log_fatal("could not find own program executable"); + exit(EXIT_FAILURE); + } + + get_etc_path(my_exec_path, etc_path); + + snprintf(rc_file, MAXPGPATH, "%s/%s", etc_path, SYSPSQLRC); + process_psqlrc_file(rc_file); + + if (envrc != NULL && strlen(envrc) > 0) + { + /* might need to free() this */ + char *envrc_alloc = pstrdup(envrc); + + expand_tilde(&envrc_alloc); + process_psqlrc_file(envrc_alloc); + } + else if (get_home_path(home)) + { + snprintf(rc_file, MAXPGPATH, "%s/%s", home, PSQLRC); + process_psqlrc_file(rc_file); + } +} + + + +static void +process_psqlrc_file(char *filename) +{ + char *psqlrc_minor, + *psqlrc_major; + +#if defined(WIN32) && (!defined(__MINGW32__)) +#define R_OK 4 +#endif + + psqlrc_minor = psprintf("%s-%s", filename, PG_VERSION); + psqlrc_major = psprintf("%s-%s", filename, PG_MAJORVERSION); + + /* check for minor version first, then major, then no version */ + if (access(psqlrc_minor, R_OK) == 0) + (void) process_file(psqlrc_minor, false); + else if (access(psqlrc_major, R_OK) == 0) + (void) process_file(psqlrc_major, false); + else if (access(filename, R_OK) == 0) + (void) process_file(filename, false); + + free(psqlrc_minor); + free(psqlrc_major); +} + + + +/* showVersion + * + * This output format is intended to match GNU standards. + */ +static void +showVersion(void) +{ + puts("psql (PostgreSQL) " PG_VERSION); +} + + + +/* + * Substitute hooks and assign hooks for psql variables. + * + * This isn't an amazingly good place for them, but neither is anywhere else. + * + * By policy, every special variable that controls any psql behavior should + * have one or both hooks, even if they're just no-ops. This ensures that + * the variable will remain present in variables.c's list even when unset, + * which ensures that it's known to tab completion. + */ + +static char * +bool_substitute_hook(char *newval) +{ + if (newval == NULL) + { + /* "\unset FOO" becomes "\set FOO off" */ + newval = pg_strdup("off"); + } + else if (newval[0] == '\0') + { + /* "\set FOO" becomes "\set FOO on" */ + pg_free(newval); + newval = pg_strdup("on"); + } + return newval; +} + +static bool +autocommit_hook(const char *newval) +{ + return ParseVariableBool(newval, "AUTOCOMMIT", &pset.autocommit); +} + +static bool +on_error_stop_hook(const char *newval) +{ + return ParseVariableBool(newval, "ON_ERROR_STOP", &pset.on_error_stop); +} + +static bool +quiet_hook(const char *newval) +{ + return ParseVariableBool(newval, "QUIET", &pset.quiet); +} + +static bool +singleline_hook(const char *newval) +{ + return ParseVariableBool(newval, "SINGLELINE", &pset.singleline); +} + +static bool +singlestep_hook(const char *newval) +{ + return ParseVariableBool(newval, "SINGLESTEP", &pset.singlestep); +} + +static char * +fetch_count_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("0"); + return newval; +} + +static bool +fetch_count_hook(const char *newval) +{ + return ParseVariableNum(newval, "FETCH_COUNT", &pset.fetch_count); +} + +static bool +histfile_hook(const char *newval) +{ + /* + * Someday we might try to validate the filename, but for now, this is + * just a placeholder to ensure HISTFILE is known to tab completion. + */ + return true; +} + +static char * +histsize_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("500"); + return newval; +} + +static bool +histsize_hook(const char *newval) +{ + return ParseVariableNum(newval, "HISTSIZE", &pset.histsize); +} + +static char * +ignoreeof_substitute_hook(char *newval) +{ + int dummy; + + /* + * This tries to mimic the behavior of bash, to wit "If set, the value is + * the number of consecutive EOF characters which must be typed as the + * first characters on an input line before bash exits. If the variable + * exists but does not have a numeric value, or has no value, the default + * value is 10. If it does not exist, EOF signifies the end of input to + * the shell." Unlike bash, however, we insist on the stored value + * actually being a valid integer. + */ + if (newval == NULL) + newval = pg_strdup("0"); + else if (!ParseVariableNum(newval, NULL, &dummy)) + newval = pg_strdup("10"); + return newval; +} + +static bool +ignoreeof_hook(const char *newval) +{ + return ParseVariableNum(newval, "IGNOREEOF", &pset.ignoreeof); +} + +static char * +echo_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("none"); + return newval; +} + +static bool +echo_hook(const char *newval) +{ + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "queries") == 0) + pset.echo = PSQL_ECHO_QUERIES; + else if (pg_strcasecmp(newval, "errors") == 0) + pset.echo = PSQL_ECHO_ERRORS; + else if (pg_strcasecmp(newval, "all") == 0) + pset.echo = PSQL_ECHO_ALL; + else if (pg_strcasecmp(newval, "none") == 0) + pset.echo = PSQL_ECHO_NONE; + else + { + PsqlVarEnumError("ECHO", newval, "none, errors, queries, all"); + return false; + } + return true; +} + +static bool +echo_hidden_hook(const char *newval) +{ + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "noexec") == 0) + pset.echo_hidden = PSQL_ECHO_HIDDEN_NOEXEC; + else + { + bool on_off; + + if (ParseVariableBool(newval, NULL, &on_off)) + pset.echo_hidden = on_off ? PSQL_ECHO_HIDDEN_ON : PSQL_ECHO_HIDDEN_OFF; + else + { + PsqlVarEnumError("ECHO_HIDDEN", newval, "on, off, noexec"); + return false; + } + } + return true; +} + +static bool +on_error_rollback_hook(const char *newval) +{ + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "interactive") == 0) + pset.on_error_rollback = PSQL_ERROR_ROLLBACK_INTERACTIVE; + else + { + bool on_off; + + if (ParseVariableBool(newval, NULL, &on_off)) + pset.on_error_rollback = on_off ? PSQL_ERROR_ROLLBACK_ON : PSQL_ERROR_ROLLBACK_OFF; + else + { + PsqlVarEnumError("ON_ERROR_ROLLBACK", newval, "on, off, interactive"); + return false; + } + } + return true; +} + +static char * +comp_keyword_case_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("preserve-upper"); + return newval; +} + +static bool +comp_keyword_case_hook(const char *newval) +{ + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "preserve-upper") == 0) + pset.comp_case = PSQL_COMP_CASE_PRESERVE_UPPER; + else if (pg_strcasecmp(newval, "preserve-lower") == 0) + pset.comp_case = PSQL_COMP_CASE_PRESERVE_LOWER; + else if (pg_strcasecmp(newval, "upper") == 0) + pset.comp_case = PSQL_COMP_CASE_UPPER; + else if (pg_strcasecmp(newval, "lower") == 0) + pset.comp_case = PSQL_COMP_CASE_LOWER; + else + { + PsqlVarEnumError("COMP_KEYWORD_CASE", newval, + "lower, upper, preserve-lower, preserve-upper"); + return false; + } + return true; +} + +static char * +histcontrol_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("none"); + return newval; +} + +static bool +histcontrol_hook(const char *newval) +{ + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "ignorespace") == 0) + pset.histcontrol = hctl_ignorespace; + else if (pg_strcasecmp(newval, "ignoredups") == 0) + pset.histcontrol = hctl_ignoredups; + else if (pg_strcasecmp(newval, "ignoreboth") == 0) + pset.histcontrol = hctl_ignoreboth; + else if (pg_strcasecmp(newval, "none") == 0) + pset.histcontrol = hctl_none; + else + { + PsqlVarEnumError("HISTCONTROL", newval, + "none, ignorespace, ignoredups, ignoreboth"); + return false; + } + return true; +} + +static bool +prompt1_hook(const char *newval) +{ + pset.prompt1 = newval ? newval : ""; + return true; +} + +static bool +prompt2_hook(const char *newval) +{ + pset.prompt2 = newval ? newval : ""; + return true; +} + +static bool +prompt3_hook(const char *newval) +{ + pset.prompt3 = newval ? newval : ""; + return true; +} + +static char * +verbosity_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("default"); + return newval; +} + +static bool +verbosity_hook(const char *newval) +{ + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "default") == 0) + pset.verbosity = PQERRORS_DEFAULT; + else if (pg_strcasecmp(newval, "verbose") == 0) + pset.verbosity = PQERRORS_VERBOSE; + else if (pg_strcasecmp(newval, "terse") == 0) + pset.verbosity = PQERRORS_TERSE; + else if (pg_strcasecmp(newval, "sqlstate") == 0) + pset.verbosity = PQERRORS_SQLSTATE; + else + { + PsqlVarEnumError("VERBOSITY", newval, "default, verbose, terse, sqlstate"); + return false; + } + + if (pset.db) + PQsetErrorVerbosity(pset.db, pset.verbosity); + return true; +} + +static char * +show_context_substitute_hook(char *newval) +{ + if (newval == NULL) + newval = pg_strdup("errors"); + return newval; +} + +static bool +show_context_hook(const char *newval) +{ + Assert(newval != NULL); /* else substitute hook messed up */ + if (pg_strcasecmp(newval, "never") == 0) + pset.show_context = PQSHOW_CONTEXT_NEVER; + else if (pg_strcasecmp(newval, "errors") == 0) + pset.show_context = PQSHOW_CONTEXT_ERRORS; + else if (pg_strcasecmp(newval, "always") == 0) + pset.show_context = PQSHOW_CONTEXT_ALWAYS; + else + { + PsqlVarEnumError("SHOW_CONTEXT", newval, "never, errors, always"); + return false; + } + + if (pset.db) + PQsetErrorContextVisibility(pset.db, pset.show_context); + return true; +} + +static bool +hide_compression_hook(const char *newval) +{ + return ParseVariableBool(newval, "HIDE_TOAST_COMPRESSION", + &pset.hide_compression); +} + +static bool +hide_tableam_hook(const char *newval) +{ + return ParseVariableBool(newval, "HIDE_TABLEAM", &pset.hide_tableam); +} + +static void +EstablishVariableSpace(void) +{ + pset.vars = CreateVariableSpace(); + + SetVariableHooks(pset.vars, "AUTOCOMMIT", + bool_substitute_hook, + autocommit_hook); + SetVariableHooks(pset.vars, "ON_ERROR_STOP", + bool_substitute_hook, + on_error_stop_hook); + SetVariableHooks(pset.vars, "QUIET", + bool_substitute_hook, + quiet_hook); + SetVariableHooks(pset.vars, "SINGLELINE", + bool_substitute_hook, + singleline_hook); + SetVariableHooks(pset.vars, "SINGLESTEP", + bool_substitute_hook, + singlestep_hook); + SetVariableHooks(pset.vars, "FETCH_COUNT", + fetch_count_substitute_hook, + fetch_count_hook); + SetVariableHooks(pset.vars, "HISTFILE", + NULL, + histfile_hook); + SetVariableHooks(pset.vars, "HISTSIZE", + histsize_substitute_hook, + histsize_hook); + SetVariableHooks(pset.vars, "IGNOREEOF", + ignoreeof_substitute_hook, + ignoreeof_hook); + SetVariableHooks(pset.vars, "ECHO", + echo_substitute_hook, + echo_hook); + SetVariableHooks(pset.vars, "ECHO_HIDDEN", + bool_substitute_hook, + echo_hidden_hook); + SetVariableHooks(pset.vars, "ON_ERROR_ROLLBACK", + bool_substitute_hook, + on_error_rollback_hook); + SetVariableHooks(pset.vars, "COMP_KEYWORD_CASE", + comp_keyword_case_substitute_hook, + comp_keyword_case_hook); + SetVariableHooks(pset.vars, "HISTCONTROL", + histcontrol_substitute_hook, + histcontrol_hook); + SetVariableHooks(pset.vars, "PROMPT1", + NULL, + prompt1_hook); + SetVariableHooks(pset.vars, "PROMPT2", + NULL, + prompt2_hook); + SetVariableHooks(pset.vars, "PROMPT3", + NULL, + prompt3_hook); + SetVariableHooks(pset.vars, "VERBOSITY", + verbosity_substitute_hook, + verbosity_hook); + SetVariableHooks(pset.vars, "SHOW_CONTEXT", + show_context_substitute_hook, + show_context_hook); + SetVariableHooks(pset.vars, "HIDE_TOAST_COMPRESSION", + bool_substitute_hook, + hide_compression_hook); + SetVariableHooks(pset.vars, "HIDE_TABLEAM", + bool_substitute_hook, + hide_tableam_hook); +} |