| /* |
| * psql - the PostgreSQL interactive terminal |
| * |
| * Copyright (c) 2000-2020, PostgreSQL Global Development Group |
| * |
| * src/bin/psql/mainloop.c |
| */ |
| #include "postgres_fe.h" |
| |
| #include "command.h" |
| #include "common.h" |
| #include "common/logging.h" |
| #include "input.h" |
| #include "mainloop.h" |
| #include "mb/pg_wchar.h" |
| #include "prompt.h" |
| #include "settings.h" |
| |
| /* callback functions for our flex lexer */ |
| const PsqlScanCallbacks psqlscan_callbacks = { |
| psql_get_variable, |
| }; |
| |
| |
| /* |
| * 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 bool need_redisplay = false; |
| 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)) |
| { |
| pg_log_error("out of memory"); |
| 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; |
| need_redisplay = false; |
| 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)) |
| { |
| pg_log_error("\\if: escaped"); |
| 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; |
| /* If query buffer came from \e, redisplay it with a prompt */ |
| if (need_redisplay) |
| { |
| if (query_buf->len > 0) |
| { |
| fputs(get_prompt(PROMPT_READY, cond_stack), stdout); |
| fputs(query_buf->data, stdout); |
| fflush(stdout); |
| } |
| need_redisplay = false; |
| } |
| /* Now we can fetch a line */ |
| 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; |
| } |
| |
| /* Recognize "help", "quit", "exit" only in interactive mode */ |
| if (pset.cur_cmd_interactive) |
| { |
| char *first_word = line; |
| char *rest_of_line = NULL; |
| bool found_help = false; |
| bool found_exit_or_quit = false; |
| bool found_q = false; |
| |
| /* |
| * The assistance words, help/exit/quit, must have no whitespace |
| * before them, and only whitespace after, with an optional |
| * semicolon. This prevents indented use of these words, perhaps |
| * as identifiers, from invoking the assistance behavior. |
| */ |
| if (pg_strncasecmp(first_word, "help", 4) == 0) |
| { |
| rest_of_line = first_word + 4; |
| found_help = true; |
| } |
| else if (pg_strncasecmp(first_word, "exit", 4) == 0 || |
| pg_strncasecmp(first_word, "quit", 4) == 0) |
| { |
| rest_of_line = first_word + 4; |
| found_exit_or_quit = true; |
| } |
| else if (strncmp(first_word, "\\q", 2) == 0) |
| { |
| rest_of_line = first_word + 2; |
| found_q = true; |
| } |
| |
| /* |
| * If we found a command word, check whether the rest of the line |
| * contains only whitespace plus maybe one semicolon. If not, |
| * ignore the command word after all. These commands are only for |
| * compatibility with other SQL clients and are not documented. |
| */ |
| if (rest_of_line != NULL) |
| { |
| /* |
| * Ignore unless rest of line is whitespace, plus maybe one |
| * semicolon |
| */ |
| while (isspace((unsigned char) *rest_of_line)) |
| ++rest_of_line; |
| if (*rest_of_line == ';') |
| ++rest_of_line; |
| while (isspace((unsigned char) *rest_of_line)) |
| ++rest_of_line; |
| if (*rest_of_line != '\0') |
| { |
| found_help = false; |
| found_exit_or_quit = false; |
| } |
| } |
| |
| /* |
| * "help" is only a command when the query buffer is empty, but we |
| * emit a one-line message even when it isn't to help confused |
| * users. The text is still added to the query buffer in that |
| * case. |
| */ |
| if (found_help) |
| { |
| if (query_buf->len != 0) |
| #ifndef WIN32 |
| puts(_("Use \\? for help or press control-C to clear the input buffer.")); |
| #else |
| puts(_("Use \\? for help.")); |
| #endif |
| else |
| { |
| 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")); |
| free(line); |
| fflush(stdout); |
| continue; |
| } |
| } |
| |
| /* |
| * "quit" and "exit" are only commands when the query buffer is |
| * empty, but we emit a one-line message even when it isn't to |
| * help confused users. The text is still added to the query |
| * buffer in that case. |
| */ |
| if (found_exit_or_quit) |
| { |
| if (query_buf->len != 0) |
| { |
| if (prompt_status == PROMPT_READY || |
| prompt_status == PROMPT_CONTINUE || |
| prompt_status == PROMPT_PAREN) |
| puts(_("Use \\q to quit.")); |
| else |
| #ifndef WIN32 |
| puts(_("Use control-D to quit.")); |
| #else |
| puts(_("Use control-C to quit.")); |
| #endif |
| } |
| else |
| { |
| /* exit app */ |
| free(line); |
| fflush(stdout); |
| successResult = EXIT_SUCCESS; |
| break; |
| } |
| } |
| |
| /* |
| * If they typed "\q" in a place where "\q" is not active, supply |
| * a hint. The text is still added to the query buffer. |
| */ |
| if (found_q && query_buf->len != 0 && |
| prompt_status != PROMPT_READY && |
| prompt_status != PROMPT_CONTINUE && |
| prompt_status != PROMPT_PAREN) |
| #ifndef WIN32 |
| puts(_("Use control-D to quit.")); |
| #else |
| puts(_("Use control-C to quit.")); |
| #endif |
| } |
| |
| /* 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)) |
| { |
| pg_log_error("out of memory"); |
| 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) |
| pg_log_error("query ignored; use \\endif or Ctrl-C to exit current \\if block"); |
| /* 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)); |
| /* ensure what came back from editing ends in a newline */ |
| if (query_buf->len > 0 && |
| query_buf->data[query_buf->len - 1] != '\n') |
| appendPQExpBufferChar(query_buf, '\n'); |
| /* 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; |
| /* we'll want to redisplay after parsing what we have */ |
| need_redisplay = true; |
| } |
| 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) |
| pg_log_error("query ignored; use \\endif or Ctrl-C to exit current \\if block"); |
| 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)) |
| { |
| pg_log_error("reached EOF without finding closing \\endif(s)"); |
| 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() */ |