summaryrefslogtreecommitdiff
path: root/src/bin/psql/mainloop.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/psql/mainloop.c')
-rw-r--r--src/bin/psql/mainloop.c368
1 files changed, 368 insertions, 0 deletions
diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c
new file mode 100644
index 00000000000..2f38ffbcff9
--- /dev/null
+++ b/src/bin/psql/mainloop.c
@@ -0,0 +1,368 @@
+#include <config.h>
+#include <c.h>
+#include "mainloop.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pqexpbuffer.h>
+
+#include "settings.h"
+#include "prompt.h"
+#include "input.h"
+#include "common.h"
+#include "command.h"
+
+
+
+/* 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(PsqlSettings *pset, FILE *source)
+{
+ PQExpBuffer query_buf; /* buffer for query being accumulated */
+ char *line; /* current line of input */
+ char *xcomment; /* start of extended comment */
+ int len; /* length of the line */
+ int successResult = EXIT_SUCCESS;
+ backslashResult slashCmdStatus;
+
+ bool eof = false; /* end of our command input? */
+ bool success;
+ char in_quote; /* == 0 for no in_quote */
+ bool was_bslash; /* backslash */
+ int paren_level;
+ unsigned int query_start;
+
+ int i, prevlen, thislen;
+
+ /* Save the prior command source */
+ FILE *prev_cmd_source;
+ bool prev_cmd_interactive;
+
+ bool die_on_error;
+ const char *interpol_char;
+
+
+ /* Save old settings */
+ prev_cmd_source = pset->cur_cmd_source;
+ prev_cmd_interactive = pset->cur_cmd_interactive;
+
+ /* Establish new source */
+ pset->cur_cmd_source = source;
+ pset->cur_cmd_interactive = ((source == stdin) && !pset->notty);
+
+
+ query_buf = createPQExpBuffer();
+ if (!query_buf) {
+ perror("createPQExpBuffer");
+ exit(EXIT_FAILURE);
+ }
+
+ xcomment = NULL;
+ in_quote = 0;
+ paren_level = 0;
+ slashCmdStatus = CMD_UNKNOWN; /* set default */
+
+
+ /* main loop to get queries and execute them */
+ while (!eof)
+ {
+ if (slashCmdStatus == CMD_NEWEDIT)
+ {
+ /*
+ * just returned from editing the line? then just copy to the
+ * input buffer
+ */
+ line = strdup(query_buf->data);
+ resetPQExpBuffer(query_buf);
+ /* reset parsing state since we are rescanning whole query */
+ xcomment = NULL;
+ in_quote = 0;
+ paren_level = 0;
+ }
+ else
+ {
+ /*
+ * otherwise, set interactive prompt if necessary
+ * and get another line
+ */
+ if (pset->cur_cmd_interactive)
+ {
+ int prompt_status;
+
+ if (in_quote && in_quote == '\'')
+ prompt_status = PROMPT_SINGLEQUOTE;
+ else if (in_quote && in_quote == '"')
+ prompt_status= PROMPT_DOUBLEQUOTE;
+ else if (xcomment != NULL)
+ prompt_status = PROMPT_COMMENT;
+ else if (query_buf->len > 0)
+ prompt_status = PROMPT_CONTINUE;
+ else
+ prompt_status = PROMPT_READY;
+
+ line = gets_interactive(get_prompt(pset, prompt_status));
+ }
+ else
+ line = gets_fromFile(source);
+ }
+
+
+ /* Setting these will not have effect until next line */
+ die_on_error = GetVariableBool(pset->vars, "die_on_error");
+ interpol_char = GetVariable(pset->vars, "sql_interpol");;
+
+
+ /*
+ * query_buf holds query already accumulated. line is the malloc'd
+ * new line of input (note it must be freed before looping around!)
+ * query_start is the next command start location within the line.
+ */
+
+ /* No more input. Time to quit, or \i done */
+ if (line == NULL || (!pset->cur_cmd_interactive && *line == '\0'))
+ {
+ if (GetVariableBool(pset->vars, "echo") && !GetVariableBool(pset->vars, "quiet"))
+ puts("EOF");
+ eof = true;
+ continue;
+ }
+
+ /* not currently inside an extended comment? */
+ if (xcomment)
+ xcomment = line;
+
+
+ /* strip trailing backslashes, they don't have a clear meaning */
+ while (1) {
+ char * cp = strrchr(line, '\\');
+ if (cp && (*(cp + 1) == '\0'))
+ *cp = '\0';
+ else
+ break;
+ }
+
+
+ /* echo back if input is from file and flag is set */
+ if (!pset->cur_cmd_interactive && GetVariableBool(pset->vars, "echo"))
+ fprintf(stderr, "%s\n", line);
+
+
+ /* interpolate variables into SQL */
+ len = strlen(line);
+ thislen = PQmblen(line);
+
+ for (i = 0; line[i]; i += (thislen = PQmblen(&line[i])) ) {
+ if (interpol_char && interpol_char[0] != '\0' && interpol_char[0] == line[i]) {
+ size_t in_length, out_length;
+ const char * value;
+ char * new;
+ bool closer; /* did we have a closing delimiter or just an end of line? */
+
+ in_length = strcspn(&line[i+thislen], interpol_char);
+ closer = line[i + thislen + in_length] == line[i];
+ line[i + thislen + in_length] = '\0';
+ value = interpolate_var(&line[i + thislen], pset);
+ out_length = strlen(value);
+
+ new = malloc(len + out_length - (in_length + (closer ? 2 : 1)) + 1);
+ if (!new) {
+ perror("malloc");
+ exit(EXIT_FAILURE);
+ }
+
+ new[0] = '\0';
+ strncat(new, line, i);
+ strcat(new, value);
+ if (closer)
+ strcat(new, line + i + 2 + in_length);
+
+ free(line);
+ line = new;
+ i += out_length;
+ }
+ }
+
+ /* nothing left on line? then ignore */
+ if (line[0] == '\0') {
+ free(line);
+ continue;
+ }
+
+ slashCmdStatus = CMD_UNKNOWN;
+
+ len = strlen(line);
+ query_start = 0;
+
+ /*
+ * Parse line, looking for command separators.
+ *
+ * The current character is at line[i], the prior character at
+ * line[i - prevlen], the next character at line[i + thislen].
+ */
+ prevlen = 0;
+ thislen = (len > 0) ? PQmblen(line) : 0;
+
+#define ADVANCE_1 (prevlen = thislen, i += thislen, thislen = PQmblen(line+i))
+
+ success = true;
+ for (i = 0; i < len; ADVANCE_1) {
+ if (!success && die_on_error)
+ break;
+
+
+ /* was the previous character a backslash? */
+ if (i > 0 && line[i - prevlen] == '\\')
+ was_bslash = true;
+ else
+ was_bslash = false;
+
+
+ /* in quote? */
+ if (in_quote) {
+ /* end of quote */
+ if (line[i] == in_quote && !was_bslash)
+ in_quote = '\0';
+ }
+
+ /* start of quote */
+ else if (line[i] == '\'' || line[i] == '"')
+ in_quote = line[i];
+
+ /* in extended comment? */
+ else if (xcomment != NULL) {
+ if (line[i] == '*' && line[i + thislen] == '/') {
+ xcomment = NULL;
+ ADVANCE_1;
+ }
+ }
+
+ /* start of extended comment? */
+ else if (line[i] == '/' && line[i + thislen] == '*') {
+ xcomment = &line[i];
+ ADVANCE_1;
+ }
+
+ /* single-line comment? truncate line */
+ else if ((line[i] == '-' && line[i + thislen] == '-') ||
+ (line[i] == '/' && line[i + thislen] == '/'))
+ {
+ line[i] = '\0'; /* remove comment */
+ break;
+ }
+
+ /* count nested parentheses */
+ else if (line[i] == '(')
+ paren_level++;
+
+ else if (line[i] == ')' && paren_level > 0)
+ paren_level--;
+
+ /* semicolon? then send query */
+ else if (line[i] == ';' && !was_bslash && paren_level==0) {
+ line[i] = '\0';
+ /* is there anything else on the line? */
+ if (line[query_start + strspn(line + query_start, " \t")]!='\0') {
+ /* insert a cosmetic newline, if this is not the first line in the buffer */
+ if (query_buf->len > 0)
+ appendPQExpBufferChar(query_buf, '\n');
+ /* append the line to the query buffer */
+ appendPQExpBufferStr(query_buf, line + query_start);
+ }
+
+ /* execute query */
+ success = SendQuery(pset, query_buf->data);
+
+ resetPQExpBuffer(query_buf);
+ query_start = i + thislen;
+ }
+
+ /* backslash command */
+ else if (was_bslash) {
+ const char * end_of_cmd = NULL;
+
+ line[i - prevlen] = '\0'; /* overwrites backslash */
+
+ /* is there anything else on the line? */
+ if (line[query_start + strspn(line + query_start, " \t")]!='\0') {
+ /* insert a cosmetic newline, if this is not the first line in the buffer */
+ if (query_buf->len > 0)
+ appendPQExpBufferChar(query_buf, '\n');
+ /* append the line to the query buffer */
+ appendPQExpBufferStr(query_buf, line + query_start);
+ }
+
+ /* handle backslash command */
+
+ slashCmdStatus = HandleSlashCmds(pset, &line[i], query_buf, &end_of_cmd);
+
+ success = slashCmdStatus != CMD_ERROR;
+
+ if (slashCmdStatus == CMD_SEND) {
+ success = SendQuery(pset, query_buf->data);
+ resetPQExpBuffer(query_buf);
+ query_start = i + thislen;
+ }
+
+ /* is there anything left after the backslash command? */
+ if (end_of_cmd) {
+ i += end_of_cmd - &line[i];
+ query_start = i;
+ }
+ else
+ break;
+ }
+ }
+
+
+ if (!success && die_on_error && !pset->cur_cmd_interactive) {
+ successResult = EXIT_USER;
+ break;
+ }
+
+
+ if (slashCmdStatus == CMD_TERMINATE) {
+ successResult = EXIT_SUCCESS;
+ break;
+ }
+
+
+ /* Put the rest of the line in the query buffer. */
+ if (line[query_start + strspn(line + query_start, " \t")]!='\0') {
+ if (query_buf->len > 0)
+ appendPQExpBufferChar(query_buf, '\n');
+ appendPQExpBufferStr(query_buf, line + query_start);
+ }
+
+ free(line);
+
+
+ /* In single line mode, send off the query if any */
+ if (query_buf->data[0] != '\0' && GetVariableBool(pset->vars, "singleline")) {
+ success = SendQuery(pset, query_buf->data);
+ resetPQExpBuffer(query_buf);
+ }
+
+
+ /* Have we lost the db connection? */
+ if (pset->db == NULL && !pset->cur_cmd_interactive) {
+ successResult = EXIT_BADCONN;
+ break;
+ }
+ } /* while */
+
+ destroyPQExpBuffer(query_buf);
+
+ pset->cur_cmd_source = prev_cmd_source;
+ pset->cur_cmd_interactive = prev_cmd_interactive;
+
+ return successResult;
+} /* MainLoop() */
+