postgresql/src/bin/psql/command.c

1710 lines
39 KiB
C
Raw Normal View History

2000-01-19 00:30:24 +01:00
/*
* psql - the PostgreSQL interactive terminal
*
* Copyright 2000 by PostgreSQL Global Development Group
2000-01-19 00:30:24 +01:00
*
2000-02-16 14:15:26 +01:00
* $Header: /cvsroot/pgsql/src/bin/psql/command.c,v 1.19 2000/02/16 13:15:26 momjian Exp $
2000-01-19 00:30:24 +01:00
*/
2000-02-16 14:15:26 +01:00
#include "postgres.h"
#include "command.h"
2000-01-19 00:30:24 +01:00
#include <errno.h>
#include <assert.h>
#include <ctype.h>
#ifndef WIN32
1999-11-05 00:14:30 +01:00
#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
2000-02-16 14:15:26 +01:00
#include "libpq-fe.h"
#include "pqexpbuffer.h"
#include "common.h"
#include "copy.h"
#include "describe.h"
#include "help.h"
#include "input.h"
#include "large_obj.h"
#include "mainloop.h"
#include "print.h"
#include "settings.h"
#include "variables.h"
/* functions for use in this file */
1999-11-05 00:14:30 +01:00
static backslashResult exec_command(const char *cmd,
const char *options_string,
const char ** continue_parse,
2000-01-19 00:30:24 +01:00
PQExpBuffer query_buf);
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);
/*----------
* 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)
*
* '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.
*
* Returns a status code indicating what action is desired, see command.h.
*----------
*/
backslashResult
HandleSlashCmds(const char *line,
1999-11-05 00:14:30 +01:00
PQExpBuffer query_buf,
2000-01-19 00:30:24 +01:00
const char **end_of_cmd)
{
1999-11-05 00:14:30 +01:00
backslashResult status = CMD_SKIP_LINE;
char *my_line;
char *options_string = NULL;
1999-11-05 00:14:30 +01:00
size_t blank_loc;
const char *continue_parse = NULL; /* tell the mainloop where the
* backslash command ended */
#ifdef USE_ASSERT_CHECKING
assert(line);
assert(end_of_cmd);
#endif
1999-11-05 00:14:30 +01:00
my_line = xstrdup(line);
/*
* Find the first whitespace. line[blank_loc] will now
1999-11-05 00:14:30 +01:00
* be the whitespace character or the \0 at the end
*
* Also look for a backslash, so stuff like \p\g works.
1999-11-05 00:14:30 +01:00
*/
blank_loc = strcspn(my_line, " \t\\");
1999-11-05 00:14:30 +01:00
if (my_line[blank_loc] == '\\')
{
continue_parse = &my_line[blank_loc];
my_line[blank_loc] = '\0';
}
1999-11-05 00:14:30 +01:00
/* do we have an option string? */
else if (my_line[blank_loc] != '\0')
{
options_string = &my_line[blank_loc + 1];
1999-11-05 00:14:30 +01:00
my_line[blank_loc] = '\0';
}
status = exec_command(my_line, options_string, &continue_parse, query_buf);
1999-11-05 00:14:30 +01:00
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.
1999-11-05 00:14:30 +01:00
*/
char new_cmd[2];
new_cmd[0] = my_line[0];
1999-11-05 00:14:30 +01:00
new_cmd[1] = '\0';
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");
1999-11-05 00:14:30 +01:00
}
1999-11-05 00:14:30 +01:00
if (status == CMD_UNKNOWN)
{
if (pset.cur_cmd_interactive)
fprintf(stderr, "Invalid command \\%s. Try \\? for help.\n", my_line);
else
psql_error("invalid command \\%s\n", my_line);
1999-11-05 00:14:30 +01:00
status = CMD_ERROR;
}
if (continue_parse && *continue_parse && *(continue_parse + 1) == '\\')
1999-11-05 00:14:30 +01:00
continue_parse += 2;
if (continue_parse)
*end_of_cmd = line + (continue_parse - my_line);
else
*end_of_cmd = line + strlen(line);
1999-11-05 00:14:30 +01:00
free(my_line);
1999-11-05 00:14:30 +01:00
return status;
}
static backslashResult
1999-11-05 00:14:30 +01:00
exec_command(const char *cmd,
const char *options_string,
const char ** continue_parse,
2000-01-19 00:30:24 +01:00
PQExpBuffer query_buf)
{
1999-11-05 00:14:30 +01:00
bool success = true; /* indicate here if the command ran ok or
* failed */
bool quiet = QUIET();
1999-11-05 00:14:30 +01:00
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. */
1999-11-05 00:14:30 +01:00
if (strcmp(cmd, "a") == 0)
{
if (pset.popt.topt.format != PRINT_ALIGNED)
success = do_pset("format", "aligned", &pset.popt, quiet);
1999-11-05 00:14:30 +01:00
else
success = do_pset("format", "unaligned", &pset.popt, quiet);
}
1999-11-05 00:14:30 +01:00
/* \C -- override table title (formerly change HTML caption) */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "C") == 0)
{
char * opt = scan_option(&string, OT_NORMAL, NULL);
success = do_pset("title", opt, &pset.popt, quiet);
free(opt);
}
1999-11-05 00:14:30 +01:00
/*----------
1999-11-05 00:14:30 +01:00
* \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
*----------
1999-11-05 00:14:30 +01:00
*/
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
{
char *opt1, *opt2;
char opt1q, opt2q;
opt1 = scan_option(&string, OT_NORMAL, &opt1q);
opt2 = scan_option(&string, OT_NORMAL, &opt2q);
if (opt2)
1999-11-05 00:14:30 +01:00
/* gave username */
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);
}
1999-11-05 00:14:30 +01:00
/* \copy */
else if (strcasecmp(cmd, "copy") == 0)
{
2000-01-19 00:30:24 +01:00
success = do_copy(options_string);
if (options_string)
string += strlen(string);
}
1999-11-05 00:14:30 +01:00
/* \copyright */
else if (strcmp(cmd, "copyright") == 0)
print_copyright();
1999-11-05 00:14:30 +01:00
/* \d* commands */
else if (cmd[0] == 'd')
{
char * name;
bool show_verbose;
name = scan_option(&string, OT_SQLID, NULL);
show_verbose = strchr(cmd, '+') ? true : false;
1999-11-05 00:14:30 +01:00
switch (cmd[1])
{
case '\0':
case '+':
if (name)
success = describeTableDetails(name, show_verbose);
1999-11-05 00:14:30 +01:00
else
/* standard listing of interesting things */
success = listTables("tvs", NULL, show_verbose);
1999-11-05 00:14:30 +01:00
break;
case 'a':
success = describeAggregates(name);
1999-11-05 00:14:30 +01:00
break;
case 'd':
success = objectDescription(name);
1999-11-05 00:14:30 +01:00
break;
case 'f':
success = describeFunctions(name, show_verbose);
1999-11-05 00:14:30 +01:00
break;
case 'l':
success = do_lo_list();
1999-11-05 00:14:30 +01:00
break;
case 'o':
success = describeOperators(name);
1999-11-05 00:14:30 +01:00
break;
case 'p':
success = permissionsList(name);
1999-11-05 00:14:30 +01:00
break;
case 'T':
success = describeTypes(name, show_verbose);
1999-11-05 00:14:30 +01:00
break;
case 't':
case 'v':
case 'i':
case 's':
case 'S':
if (cmd[1] == 'S' && cmd[2] == '\0')
success = listTables("Stvs", NULL, show_verbose);
1999-11-05 00:14:30 +01:00
else
success = listTables(&cmd[1], name, show_verbose);
1999-11-05 00:14:30 +01:00
break;
default:
status = CMD_UNKNOWN;
}
free(name);
1999-11-05 00:14:30 +01:00
}
1999-11-05 00:14:30 +01:00
/*
* \e or \edit -- edit the current query buffer (or a file and make it
* the query buffer
*/
else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
{
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 and \qecho */
else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho")==0)
1999-11-05 00:14:30 +01:00
{
char * value;
char quoted;
bool no_newline = false;
bool first = true;
FILE * fout;
if (strcmp(cmd, "qecho")==0)
fout = pset.queryFout;
else
fout = 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);
1999-11-05 00:14:30 +01:00
}
/* \f -- change field separator */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "f") == 0)
{
char * fname = scan_option(&string, OT_NORMAL, NULL);
success = do_pset("fieldsep", fname, &pset.popt, quiet);
free(fname);
}
1999-11-05 00:14:30 +01:00
/* \g means send query */
else if (strcmp(cmd, "g") == 0)
{
char * fname = scan_option(&string, OT_NORMAL, NULL);
if (!fname)
pset.gfname = NULL;
1999-11-05 00:14:30 +01:00
else
pset.gfname = xstrdup(fname);
free(fname);
1999-11-05 00:14:30 +01:00
status = CMD_SEND;
}
1999-11-05 00:14:30 +01:00
/* help */
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
{
helpSQL(options_string ? &options_string[strspn(options_string, " \t")] : NULL);
/* set pointer to end of line */
if (string)
string += strlen(string);
}
1999-11-05 00:14:30 +01:00
/* HTML mode */
else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0)
{
if (pset.popt.topt.format != PRINT_HTML)
success = do_pset("format", "html", &pset.popt, quiet);
else
success = do_pset("format", "aligned", &pset.popt, quiet);
}
1999-11-05 00:14:30 +01:00
/* \i is include file */
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
{
char * fname = scan_option(&string, OT_NORMAL, NULL);
if (!fname)
2000-01-19 00:30:24 +01:00
{
psql_error("\\%s: missing required argument\n", cmd);
1999-11-05 00:14:30 +01:00
success = false;
}
else
{
success = process_file(fname);
free (fname);
}
}
1999-11-05 00:14:30 +01:00
/* \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);
1999-11-05 00:14:30 +01:00
/*
* large object things
*/
1999-11-05 00:14:30 +01:00
else if (strncmp(cmd, "lo_", 3) == 0)
{
char *opt1, *opt2;
opt1 = scan_option(&string, OT_NORMAL, NULL);
opt2 = scan_option(&string, OT_NORMAL, NULL);
1999-11-05 00:14:30 +01:00
if (strcmp(cmd + 3, "export") == 0)
{
if (!opt2)
1999-11-05 00:14:30 +01:00
{
2000-01-19 00:30:24 +01:00
psql_error("\\%s: missing required argument\n", cmd);
1999-11-05 00:14:30 +01:00
success = false;
}
else
success = do_lo_export(opt1, opt2);
1999-11-05 00:14:30 +01:00
}
else if (strcmp(cmd + 3, "import") == 0)
{
if (!opt1)
1999-11-05 00:14:30 +01:00
{
2000-01-19 00:30:24 +01:00
psql_error("\\%s: missing required argument\n", cmd);
1999-11-05 00:14:30 +01:00
success = false;
}
else
success = do_lo_import(opt1, opt2);
1999-11-05 00:14:30 +01:00
}
else if (strcmp(cmd + 3, "list") == 0)
success = do_lo_list();
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd + 3, "unlink") == 0)
{
if (!opt1)
1999-11-05 00:14:30 +01:00
{
2000-01-19 00:30:24 +01:00
psql_error("\\%s: missing required argument\n", cmd);
1999-11-05 00:14:30 +01:00
success = false;
}
else
success = do_lo_unlink(opt1);
1999-11-05 00:14:30 +01:00
}
else
status = CMD_UNKNOWN;
free(opt1);
free(opt2);
}
1999-11-05 00:14:30 +01:00
/* \o -- set query output */
else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0)
{
char * fname = scan_option(&string, OT_NORMAL, NULL);
success = setQFout(fname);
free(fname);
}
1999-11-05 00:14:30 +01:00
/* \p prints the current query buffer */
else if (strcmp(cmd, "p") == 0 || strcmp(cmd, "print") == 0)
{
if (query_buf && query_buf->len > 0)
puts(query_buf->data);
else if (!quiet)
1999-11-05 00:14:30 +01:00
puts("Query buffer is empty.");
fflush(stdout);
}
1999-11-05 00:14:30 +01:00
/* \pset -- set printing parameters */
else if (strcmp(cmd, "pset") == 0)
{
char * opt0 = scan_option(&string, OT_NORMAL, NULL);
char * opt1 = scan_option(&string, OT_NORMAL, NULL);
if (!opt0)
1999-11-05 00:14:30 +01:00
{
2000-01-19 00:30:24 +01:00
psql_error("\\%s: missing required argument\n", cmd);
1999-11-05 00:14:30 +01:00
success = false;
}
else
success = do_pset(opt0, opt1, &pset.popt, quiet);
free(opt0);
free(opt1);
}
1999-11-05 00:14:30 +01:00
/* \q or \quit */
else if (strcmp(cmd, "q") == 0 || strcmp(cmd, "quit") == 0)
status = CMD_TERMINATE;
/* reset(clear) the buffer */
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
{
resetPQExpBuffer(query_buf);
if (!quiet)
puts("Query buffer reset (cleared).");
}
1999-11-05 00:14:30 +01:00
/* \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);
1999-11-05 00:14:30 +01:00
success = saveHistory(fname ? fname : "/dev/tty");
if (success && !quiet && fname)
1999-11-05 00:14:30 +01:00
printf("Wrote history to %s.\n", fname);
free(fname);
1999-11-05 00:14:30 +01:00
}
/* \set -- generalized set variable/option command */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "set") == 0)
{
char * opt0 = scan_option(&string, OT_NORMAL, NULL);
if (!opt0)
1999-11-05 00:14:30 +01:00
{
/* list all variables */
/*
* XXX
* This is in utter violation of the GetVariable abstraction, but I
* have not bothered to do it better.
1999-11-05 00:14:30 +01:00
*/
struct _variable *ptr;
for (ptr = pset.vars; ptr->next; ptr = ptr->next)
1999-11-05 00:14:30 +01:00
fprintf(stdout, "%s = '%s'\n", ptr->next->name, ptr->next->value);
success = true;
}
else
{
/*
* 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))
1999-11-05 00:14:30 +01:00
{
2000-01-19 00:30:24 +01:00
psql_error("\\%s: error\n", cmd);
1999-11-05 00:14:30 +01:00
success = false;
}
free(newval);
1999-11-05 00:14:30 +01:00
}
free(opt0);
}
1999-11-05 00:14:30 +01:00
/* \t -- turn off headers and row count */
else if (strcmp(cmd, "t") == 0)
success = do_pset("tuples_only", NULL, &pset.popt, quiet);
1999-11-05 00:14:30 +01:00
/* \T -- define html <table ...> attributes */
else if (strcmp(cmd, "T") == 0)
{
char * value = scan_option(&string, OT_NORMAL, NULL);
success = do_pset("tableattr", value, &pset.popt, quiet);
free(value);
}
1999-11-05 00:14:30 +01:00
/* \unset */
else if (strcmp(cmd, "unset") == 0)
{
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))
{
2000-01-19 00:30:24 +01:00
psql_error("\\%s: error\n", cmd);
success = false;
}
free(opt);
}
1999-11-05 00:14:30 +01:00
/* \w -- write query buffer to file */
else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0)
{
FILE *fd = NULL;
bool is_pipe = false;
char *fname = NULL;
1999-11-05 00:14:30 +01:00
if (!query_buf)
{
psql_error("no query buffer");
status = CMD_ERROR;
}
else
{
fname = scan_option(&string, OT_NORMAL, NULL);
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;
}
}
}
1999-11-05 00:14:30 +01:00
if (fd)
{
int result;
1999-11-05 00:14:30 +01:00
if (query_buf && query_buf->len > 0)
fprintf(fd, "%s\n", query_buf->data);
if (is_pipe)
1999-11-05 00:14:30 +01:00
result = pclose(fd);
else
result = fclose(fd);
1999-11-05 00:14:30 +01:00
if (result == EOF)
{
psql_error("%s: %s\n", fname, strerror(errno));
1999-11-05 00:14:30 +01:00
success = false;
}
}
free(fname);
}
1999-11-05 00:14:30 +01:00
/* \x -- toggle expanded table representation */
else if (strcmp(cmd, "x") == 0)
success = do_pset("expanded", NULL, &pset.popt, quiet);
/* \z -- list table rights (grant/revoke) */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "z") == 0)
{
char * opt = scan_option(&string, OT_SQLID, NULL);
success = permissionsList(opt);
free(opt);
}
/* \! -- shell escape */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "!") == 0)
{
1999-11-05 00:14:30 +01:00
success = do_shell(options_string);
/* wind pointer to end of line */
if (string)
string += strlen(string);
}
1999-11-05 00:14:30 +01:00
/* \? -- slash command help */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "?") == 0)
slashUsage();
#if 1
2000-01-19 00:30:24 +01:00
/*
1999-11-05 00:14:30 +01:00
* 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 = 0;
char *value;
1999-11-05 00:14:30 +01:00
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);
}
1999-11-05 00:14:30 +01:00
}
#endif
1999-11-05 00:14:30 +01:00
else
status = CMD_UNKNOWN;
1999-11-05 00:14:30 +01:00
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);
1999-11-05 00:14:30 +01:00
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.
*
* The return value is malloc()'ed.
*/
static char *
unescape(const unsigned char *source, size_t len)
{
const unsigned char *p;
1999-11-05 00:14:30 +01:00
bool esc = false; /* Last character we saw was the escape
* character */
char *destination,
*tmp;
size_t length;
#ifdef USE_ASSERT_CHECKING
1999-11-05 00:14:30 +01:00
assert(source);
#endif
length = Min(len, strlen(source)) + 1;
tmp = destination = malloc(length);
1999-11-05 00:14:30 +01:00
if (!tmp)
{
2000-01-19 00:30:24 +01:00
psql_error("out of memory\n");
1999-11-05 00:14:30 +01:00
exit(EXIT_FAILURE);
}
for (p = source; p-source < len && *p; p += PQmblen(p, pset.encoding))
{
1999-11-05 00:14:30 +01:00
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;
1999-11-05 00:14:30 +01:00
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':
{
long int l;
char *end;
l = strtol(p, &end, 0);
c = l;
p = end - 1;
break;
}
default:
c = *p;
}
*tmp++ = c;
esc = false;
}
else if (*p == '\\')
esc = true;
else
{
*tmp++ = *p;
esc = false;
}
}
1999-11-05 00:14:30 +01:00
*tmp = '\0';
return destination;
}
/* do_connect
* -- handler for \connect
*
* Connects to a database (new_dbname) as a certain user (new_user).
* The new user can be NULL. A db name of "-" is the same as the old one.
* (That is, the one currently in pset. But pset.db can also be NULL. A NULL
* dbname is handled by libpq.)
* Returns true if all ok, false if the new connection couldn't be established
* but the old one was set back. Otherwise it terminates the program.
*/
static bool
do_connect(const char *new_dbname, const char *new_user)
{
PGconn *oldconn = pset.db;
1999-11-05 00:14:30 +01:00
const char *dbparam = NULL;
const char *userparam = NULL;
const char *pwparam = NULL;
1999-11-05 00:14:30 +01:00
char *prompted_password = NULL;
bool need_pass;
bool success = false;
/* Delete variables (in case we fail before setting them anew) */
SetVariable(pset.vars, "DBNAME", NULL);
SetVariable(pset.vars, "USER", NULL);
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)
1999-11-05 00:14:30 +01:00
dbparam = PQdb(oldconn);
else
dbparam = new_dbname;
/* If user is "" then use the old one */
if (new_user && PQuser(oldconn) && strcmp(new_user, "")==0)
1999-11-05 00:14:30 +01:00
userparam = PQuser(oldconn);
else
userparam = new_user;
/* need to prompt for password? */
if (pset.getPassword)
1999-11-05 00:14:30 +01:00
pwparam = prompted_password = simple_prompt("Password: ", 100, false); /* need to save for
* free() */
/*
* Use old password if no new one given (if you didn't have an old
* one, fine)
*/
if (!pwparam && oldconn)
1999-11-05 00:14:30 +01:00
pwparam = PQpass(oldconn);
1999-11-05 00:14:30 +01:00
do
{
need_pass = false;
pset.db = PQsetdbLogin(PQhost(oldconn), PQport(oldconn),
1999-11-05 00:14:30 +01:00
NULL, NULL, dbparam, userparam, pwparam);
if (PQstatus(pset.db) == CONNECTION_BAD &&
strcmp(PQerrorMessage(pset.db), "fe_sendauth: no password supplied\n") == 0)
1999-11-05 00:14:30 +01:00
{
need_pass = true;
free(prompted_password);
prompted_password = NULL;
pwparam = prompted_password = simple_prompt("Password: ", 100, false);
}
} while (need_pass);
free(prompted_password);
/*
* If connection failed, try at least keep the old one. That's
* probably more convenient than just kicking you out of the program.
*/
if (!pset.db || PQstatus(pset.db) == CONNECTION_BAD)
1999-11-05 00:14:30 +01:00
{
if (pset.cur_cmd_interactive)
{
2000-01-19 00:30:24 +01:00
psql_error("%s", PQerrorMessage(pset.db));
PQfinish(pset.db);
if (oldconn)
{
fputs("Previous connection kept\n", stderr);
pset.db = oldconn;
}
else
pset.db = NULL;
}
else
{
/* we don't want unpredictable things to
* happen in scripting mode */
2000-01-19 00:30:24 +01:00
psql_error("\\connect: %s", PQerrorMessage(pset.db));
PQfinish(pset.db);
1999-11-05 00:14:30 +01:00
if (oldconn)
PQfinish(oldconn);
pset.db = NULL;
1999-11-05 00:14:30 +01:00
}
}
1999-11-05 00:14:30 +01:00
else
{
if (!QUIET())
1999-11-05 00:14:30 +01:00
{
if (userparam != new_user) /* no new user */
printf("You are now connected to database %s.\n", dbparam);
else if (dbparam != new_dbname) /* no new db */
printf("You are now connected as new user %s.\n", new_user);
else /* both new */
1999-11-05 00:14:30 +01:00
printf("You are now connected to database %s as user %s.\n",
PQdb(pset.db), PQuser(pset.db));
1999-11-05 00:14:30 +01:00
}
1999-11-05 00:14:30 +01:00
if (oldconn)
PQfinish(oldconn);
1999-11-05 00:14:30 +01:00
success = true;
}
2000-01-19 00:30:24 +01:00
PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
pset.encoding = PQclientEncoding(pset.db);
2000-01-19 00:30:24 +01:00
/* Update variables */
SetVariable(pset.vars, "DBNAME", PQdb(pset.db));
SetVariable(pset.vars, "USER", PQuser(pset.db));
SetVariable(pset.vars, "HOST", PQhost(pset.db));
SetVariable(pset.vars, "PORT", PQport(pset.db));
pset.issuper = test_superuser(PQuser(pset.db));
1999-11-05 00:14:30 +01:00
return success;
}
/*
* Test if the given user is a database superuser.
* (Is used to set up the prompt right.)
*/
bool
test_superuser(const char * username)
{
PGresult *res;
char buf[64 + NAMEDATALEN];
bool answer;
if (!username)
return false;
sprintf(buf, "SELECT usesuper FROM pg_user WHERE usename = '%.*s'", NAMEDATALEN, username);
res = PSQLexec(buf);
answer =
(PQntuples(res)>0 && PQnfields(res)>0
&& !PQgetisnull(res,0,0)
&& PQgetvalue(res,0,0)
&& strcmp(PQgetvalue(res,0,0), "t")==0);
PQclear(res);
return answer;
}
/*
* do_edit -- handler for \e
*
* If you do not specify a filename, the current query buffer will be copied
* into a temporary one.
*/
static bool
editFile(const char *fname)
{
const char *editorName;
1999-11-05 00:14:30 +01:00
char *sys;
int result;
#ifdef USE_ASSERT_CHECKING
1999-11-05 00:14:30 +01:00
assert(fname);
#else
1999-11-05 00:14:30 +01:00
if (!fname)
return false;
#endif
1999-11-05 00:14:30 +01:00
/* Find an editor to use */
editorName = getenv("PSQL_EDITOR");
if (!editorName)
editorName = getenv("EDITOR");
if (!editorName)
editorName = getenv("VISUAL");
if (!editorName)
editorName = DEFAULT_EDITOR;
sys = malloc(strlen(editorName) + strlen(fname) + 32 + 1);
if (!sys)
return false;
sprintf(sys, "exec %s %s", editorName, fname);
result = system(sys);
2000-01-19 00:30:24 +01:00
if (result == -1)
psql_error("could not start editor %s\n", editorName);
2000-01-19 00:30:24 +01:00
else if (result == 127)
psql_error("could not start /bin/sh\n");
1999-11-05 00:14:30 +01:00
free(sys);
return result == 0;
}
/* call this one */
static bool
do_edit(const char *filename_arg, PQExpBuffer query_buf)
{
char fnametmp[MAXPGPATH];
1999-11-05 00:14:30 +01:00
FILE *stream;
const char *fname;
bool error = false;
#ifndef WIN32
1999-11-05 00:14:30 +01:00
struct stat before,
after;
#endif
1999-11-05 00:14:30 +01:00
if (filename_arg)
fname = filename_arg;
1999-11-05 00:14:30 +01:00
else
{
/* make a temp file to edit */
#ifndef WIN32
1999-11-05 00:14:30 +01:00
mode_t oldumask;
const char *tmpdirenv = getenv("TMPDIR");
sprintf(fnametmp, "%s/psql.edit.%ld.%ld",
tmpdirenv ? tmpdirenv : "/tmp",
(long) geteuid(), (long) getpid());
#else
1999-11-05 00:14:30 +01:00
GetTempFileName(".", "psql", 0, fnametmp);
#endif
1999-11-05 00:14:30 +01:00
fname = (const char *) fnametmp;
#ifndef WIN32
1999-11-05 00:14:30 +01:00
oldumask = umask(0177);
#endif
1999-11-05 00:14:30 +01:00
stream = fopen(fname, "w");
#ifndef WIN32
1999-11-05 00:14:30 +01:00
umask(oldumask);
#endif
1999-11-05 00:14:30 +01:00
if (!stream)
{
2000-01-19 00:30:24 +01:00
psql_error("couldn't open temp file %s: %s\n", fname, strerror(errno));
1999-11-05 00:14:30 +01:00
error = true;
}
else
{
unsigned int ql = query_buf->len;
if (ql == 0 || query_buf->data[ql - 1] != '\n')
{
appendPQExpBufferChar(query_buf, '\n');
ql++;
}
if (fwrite(query_buf->data, 1, ql, stream) != ql)
{
2000-01-19 00:30:24 +01:00
psql_error("%s: %s\n", fname, strerror(errno));
1999-11-05 00:14:30 +01:00
fclose(stream);
remove(fname);
error = true;
}
else
fclose(stream);
}
}
1999-11-05 00:14:30 +01:00
#ifndef WIN32
if (!error && stat(fname, &before) != 0)
{
2000-01-19 00:30:24 +01:00
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
#endif
1999-11-05 00:14:30 +01:00
/* call editor */
if (!error)
error = !editFile(fname);
#ifndef WIN32
1999-11-05 00:14:30 +01:00
if (!error && stat(fname, &after) != 0)
{
2000-01-19 00:30:24 +01:00
psql_error("%s: %s\n", fname, strerror(errno));
1999-11-05 00:14:30 +01:00
error = true;
}
1999-11-05 00:14:30 +01:00
if (!error && before.st_mtime != after.st_mtime)
{
#else
1999-11-05 00:14:30 +01:00
if (!error)
{
#endif
1999-11-05 00:14:30 +01:00
stream = fopen(fname, "r");
if (!stream)
{
2000-01-19 00:30:24 +01:00
psql_error("%s: %s\n", fname, strerror(errno));
1999-11-05 00:14:30 +01:00
error = true;
}
else
{
/* read file back in */
char line[1024];
size_t result;
resetPQExpBuffer(query_buf);
do
{
result = fread(line, 1, 1024, stream);
if (ferror(stream))
{
2000-01-19 00:30:24 +01:00
psql_error("%s: %s\n", fname, strerror(errno));
1999-11-05 00:14:30 +01:00
error = true;
break;
}
appendBinaryPQExpBuffer(query_buf, line, result);
} while (!feof(stream));
appendPQExpBufferChar(query_buf, '\0');
fclose(stream);
}
1999-11-05 00:14:30 +01:00
/* remove temp file */
if (!filename_arg)
2000-01-19 00:30:24 +01:00
{
if (remove(fname)==-1)
{
psql_error("%s: %s\n", fname, strerror(errno));
error=true;
}
}
}
1999-11-05 00:14:30 +01:00
return !error;
}
/*
* process_file
*
* Read commands from filename and then them to the main processing loop
* Handler for \i, but can be used for other things as well.
*/
bool
2000-01-19 00:30:24 +01:00
process_file(char *filename)
{
1999-11-05 00:14:30 +01:00
FILE *fd;
int result;
char *oldfilename;
1999-11-05 00:14:30 +01:00
if (!filename)
return false;
1999-11-05 00:14:30 +01:00
fd = fopen(filename, "r");
1999-11-05 00:14:30 +01:00
if (!fd)
{
2000-01-19 00:30:24 +01:00
psql_error("%s: %s\n", filename, strerror(errno));
1999-11-05 00:14:30 +01:00
return false;
}
oldfilename = pset.inputfile;
2000-01-19 00:30:24 +01:00
pset.inputfile = filename;
result = MainLoop(fd);
1999-11-05 00:14:30 +01:00
fclose(fd);
pset.inputfile = oldfilename;
1999-11-05 00:14:30 +01:00
return (result == EXIT_SUCCESS);
}
/*
* do_pset
*
*/
static const char *
_align2string(enum printFormat in)
{
1999-11-05 00:14:30 +01:00
switch (in)
{
case PRINT_NOTHING:
return "nothing";
break;
case PRINT_UNALIGNED:
return "unaligned";
break;
case PRINT_ALIGNED:
return "aligned";
break;
case PRINT_HTML:
return "html";
break;
case PRINT_LATEX:
return "latex";
break;
}
return "unknown";
}
bool
1999-11-05 00:14:30 +01:00
do_pset(const char *param, const char *value, printQueryOpt * popt, bool quiet)
{
1999-11-05 00:14:30 +01:00
size_t vallen = 0;
#ifdef USE_ASSERT_CHECKING
1999-11-05 00:14:30 +01:00
assert(param);
#else
1999-11-05 00:14:30 +01:00
if (!param)
return false;
#endif
1999-11-05 00:14:30 +01:00
if (value)
vallen = strlen(value);
/* set format */
if (strcmp(param, "format") == 0)
{
if (!value)
;
else if (strncasecmp("unaligned", value, vallen) == 0)
popt->topt.format = PRINT_UNALIGNED;
else if (strncasecmp("aligned", value, vallen) == 0)
popt->topt.format = PRINT_ALIGNED;
else if (strncasecmp("html", value, vallen) == 0)
popt->topt.format = PRINT_HTML;
else if (strncasecmp("latex", value, vallen) == 0)
popt->topt.format = PRINT_LATEX;
else
{
2000-01-19 00:30:24 +01:00
psql_error("\\pset: allowed formats are unaligned, aligned, html, latex\n");
1999-11-05 00:14:30 +01:00
return false;
}
if (!quiet)
printf("Output format is %s.\n", _align2string(popt->topt.format));
}
1999-11-05 00:14:30 +01:00
/* set border style/width */
else if (strcmp(param, "border") == 0)
{
if (value)
popt->topt.border = atoi(value);
1999-11-05 00:14:30 +01:00
if (!quiet)
printf("Border style is %d.\n", popt->topt.border);
}
1999-11-05 00:14:30 +01:00
/* set expanded/vertical mode */
else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0)
{
popt->topt.expanded = !popt->topt.expanded;
if (!quiet)
printf("Expanded display is %s.\n", popt->topt.expanded ? "on" : "off");
}
1999-11-05 00:14:30 +01:00
/* null display */
else if (strcmp(param, "null") == 0)
{
if (value)
{
free(popt->nullPrint);
popt->nullPrint = xstrdup(value);
}
if (!quiet)
printf("Null display is \"%s\".\n", popt->nullPrint ? popt->nullPrint : "");
}
1999-11-05 00:14:30 +01:00
/* field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
if (value)
{
free(popt->topt.fieldSep);
popt->topt.fieldSep = xstrdup(value);
}
if (!quiet)
2000-01-19 00:30:24 +01:00
printf("Field separator is '%s'.\n", popt->topt.fieldSep);
}
/* record separator for unaligned text */
else if (strcmp(param, "recordsep") == 0)
{
if (value)
{
free(popt->topt.recordSep);
popt->topt.recordSep = xstrdup(value);
}
if (!quiet) {
if (strcmp(popt->topt.recordSep, "\n")==0)
printf("Record separator is <newline>.");
else
printf("Record separator is '%s'.\n", popt->topt.recordSep);
}
1999-11-05 00:14:30 +01:00
}
1999-11-05 00:14:30 +01:00
/* toggle between full and barebones format */
else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
{
popt->topt.tuples_only = !popt->topt.tuples_only;
if (!quiet)
{
if (popt->topt.tuples_only)
puts("Showing only tuples.");
else
puts("Tuples only is off.");
}
}
/* set title override */
else if (strcmp(param, "title") == 0)
{
free(popt->title);
if (!value)
popt->title = NULL;
else
popt->title = xstrdup(value);
if (!quiet)
{
if (popt->title)
printf("Title is \"%s\".\n", popt->title);
else
printf("Title is unset.\n");
}
}
1999-11-05 00:14:30 +01:00
/* set HTML table tag options */
else if (strcmp(param, "T") == 0 || strcmp(param, "tableattr") == 0)
{
free(popt->topt.tableAttr);
if (!value)
popt->topt.tableAttr = NULL;
else
popt->topt.tableAttr = xstrdup(value);
if (!quiet)
{
if (popt->topt.tableAttr)
printf("Table attribute is \"%s\".\n", popt->topt.tableAttr);
else
printf("Table attributes unset.\n");
}
}
1999-11-05 00:14:30 +01:00
/* toggle use of pager */
else if (strcmp(param, "pager") == 0)
{
popt->topt.pager = !popt->topt.pager;
if (!quiet)
{
if (popt->topt.pager)
puts("Using pager is on.");
else
puts("Using pager is off.");
}
}
1999-11-05 00:14:30 +01:00
else
{
2000-01-19 00:30:24 +01:00
psql_error("\\pset: unknown option: %s\n", param);
1999-11-05 00:14:30 +01:00
return false;
}
return true;
}
#define DEFAULT_SHELL "/bin/sh"
static bool
do_shell(const char *command)
{
1999-11-05 00:14:30 +01:00
int result;
1999-11-05 00:14:30 +01:00
if (!command)
{
char *sys;
const char *shellName;
1999-11-05 00:14:30 +01:00
shellName = getenv("SHELL");
if (shellName == NULL)
shellName = DEFAULT_SHELL;
sys = malloc(strlen(shellName) + 16);
2000-01-19 00:30:24 +01:00
if (!sys) {
psql_error("out of memory\n");
if (pset.cur_cmd_interactive)
return false;
else
exit(EXIT_FAILURE);
}
1999-11-05 00:14:30 +01:00
sprintf(sys, "exec %s", shellName);
result = system(sys);
free(sys);
}
else
result = system(command);
1999-11-05 00:14:30 +01:00
if (result == 127 || result == -1)
{
2000-01-19 00:30:24 +01:00
psql_error("\\!: failed\n");
1999-11-05 00:14:30 +01:00
return false;
}
return true;
}