summaryrefslogtreecommitdiff
path: root/src/bin/psql/command.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/psql/command.c')
-rw-r--r--src/bin/psql/command.c944
1 files changed, 581 insertions, 363 deletions
diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c
index 63ac6fbff42..ba198d7d914 100644
--- a/src/bin/psql/command.c
+++ b/src/bin/psql/command.c
@@ -3,12 +3,13 @@
*
* Copyright 2000 by PostgreSQL Global Development Group
*
- * $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.17 2000/02/05 12:27:56 ishii Exp $
+ * $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.18 2000/02/07 23:10:04 petere Exp $
*/
#include <c.h>
#include "command.h"
#include <errno.h>
+#include <assert.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
@@ -17,54 +18,40 @@
#include <sys/types.h> /* for umask() */
#include <sys/stat.h> /* for umask(), stat() */
#include <unistd.h> /* for geteuid(), getpid(), stat() */
+#else
+#include <win32.h>
#endif
-#include <assert.h>
#include <libpq-fe.h>
#include <pqexpbuffer.h>
-#include "stringutils.h"
-#include "mainloop.h"
+#include "common.h"
#include "copy.h"
+#include "describe.h"
#include "help.h"
-#include "settings.h"
-#include "common.h"
+#include "input.h"
#include "large_obj.h"
+#include "mainloop.h"
#include "print.h"
-#include "describe.h"
-#include "input.h"
+#include "settings.h"
#include "variables.h"
-#ifdef WIN32
-#include "../../interfaces/libpq/win32.h"
-#define popen(x,y) _popen(x,y)
-#define pclose(x) _pclose(x)
-#endif
-
/* functions for use in this file */
static backslashResult exec_command(const char *cmd,
- char *const * options,
const char *options_string,
+ const char ** continue_parse,
PQExpBuffer query_buf);
-static bool do_edit(const char *filename_arg, PQExpBuffer query_buf);
-
-static char * unescape(const char *source);
-
-static bool do_connect(const char *new_dbname,
- const char *new_user);
-
+enum option_type { OT_NORMAL, OT_SQLID };
+static char * scan_option(char ** string, enum option_type type, char * quote);
+static char * unescape(const unsigned char *source, size_t len);
+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);
-/*
- * Perhaps this should be changed to "infinity",
- * but there is no convincing reason to bother
- * at this point.
- */
-#define NR_OPTIONS 16
/*----------
@@ -79,7 +66,7 @@ static bool do_shell(const char *command);
*
* 'query_buf' contains the query-so-far, which may be modified by
* execution of the backslash command (for example, \r clears it)
- * query_buf can be NULL if there is no query-so-far.
+ * query_buf can be NULL if there is no query so far.
*
* Returns a status code indicating what action is desired, see command.h.
*----------
@@ -92,18 +79,13 @@ HandleSlashCmds(const char *line,
{
backslashResult status = CMD_SKIP_LINE;
char *my_line;
- char *options[NR_OPTIONS+1];
- char *token;
- const char *options_string = NULL;
- const char *cmd;
+ char *options_string = NULL;
size_t blank_loc;
- int i;
const char *continue_parse = NULL; /* tell the mainloop where the
* backslash command ended */
#ifdef USE_ASSERT_CHECKING
assert(line);
- assert(query_buf);
assert(end_of_cmd);
#endif
@@ -129,145 +111,37 @@ HandleSlashCmds(const char *line,
my_line[blank_loc] = '\0';
}
- options[0] = NULL;
-
- if (options_string)
- {
- char quote;
- unsigned int pos;
-
- options_string = &options_string[strspn(options_string, " \t")]; /* skip leading
- * whitespace */
-
- i = 0;
- token = strtokx(options_string, " \t", "\"'`", '\\', &quote, &pos, pset.encoding);
-
- for (i = 0; token && i < NR_OPTIONS; i++)
- {
- switch (quote)
- {
- case '"':
- options[i] = unescape(token);
- break;
- case '\'':
- options[i] = xstrdup(token);
- break;
- case '`':
- {
- bool error = false;
- FILE *fd = NULL;
- char *file = unescape(token);
- PQExpBufferData output;
- char buf[512];
- size_t result;
-
- fd = popen(file, "r");
- if (!fd)
- {
- psql_error("%s: %s\n", file, strerror(errno));
- error = true;
- }
-
- if (!error)
- {
- initPQExpBuffer(&output);
-
- 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 (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';
- }
-
- free(file);
- if (!error)
- options[i] = output.data;
- else
- {
- options[i] = xstrdup("");
- termPQExpBuffer(&output);
- }
- break;
- }
- case 0:
- default:
- if (token[0] == '\\')
- continue_parse = options_string + pos;
- else if (token[0] == '$')
- {
- const char * value = GetVariable(pset.vars, token+1);
- if (!value)
- value = "";
- options[i] = xstrdup(value);
- }
- else
- options[i] = xstrdup(token);
- }
-
- if (continue_parse)
- break;
-
- token = strtokx(NULL, " \t", "\"'`", '\\', &quote, &pos, pset.encoding);
- } /* for */
-
- options[i] = NULL;
- }
-
- cmd = my_line;
- status = exec_command(cmd, options, options_string, query_buf);
+ status = exec_command(my_line, options_string, &continue_parse, query_buf);
if (status == CMD_UNKNOWN)
{
-
/*
- * If the command was not recognized, try inserting a space after
- * the first letter and call again. The one letter commands allow
- * arguments to start immediately after the command, but that is
- * no longer encouraged.
+ * If the command was not recognized, try inserting a space after the
+ * first letter and call again. The one letter commands allow arguments
+ * to start immediately after the command, but that is no longer
+ * encouraged.
*/
- const char *new_options[NR_OPTIONS+1];
char new_cmd[2];
- int i;
-
- for (i = 1; i < NR_OPTIONS+1; i++)
- new_options[i] = options[i - 1];
- new_options[0] = cmd + 1;
- new_cmd[0] = cmd[0];
+ new_cmd[0] = my_line[0];
new_cmd[1] = '\0';
- status = exec_command(new_cmd, (char *const *) new_options, my_line + 2, query_buf);
+ status = exec_command(new_cmd, my_line + 1, &continue_parse, query_buf);
+
+ if (status != CMD_UNKNOWN && isalpha(new_cmd[0]))
+ psql_error("Warning: this syntax is deprecated\n");
}
if (status == CMD_UNKNOWN)
{
if (pset.cur_cmd_interactive)
- fprintf(stderr, "Invalid command \\%s. Try \\? for help.\n", cmd);
+ fprintf(stderr, "Invalid command \\%s. Try \\? for help.\n", my_line);
else
- psql_error("invalid command \\%s\n", cmd);
+ psql_error("invalid command \\%s\n", my_line);
status = CMD_ERROR;
}
- if (continue_parse && *(continue_parse + 1) == '\\')
+ if (continue_parse && *continue_parse && *(continue_parse + 1) == '\\')
continue_parse += 2;
@@ -276,10 +150,6 @@ HandleSlashCmds(const char *line,
else
*end_of_cmd = line + strlen(line);
- /* clean up */
- for (i = 0; i < NR_OPTIONS && options[i]; i++)
- free(options[i]);
-
free(my_line);
return status;
@@ -287,19 +157,26 @@ HandleSlashCmds(const char *line,
-
static backslashResult
exec_command(const char *cmd,
- char *const * options,
const char *options_string,
+ const char ** continue_parse,
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;
+ /*
+ * 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 = xstrdup(options_string);
+ else
+ string = string_cpy = NULL;
/* \a -- toggle field alignment This makes little sense but we keep it around. */
if (strcmp(cmd, "a") == 0)
@@ -310,44 +187,53 @@ exec_command(const char *cmd,
success = do_pset("format", "unaligned", &pset.popt, quiet);
}
-
/* \C -- override table title (formerly change HTML caption) */
else if (strcmp(cmd, "C") == 0)
- success = do_pset("title", options[0], &pset.popt, quiet);
-
+ {
+ char * opt = scan_option(&string, OT_NORMAL, NULL);
+ success = do_pset("title", opt, &pset.popt, quiet);
+ free(opt);
+ }
/*----------
* \c or \connect -- connect to new database or as different user
*
- * \c foo bar: connect to db "foo" as user "bar"
- * \c foo [-]: connect to db "foo" as current user
- * \c - bar: connect to current db as user "bar"
- * \c: connect to default db as default user
+ * \c foo bar connect to db "foo" as user "bar"
+ * \c foo [-] connect to db "foo" as current user
+ * \c - bar connect to current db as user "bar"
+ * \c connect to default db as default user
*----------
*/
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
{
- if (options[1])
+ char *opt1, *opt2;
+ char opt1q, opt2q;
+
+ opt1 = scan_option(&string, OT_NORMAL, &opt1q);
+ opt2 = scan_option(&string, OT_NORMAL, &opt2q);
+
+ if (opt2)
/* gave username */
- success = do_connect(options[0], options[1]);
- else
- {
- if (options[0])
- /* gave database name */
- success = do_connect(options[0], ""); /* empty string is same
- * username as before,
- * NULL would mean libpq
- * default */
- else
- /* connect to default db as default user */
- success = do_connect(NULL, NULL);
- }
- }
+ success = do_connect(!opt1q && (strcmp(opt1, "-")==0 || strcmp(opt1, "")==0) ? "" : opt1,
+ !opt2q && (strcmp(opt2, "-")==0 || strcmp(opt2, "")==0) ? "" : opt2);
+ else if (opt1)
+ /* gave database name */
+ success = do_connect(!opt1q && (strcmp(opt1, "-")==0 || strcmp(opt1, "")==0) ? "" : opt1, "");
+ else
+ /* connect to default db as default user */
+ success = do_connect(NULL, NULL);
+ free(opt1);
+ free(opt2);
+ }
/* \copy */
else if (strcasecmp(cmd, "copy") == 0)
+ {
success = do_copy(options_string);
+ if (options_string)
+ string += strlen(string);
+ }
/* \copyright */
else if (strcmp(cmd, "copyright") == 0)
@@ -356,38 +242,42 @@ exec_command(const char *cmd,
/* \d* commands */
else if (cmd[0] == 'd')
{
- bool show_verbose = strchr(cmd, '+') ? true : false;
+ char * name;
+ bool show_verbose;
+
+ name = scan_option(&string, OT_SQLID, NULL);
+ show_verbose = strchr(cmd, '+') ? true : false;
switch (cmd[1])
{
case '\0':
case '+':
- if (options[0])
- success = describeTableDetails(options[0], show_verbose);
+ if (name)
+ success = describeTableDetails(name, show_verbose);
else
/* standard listing of interesting things */
success = listTables("tvs", NULL, show_verbose);
break;
case 'a':
- success = describeAggregates(options[0]);
+ success = describeAggregates(name);
break;
case 'd':
- success = objectDescription(options[0]);
+ success = objectDescription(name);
break;
case 'f':
- success = describeFunctions(options[0], show_verbose);
+ success = describeFunctions(name, show_verbose);
break;
case 'l':
success = do_lo_list();
break;
case 'o':
- success = describeOperators(options[0]);
+ success = describeOperators(name);
break;
case 'p':
- success = permissionsList(options[0]);
+ success = permissionsList(name);
break;
case 'T':
- success = describeTypes(options[0], show_verbose);
+ success = describeTypes(name, show_verbose);
break;
case 't':
case 'v':
@@ -397,11 +287,12 @@ exec_command(const char *cmd,
if (cmd[1] == 'S' && cmd[2] == '\0')
success = listTables("Stvs", NULL, show_verbose);
else
- success = listTables(&cmd[1], options[0], show_verbose);
+ success = listTables(&cmd[1], name, show_verbose);
break;
default:
status = CMD_UNKNOWN;
}
+ free(name);
}
@@ -410,46 +301,81 @@ exec_command(const char *cmd,
* the query buffer
*/
else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
- status = do_edit(options[0], query_buf) ? CMD_NEWEDIT : CMD_ERROR;
+ {
+ char * fname;
+ if (!query_buf)
+ {
+ psql_error("no query buffer");
+ status = CMD_ERROR;
+ }
+ else
+ {
+ fname = scan_option(&string, OT_NORMAL, NULL);
+ status = do_edit(fname, query_buf) ? CMD_NEWEDIT : CMD_ERROR;
+ free(fname);
+ }
+ }
- /* \echo */
- else if (strcmp(cmd, "echo") == 0)
+ /* \echo and \qecho */
+ else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho")==0)
{
- int i;
+ char * value;
+ char quoted;
+ bool no_newline = false;
+ bool first = true;
+ FILE * fout;
+
+ if (strcmp(cmd, "qecho")==0)
+ fout = pset.queryFout;
+ else
+ fout = stdout;
- for (i = 0; i < 16 && options[i]; i++)
- fputs(options[i], stdout);
- fputs("\n", stdout);
+ while((value = scan_option(&string, OT_NORMAL, &quoted)))
+ {
+ if (!quoted && strcmp(value, "-n")==0)
+ no_newline = true;
+ else
+ {
+ if (first)
+ first = false;
+ else
+ fputc(' ', fout);
+ fputs(value, fout);
+ }
+ free(value);
+ }
+ if (!no_newline)
+ fputs("\n", fout);
}
/* \f -- change field separator */
else if (strcmp(cmd, "f") == 0)
- success = do_pset("fieldsep", options[0], &pset.popt, quiet);
+ {
+ char * fname = scan_option(&string, OT_NORMAL, NULL);
+ success = do_pset("fieldsep", fname, &pset.popt, quiet);
+ free(fname);
+ }
/* \g means send query */
else if (strcmp(cmd, "g") == 0)
{
- if (!options[0])
+ char * fname = scan_option(&string, OT_NORMAL, NULL);
+ if (!fname)
pset.gfname = NULL;
else
- pset.gfname = xstrdup(options[0]);
+ pset.gfname = xstrdup(fname);
+ free(fname);
status = CMD_SEND;
}
/* help */
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
{
- char buf[256] = "";
- int i;
- for (i=0; options && options[i] && strlen(buf)<255; i++)
- {
- strncat(buf, options[i], 255 - strlen(buf));
- if (strlen(buf)<255 && options[i+1])
- strcat(buf, " ");
- }
- buf[255] = '\0';
- helpSQL(buf);
+ helpSQL(options_string ? &options_string[strspn(options_string, " \t")] : NULL);
+ /* set pointer to end of line */
+ if (string)
+ string += strlen(string);
}
/* HTML mode */
@@ -465,46 +391,55 @@ exec_command(const char *cmd,
/* \i is include file */
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
{
- if (!options[0])
+ char * fname = scan_option(&string, OT_NORMAL, NULL);
+ if (!fname)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
- success = process_file(options[0]);
+ {
+ success = process_file(fname);
+ free (fname);
+ }
}
-
/* \l is list databases */
else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0)
success = listAllDbs(false);
else if (strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
success = listAllDbs(true);
-
- /* large object things */
+ /*
+ * large object things
+ */
else if (strncmp(cmd, "lo_", 3) == 0)
{
+ char *opt1, *opt2;
+
+ opt1 = scan_option(&string, OT_NORMAL, NULL);
+ opt2 = scan_option(&string, OT_NORMAL, NULL);
+
if (strcmp(cmd + 3, "export") == 0)
{
- if (!options[1])
+ if (!opt2)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
- success = do_lo_export(options[0], options[1]);
+ success = do_lo_export(opt1, opt2);
}
else if (strcmp(cmd + 3, "import") == 0)
{
- if (!options[0])
+ if (!opt1)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
- success = do_lo_import(options[0], options[1]);
+ success = do_lo_import(opt1, opt2);
}
else if (strcmp(cmd + 3, "list") == 0)
@@ -512,23 +447,30 @@ exec_command(const char *cmd,
else if (strcmp(cmd + 3, "unlink") == 0)
{
- if (!options[0])
+ if (!opt1)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
- success = do_lo_unlink(options[0]);
+ success = do_lo_unlink(opt1);
}
else
status = CMD_UNKNOWN;
+
+ free(opt1);
+ free(opt2);
}
+
/* \o -- set query output */
else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
- success = setQFout(options[0]);
-
+ {
+ char * fname = scan_option(&string, OT_NORMAL, NULL);
+ success = setQFout(fname);
+ free(fname);
+ }
/* \p prints the current query buffer */
else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
@@ -543,29 +485,24 @@ exec_command(const char *cmd,
/* \pset -- set printing parameters */
else if (strcmp(cmd, "pset") == 0)
{
- if (!options[0])
+ char * opt0 = scan_option(&string, OT_NORMAL, NULL);
+ char * opt1 = scan_option(&string, OT_NORMAL, NULL);
+ if (!opt0)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
- success = do_pset(options[0], options[1], &pset.popt, quiet);
+ success = do_pset(opt0, opt1, &pset.popt, quiet);
+
+ free(opt0);
+ free(opt1);
}
/* \q or \quit */
else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
status = CMD_TERMINATE;
- /* \qecho */
- else if (strcmp(cmd, "qecho") == 0)
- {
- int i;
-
- for (i = 0; i < 16 && options[i]; i++)
- fputs(options[i], pset.queryFout);
- fputs("\n", pset.queryFout);
- }
-
/* reset(clear) the buffer */
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
{
@@ -574,34 +511,31 @@ exec_command(const char *cmd,
puts("Query buffer reset (cleared).");
}
-
/* \s save history in a file or show it on the screen */
else if (strcmp(cmd, "s") == 0)
{
- const char *fname;
+ char *fname = scan_option(&string, OT_NORMAL, NULL);
- if (!options[0])
- fname = "/dev/tty";
- else
- fname = options[0];
-
- success = saveHistory(fname);
+ success = saveHistory(fname ? fname : "/dev/tty");
- if (success && !quiet && options[0])
+ if (success && !quiet && fname)
printf("Wrote history to %s.\n", fname);
+ free(fname);
}
-
- /* \set -- generalized set option command */
+ /* \set -- generalized set variable/option command */
else if (strcmp(cmd, "set") == 0)
{
- if (!options[0])
+ char * opt0 = scan_option(&string, OT_NORMAL, NULL);
+
+ if (!opt0)
{
/* list all variables */
/*
- * (This is in utter violation of the GetVariable abstraction,
- * but I have not dreamt up a better way.)
+ * XXX
+ * This is in utter violation of the GetVariable abstraction, but I
+ * have not bothered to do it better.
*/
struct _variable *ptr;
@@ -611,15 +545,36 @@ exec_command(const char *cmd,
}
else
{
- const char * val = options[1];
- if (!val)
- val = "";
- if (!SetVariable(pset.vars, options[0], val))
+ /*
+ * Set variable to the concatenation of the arguments.
+ */
+ char * newval = NULL;
+ char * opt;
+
+ opt = scan_option(&string, OT_NORMAL, NULL);
+ newval = xstrdup(opt ? opt : "");
+ free(opt);
+
+ while ((opt = scan_option(&string, OT_NORMAL, NULL)))
+ {
+ newval = realloc(newval, strlen(newval) + strlen(opt) + 1);
+ if (!newval)
+ {
+ psql_error("out of memory");
+ exit(EXIT_FAILURE);
+ }
+ strcat(newval, opt);
+ free(opt);
+ }
+
+ if (!SetVariable(pset.vars, opt0, newval))
{
psql_error("\\%s: error\n", cmd);
success = false;
}
+ free(newval);
}
+ free(opt0);
}
/* \t -- turn off headers and row count */
@@ -629,48 +584,67 @@ exec_command(const char *cmd,
/* \T -- define html <table ...> attributes */
else if (strcmp(cmd, "T") == 0)
- success = do_pset("tableattr", options[0], &pset.popt, quiet);
+ {
+ char * value = scan_option(&string, OT_NORMAL, NULL);
+ success = do_pset("tableattr", value, &pset.popt, quiet);
+ free(value);
+ }
/* \unset */
else if (strcmp(cmd, "unset") == 0)
{
- if (!SetVariable(pset.vars, options[0], NULL))
+ char * opt = scan_option(&string, OT_NORMAL, NULL);
+ if (!opt)
+ {
+ psql_error("\\%s: missing required argument", cmd);
+ success = false;
+ }
+ if (!SetVariable(pset.vars, opt, NULL))
{
psql_error("\\%s: error\n", cmd);
-
success = false;
- }
+ }
+ free(opt);
}
/* \w -- write query buffer to file */
else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
{
FILE *fd = NULL;
- bool pipe = false;
+ bool is_pipe = false;
+ char *fname = NULL;
- if (!options[0])
- {
- psql_error("\\%s: missing required argument\n", cmd);
- success = false;
- }
- else
- {
- if (options[0][0] == '|')
- {
- pipe = true;
- fd = popen(&options[0][1], "w");
- }
- else
- {
- fd = fopen(options[0], "w");
- }
+ if (!query_buf)
+ {
+ psql_error("no query buffer");
+ status = CMD_ERROR;
+ }
+ else
+ {
+ fname = scan_option(&string, OT_NORMAL, NULL);
- if (!fd)
- {
- psql_error("%s: %s\n", options[0], strerror(errno));
- success = false;
- }
- }
+ if (!fname)
+ {
+ psql_error("\\%s: missing required argument\n", cmd);
+ success = false;
+ }
+ else
+ {
+ if (fname[0] == '|')
+ {
+ is_pipe = true;
+ fd = popen(&fname[1], "w");
+ }
+ else
+ fd = fopen(fname, "w");
+
+ if (!fd)
+ {
+ psql_error("%s: %s\n", fname, strerror(errno));
+ success = false;
+ }
+ }
+ }
if (fd)
{
@@ -679,17 +653,19 @@ exec_command(const char *cmd,
if (query_buf && query_buf->len > 0)
fprintf(fd, "%s\n", query_buf->data);
- if (pipe)
+ if (is_pipe)
result = pclose(fd);
else
result = fclose(fd);
if (result == EOF)
{
- psql_error("%s: %s\n", options[0], strerror(errno));
+ psql_error("%s: %s\n", fname, strerror(errno));
success = false;
}
}
+
+ free(fname);
}
/* \x -- toggle expanded table representation */
@@ -697,30 +673,43 @@ exec_command(const char *cmd,
success = do_pset("expanded", NULL, &pset.popt, quiet);
- /* list table rights (grant/revoke) */
+ /* \z -- list table rights (grant/revoke) */
else if (strcmp(cmd, "z") == 0)
- success = permissionsList(options[0]);
-
+ {
+ char * opt = scan_option(&string, OT_SQLID, NULL);
+ success = permissionsList(opt);
+ free(opt);
+ }
+ /* \! -- shell escape */
else if (strcmp(cmd, "!") == 0)
+ {
success = do_shell(options_string);
+ /* wind pointer to end of line */
+ if (string)
+ string += strlen(string);
+ }
+ /* \? -- slash command help */
else if (strcmp(cmd, "?") == 0)
slashUsage();
-
-#if 0
+#if 1
/*
* These commands don't do anything. I just use them to test the
* parser.
*/
else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0)
{
- int i;
+ int i = 0;
+ char *value;
- fprintf(stderr, "+ optline = |%s|\n", options_string);
- for (i = 0; options[i]; i++)
- fprintf(stderr, "+ opt%d = |%s|\n", i, options[i]);
+ fprintf(stderr, "+ optstr = |%s|\n", options_string);
+ while((value = scan_option(&string, OT_NORMAL, NULL)))
+ {
+ fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value);
+ free(value);
+ }
}
#endif
@@ -729,23 +718,300 @@ exec_command(const char *cmd,
if (!success)
status = CMD_ERROR;
+
+ /* eat the rest of the options string */
+ while(scan_option(&string, OT_NORMAL, NULL)) ;
+
+ if (options_string && continue_parse)
+ *continue_parse = options_string + (string - string_cpy);
+ free(string_cpy);
+
return status;
}
/*
+ * scan_option()
+ */
+static char *
+scan_option(char ** string, enum option_type type, char * quote)
+{
+ unsigned int pos = 0;
+ 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+pos, " \t");
+
+ switch (options_string[pos])
+ {
+ /*
+ * Double quoted string
+ */
+ case '"':
+ {
+ unsigned int jj;
+ unsigned short int bslash_count = 0;
+
+ /* scan for end of quote */
+ 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 end of line\n");
+ *string = &options_string[jj];
+ return NULL;
+ }
+
+ return_val = malloc(jj-pos+2);
+ if (!return_val)
+ {
+ psql_error("out of memory\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (type == OT_NORMAL)
+ {
+ strncpy(return_val, &options_string[pos], jj-pos+1);
+ return_val[jj-pos+1] = '\0';
+ }
+ /*
+ * If this is expected to be an SQL identifier like option
+ * then we strip out the double quotes
+ */
+ else if (type == OT_SQLID)
+ {
+ unsigned int k, cc;
+
+ bslash_count = 0;
+ cc = 0;
+ for (k = pos+1; options_string[k]; k += PQmblen(&options_string[k], pset.encoding))
+ {
+ if (options_string[k] == '"' && bslash_count % 2 == 0)
+ break;
+
+ if (options_string[jj] == '\\')
+ bslash_count++;
+ else
+ bslash_count=0;
+
+ return_val[cc++] = options_string[k];
+ }
+ return_val[cc] = '\0';
+ }
+
+ *string = options_string + jj+1;
+ if (quote)
+ *quote = '"';
+
+ return return_val;
+ }
+
+ /*
+ * 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 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 = NULL;
+ 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 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;
+ }
+
+ if (!error)
+ {
+ initPQExpBuffer(&output);
+
+ 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 (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';
+ }
+
+ if (!error)
+ return_val = output.data;
+ else
+ {
+ return_val = xstrdup("");
+ termPQExpBuffer(&output);
+ }
+ options_string[pos + 1 + len] = '`';
+ *string = options_string + pos + len + 2;
+ if (quote)
+ *quote = '`';
+ return return_val;
+ }
+
+ /*
+ * end of line
+ */
+ case 0:
+ *string = &options_string[pos];
+ return NULL;
+
+ /*
+ * Variable substitution
+ */
+ case ':':
+ {
+ size_t token_end;
+ const char * value;
+ char save_char;
+
+ token_end = strcspn(&options_string[pos+1], " \t");
+ save_char = options_string[pos+token_end+1];
+ options_string[pos+token_end+1] = '\0';
+ value = GetVariable(pset.vars, options_string+pos+1);
+ if (!value)
+ value = "";
+ return_val = xstrdup(value);
+ options_string[pos+token_end+1] = save_char;
+ *string = &options_string[pos + token_end+1];
+ return return_val;
+ }
+
+ /*
+ * Next command
+ */
+ case '\\':
+ *string = options_string + pos;
+ return NULL;
+ break;
+
+ /*
+ * A normal word
+ */
+ default:
+ {
+ size_t token_end;
+ char * cp;
+
+ token_end = strcspn(&options_string[pos], " \t");
+ return_val = malloc(token_end + 1);
+ if (!return_val)
+ {
+ psql_error("out of memory\n");
+ exit(EXIT_FAILURE);
+ }
+ strncpy(return_val, &options_string[pos], token_end);
+ return_val[token_end] = 0;
+
+ if (type == OT_SQLID)
+ for (cp = return_val; *cp; cp += PQmblen(cp, pset.encoding))
+ if (isascii(*cp))
+ *cp = tolower(*cp);
+
+ *string = &options_string[pos+token_end];
+ return return_val;
+ }
+
+ }
+}
+
+
+
+/*
* unescape
*
* Replaces \n, \t, and the like.
- * Also interpolates ${variables}.
*
* The return value is malloc()'ed.
*/
static char *
-unescape(const char *source)
+unescape(const unsigned char *source, size_t len)
{
- unsigned char *p;
+ const unsigned char *p;
bool esc = false; /* Last character we saw was the escape
* character */
char *destination,
@@ -756,16 +1022,16 @@ unescape(const char *source)
assert(source);
#endif
- length = strlen(source) + 1;
+ length = Min(len, strlen(source)) + 1;
- tmp = destination = (char *) malloc(length);
+ tmp = destination = malloc(length);
if (!tmp)
{
psql_error("out of memory\n");
exit(EXIT_FAILURE);
}
- for (p = (char *) source; *p; p += PQmblen(p, pset.encoding))
+ for (p = source; p-source < len && *p; p += PQmblen(p, pset.encoding))
{
if (esc)
{
@@ -776,12 +1042,15 @@ unescape(const char *source)
case 'n':
c = '\n';
break;
- case 'r':
- c = '\r';
- break;
case 't':
c = '\t';
break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'r':
+ c = '\r';
+ break;
case 'f':
c = '\f';
break;
@@ -814,44 +1083,6 @@ unescape(const char *source)
else if (*p == '\\')
esc = true;
- else if (*p == '$')
- {
- if (*(p + 1) == '{')
- {
- unsigned int len;
- char *copy;
- const char *value;
-#ifndef WIN32
- void *new;
-#else
- char *new;
-#endif
-
- len = strcspn(p + 2, "}");
- copy = xstrdup(p + 2);
- copy[len] = '\0';
- value = GetVariable(pset.vars, copy);
- if (!value)
- value = "";
- length += strlen(value) - (len + 3);
- new = realloc(destination, length);
- if (!new)
- {
- psql_error("out of memory\n");
- exit(EXIT_FAILURE);
- }
- tmp = new + (tmp - destination);
- destination = new;
-
- strcpy(tmp, value);
- tmp += strlen(value);
- p += len + 2;
- free(copy);
- }
- else
- *tmp++ = '$';
- }
-
else
{
*tmp++ = *p;
@@ -865,7 +1096,6 @@ unescape(const char *source)
-
/* do_connect
* -- handler for \connect
*
@@ -893,8 +1123,8 @@ do_connect(const char *new_dbname, const char *new_user)
SetVariable(pset.vars, "HOST", NULL);
SetVariable(pset.vars, "PORT", NULL);
- /* If dbname is "-" then use old name, else new one (even if NULL) */
- if (oldconn && new_dbname && PQdb(oldconn) && strcmp(new_dbname, "-") == 0)
+ /* If dbname is "" then use old name, else new one (even if NULL) */
+ if (oldconn && new_dbname && PQdb(oldconn) && strcmp(new_dbname, "") == 0)
dbparam = PQdb(oldconn);
else
dbparam = new_dbname;
@@ -1001,7 +1231,7 @@ do_connect(const char *new_dbname, const char *new_user)
/*
* Test if the given user is a database superuser.
- * (Used to set up the prompt right.)
+ * (Is used to set up the prompt right.)
*/
bool
test_superuser(const char * username)
@@ -1037,7 +1267,7 @@ test_superuser(const char * username)
static bool
editFile(const char *fname)
{
- char *editorName;
+ const char *editorName;
char *sys;
int result;
@@ -1063,7 +1293,7 @@ editFile(const char *fname)
sprintf(sys, "exec %s %s", editorName, fname);
result = system(sys);
if (result == -1)
- psql_error("could not start editor\n");
+ psql_error("could not start editor %s\n", editorName);
else if (result == 127)
psql_error("could not start /bin/sh\n");
free(sys);
@@ -1087,14 +1317,6 @@ do_edit(const char *filename_arg, PQExpBuffer query_buf)
#endif
-#ifdef USE_ASSERT_CHECKING
- assert(query_buf);
-#else
- if (!query_buf)
- return false;
-#endif
-
-
if (filename_arg)
fname = filename_arg;
@@ -1233,11 +1455,7 @@ process_file(char *filename)
if (!filename)
return false;
-#ifdef __CYGWIN32__
- fd = fopen(filename, "rb");
-#else
fd = fopen(filename, "r");
-#endif
if (!fd)
{
@@ -1454,7 +1672,7 @@ do_pset(const char *param, const char *value, printQueryOpt * popt, bool quiet)
-#define DEFAULT_SHELL "/bin/sh"
+#define DEFAULT_SHELL "/bin/sh"
static bool
do_shell(const char *command)
@@ -1464,7 +1682,7 @@ do_shell(const char *command)
if (!command)
{
char *sys;
- char *shellName;
+ const char *shellName;
shellName = getenv("SHELL");
if (shellName == NULL)