static char * gets_basic(const char prompt[]) { fputs(prompt, stdout); fflush(stdout); return gets_fromFile(stdin); }
/* * gets_interactive() * * Gets a line of interactive input, using readline if desired. * The result is a malloc'd string. * * Caller *must* have set up sigint_interrupt_jmp before calling. */ char * gets_interactive(const char *prompt) { #ifdef USE_READLINE if (useReadline) { char *result; /* Enable SIGINT to longjmp to sigint_interrupt_jmp */ sigint_interrupt_enabled = true; /* On some platforms, readline is declared as readline(char *) */ result = readline((char *) prompt); /* Disable SIGINT again */ sigint_interrupt_enabled = false; return result; } #endif fputs(prompt, stdout); fflush(stdout); return gets_fromFile(stdin); }
/* * gets_interactive() * * Gets a line of interactive input, using readline if desired. * * prompt: the prompt string to be used * query_buf: buffer containing lines already read in the current command * (query_buf is not modified here, but may be consulted for tab completion) * * The result is a malloc'd string. * * Caller *must* have set up sigint_interrupt_jmp before calling. */ char * gets_interactive(const char *prompt, PQExpBuffer query_buf) { #ifdef USE_READLINE if (useReadline) { char *result; /* * Some versions of readline don't notice SIGWINCH signals that arrive * when not actively reading input. The simplest fix is to always * re-read the terminal size. This leaves a window for SIGWINCH to be * missed between here and where readline() enables libreadline's * signal handler, but that's probably short enough to be ignored. */ #ifdef HAVE_RL_RESET_SCREEN_SIZE rl_reset_screen_size(); #endif /* Make current query_buf available to tab completion callback */ tab_completion_query_buf = query_buf; /* Enable SIGINT to longjmp to sigint_interrupt_jmp */ sigint_interrupt_enabled = true; /* On some platforms, readline is declared as readline(char *) */ result = readline((char *) prompt); /* Disable SIGINT again */ sigint_interrupt_enabled = false; /* Pure neatnik-ism */ tab_completion_query_buf = NULL; return result; } #endif fputs(prompt, stdout); fflush(stdout); return gets_fromFile(stdin); }
/* * Subroutine to actually try to execute a backslash command. */ static backslashResult exec_command(const char *cmd, PsqlScanState scan_state, PQExpBuffer query_buf) { bool success = true; /* indicate here if the command ran ok or * failed */ backslashResult status = PSQL_CMD_SKIP_LINE; /* * \a -- toggle field alignment This makes little sense but we keep it * around. */ if (strcmp(cmd, "a") == 0) { 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); } /* \C -- override table title (formerly change HTML caption) */ else if (strcmp(cmd, "C") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("title", opt, &pset.popt, pset.quiet); free(opt); } /* * \c or \connect -- connect to database using the specified parameters. * * \c dbname user host port * * If any of these parameters are omitted or specified as '-', the current * value of the parameter will be used instead. If the parameter has no * current value, the default value for that parameter will be used. Some * examples: * * \c - - hst Connect to current database on current port of host * "hst" as current user. \c - usr - prt Connect to current database on * "prt" port of current host as user "usr". \c dbs Connect to * "dbs" database on current port of current host as current user. */ else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0) { char *opt1, *opt2, *opt3, *opt4; opt1 = read_connect_arg(scan_state); opt2 = read_connect_arg(scan_state); opt3 = read_connect_arg(scan_state); opt4 = read_connect_arg(scan_state); success = do_connect(opt1, opt2, opt3, opt4); free(opt1); free(opt2); free(opt3); free(opt4); } /* \cd */ else if (strcmp(cmd, "cd") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); char *dir; if (opt) dir = opt; else { #ifndef WIN32 struct passwd *pw; pw = getpwuid(geteuid()); if (!pw) { psql_error("could not get home directory: %s\n", strerror(errno)); 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) { psql_error("\\%s: could not change directory to \"%s\": %s\n", cmd, dir, strerror(errno)); success = false; } if (pset.dirname) free(pset.dirname); pset.dirname = pg_strdup(dir); canonicalize_path(pset.dirname); if (opt) free(opt); } /* \conninfo -- display information about the current connection */ else if (strcmp(cmd, "conninfo") == 0) { char *db = PQdb(pset.db); char *host = PQhost(pset.db); if (db == NULL) printf(_("You are not connected.\n")); else { if (host == NULL) host = DEFAULT_PGSOCKET_DIR; /* If the host is an absolute path, the connection is via socket */ if (is_absolute_path(host)) 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 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)); } } /* \copy */ else if (pg_strcasecmp(cmd, "copy") == 0) { /* Default fetch-it-all-and-print mode */ instr_time before, after; char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); if (pset.timing) INSTR_TIME_SET_CURRENT(before); success = do_copy(opt); if (pset.timing && success) { INSTR_TIME_SET_CURRENT(after); INSTR_TIME_SUBTRACT(after, before); printf(_("Time: %.3f ms\n"), INSTR_TIME_GET_MILLISEC(after)); } free(opt); } /* \copyright */ else if (strcmp(cmd, "copyright") == 0) print_copyright(); /* \d* commands */ else if (cmd[0] == 'd') { 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': /* GPDB: This is a change from old behavior: We used to show just system tables */ if (pattern) success = describeTableDetails(pattern, show_verbose, show_system); else /* standard listing of interesting things */ success = listTables("tvsxr", NULL, show_verbose, show_system); 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_system); break; case 'C': success = listCasts(pattern); 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_system); break; case 'f': /* function subsystem */ switch (cmd[2]) { case '\0': case '+': case 'S': case 'a': case 'n': case 't': case 'w': success = describeFunctions(&cmd[2], 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); break; case 'l': success = do_lo_list(); break; case 'n': success = listSchemas(pattern, show_verbose); break; case 'o': success = describeOperators(pattern, show_system); break; case 'p': success = permissionsList(pattern); break; case 'T': success = describeTypes(pattern, show_verbose, show_system); break; case 't': case 'v': case 'i': case 's': case 'E': /* PostgreSQL use dx for extension, change to dE for foreign table */ /* case 'S': // GPDB: We used to show just system tables for this */ case 'P': /* GPDB: Parent-only tables, no children */ 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); } else //success = PSQL_CMD_UNKNOWN; /* GPDB uses \dr for foreign tables ? */ success = listTables(&cmd[1], pattern, show_verbose, show_system); break; case 'u': success = describeRoles(pattern, show_verbose); 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 'x': /* Extensions */ if (show_verbose) success = listExtensionContents(pattern); else success = listExtensions(pattern); break; default: status = PSQL_CMD_UNKNOWN; } if (pattern) free(pattern); } /* * \e or \edit -- edit the current query buffer, or edit a file and make * it the query buffer */ else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0) { if (!query_buf) { psql_error("no query buffer\n"); status = PSQL_CMD_ERROR; } else { char *fname; fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); expand_tilde(&fname); if (fname) canonicalize_path(fname); if (do_edit(fname, query_buf, NULL)) status = PSQL_CMD_NEWEDIT; else status = PSQL_CMD_ERROR; free(fname); } } /* * \ef -- edit the named function, or present a blank CREATE FUNCTION * template if no argument is given */ else if (strcmp(cmd, "ef") == 0) { if (!query_buf) { psql_error("no query buffer\n"); status = PSQL_CMD_ERROR; } else { char *func; Oid foid = InvalidOid; func = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, true); if (!func) { /* set up an empty command to fill in */ printfPQExpBuffer(query_buf, "CREATE FUNCTION ( )\n" " RETURNS \n" " LANGUAGE \n" " -- common options: IMMUTABLE STABLE STRICT SECURITY DEFINER\n" "AS $function$\n" "\n$function$\n"); } else if (!lookup_function_oid(pset.db, func, &foid)) { /* error already reported */ status = PSQL_CMD_ERROR; } else if (!get_create_function_cmd(pset.db, foid, query_buf)) { /* error already reported */ status = PSQL_CMD_ERROR; } if (func) free(func); } if (status != PSQL_CMD_ERROR) { bool edited = false; if (!do_edit(0, query_buf, &edited)) status = PSQL_CMD_ERROR; else if (!edited) puts(_("No changes")); else status = PSQL_CMD_NEWEDIT; } } /* \echo and \qecho */ else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0) { char *value; char quoted; bool no_newline = false; bool first = true; FILE *fout; if (strcmp(cmd, "qecho") == 0) fout = pset.queryFout; else fout = stdout; while ((value = psql_scan_slash_option(scan_state, OT_NORMAL, "ed, false))) { if (!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); } /* \encoding -- set/show client side encoding */ else if (strcmp(cmd, "encoding") == 0) { 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) psql_error("%s: invalid encoding name or conversion procedure not found\n", 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); } } /* \f -- change field separator */ else if (strcmp(cmd, "f") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); success = do_pset("fieldsep", fname, &pset.popt, pset.quiet); free(fname); } /* \g means send query */ else if (strcmp(cmd, "g") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, false); if (!fname) pset.gfname = NULL; else { expand_tilde(&fname); pset.gfname = pg_strdup(fname); } free(fname); status = PSQL_CMD_SEND; } /* help */ else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) { 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); } /* HTML mode */ else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) { 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); } /* \i is include file */ else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); if (!fname) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { expand_tilde(&fname); success = (process_file(fname, false) == EXIT_SUCCESS); free(fname); } } /* \l is list databases */ else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0) success = listAllDbs(false); else if (strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0) success = listAllDbs(true); /* * large object things */ else if (strncmp(cmd, "lo_", 3) == 0) { 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) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { expand_tilde(&opt2); success = do_lo_export(opt1, opt2); } } else if (strcmp(cmd + 3, "import") == 0) { if (!opt1) { psql_error("\\%s: missing required argument\n", 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) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else success = do_lo_unlink(opt1); } else status = PSQL_CMD_UNKNOWN; free(opt1); free(opt2); } /* \o -- set query output */ else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); expand_tilde(&fname); success = setQFout(fname); free(fname); } /* \p prints the current query buffer */ else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0) { if (query_buf && query_buf->len > 0) puts(query_buf->data); else if (!pset.quiet) puts(_("Query buffer is empty.")); fflush(stdout); } /* \password -- set user password */ else if (strcmp(cmd, "password") == 0) { char *pw1; char *pw2; pw1 = simple_prompt("Enter new password: "******"Enter it again: ", 100, false); if (strcmp(pw1, pw2) != 0) { fprintf(stderr, _("Passwords didn't match.\n")); success = false; } else { char *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true); char *user; char *encrypted_password; if (opt0) user = opt0; else user = PQuser(pset.db); encrypted_password = PQencryptPassword(pw1, user); if (!encrypted_password) { fprintf(stderr, _("Password encryption failed.\n")); success = false; } else { PQExpBufferData buf; PGresult *res; initPQExpBuffer(&buf); printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ", fmtId(user)); appendStringLiteralConn(&buf, encrypted_password, pset.db); res = PSQLexec(buf.data, false); termPQExpBuffer(&buf); if (!res) success = false; else PQclear(res); PQfreemem(encrypted_password); } } free(pw1); free(pw2); } /* \prompt -- prompt and set variable */ else if (strcmp(cmd, "prompt") == 0) { 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) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { char *result; if (arg2) { prompt_text = arg1; opt = arg2; } else opt = arg1; if (!pset.inputfile) result = simple_prompt(prompt_text, 4096, true); else { if (prompt_text) { fputs(prompt_text, stdout); fflush(stdout); } result = gets_fromFile(stdin); } if (!SetVariable(pset.vars, opt, result)) { psql_error("\\%s: error\n", cmd); success = false; } free(result); if (prompt_text) free(prompt_text); free(opt); } } /* \pset -- set printing parameters */ else if (strcmp(cmd, "pset") == 0) { 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) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else success = do_pset(opt0, opt1, &pset.popt, pset.quiet); free(opt0); free(opt1); } /* \q or \quit */ else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0) status = PSQL_CMD_TERMINATE; /* reset(clear) the buffer */ else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0) { resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); if (!pset.quiet) puts(_("Query buffer reset (cleared).")); } /* \s save history in a file or show it on the screen */ else if (strcmp(cmd, "s") == 0) { char *fname = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); #if defined(WIN32) && !defined(__CYGWIN__) /* * XXX This does not work for all terminal environments or for output * containing non-ASCII characters; see comments in simple_prompt(). */ #define DEVTTY "con" #else #define DEVTTY "/dev/tty" #endif expand_tilde(&fname); /* This scrolls off the screen when using /dev/tty */ success = saveHistory(fname ? fname : DEVTTY, -1, false, false); if (success && !pset.quiet && fname) printf(gettext("Wrote history to file \"%s/%s\".\n"), pset.dirname ? pset.dirname : ".", fname); if (!fname) putchar('\n'); free(fname); } /* \set -- generalized set variable/option command */ else if (strcmp(cmd, "set") == 0) { 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 = realloc(newval, strlen(newval) + strlen(opt) + 1); if (!newval) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } strcat(newval, opt); free(opt); } if (!SetVariable(pset.vars, opt0, newval)) { psql_error("\\%s: error\n", cmd); success = false; } free(newval); } free(opt0); } /* \t -- turn off headers and row count */ else if (strcmp(cmd, "t") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("tuples_only", opt, &pset.popt, pset.quiet); free(opt); } /* \T -- define html <table ...> attributes */ else if (strcmp(cmd, "T") == 0) { char *value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); success = do_pset("tableattr", value, &pset.popt, pset.quiet); free(value); } /* \timing -- toggle timing of queries */ else if (strcmp(cmd, "timing") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (opt) pset.timing = ParseVariableBool(opt); else pset.timing = !pset.timing; if (!pset.quiet) { if (pset.timing) puts(_("Timing is on.")); else puts(_("Timing is off.")); } free(opt); } /* \unset */ else if (strcmp(cmd, "unset") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false); if (!opt) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else if (!SetVariable(pset.vars, opt, NULL)) { psql_error("\\%s: error\n", cmd); success = false; } free(opt); } /* \w -- write query buffer to file */ else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0) { FILE *fd = NULL; bool is_pipe = false; char *fname = NULL; if (!query_buf) { psql_error("no query buffer\n"); status = PSQL_CMD_ERROR; } else { fname = psql_scan_slash_option(scan_state, OT_FILEPIPE, NULL, true); expand_tilde(&fname); if (!fname) { psql_error("\\%s: missing required argument\n", cmd); success = false; } else { if (fname[0] == '|') { is_pipe = true; fd = popen(&fname[1], "w"); } else { canonicalize_path(fname); fd = fopen(fname, "w"); } if (!fd) { psql_error("%s: %s\n", fname, strerror(errno)); success = false; } } } if (fd) { int result; if (query_buf && query_buf->len > 0) fprintf(fd, "%s\n", query_buf->data); if (is_pipe) result = pclose(fd); else result = fclose(fd); if (result == EOF) { psql_error("%s: %s\n", fname, strerror(errno)); success = false; } } free(fname); } /* \x -- toggle expanded table representation */ else if (strcmp(cmd, "x") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = do_pset("expanded", opt, &pset.popt, pset.quiet); free(opt); } /* \z -- list table rights (equivalent to \dp) */ else if (strcmp(cmd, "z") == 0) { char *pattern = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true); success = permissionsList(pattern); if (pattern) free(pattern); } /* \! -- shell escape */ else if (strcmp(cmd, "!") == 0) { char *opt = psql_scan_slash_option(scan_state, OT_WHOLE_LINE, NULL, false); success = do_shell(opt); free(opt); } /* \? -- slash command help */ else if (strcmp(cmd, "?") == 0) slashUsage(pset.popt.topt.pager); #if 0 /* * These commands don't do anything. I just use them to test the parser. */ else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0) { int i = 0; char *value; while ((value = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, true))) { fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value); free(value); } } #endif else status = PSQL_CMD_UNKNOWN; if (!success) status = PSQL_CMD_ERROR; return status; }
/* * Main processing loop for reading lines of input * and sending them to the backend. * * This loop is re-entrant. May be called by \i command * which reads input from a file. */ int MainLoop(FILE *source) { PsqlScanState scan_state; /* lexer working state */ volatile PQExpBuffer query_buf; /* buffer for query being accumulated */ volatile PQExpBuffer previous_buf; /* if there isn't anything in the new * buffer yet, use this one for \e, * etc. */ PQExpBuffer history_buf; /* earlier lines of a multi-line command, not * yet saved to readline history */ char *line; /* current line of input */ int added_nl_pos; bool success; bool line_saved_in_history; volatile int successResult = EXIT_SUCCESS; volatile backslashResult slashCmdStatus = PSQL_CMD_UNKNOWN; volatile promptStatus_t prompt_status = PROMPT_READY; volatile int count_eof = 0; volatile bool die_on_error = false; /* Save the prior command source */ FILE *prev_cmd_source; bool prev_cmd_interactive; uint64 prev_lineno; /* Save old settings */ prev_cmd_source = pset.cur_cmd_source; prev_cmd_interactive = pset.cur_cmd_interactive; prev_lineno = pset.lineno; /* Establish new source */ pset.cur_cmd_source = source; pset.cur_cmd_interactive = ((source == stdin) && !pset.notty); pset.lineno = 0; /* Create working state */ scan_state = psql_scan_create(); query_buf = createPQExpBuffer(); previous_buf = createPQExpBuffer(); history_buf = createPQExpBuffer(); if (PQExpBufferBroken(query_buf) || PQExpBufferBroken(previous_buf) || PQExpBufferBroken(history_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* main loop to get queries and execute them */ while (successResult == EXIT_SUCCESS) { /* * Clean up after a previous Control-C */ if (cancel_pressed) { if (!pset.cur_cmd_interactive) { /* * You get here if you stopped a script with Ctrl-C. */ successResult = EXIT_USER; break; } cancel_pressed = false; } /* * Establish longjmp destination for exiting from wait-for-input. We * must re-do this each time through the loop for safety, since the * jmpbuf might get changed during command execution. */ if (sigsetjmp(sigint_interrupt_jmp, 1) != 0) { /* got here with longjmp */ /* reset parsing state */ psql_scan_finish(scan_state); psql_scan_reset(scan_state); resetPQExpBuffer(query_buf); resetPQExpBuffer(history_buf); count_eof = 0; slashCmdStatus = PSQL_CMD_UNKNOWN; prompt_status = PROMPT_READY; cancel_pressed = false; if (pset.cur_cmd_interactive) putc('\n', stdout); else { successResult = EXIT_USER; break; } } fflush(stdout); /* * get another line */ if (pset.cur_cmd_interactive) { /* May need to reset prompt, eg after \r command */ if (query_buf->len == 0) prompt_status = PROMPT_READY; line = gets_interactive(get_prompt(prompt_status)); } else { line = gets_fromFile(source); if (!line && ferror(source)) successResult = EXIT_FAILURE; } /* * query_buf holds query already accumulated. line is the malloc'd * new line of input (note it must be freed before looping around!) */ /* No more input. Time to quit, or \i done */ if (line == NULL) { if (pset.cur_cmd_interactive) { /* This tries to mimic bash's IGNOREEOF feature. */ count_eof++; if (count_eof < GetVariableNum(pset.vars, "IGNOREEOF", 0, 10, false)) { if (!pset.quiet) printf(_("Use \"\\q\" to leave %s.\n"), pset.progname); continue; } puts(pset.quiet ? "" : "\\q"); } break; } count_eof = 0; pset.lineno++; /* ignore UTF-8 Unicode byte-order mark */ if (pset.lineno == 1 && pset.encoding == PG_UTF8 && strncmp(line, "\xef\xbb\xbf", 3) == 0) memmove(line, line + 3, strlen(line + 3) + 1); /* nothing left on line? then ignore */ if (line[0] == '\0' && !psql_scan_in_quote(scan_state)) { free(line); continue; } /* A request for help? Be friendly and give them some guidance */ if (pset.cur_cmd_interactive && query_buf->len == 0 && pg_strncasecmp(line, "help", 4) == 0 && (line[4] == '\0' || line[4] == ';' || isspace((unsigned char) line[4]))) { free(line); puts(_("You are using psql, the command-line interface to PostgreSQL.")); printf(_("Type: \\copyright for distribution terms\n" " \\h for help with SQL commands\n" " \\? for help with psql commands\n" " \\g or terminate with semicolon to execute query\n" " \\q to quit\n")); fflush(stdout); continue; } /* echo back if flag is set */ if (pset.echo == PSQL_ECHO_ALL && !pset.cur_cmd_interactive) puts(line); fflush(stdout); /* insert newlines into query buffer between source lines */ if (query_buf->len > 0) { appendPQExpBufferChar(query_buf, '\n'); added_nl_pos = query_buf->len; } else added_nl_pos = -1; /* flag we didn't add one */ /* Setting this will not have effect until next line. */ die_on_error = pset.on_error_stop; /* * Parse line, looking for command separators. */ psql_scan_setup(scan_state, line, strlen(line)); success = true; line_saved_in_history = false; while (success || !die_on_error) { PsqlScanResult scan_result; promptStatus_t prompt_tmp = prompt_status; scan_result = psql_scan(scan_state, query_buf, &prompt_tmp); prompt_status = prompt_tmp; if (PQExpBufferBroken(query_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* * Send command if semicolon found, or if end of line and we're in * single-line mode. */ if (scan_result == PSCAN_SEMICOLON || (scan_result == PSCAN_EOL && pset.singleline)) { /* * Save query in history. We use history_buf to accumulate * multi-line queries into a single history entry. */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute query */ success = SendQuery(query_buf->data); slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); added_nl_pos = -1; /* we need not do psql_scan_reset() here */ } else if (scan_result == PSCAN_BACKSLASH) { /* handle backslash command */ /* * If we added a newline to query_buf, and nothing else has * been inserted in query_buf by the lexer, then strip off the * newline again. This avoids any change to query_buf when a * line contains only a backslash command. Also, in this * situation we force out any previous lines as a separate * history entry; we don't want SQL and backslash commands * intermixed in history if at all possible. */ if (query_buf->len == added_nl_pos) { query_buf->data[--query_buf->len] = '\0'; pg_send_history(history_buf); } added_nl_pos = -1; /* save backslash command in history */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute backslash command */ slashCmdStatus = HandleSlashCmds(scan_state, query_buf->len > 0 ? query_buf : previous_buf); success = slashCmdStatus != PSQL_CMD_ERROR; if ((slashCmdStatus == PSQL_CMD_SEND || slashCmdStatus == PSQL_CMD_NEWEDIT) && query_buf->len == 0) { /* copy previous buffer to current for handling */ appendPQExpBufferStr(query_buf, previous_buf->data); } if (slashCmdStatus == PSQL_CMD_SEND) { success = SendQuery(query_buf->data); /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); /* flush any paren nesting info after forced send */ psql_scan_reset(scan_state); } else if (slashCmdStatus == PSQL_CMD_NEWEDIT) { /* rescan query_buf as new input */ psql_scan_finish(scan_state); free(line); line = pg_strdup(query_buf->data); resetPQExpBuffer(query_buf); /* reset parsing state since we are rescanning whole line */ psql_scan_reset(scan_state); psql_scan_setup(scan_state, line, strlen(line)); line_saved_in_history = false; prompt_status = PROMPT_READY; } else if (slashCmdStatus == PSQL_CMD_TERMINATE) break; } /* fall out of loop if lexer reached EOL */ if (scan_result == PSCAN_INCOMPLETE || scan_result == PSCAN_EOL) break; } /* Add line to pending history if we didn't execute anything yet */ if (pset.cur_cmd_interactive && !line_saved_in_history) pg_append_history(line, history_buf); psql_scan_finish(scan_state); free(line); if (slashCmdStatus == PSQL_CMD_TERMINATE) { successResult = EXIT_SUCCESS; break; } if (!pset.cur_cmd_interactive) { if (!success && die_on_error) successResult = EXIT_USER; /* Have we lost the db connection? */ else if (!pset.db) successResult = EXIT_BADCONN; } } /* while !endoffile/session */ /* * Process query at the end of file without a semicolon */ if (query_buf->len > 0 && !pset.cur_cmd_interactive && successResult == EXIT_SUCCESS) { /* save query in history */ if (pset.cur_cmd_interactive) pg_send_history(history_buf); /* execute query */ success = SendQuery(query_buf->data); if (!success && die_on_error) successResult = EXIT_USER; else if (pset.db == NULL) successResult = EXIT_BADCONN; } /* * Let's just make real sure the SIGINT handler won't try to use * sigint_interrupt_jmp after we exit this routine. If there is an outer * MainLoop instance, it will reset sigint_interrupt_jmp to point to * itself at the top of its loop, before any further interactive input * happens. */ sigint_interrupt_enabled = false; destroyPQExpBuffer(query_buf); destroyPQExpBuffer(previous_buf); destroyPQExpBuffer(history_buf); psql_scan_destroy(scan_state); pset.cur_cmd_source = prev_cmd_source; pset.cur_cmd_interactive = prev_cmd_interactive; pset.lineno = prev_lineno; return successResult; } /* MainLoop() */
/* * Main processing loop for reading lines of input * and sending them to the backend. * * This loop is re-entrant. May be called by \i command * which reads input from a file. */ int MainLoop(FILE *source) { PsqlScanState scan_state; /* lexer working state */ PQExpBuffer query_buf; /* buffer for query being accumulated */ PQExpBuffer previous_buf; /* if there isn't anything in the new * buffer yet, use this one for \e, etc. */ char *line; /* current line of input */ int added_nl_pos; bool success; volatile int successResult = EXIT_SUCCESS; volatile backslashResult slashCmdStatus = CMD_UNKNOWN; volatile promptStatus_t prompt_status = PROMPT_READY; volatile int count_eof = 0; volatile bool die_on_error = false; /* Save the prior command source */ FILE *prev_cmd_source; bool prev_cmd_interactive; unsigned int prev_lineno; /* Save old settings */ prev_cmd_source = pset.cur_cmd_source; prev_cmd_interactive = pset.cur_cmd_interactive; prev_lineno = pset.lineno; /* Establish new source */ pset.cur_cmd_source = source; pset.cur_cmd_interactive = ((source == stdin) && !pset.notty); pset.lineno = 0; /* Create working state */ scan_state = psql_scan_create(); query_buf = createPQExpBuffer(); previous_buf = createPQExpBuffer(); if (!query_buf || !previous_buf) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* main loop to get queries and execute them */ while (successResult == EXIT_SUCCESS) { /* * Welcome code for Control-C */ if (cancel_pressed) { if (!pset.cur_cmd_interactive) { /* * You get here if you stopped a script with Ctrl-C and a * query cancel was issued. In that case we don't do the * longjmp, so the query routine can finish nicely. */ successResult = EXIT_USER; break; } cancel_pressed = false; } #ifndef WIN32 if (sigsetjmp(main_loop_jmp, 1) != 0) { /* got here with longjmp */ /* reset parsing state */ resetPQExpBuffer(query_buf); psql_scan_finish(scan_state); psql_scan_reset(scan_state); count_eof = 0; slashCmdStatus = CMD_UNKNOWN; prompt_status = PROMPT_READY; if (pset.cur_cmd_interactive) putc('\n', stdout); else { successResult = EXIT_USER; break; } } /* * establish the control-C handler only after main_loop_jmp is * ready */ pqsignal(SIGINT, handle_sigint); /* control-C => cancel */ #else /* WIN32 */ setup_cancel_handler(); #endif fflush(stdout); if (slashCmdStatus == CMD_NEWEDIT) { /* * just returned from editing the line? then just copy to the * input buffer */ line = pg_strdup(query_buf->data); /* reset parsing state since we are rescanning whole line */ resetPQExpBuffer(query_buf); psql_scan_reset(scan_state); slashCmdStatus = CMD_UNKNOWN; prompt_status = PROMPT_READY; } /* * otherwise, get another line */ else if (pset.cur_cmd_interactive) { /* May need to reset prompt, eg after \r command */ if (query_buf->len == 0) prompt_status = PROMPT_READY; line = gets_interactive(get_prompt(prompt_status)); } else line = gets_fromFile(source); /* * query_buf holds query already accumulated. line is the * malloc'd new line of input (note it must be freed before * looping around!) */ /* No more input. Time to quit, or \i done */ if (line == NULL) { if (pset.cur_cmd_interactive) { /* This tries to mimic bash's IGNOREEOF feature. */ count_eof++; if (count_eof < GetVariableNum(pset.vars, "IGNOREEOF", 0, 10, false)) { if (!QUIET()) printf(gettext("Use \"\\q\" to leave %s.\n"), pset.progname); continue; } puts(QUIET() ? "" : "\\q"); } break; } count_eof = 0; pset.lineno++; /* nothing left on line? then ignore */ if (line[0] == '\0' && !psql_scan_in_quote(scan_state)) { free(line); continue; } /* echo back if flag is set */ if (!pset.cur_cmd_interactive && VariableEquals(pset.vars, "ECHO", "all")) puts(line); fflush(stdout); /* insert newlines into query buffer between source lines */ if (query_buf->len > 0) { appendPQExpBufferChar(query_buf, '\n'); added_nl_pos = query_buf->len; } else added_nl_pos = -1; /* flag we didn't add one */ /* Setting this will not have effect until next line. */ die_on_error = GetVariableBool(pset.vars, "ON_ERROR_STOP"); /* * Parse line, looking for command separators. */ psql_scan_setup(scan_state, line, strlen(line)); success = true; while (success || !die_on_error) { PsqlScanResult scan_result; promptStatus_t prompt_tmp = prompt_status; scan_result = psql_scan(scan_state, query_buf, &prompt_tmp); prompt_status = prompt_tmp; /* * Send command if semicolon found, or if end of line and * we're in single-line mode. */ if (scan_result == PSCAN_SEMICOLON || (scan_result == PSCAN_EOL && GetVariableBool(pset.vars, "SINGLELINE"))) { /* execute query */ success = SendQuery(query_buf->data); slashCmdStatus = success ? CMD_SEND : CMD_ERROR; resetPQExpBuffer(previous_buf); appendPQExpBufferStr(previous_buf, query_buf->data); resetPQExpBuffer(query_buf); added_nl_pos = -1; /* we need not do psql_scan_reset() here */ } else if (scan_result == PSCAN_BACKSLASH) { /* handle backslash command */ /* * If we added a newline to query_buf, and nothing else * has been inserted in query_buf by the lexer, then strip * off the newline again. This avoids any change to * query_buf when a line contains only a backslash * command. */ if (query_buf->len == added_nl_pos) query_buf->data[--query_buf->len] = '\0'; added_nl_pos = -1; slashCmdStatus = HandleSlashCmds(scan_state, query_buf->len > 0 ? query_buf : previous_buf); success = slashCmdStatus != CMD_ERROR; if ((slashCmdStatus == CMD_SEND || slashCmdStatus == CMD_NEWEDIT) && query_buf->len == 0) { /* copy previous buffer to current for handling */ appendPQExpBufferStr(query_buf, previous_buf->data); } if (slashCmdStatus == CMD_SEND) { success = SendQuery(query_buf->data); resetPQExpBuffer(previous_buf); appendPQExpBufferStr(previous_buf, query_buf->data); resetPQExpBuffer(query_buf); /* flush any paren nesting info after forced send */ psql_scan_reset(scan_state); } if (slashCmdStatus == CMD_TERMINATE) break; } /* fall out of loop if lexer reached EOL */ if (scan_result == PSCAN_INCOMPLETE || scan_result == PSCAN_EOL) break; } psql_scan_finish(scan_state); free(line); if (slashCmdStatus == CMD_TERMINATE) { successResult = EXIT_SUCCESS; break; } if (!pset.cur_cmd_interactive) { if (!success && die_on_error) successResult = EXIT_USER; /* Have we lost the db connection? */ else if (!pset.db) successResult = EXIT_BADCONN; } } /* while !endoffile/session */ /* * Process query at the end of file without a semicolon */ if (query_buf->len > 0 && !pset.cur_cmd_interactive && successResult == EXIT_SUCCESS) { success = SendQuery(query_buf->data); if (!success && die_on_error) successResult = EXIT_USER; else if (pset.db == NULL) successResult = EXIT_BADCONN; } /* * Reset SIGINT handler because main_loop_jmp will be invalid as soon * as we exit this routine. If there is an outer MainLoop instance, * it will re-enable ^C catching as soon as it gets back to the top of * its loop and resets main_loop_jmp to point to itself. */ #ifndef WIN32 pqsignal(SIGINT, SIG_DFL); #endif destroyPQExpBuffer(query_buf); destroyPQExpBuffer(previous_buf); psql_scan_destroy(scan_state); pset.cur_cmd_source = prev_cmd_source; pset.cur_cmd_interactive = prev_cmd_interactive; pset.lineno = prev_lineno; return successResult; } /* MainLoop() */
/* * Main processing loop for reading lines of input * and sending them to the backend. * * This loop is re-entrant. May be called by \i command * which reads input from a file. */ int MainLoop(FILE *source) { PsqlScanState scan_state; /* lexer working state */ ConditionalStack cond_stack; /* \if status stack */ volatile PQExpBuffer query_buf; /* buffer for query being accumulated */ volatile PQExpBuffer previous_buf; /* if there isn't anything in the new * buffer yet, use this one for \e, * etc. */ PQExpBuffer history_buf; /* earlier lines of a multi-line command, not * yet saved to readline history */ char *line; /* current line of input */ int added_nl_pos; bool success; bool line_saved_in_history; volatile int successResult = EXIT_SUCCESS; volatile backslashResult slashCmdStatus = PSQL_CMD_UNKNOWN; volatile promptStatus_t prompt_status = PROMPT_READY; volatile int count_eof = 0; volatile bool die_on_error = false; FILE *prev_cmd_source; bool prev_cmd_interactive; uint64 prev_lineno; /* Save the prior command source */ prev_cmd_source = pset.cur_cmd_source; prev_cmd_interactive = pset.cur_cmd_interactive; prev_lineno = pset.lineno; /* pset.stmt_lineno does not need to be saved and restored */ /* Establish new source */ pset.cur_cmd_source = source; pset.cur_cmd_interactive = ((source == stdin) && !pset.notty); pset.lineno = 0; pset.stmt_lineno = 1; /* Create working state */ scan_state = psql_scan_create(&psqlscan_callbacks); cond_stack = conditional_stack_create(); psql_scan_set_passthrough(scan_state, (void *) cond_stack); query_buf = createPQExpBuffer(); previous_buf = createPQExpBuffer(); history_buf = createPQExpBuffer(); if (PQExpBufferBroken(query_buf) || PQExpBufferBroken(previous_buf) || PQExpBufferBroken(history_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* main loop to get queries and execute them */ while (successResult == EXIT_SUCCESS) { /* * Clean up after a previous Control-C */ if (cancel_pressed) { if (!pset.cur_cmd_interactive) { /* * You get here if you stopped a script with Ctrl-C. */ successResult = EXIT_USER; break; } cancel_pressed = false; } /* * Establish longjmp destination for exiting from wait-for-input. We * must re-do this each time through the loop for safety, since the * jmpbuf might get changed during command execution. */ if (sigsetjmp(sigint_interrupt_jmp, 1) != 0) { /* got here with longjmp */ /* reset parsing state */ psql_scan_finish(scan_state); psql_scan_reset(scan_state); resetPQExpBuffer(query_buf); resetPQExpBuffer(history_buf); count_eof = 0; slashCmdStatus = PSQL_CMD_UNKNOWN; prompt_status = PROMPT_READY; pset.stmt_lineno = 1; cancel_pressed = false; if (pset.cur_cmd_interactive) { putc('\n', stdout); /* * if interactive user is in an \if block, then Ctrl-C will * exit from the innermost \if. */ if (!conditional_stack_empty(cond_stack)) { psql_error("\\if: escaped\n"); conditional_stack_pop(cond_stack); } } else { successResult = EXIT_USER; break; } } fflush(stdout); /* * get another line */ if (pset.cur_cmd_interactive) { /* May need to reset prompt, eg after \r command */ if (query_buf->len == 0) prompt_status = PROMPT_READY; line = gets_interactive(get_prompt(prompt_status, cond_stack), query_buf); } else { line = gets_fromFile(source); if (!line && ferror(source)) successResult = EXIT_FAILURE; } /* * query_buf holds query already accumulated. line is the malloc'd * new line of input (note it must be freed before looping around!) */ /* No more input. Time to quit, or \i done */ if (line == NULL) { if (pset.cur_cmd_interactive) { /* This tries to mimic bash's IGNOREEOF feature. */ count_eof++; if (count_eof < pset.ignoreeof) { if (!pset.quiet) printf(_("Use \"\\q\" to leave %s.\n"), pset.progname); continue; } puts(pset.quiet ? "" : "\\q"); } break; } count_eof = 0; pset.lineno++; /* ignore UTF-8 Unicode byte-order mark */ if (pset.lineno == 1 && pset.encoding == PG_UTF8 && strncmp(line, "\xef\xbb\xbf", 3) == 0) memmove(line, line + 3, strlen(line + 3) + 1); /* Detect attempts to run custom-format dumps as SQL scripts */ if (pset.lineno == 1 && !pset.cur_cmd_interactive && strncmp(line, "PGDMP", 5) == 0) { free(line); puts(_("The input is a PostgreSQL custom-format dump.\n" "Use the pg_restore command-line client to restore this dump to a database.\n")); fflush(stdout); successResult = EXIT_FAILURE; break; } /* no further processing of empty lines, unless within a literal */ if (line[0] == '\0' && !psql_scan_in_quote(scan_state)) { free(line); continue; } /* A request for help? Be friendly and give them some guidance */ if (pset.cur_cmd_interactive && query_buf->len == 0 && pg_strncasecmp(line, "help", 4) == 0 && (line[4] == '\0' || line[4] == ';' || isspace((unsigned char) line[4]))) { free(line); puts(_("You are using psql, the command-line interface to PostgreSQL.")); printf(_("Type: \\copyright for distribution terms\n" " \\h for help with SQL commands\n" " \\? for help with psql commands\n" " \\g or terminate with semicolon to execute query\n" " \\q to quit\n")); fflush(stdout); continue; } /* echo back if flag is set, unless interactive */ if (pset.echo == PSQL_ECHO_ALL && !pset.cur_cmd_interactive) { puts(line); fflush(stdout); } /* insert newlines into query buffer between source lines */ if (query_buf->len > 0) { appendPQExpBufferChar(query_buf, '\n'); added_nl_pos = query_buf->len; } else added_nl_pos = -1; /* flag we didn't add one */ /* Setting this will not have effect until next line. */ die_on_error = pset.on_error_stop; /* * Parse line, looking for command separators. */ psql_scan_setup(scan_state, line, strlen(line), pset.encoding, standard_strings()); success = true; line_saved_in_history = false; while (success || !die_on_error) { PsqlScanResult scan_result; promptStatus_t prompt_tmp = prompt_status; size_t pos_in_query; char *tmp_line; pos_in_query = query_buf->len; scan_result = psql_scan(scan_state, query_buf, &prompt_tmp); prompt_status = prompt_tmp; if (PQExpBufferBroken(query_buf)) { psql_error("out of memory\n"); exit(EXIT_FAILURE); } /* * Increase statement line number counter for each linebreak added * to the query buffer by the last psql_scan() call. There only * will be ones to add when navigating to a statement in * readline's history containing newlines. */ tmp_line = query_buf->data + pos_in_query; while (*tmp_line != '\0') { if (*(tmp_line++) == '\n') pset.stmt_lineno++; } if (scan_result == PSCAN_EOL) pset.stmt_lineno++; /* * Send command if semicolon found, or if end of line and we're in * single-line mode. */ if (scan_result == PSCAN_SEMICOLON || (scan_result == PSCAN_EOL && pset.singleline)) { /* * Save line in history. We use history_buf to accumulate * multi-line queries into a single history entry. Note that * history accumulation works on input lines, so it doesn't * matter whether the query will be ignored due to \if. */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute query unless we're in an inactive \if branch */ if (conditional_active(cond_stack)) { success = SendQuery(query_buf->data); slashCmdStatus = success ? PSQL_CMD_SEND : PSQL_CMD_ERROR; pset.stmt_lineno = 1; /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); added_nl_pos = -1; /* we need not do psql_scan_reset() here */ } else { /* if interactive, warn about non-executed query */ if (pset.cur_cmd_interactive) psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n"); /* fake an OK result for purposes of loop checks */ success = true; slashCmdStatus = PSQL_CMD_SEND; pset.stmt_lineno = 1; /* note that query_buf doesn't change state */ } } else if (scan_result == PSCAN_BACKSLASH) { /* handle backslash command */ /* * If we added a newline to query_buf, and nothing else has * been inserted in query_buf by the lexer, then strip off the * newline again. This avoids any change to query_buf when a * line contains only a backslash command. Also, in this * situation we force out any previous lines as a separate * history entry; we don't want SQL and backslash commands * intermixed in history if at all possible. */ if (query_buf->len == added_nl_pos) { query_buf->data[--query_buf->len] = '\0'; pg_send_history(history_buf); } added_nl_pos = -1; /* save backslash command in history */ if (pset.cur_cmd_interactive && !line_saved_in_history) { pg_append_history(line, history_buf); pg_send_history(history_buf); line_saved_in_history = true; } /* execute backslash command */ slashCmdStatus = HandleSlashCmds(scan_state, cond_stack, query_buf, previous_buf); success = slashCmdStatus != PSQL_CMD_ERROR; /* * Resetting stmt_lineno after a backslash command isn't * always appropriate, but it's what we've done historically * and there have been few complaints. */ pset.stmt_lineno = 1; if (slashCmdStatus == PSQL_CMD_SEND) { /* should not see this in inactive branch */ Assert(conditional_active(cond_stack)); success = SendQuery(query_buf->data); /* transfer query to previous_buf by pointer-swapping */ { PQExpBuffer swap_buf = previous_buf; previous_buf = query_buf; query_buf = swap_buf; } resetPQExpBuffer(query_buf); /* flush any paren nesting info after forced send */ psql_scan_reset(scan_state); } else if (slashCmdStatus == PSQL_CMD_NEWEDIT) { /* should not see this in inactive branch */ Assert(conditional_active(cond_stack)); /* rescan query_buf as new input */ psql_scan_finish(scan_state); free(line); line = pg_strdup(query_buf->data); resetPQExpBuffer(query_buf); /* reset parsing state since we are rescanning whole line */ psql_scan_reset(scan_state); psql_scan_setup(scan_state, line, strlen(line), pset.encoding, standard_strings()); line_saved_in_history = false; prompt_status = PROMPT_READY; } else if (slashCmdStatus == PSQL_CMD_TERMINATE) break; } /* fall out of loop if lexer reached EOL */ if (scan_result == PSCAN_INCOMPLETE || scan_result == PSCAN_EOL) break; } /* Add line to pending history if we didn't execute anything yet */ if (pset.cur_cmd_interactive && !line_saved_in_history) pg_append_history(line, history_buf); psql_scan_finish(scan_state); free(line); if (slashCmdStatus == PSQL_CMD_TERMINATE) { successResult = EXIT_SUCCESS; break; } if (!pset.cur_cmd_interactive) { if (!success && die_on_error) successResult = EXIT_USER; /* Have we lost the db connection? */ else if (!pset.db) successResult = EXIT_BADCONN; } } /* while !endoffile/session */ /* * If we have a non-semicolon-terminated query at the end of file, we * process it unless the input source is interactive --- in that case it * seems better to go ahead and quit. Also skip if this is an error exit. */ if (query_buf->len > 0 && !pset.cur_cmd_interactive && successResult == EXIT_SUCCESS) { /* save query in history */ /* currently unneeded since we don't use this block if interactive */ #ifdef NOT_USED if (pset.cur_cmd_interactive) pg_send_history(history_buf); #endif /* execute query unless we're in an inactive \if branch */ if (conditional_active(cond_stack)) { success = SendQuery(query_buf->data); } else { if (pset.cur_cmd_interactive) psql_error("query ignored; use \\endif or Ctrl-C to exit current \\if block\n"); success = true; } if (!success && die_on_error) successResult = EXIT_USER; else if (pset.db == NULL) successResult = EXIT_BADCONN; } /* * Check for unbalanced \if-\endifs unless user explicitly quit, or the * script is erroring out */ if (slashCmdStatus != PSQL_CMD_TERMINATE && successResult != EXIT_USER && !conditional_stack_empty(cond_stack)) { psql_error("reached EOF without finding closing \\endif(s)\n"); if (die_on_error && !pset.cur_cmd_interactive) successResult = EXIT_USER; } /* * Let's just make real sure the SIGINT handler won't try to use * sigint_interrupt_jmp after we exit this routine. If there is an outer * MainLoop instance, it will reset sigint_interrupt_jmp to point to * itself at the top of its loop, before any further interactive input * happens. */ sigint_interrupt_enabled = false; destroyPQExpBuffer(query_buf); destroyPQExpBuffer(previous_buf); destroyPQExpBuffer(history_buf); psql_scan_destroy(scan_state); conditional_stack_destroy(cond_stack); pset.cur_cmd_source = prev_cmd_source; pset.cur_cmd_interactive = prev_cmd_interactive; pset.lineno = prev_lineno; return successResult; } /* MainLoop() */