1
0
mirror of https://github.com/postgres/postgres.git synced 2025-07-02 09:02:37 +03:00

Re-implement psql's input scanning to use a flex-generated lexer, as per

recent discussion.  The lexer is used for both SQL command text and
backslash commands.  The purpose of this change is to make it easier to
track the behavior of the backend's SQL lexer --- essentially identical
flex rules are now used by psql.  Also, this cleans up a lot of very
squirrelly code in mainloop.c and command.c.  The flex code is somewhat
bulkier than the removed code, but should be lots easier to maintain.
This commit is contained in:
Tom Lane
2004-02-19 19:40:09 +00:00
parent 737f1cd44b
commit 4b39aa3a7c
8 changed files with 1805 additions and 838 deletions

View File

@ -3,7 +3,7 @@
*
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
*
* $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.112 2004/01/26 22:35:32 tgl Exp $
* $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.113 2004/02/19 19:40:08 tgl Exp $
*/
#include "postgres_fe.h"
#include "command.h"
@ -36,47 +36,33 @@
#include "large_obj.h"
#include "mainloop.h"
#include "print.h"
#include "psqlscan.h"
#include "settings.h"
#include "variables.h"
#include "mb/pg_wchar.h"
/* functions for use in this file */
static backslashResult exec_command(const char *cmd,
const char *options_string,
const char **continue_parse,
PQExpBuffer query_buf,
volatile int *paren_level);
/* different ways for scan_option to handle parameter words */
enum option_type
{
OT_NORMAL, /* normal case */
OT_SQLID, /* treat as SQL identifier */
OT_SQLIDHACK, /* SQL identifier, but don't downcase */
OT_FILEPIPE /* it's a filename or pipe */
};
static char *scan_option(char **string, enum option_type type,
char *quote, bool semicolon);
static char *unescape(const unsigned char *source, size_t len);
PsqlScanState scan_state,
PQExpBuffer query_buf);
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf);
static bool do_connect(const char *new_dbname, const char *new_user);
static bool do_shell(const char *command);
/*----------
* HandleSlashCmds:
*
* Handles all the different commands that start with '\',
* ordinarily called by MainLoop().
*
* 'line' is the current input line, which should not start with a '\'
* but with the actual command name
* (that is taken care of by MainLoop)
* scan_state is a lexer working state that is set to continue scanning
* just after the '\'. The lexer is advanced past the command and all
* arguments on return.
*
* 'query_buf' contains the query-so-far, which may be modified by
* execution of the backslash command (for example, \r clears it)
* execution of the backslash command (for example, \r clears it).
* query_buf can be NULL if there is no query so far.
*
* Returns a status code indicating what action is desired, see command.h.
@ -84,124 +70,88 @@ static bool do_shell(const char *command);
*/
backslashResult
HandleSlashCmds(const char *line,
PQExpBuffer query_buf,
const char **end_of_cmd,
volatile int *paren_level)
HandleSlashCmds(PsqlScanState scan_state,
PQExpBuffer query_buf)
{
backslashResult status = CMD_SKIP_LINE;
char *my_line;
char *options_string = NULL;
size_t blank_loc;
const char *continue_parse = NULL; /* tell the mainloop where the
* backslash command ended */
char *cmd;
char *arg;
psql_assert(line);
my_line = pg_strdup(line);
psql_assert(scan_state);
/*
* Find the first whitespace. line[blank_loc] will now be the
* whitespace character or the \0 at the end
*
* Also look for a backslash, so stuff like \p\g works.
*/
blank_loc = strcspn(my_line, " \t\n\r\\");
/* Parse off the command name */
cmd = psql_scan_slash_command(scan_state);
if (my_line[blank_loc] == '\\')
{
continue_parse = &my_line[blank_loc];
my_line[blank_loc] = '\0';
/* If it's a double backslash, we skip it. */
if (my_line[blank_loc + 1] == '\\')
continue_parse += 2;
}
/* do we have an option string? */
else if (my_line[blank_loc] != '\0')
{
options_string = &my_line[blank_loc + 1];
my_line[blank_loc] = '\0';
}
/* And try to execute it */
status = exec_command(cmd, scan_state, query_buf);
status = exec_command(my_line, options_string, &continue_parse, query_buf, paren_level);
if (status == CMD_UNKNOWN)
if (status == CMD_UNKNOWN && strlen(cmd) > 1)
{
/*
* If the command was not recognized, try to parse it as a
* one-letter command with immediately following argument (a
* still-supported, but no longer encouraged, syntax).
*/
char new_cmd[2];
char new_cmd[2];
new_cmd[0] = my_line[0];
/* don't change cmd until we know it's okay */
new_cmd[0] = cmd[0];
new_cmd[1] = '\0';
/* use line for options, because my_line was clobbered above */
status = exec_command(new_cmd, line + 1, &continue_parse, query_buf, paren_level);
psql_scan_slash_pushback(scan_state, cmd + 1);
/*
* continue_parse must be relative to my_line for calculation
* below
*/
continue_parse += my_line - line;
status = exec_command(new_cmd, scan_state, query_buf);
if (status != CMD_UNKNOWN)
{
/* adjust cmd for possible messages below */
cmd[1] = '\0';
#if 0 /* turned out to be too annoying */
if (status != CMD_UNKNOWN && isalpha((unsigned char) new_cmd[0]))
psql_error("Warning: This syntax is deprecated.\n");
if (isalpha((unsigned char) cmd[0]))
psql_error("Warning: This syntax is deprecated.\n");
#endif
}
}
if (status == CMD_UNKNOWN)
{
if (pset.cur_cmd_interactive)
fprintf(stderr, gettext("Invalid command \\%s. Try \\? for help.\n"), my_line);
fprintf(stderr, gettext("Invalid command \\%s. Try \\? for help.\n"), cmd);
else
psql_error("invalid command \\%s\n", my_line);
psql_error("invalid command \\%s\n", cmd);
status = CMD_ERROR;
}
if (continue_parse && *continue_parse && *(continue_parse + 1) == '\\')
continue_parse += 2;
if (end_of_cmd)
/* eat the rest of the options, if any */
while ((arg = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false)))
{
if (continue_parse)
*end_of_cmd = line + (continue_parse - my_line);
else
*end_of_cmd = line + strlen(line);
if (status != CMD_ERROR)
psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
free(arg);
}
free(my_line);
/* if there is a trailing \\, swallow it */
psql_scan_slash_command_end(scan_state);
free(cmd);
return status;
}
/*
* Subroutine to actually try to execute a backslash command.
*/
static backslashResult
exec_command(const char *cmd,
const char *options_string,
const char **continue_parse,
PQExpBuffer query_buf,
volatile int *paren_level)
PsqlScanState scan_state,
PQExpBuffer query_buf)
{
bool success = true; /* indicate here if the command ran ok or
* failed */
bool quiet = QUIET();
backslashResult status = CMD_SKIP_LINE;
char *string,
*string_cpy,
*val;
/*
* The 'string' variable will be overwritten to point to the next
* token, hence we need an extra pointer so we can free this at the
* end.
*/
if (options_string)
string = string_cpy = pg_strdup(options_string);
else
string = string_cpy = NULL;
/*
* \a -- toggle field alignment This makes little sense but we keep it
@ -218,7 +168,8 @@ exec_command(const char *cmd,
/* \C -- override table title (formerly change HTML caption) */
else if (strcmp(cmd, "C") == 0)
{
char *opt = scan_option(&string, OT_NORMAL, NULL, true);
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
success = do_pset("title", opt, &pset.popt, quiet);
free(opt);
@ -249,8 +200,10 @@ exec_command(const char *cmd,
* files can be expected to double-quote all mixed-case \connect
* arguments, and then we can get rid of OT_SQLIDHACK.
*/
opt1 = scan_option(&string, OT_SQLIDHACK, &opt1q, true);
opt2 = scan_option(&string, OT_SQLIDHACK, &opt2q, true);
opt1 = psql_scan_slash_option(scan_state,
OT_SQLIDHACK, &opt1q, true);
opt2 = psql_scan_slash_option(scan_state,
OT_SQLIDHACK, &opt2q, true);
if (opt2)
/* gave username */
@ -270,7 +223,8 @@ exec_command(const char *cmd,
/* \cd */
else if (strcmp(cmd, "cd") == 0)
{
char *opt = scan_option(&string, OT_NORMAL, NULL, true);
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
char *dir;
if (opt)
@ -311,9 +265,11 @@ exec_command(const char *cmd,
/* \copy */
else if (strcasecmp(cmd, "copy") == 0)
{
success = do_copy(options_string);
if (options_string)
string += strlen(string);
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
success = do_copy(opt);
free(opt);
}
/* \copyright */
@ -327,7 +283,8 @@ exec_command(const char *cmd,
bool show_verbose;
/* We don't do SQLID reduction on the pattern yet */
pattern = scan_option(&string, OT_NORMAL, NULL, true);
pattern = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
show_verbose = strchr(cmd, '+') ? true : false;
@ -412,7 +369,8 @@ exec_command(const char *cmd,
}
else
{
fname = scan_option(&string, OT_NORMAL, NULL, true);
fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
expand_tilde(&fname);
status = do_edit(fname, query_buf) ? CMD_NEWEDIT : CMD_ERROR;
free(fname);
@ -433,7 +391,8 @@ exec_command(const char *cmd,
else
fout = stdout;
while ((value = scan_option(&string, OT_NORMAL, &quoted, false)))
while ((value = psql_scan_slash_option(scan_state,
OT_NORMAL, &quoted, false)))
{
if (!quoted && strcmp(value, "-n") == 0)
no_newline = true;
@ -454,7 +413,8 @@ exec_command(const char *cmd,
/* \encoding -- set/show client side encoding */
else if (strcmp(cmd, "encoding") == 0)
{
char *encoding = scan_option(&string, OT_NORMAL, NULL, false);
char *encoding = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (!encoding)
{
@ -481,7 +441,8 @@ exec_command(const char *cmd,
/* \f -- change field separator */
else if (strcmp(cmd, "f") == 0)
{
char *fname = scan_option(&string, OT_NORMAL, NULL, false);
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
success = do_pset("fieldsep", fname, &pset.popt, quiet);
free(fname);
@ -490,7 +451,8 @@ exec_command(const char *cmd,
/* \g means send query */
else if (strcmp(cmd, "g") == 0)
{
char *fname = scan_option(&string, OT_FILEPIPE, NULL, false);
char *fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, false);
if (!fname)
pset.gfname = NULL;
@ -506,11 +468,11 @@ exec_command(const char *cmd,
/* help */
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
{
helpSQL(options_string ? &options_string[strspn(options_string, " \t\n\r")] : NULL,
pset.popt.topt.pager);
/* set pointer to end of line */
if (string)
string += strlen(string);
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
helpSQL(opt, pset.popt.topt.pager);
free(opt);
}
/* HTML mode */
@ -526,7 +488,8 @@ exec_command(const char *cmd,
/* \i is include file */
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
{
char *fname = scan_option(&string, OT_NORMAL, NULL, true);
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
if (!fname)
{
@ -555,8 +518,10 @@ exec_command(const char *cmd,
char *opt1,
*opt2;
opt1 = scan_option(&string, OT_NORMAL, NULL, true);
opt2 = scan_option(&string, OT_NORMAL, NULL, true);
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)
{
@ -611,7 +576,8 @@ exec_command(const char *cmd,
/* \o -- set query output */
else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
{
char *fname = scan_option(&string, OT_FILEPIPE, NULL, true);
char *fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, true);
expand_tilde(&fname);
success = setQFout(fname);
@ -631,8 +597,10 @@ exec_command(const char *cmd,
/* \pset -- set printing parameters */
else if (strcmp(cmd, "pset") == 0)
{
char *opt0 = scan_option(&string, OT_NORMAL, NULL, false);
char *opt1 = scan_option(&string, OT_NORMAL, NULL, false);
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)
{
@ -654,8 +622,7 @@ exec_command(const char *cmd,
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
{
resetPQExpBuffer(query_buf);
if (paren_level)
*paren_level = 0;
psql_scan_reset(scan_state);
if (!quiet)
puts(gettext("Query buffer reset (cleared)."));
}
@ -663,7 +630,8 @@ exec_command(const char *cmd,
/* \s save history in a file or show it on the screen */
else if (strcmp(cmd, "s") == 0)
{
char *fname = scan_option(&string, OT_NORMAL, NULL, true);
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
expand_tilde(&fname);
success = saveHistory(fname ? fname : "/dev/tty");
@ -676,7 +644,8 @@ exec_command(const char *cmd,
/* \set -- generalized set variable/option command */
else if (strcmp(cmd, "set") == 0)
{
char *opt0 = scan_option(&string, OT_NORMAL, NULL, false);
char *opt0 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (!opt0)
{
@ -689,14 +658,16 @@ exec_command(const char *cmd,
/*
* Set variable to the concatenation of the arguments.
*/
char *newval = NULL;
char *newval;
char *opt;
opt = scan_option(&string, OT_NORMAL, NULL, false);
opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
newval = pg_strdup(opt ? opt : "");
free(opt);
while ((opt = scan_option(&string, OT_NORMAL, NULL, false)))
while ((opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false)))
{
newval = realloc(newval, strlen(newval) + strlen(opt) + 1);
if (!newval)
@ -732,7 +703,8 @@ exec_command(const char *cmd,
/* \T -- define html <table ...> attributes */
else if (strcmp(cmd, "T") == 0)
{
char *value = scan_option(&string, OT_NORMAL, NULL, false);
char *value = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
success = do_pset("tableattr", value, &pset.popt, quiet);
free(value);
@ -754,7 +726,8 @@ exec_command(const char *cmd,
/* \unset */
else if (strcmp(cmd, "unset") == 0)
{
char *opt = scan_option(&string, OT_NORMAL, NULL, false);
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (!opt)
{
@ -783,7 +756,8 @@ exec_command(const char *cmd,
}
else
{
fname = scan_option(&string, OT_FILEPIPE, NULL, true);
fname = psql_scan_slash_option(scan_state,
OT_FILEPIPE, NULL, true);
expand_tilde(&fname);
if (!fname)
@ -839,7 +813,8 @@ exec_command(const char *cmd,
/* \z -- list table rights (equivalent to \dp) */
else if (strcmp(cmd, "z") == 0)
{
char *pattern = scan_option(&string, OT_NORMAL, NULL, true);
char *pattern = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
success = permissionsList(pattern);
if (pattern)
@ -849,10 +824,11 @@ exec_command(const char *cmd,
/* \! -- shell escape */
else if (strcmp(cmd, "!") == 0)
{
success = do_shell(options_string);
/* wind pointer to end of line */
if (string)
string += strlen(string);
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
success = do_shell(opt);
free(opt);
}
/* \? -- slash command help */
@ -870,8 +846,8 @@ exec_command(const char *cmd,
int i = 0;
char *value;
fprintf(stderr, "+ optstr = |%s|\n", options_string);
while ((value = scan_option(&string, OT_NORMAL, NULL, true)))
while ((value = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true)))
{
fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value);
free(value);
@ -885,431 +861,11 @@ exec_command(const char *cmd,
if (!success)
status = CMD_ERROR;
/* eat the rest of the options string */
while ((val = scan_option(&string, OT_NORMAL, NULL, false)))
{
if (status != CMD_UNKNOWN)
psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, val);
if (val)
free(val);
}
if (options_string && continue_parse)
*continue_parse = options_string + (string - string_cpy);
free(string_cpy);
return status;
}
/*
* scan_option()
*
* *string points to possible option string on entry; on exit, it's updated
* to point past the option string (if any).
*
* type tells what processing, if any, to perform on the option string;
* for example, if it's a SQL identifier, we want to downcase any unquoted
* letters.
*
* if quote is not NULL, *quote is set to 0 if no quoting was found, else
* the quote symbol.
*
* if semicolon is true, trailing semicolon(s) that would otherwise be taken
* as part of the option string will be stripped.
*
* Return value is NULL if no option found, else a malloc'd copy of the
* processed option value.
*/
static char *
scan_option(char **string, enum option_type type, char *quote, bool semicolon)
{
unsigned int pos;
char *options_string;
char *return_val;
if (quote)
*quote = 0;
if (!string || !(*string))
return NULL;
options_string = *string;
/* skip leading whitespace */
pos = strspn(options_string, " \t\n\r");
switch (options_string[pos])
{
/*
* End of line: no option present
*/
case '\0':
*string = &options_string[pos];
return NULL;
/*
* Next command: treat like end of line
*
* XXX this means we can't conveniently accept options that start
* with a backslash; therefore, option processing that
* encourages use of backslashes is rather broken.
*/
case '\\':
*string = &options_string[pos];
return NULL;
/*
* A single quote has a psql internal meaning, such as for
* delimiting file names, and it also allows for such escape
* sequences as \t.
*/
case '\'':
{
unsigned int jj;
unsigned short int bslash_count = 0;
for (jj = pos + 1; options_string[jj]; jj += PQmblen(&options_string[jj], pset.encoding))
{
if (options_string[jj] == '\'' && bslash_count % 2 == 0)
break;
if (options_string[jj] == '\\')
bslash_count++;
else
bslash_count = 0;
}
if (options_string[jj] == 0)
{
psql_error("parse error at the end of line\n");
*string = &options_string[jj];
return NULL;
}
return_val = unescape(&options_string[pos + 1], jj - pos - 1);
*string = &options_string[jj + 1];
if (quote)
*quote = '\'';
return return_val;
}
/*
* Backticks are for command substitution, like in shells
*/
case '`':
{
bool error = false;
FILE *fd;
char *file;
PQExpBufferData output;
char buf[512];
size_t result,
len;
len = strcspn(options_string + pos + 1, "`");
if (options_string[pos + 1 + len] == 0)
{
psql_error("parse error at the end of line\n");
*string = &options_string[pos + 1 + len];
return NULL;
}
options_string[pos + 1 + len] = '\0';
file = options_string + pos + 1;
fd = popen(file, "r");
if (!fd)
{
psql_error("%s: %s\n", file, strerror(errno));
error = true;
}
initPQExpBuffer(&output);
if (!error)
{
do
{
result = fread(buf, 1, 512, fd);
if (ferror(fd))
{
psql_error("%s: %s\n", file, strerror(errno));
error = true;
break;
}
appendBinaryPQExpBuffer(&output, buf, result);
} while (!feof(fd));
appendPQExpBufferChar(&output, '\0');
}
if (fd && pclose(fd) == -1)
{
psql_error("%s: %s\n", file, strerror(errno));
error = true;
}
if (!error)
{
if (output.data[strlen(output.data) - 1] == '\n')
output.data[strlen(output.data) - 1] = '\0';
return_val = output.data;
}
else
{
return_val = pg_strdup("");
termPQExpBuffer(&output);
}
options_string[pos + 1 + len] = '`';
*string = options_string + pos + len + 2;
if (quote)
*quote = '`';
return return_val;
}
/*
* Variable substitution
*/
case ':':
{
size_t token_end;
const char *value;
char save_char;
token_end = strcspn(&options_string[pos + 1], " \t\n\r");
save_char = options_string[pos + token_end + 1];
options_string[pos + token_end + 1] = '\0';
value = GetVariable(pset.vars, options_string + pos + 1);
return_val = pg_strdup(value ? value : "");
options_string[pos + token_end + 1] = save_char;
*string = &options_string[pos + token_end + 1];
/* XXX should we set *quote to ':' here? */
return return_val;
}
/*
* | could be the beginning of a pipe if so, take rest of line
* as command
*/
case '|':
if (type == OT_FILEPIPE)
{
*string += strlen(*string);
return pg_strdup(options_string + pos);
}
/* fallthrough for other option types */
/*
* Default case: token extends to next whitespace, except that
* whitespace within double quotes doesn't end the token.
*
* If we are processing the option as a SQL identifier, then
* downcase unquoted letters and remove double-quotes --- but
* doubled double-quotes become output double-quotes, per
* spec.
*
* Note that a string like FOO"BAR"BAZ will be converted to
* fooBARbaz; this is somewhat inconsistent with the SQL spec,
* which would have us parse it as several identifiers. But
* for psql's purposes, we want a string like "foo"."bar" to
* be treated as one option, so there's little choice.
*/
default:
{
bool inquotes = false;
size_t token_len;
char *cp;
/* Find end of option */
cp = &options_string[pos];
for (;;)
{
/* Find next quote, whitespace, or end of string */
cp += strcspn(cp, "\" \t\n\r");
if (inquotes)
{
if (*cp == '\0')
{
psql_error("parse error at the end of line\n");
*string = cp;
return NULL;
}
if (*cp == '"')
inquotes = false;
cp++;
}
else
{
if (*cp != '"')
break; /* whitespace or end of string */
if (quote)
*quote = '"';
inquotes = true;
cp++;
}
}
*string = cp;
/* Copy the option */
token_len = cp - &options_string[pos];
return_val = pg_malloc(token_len + 1);
memcpy(return_val, &options_string[pos], token_len);
return_val[token_len] = '\0';
/* Strip any trailing semi-colons if requested */
if (semicolon)
{
int i;
for (i = token_len - 1;
i >= 0 && return_val[i] == ';';
i--)
/* skip */ ;
if (i < 0)
{
/* nothing left after stripping the semicolon... */
free(return_val);
return NULL;
}
if (i < (int) token_len - 1)
return_val[i + 1] = '\0';
}
/*
* If SQL identifier processing was requested, then we
* strip out excess double quotes and downcase unquoted
* letters.
*/
if (type == OT_SQLID || type == OT_SQLIDHACK)
{
inquotes = false;
cp = return_val;
while (*cp)
{
if (*cp == '"')
{
if (inquotes && cp[1] == '"')
{
/* Keep the first quote, remove the second */
cp++;
}
inquotes = !inquotes;
/* Collapse out quote at *cp */
memmove(cp, cp + 1, strlen(cp));
/* do not advance cp */
}
else
{
if (!inquotes && type == OT_SQLID)
{
if (isupper((unsigned char) *cp))
*cp = tolower((unsigned char) *cp);
}
cp += PQmblen(cp, pset.encoding);
}
}
}
return return_val;
}
}
}
/*
* unescape
*
* Replaces \n, \t, and the like.
*
* The return value is malloc'ed.
*/
static char *
unescape(const unsigned char *source, size_t len)
{
const unsigned char *p;
bool esc = false; /* Last character we saw was the escape
* character */
char *destination,
*tmp;
size_t length;
psql_assert(source);
length = Min(len, strlen(source)) + 1;
tmp = destination = pg_malloc(length);
for (p = source; p - source < (int) len && *p; p += PQmblen(p, pset.encoding))
{
if (esc)
{
char c;
switch (*p)
{
case 'n':
c = '\n';
break;
case 't':
c = '\t';
break;
case 'b':
c = '\b';
break;
case 'r':
c = '\r';
break;
case 'f':
c = '\f';
break;
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
c = parse_char((char **) &p);
break;
default:
c = *p;
}
*tmp++ = c;
esc = false;
}
else if (*p == '\\')
esc = true;
else
{
int i;
const unsigned char *mp = p;
for (i = 0; i < PQmblen(p, pset.encoding); i++)
*tmp++ = *mp++;
esc = false;
}
}
*tmp = '\0';
return destination;
}
/* do_connect
* -- handler for \connect
*