postgres/src/bin/psql/mainloop.c

408 lines
9.0 KiB
C
Raw Normal View History

#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)
{
1999-11-04 23:14:30 +00:00
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;
1999-11-04 23:14:30 +00:00
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;
1999-11-04 23:14:30 +00:00
int i,
prevlen,
thislen;
1999-11-04 23:14:30 +00:00
/* Save the prior command source */
FILE *prev_cmd_source;
bool prev_cmd_interactive;
1999-11-04 23:14:30 +00:00
bool die_on_error;
const char *interpol_char;
1999-11-04 23:14:30 +00:00
/* Save old settings */
prev_cmd_source = pset->cur_cmd_source;
prev_cmd_interactive = pset->cur_cmd_interactive;
1999-11-04 23:14:30 +00:00
/* Establish new source */
pset->cur_cmd_source = source;
pset->cur_cmd_interactive = ((source == stdin) && !pset->notty);
1999-11-04 23:14:30 +00:00
query_buf = createPQExpBuffer();
if (!query_buf)
{
perror("createPQExpBuffer");
exit(EXIT_FAILURE);
}
1999-11-04 23:14:30 +00:00
xcomment = NULL;
in_quote = 0;
paren_level = 0;
slashCmdStatus = CMD_UNKNOWN; /* set default */
1999-11-04 23:14:30 +00:00
/* main loop to get queries and execute them */
while (!eof)
{
1999-11-04 23:14:30 +00:00
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
1999-11-04 23:14:30 +00:00
{
/*
* 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);
}
1999-11-04 23:14:30 +00:00
/* 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");;
1999-11-04 23:14:30 +00:00
/*
* 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.
*/
1999-11-04 23:14:30 +00:00
/* No more input. Time to quit, or \i done */
if (line == NULL)
1999-11-04 23:14:30 +00:00
{
if (GetVariableBool(pset->vars, "echo") && !GetVariableBool(pset->vars, "quiet"))
1999-11-04 23:26:02 +00:00
puts("EOF\n");
else
puts(""); /* put out newline */
1999-11-04 23:14:30 +00:00
eof = true;
continue;
}
1999-11-04 23:14:30 +00:00
/* not currently inside an extended comment? */
if (xcomment)
xcomment = line;
1999-11-04 23:14:30 +00:00
/* strip trailing backslashes, they don't have a clear meaning */
while (1)
{
char *cp = strrchr(line, '\\');
1999-11-04 23:14:30 +00:00
if (cp && (*(cp + 1) == '\0'))
*cp = '\0';
else
break;
}
1999-11-04 23:14:30 +00:00
/* 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;
}
}
1999-11-04 23:14:30 +00:00
/* nothing left on line? then ignore */
if (line[0] == '\0')
{
free(line);
continue;
}
1999-11-04 23:14:30 +00:00
slashCmdStatus = CMD_UNKNOWN;
1999-11-04 23:14:30 +00:00
len = strlen(line);
query_start = 0;
1999-11-04 23:14:30 +00:00
/*
* 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))
1999-11-04 23:14:30 +00:00
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;
}
}
1999-11-04 23:14:30 +00:00
if (!success && die_on_error && !pset->cur_cmd_interactive)
{
successResult = EXIT_USER;
break;
}
1999-11-04 23:14:30 +00:00
if (slashCmdStatus == CMD_TERMINATE)
{
successResult = EXIT_SUCCESS;
break;
}
1999-11-04 23:14:30 +00:00
/* 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);
}
1999-11-04 23:14:30 +00:00
free(line);
1999-11-04 23:14:30 +00:00
/* 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);
}
1999-11-04 23:14:30 +00:00
/* Have we lost the db connection? */
if (pset->db == NULL && !pset->cur_cmd_interactive)
{
successResult = EXIT_BADCONN;
break;
}
} /* while */
1999-11-04 23:14:30 +00:00
destroyPQExpBuffer(query_buf);
1999-11-04 23:14:30 +00:00
pset->cur_cmd_source = prev_cmd_source;
pset->cur_cmd_interactive = prev_cmd_interactive;
1999-11-04 23:14:30 +00:00
return successResult;
} /* MainLoop() */