postgresql/src/bin/psql/mainloop.c

533 lines
13 KiB
C

/*
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
*
* $Header: /cvsroot/pgsql/src/bin/psql/mainloop.c,v 1.57 2003/08/04 23:59:40 tgl Exp $
*/
#include "postgres_fe.h"
#include "mainloop.h"
#include "pqexpbuffer.h"
#include "settings.h"
#include "prompt.h"
#include "input.h"
#include "common.h"
#include "command.h"
#ifndef WIN32
#include <setjmp.h>
sigjmp_buf main_loop_jmp;
#endif
/*
* 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.
*
* FIXME: rewrite this whole thing with flex
*/
int
MainLoop(FILE *source)
{
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 len; /* length of the line */
volatile int successResult = EXIT_SUCCESS;
volatile backslashResult slashCmdStatus = CMD_UNKNOWN;
bool success;
volatile char in_quote = 0; /* == 0 for no in_quote */
volatile int in_xcomment = 0; /* in extended comment */
volatile int paren_level = 0;
unsigned int query_start;
volatile int count_eof = 0;
volatile unsigned int bslash_count = 0;
int i,
prevlen,
thislen;
/* Save the prior command source */
FILE *prev_cmd_source;
bool prev_cmd_interactive;
unsigned int prev_lineno;
volatile bool die_on_error = false;
/* 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();
previous_buf = createPQExpBuffer();
if (!query_buf || !previous_buf)
{
psql_error("out of memory\n");
exit(EXIT_FAILURE);
}
prev_lineno = pset.lineno;
pset.lineno = 0;
/* 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;
fflush(stdout);
}
#ifndef WIN32
if (sigsetjmp(main_loop_jmp, 1) != 0)
{
/* got here with longjmp */
if (pset.cur_cmd_interactive)
{
putc('\n', stdout);
resetPQExpBuffer(query_buf);
/* reset parsing state */
in_xcomment = 0;
in_quote = 0;
paren_level = 0;
count_eof = 0;
slashCmdStatus = CMD_UNKNOWN;
fflush(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 */
#endif /* not WIN32 */
if (slashCmdStatus == CMD_NEWEDIT)
{
/*
* just returned from editing the line? then just copy to the
* input buffer
*/
line = xstrdup(query_buf->data);
resetPQExpBuffer(query_buf);
/* reset parsing state since we are rescanning whole line */
in_xcomment = 0;
in_quote = 0;
paren_level = 0;
slashCmdStatus = CMD_UNKNOWN;
}
/*
* otherwise, set interactive prompt if necessary and get another
* line
*/
else if (pset.cur_cmd_interactive)
{
int prompt_status;
fflush(stdout);
if (in_quote && in_quote == '\'')
prompt_status = PROMPT_SINGLEQUOTE;
else if (in_quote && in_quote == '"')
prompt_status = PROMPT_DOUBLEQUOTE;
else if (in_xcomment)
prompt_status = PROMPT_COMMENT;
else if (paren_level)
prompt_status = PROMPT_PAREN;
else if (query_buf->len > 0)
prompt_status = PROMPT_CONTINUE;
else
prompt_status = PROMPT_READY;
line = gets_interactive(get_prompt(prompt_status));
}
else
line = gets_fromFile(source);
/* Setting this will not have effect until next line. */
die_on_error = GetVariableBool(pset.vars, "ON_ERROR_STOP");
/*
* 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)
{
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' && !in_quote)
{
free(line);
continue;
}
/* echo back if flag is set */
if (!pset.cur_cmd_interactive && VariableEquals(pset.vars, "ECHO", "all"))
puts(line);
fflush(stdout);
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].
*/
#define ADVANCE_1 (prevlen = thislen, i += thislen, thislen = PQmblen(line+i, pset.encoding))
success = true;
prevlen = 0;
thislen = ((len > 0) ? PQmblen(line, pset.encoding) : 0);
for (i = 0; (i < len) && (success || !die_on_error); ADVANCE_1)
{
/* was the previous character a backslash? */
if (i > 0 && line[i - prevlen] == '\\')
bslash_count++;
else
bslash_count = 0;
rescan:
/*
* It is important to place the in_* test routines before the
* in_* detection routines. i.e. we have to test if we are in
* a quote before testing for comments. bjm 2000-06-30
*/
/* in quote? */
if (in_quote)
{
/*
* end of quote if matching non-backslashed character.
* backslashes don't count for double quotes, though.
*/
if (line[i] == in_quote &&
(bslash_count % 2 == 0 || in_quote == '"'))
in_quote = 0;
}
/* start of extended comment? */
else if (line[i] == '/' && line[i + thislen] == '*')
{
in_xcomment++;
if (in_xcomment == 1)
ADVANCE_1;
}
/* in or end of extended comment? */
else if (in_xcomment)
{
if (line[i] == '*' && line[i + thislen] == '/' &&
!--in_xcomment)
ADVANCE_1;
}
/* start of quote? */
else if (line[i] == '\'' || line[i] == '"')
in_quote = line[i];
/* single-line comment? truncate line */
else if (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--;
/* colon -> substitute variable */
/* we need to be on the watch for the '::' operator */
else if (line[i] == ':' && !bslash_count
&& strspn(line + i + thislen, VALID_VARIABLE_CHARS) > 0
&& !(prevlen > 0 && line[i - prevlen] == ':')
)
{
size_t in_length,
out_length;
const char *value;
char *new;
char after; /* the character after the
* variable name will be
* temporarily overwritten */
in_length = strspn(&line[i + thislen], VALID_VARIABLE_CHARS);
/* mark off the possible variable name */
after = line[i + thislen + in_length];
line[i + thislen + in_length] = '\0';
value = GetVariable(pset.vars, &line[i + thislen]);
/* restore overwritten character */
line[i + thislen + in_length] = after;
if (value)
{
/* It is a variable, perform substitution */
out_length = strlen(value);
new = malloc(len + out_length - in_length + 1);
if (!new)
{
psql_error("out of memory\n");
exit(EXIT_FAILURE);
}
sprintf(new, "%.*s%s%s", i, line, value,
&line[i + thislen + in_length]);
free(line);
line = new;
len = strlen(new);
if (i < len)
{
thislen = PQmblen(line + i, pset.encoding);
goto rescan; /* reparse the just substituted */
}
}
else
{
/*
* if the variable doesn't exist we'll leave the
* string as is ... move on ...
*/
}
}
/* semicolon? then send query */
else if (line[i] == ';' && !bslash_count && !paren_level)
{
line[i] = '\0';
/* is there anything else on the line? */
if (line[query_start + strspn(line + query_start, " \t\n\r")] != '\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);
appendPQExpBufferChar(query_buf, ';');
}
/* 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);
query_start = i + thislen;
}
/*
* if you have a burning need to send a semicolon or colon to
* the backend ...
*/
else if (bslash_count && (line[i] == ';' || line[i] == ':'))
{
/* remove the backslash */
memmove(line + i - prevlen, line + i, len - i + 1);
len--;
i--;
}
/* backslash command */
else if (bslash_count)
{
const char *end_of_cmd = NULL;
line[i - prevlen] = '\0'; /* overwrites backslash */
/* is there anything else on the line for the command? */
if (line[query_start + strspn(line + query_start, " \t\n\r")] != '\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(&line[i],
query_buf->len > 0 ? query_buf : previous_buf,
&end_of_cmd,
&paren_level);
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);
query_start = i + thislen;
resetPQExpBuffer(previous_buf);
appendPQExpBufferStr(previous_buf, query_buf->data);
resetPQExpBuffer(query_buf);
}
if (query_buf->len == 0 && previous_buf->len == 0)
paren_level = 0;
/* process anything left after the backslash command */
i = end_of_cmd - line;
query_start = i;
}
} /* for (line) */
if (slashCmdStatus == CMD_TERMINATE)
{
successResult = EXIT_SUCCESS;
break;
}
/* Put the rest of the line in the query buffer. */
if (in_quote || line[query_start + strspn(line + query_start, " \t\n\r")] != '\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(query_buf->data);
slashCmdStatus = (success ? CMD_SEND : CMD_ERROR);
resetPQExpBuffer(previous_buf);
appendPQExpBufferStr(previous_buf, query_buf->data);
resetPQExpBuffer(query_buf);
}
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);
pset.cur_cmd_source = prev_cmd_source;
pset.cur_cmd_interactive = prev_cmd_interactive;
pset.lineno = prev_lineno;
return successResult;
} /* MainLoop() */