mirror of
https://github.com/postgres/postgres.git
synced 2025-04-29 13:56:47 +03:00
conversion of basic ASCII letters. Remove all uses of strcasecmp and strncasecmp in favor of new functions pg_strcasecmp and pg_strncasecmp; remove most but not all direct uses of toupper and tolower in favor of pg_toupper and pg_tolower. These functions use the same notions of case folding already developed for identifier case conversion. I left the straight locale-based folding in place for situations where we are just manipulating user data and not trying to match it to built-in strings --- for example, the SQL upper() function is still locale dependent. Perhaps this will prove not to be what's wanted, but at the moment we can initdb and pass regression tests in Turkish locale.
1531 lines
32 KiB
C
1531 lines
32 KiB
C
/*
|
|
* psql - the PostgreSQL interactive terminal
|
|
*
|
|
* Copyright (c) 2000-2003, PostgreSQL Global Development Group
|
|
*
|
|
* $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.116 2004/05/07 00:24:58 tgl Exp $
|
|
*/
|
|
#include "postgres_fe.h"
|
|
#include "command.h"
|
|
|
|
#include <errno.h>
|
|
#include <ctype.h>
|
|
#ifdef HAVE_PWD_H
|
|
#include <pwd.h>
|
|
#endif
|
|
#ifndef WIN32
|
|
#include <sys/types.h> /* for umask() */
|
|
#include <sys/stat.h> /* for stat() */
|
|
#include <fcntl.h> /* open() flags */
|
|
#include <unistd.h> /* for geteuid(), getpid(), stat() */
|
|
#else
|
|
#include <win32.h>
|
|
#include <io.h>
|
|
#include <fcntl.h>
|
|
#include <direct.h>
|
|
#endif
|
|
|
|
#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 "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,
|
|
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().
|
|
*
|
|
* 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).
|
|
* 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(PsqlScanState scan_state,
|
|
PQExpBuffer query_buf)
|
|
{
|
|
backslashResult status = CMD_SKIP_LINE;
|
|
char *cmd;
|
|
char *arg;
|
|
|
|
psql_assert(scan_state);
|
|
|
|
/* Parse off the command name */
|
|
cmd = psql_scan_slash_command(scan_state);
|
|
|
|
/* And try to execute it */
|
|
status = exec_command(cmd, scan_state, query_buf);
|
|
|
|
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];
|
|
|
|
/* don't change cmd until we know it's okay */
|
|
new_cmd[0] = cmd[0];
|
|
new_cmd[1] = '\0';
|
|
|
|
psql_scan_slash_pushback(scan_state, cmd + 1);
|
|
|
|
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 (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"), cmd);
|
|
else
|
|
psql_error("invalid command \\%s\n", cmd);
|
|
status = CMD_ERROR;
|
|
}
|
|
|
|
/* eat the rest of the options, if any */
|
|
while ((arg = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false)))
|
|
{
|
|
if (status != CMD_ERROR)
|
|
psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
|
|
free(arg);
|
|
}
|
|
|
|
/* 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,
|
|
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;
|
|
|
|
/*
|
|
* \a -- toggle field alignment This makes little sense but we keep it
|
|
* around.
|
|
*/
|
|
if (strcmp(cmd, "a") == 0)
|
|
{
|
|
if (pset.popt.topt.format != PRINT_ALIGNED)
|
|
success = do_pset("format", "aligned", &pset.popt, quiet);
|
|
else
|
|
success = do_pset("format", "unaligned", &pset.popt, quiet);
|
|
}
|
|
|
|
/* \C -- override table title (formerly change HTML caption) */
|
|
else if (strcmp(cmd, "C") == 0)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
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
|
|
*----------
|
|
*/
|
|
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
|
|
{
|
|
char *opt1,
|
|
*opt2;
|
|
char opt1q,
|
|
opt2q;
|
|
|
|
/*
|
|
* Ideally we should treat the arguments as SQL identifiers. But
|
|
* for backwards compatibility with 7.2 and older pg_dump files,
|
|
* we have to take unquoted arguments verbatim (don't downcase
|
|
* them). For now, double-quoted arguments may be stripped of
|
|
* double quotes (as if SQL identifiers). By 7.4 or so, pg_dump
|
|
* files can be expected to double-quote all mixed-case \connect
|
|
* arguments, and then we can get rid of OT_SQLIDHACK.
|
|
*/
|
|
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 */
|
|
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);
|
|
}
|
|
|
|
/* \cd */
|
|
else if (strcmp(cmd, "cd") == 0)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
char *dir;
|
|
|
|
if (opt)
|
|
dir = opt;
|
|
else
|
|
{
|
|
#ifndef WIN32
|
|
struct passwd *pw;
|
|
|
|
pw = getpwuid(geteuid());
|
|
if (!pw)
|
|
{
|
|
psql_error("could not get home directory: %s\n", strerror(errno));
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
dir = pw->pw_dir;
|
|
#else /* WIN32 */
|
|
|
|
/*
|
|
* On Windows, 'cd' without arguments prints the current
|
|
* directory, so if someone wants to code this here instead...
|
|
*/
|
|
dir = "/";
|
|
#endif /* WIN32 */
|
|
}
|
|
|
|
if (chdir(dir) == -1)
|
|
{
|
|
psql_error("\\%s: could not change directory to \"%s\": %s\n",
|
|
cmd, dir, strerror(errno));
|
|
success = false;
|
|
}
|
|
|
|
if (opt)
|
|
free(opt);
|
|
}
|
|
|
|
/* \copy */
|
|
else if (pg_strcasecmp(cmd, "copy") == 0)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE, NULL, false);
|
|
|
|
success = do_copy(opt);
|
|
free(opt);
|
|
}
|
|
|
|
/* \copyright */
|
|
else if (strcmp(cmd, "copyright") == 0)
|
|
print_copyright();
|
|
|
|
/* \d* commands */
|
|
else if (cmd[0] == 'd')
|
|
{
|
|
char *pattern;
|
|
bool show_verbose;
|
|
|
|
/* We don't do SQLID reduction on the pattern yet */
|
|
pattern = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
show_verbose = strchr(cmd, '+') ? true : false;
|
|
|
|
switch (cmd[1])
|
|
{
|
|
case '\0':
|
|
case '+':
|
|
if (pattern)
|
|
success = describeTableDetails(pattern, show_verbose);
|
|
else
|
|
/* standard listing of interesting things */
|
|
success = listTables("tvs", NULL, show_verbose);
|
|
break;
|
|
case 'a':
|
|
success = describeAggregates(pattern, show_verbose);
|
|
break;
|
|
case 'c':
|
|
success = listConversions(pattern);
|
|
break;
|
|
case 'C':
|
|
success = listCasts(pattern);
|
|
break;
|
|
case 'd':
|
|
success = objectDescription(pattern);
|
|
break;
|
|
case 'D':
|
|
success = listDomains(pattern);
|
|
break;
|
|
case 'f':
|
|
success = describeFunctions(pattern, show_verbose);
|
|
break;
|
|
case 'g':
|
|
success = describeGroups(pattern);
|
|
break;
|
|
case 'l':
|
|
success = do_lo_list();
|
|
break;
|
|
case 'n':
|
|
success = listSchemas(pattern);
|
|
break;
|
|
case 'o':
|
|
success = describeOperators(pattern);
|
|
break;
|
|
case 'p':
|
|
success = permissionsList(pattern);
|
|
break;
|
|
case 'T':
|
|
success = describeTypes(pattern, show_verbose);
|
|
break;
|
|
case 't':
|
|
case 'v':
|
|
case 'i':
|
|
case 's':
|
|
case 'S':
|
|
success = listTables(&cmd[1], pattern, show_verbose);
|
|
break;
|
|
case 'u':
|
|
success = describeUsers(pattern);
|
|
break;
|
|
|
|
default:
|
|
status = CMD_UNKNOWN;
|
|
}
|
|
|
|
if (pattern)
|
|
free(pattern);
|
|
}
|
|
|
|
|
|
/*
|
|
* \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\n");
|
|
status = CMD_ERROR;
|
|
}
|
|
else
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
/* \echo and \qecho */
|
|
else if (strcmp(cmd, "echo") == 0 || strcmp(cmd, "qecho") == 0)
|
|
{
|
|
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 = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, "ed, false)))
|
|
{
|
|
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);
|
|
}
|
|
|
|
/* \encoding -- set/show client side encoding */
|
|
else if (strcmp(cmd, "encoding") == 0)
|
|
{
|
|
char *encoding = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!encoding)
|
|
{
|
|
/* show encoding */
|
|
puts(pg_encoding_to_char(pset.encoding));
|
|
}
|
|
else
|
|
{
|
|
/* set encoding */
|
|
if (PQsetClientEncoding(pset.db, encoding) == -1)
|
|
psql_error("%s: invalid encoding name or conversion procedure not found\n", encoding);
|
|
else
|
|
{
|
|
/* save encoding info into psql internal data */
|
|
pset.encoding = PQclientEncoding(pset.db);
|
|
pset.popt.topt.encoding = pset.encoding;
|
|
SetVariable(pset.vars, "ENCODING",
|
|
pg_encoding_to_char(pset.encoding));
|
|
}
|
|
free(encoding);
|
|
}
|
|
}
|
|
|
|
/* \f -- change field separator */
|
|
else if (strcmp(cmd, "f") == 0)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
success = do_pset("fieldsep", fname, &pset.popt, quiet);
|
|
free(fname);
|
|
}
|
|
|
|
/* \g means send query */
|
|
else if (strcmp(cmd, "g") == 0)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_FILEPIPE, NULL, false);
|
|
|
|
if (!fname)
|
|
pset.gfname = NULL;
|
|
else
|
|
{
|
|
expand_tilde(&fname);
|
|
pset.gfname = pg_strdup(fname);
|
|
}
|
|
free(fname);
|
|
status = CMD_SEND;
|
|
}
|
|
|
|
/* help */
|
|
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE, NULL, false);
|
|
|
|
helpSQL(opt, pset.popt.topt.pager);
|
|
free(opt);
|
|
}
|
|
|
|
/* 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);
|
|
}
|
|
|
|
|
|
/* \i is include file */
|
|
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
if (!fname)
|
|
{
|
|
psql_error("\\%s: missing required argument\n", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
expand_tilde(&fname);
|
|
success = (process_file(fname) == EXIT_SUCCESS);
|
|
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
|
|
*/
|
|
else if (strncmp(cmd, "lo_", 3) == 0)
|
|
{
|
|
char *opt1,
|
|
*opt2;
|
|
|
|
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)
|
|
{
|
|
if (!opt2)
|
|
{
|
|
psql_error("\\%s: missing required argument\n", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
expand_tilde(&opt2);
|
|
success = do_lo_export(opt1, opt2);
|
|
}
|
|
}
|
|
|
|
else if (strcmp(cmd + 3, "import") == 0)
|
|
{
|
|
if (!opt1)
|
|
{
|
|
psql_error("\\%s: missing required argument\n", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
{
|
|
expand_tilde(&opt1);
|
|
success = do_lo_import(opt1, opt2);
|
|
}
|
|
}
|
|
|
|
else if (strcmp(cmd + 3, "list") == 0)
|
|
success = do_lo_list();
|
|
|
|
else if (strcmp(cmd + 3, "unlink") == 0)
|
|
{
|
|
if (!opt1)
|
|
{
|
|
psql_error("\\%s: missing required argument\n", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
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)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_FILEPIPE, NULL, true);
|
|
|
|
expand_tilde(&fname);
|
|
success = setQFout(fname);
|
|
free(fname);
|
|
}
|
|
|
|
/* \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)
|
|
puts(gettext("Query buffer is empty."));
|
|
fflush(stdout);
|
|
}
|
|
|
|
/* \pset -- set printing parameters */
|
|
else if (strcmp(cmd, "pset") == 0)
|
|
{
|
|
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)
|
|
{
|
|
psql_error("\\%s: missing required argument\n", cmd);
|
|
success = false;
|
|
}
|
|
else
|
|
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;
|
|
|
|
/* reset(clear) the buffer */
|
|
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
|
|
{
|
|
resetPQExpBuffer(query_buf);
|
|
psql_scan_reset(scan_state);
|
|
if (!quiet)
|
|
puts(gettext("Query buffer reset (cleared)."));
|
|
}
|
|
|
|
/* \s save history in a file or show it on the screen */
|
|
else if (strcmp(cmd, "s") == 0)
|
|
{
|
|
char *fname = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
expand_tilde(&fname);
|
|
/* This scrolls off the screen when using /dev/tty */
|
|
success = saveHistory(fname ? fname : "/dev/tty");
|
|
|
|
if (success && !quiet && fname)
|
|
printf(gettext("Wrote history to file \"%s\".\n"), fname);
|
|
free(fname);
|
|
}
|
|
|
|
/* \set -- generalized set variable/option command */
|
|
else if (strcmp(cmd, "set") == 0)
|
|
{
|
|
char *opt0 = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!opt0)
|
|
{
|
|
/* list all variables */
|
|
PrintVariables(pset.vars);
|
|
success = true;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Set variable to the concatenation of the arguments.
|
|
*/
|
|
char *newval;
|
|
char *opt;
|
|
|
|
opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
newval = pg_strdup(opt ? opt : "");
|
|
free(opt);
|
|
|
|
while ((opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false)))
|
|
{
|
|
newval = realloc(newval, strlen(newval) + strlen(opt) + 1);
|
|
if (!newval)
|
|
{
|
|
psql_error("out of memory\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
strcat(newval, opt);
|
|
free(opt);
|
|
}
|
|
|
|
if (SetVariable(pset.vars, opt0, newval))
|
|
{
|
|
/* Check for special variables */
|
|
if (strcmp(opt0, "VERBOSITY") == 0)
|
|
SyncVerbosityVariable();
|
|
}
|
|
else
|
|
{
|
|
psql_error("\\%s: error\n", cmd);
|
|
success = false;
|
|
}
|
|
free(newval);
|
|
}
|
|
free(opt0);
|
|
}
|
|
|
|
/* \t -- turn off headers and row count */
|
|
else if (strcmp(cmd, "t") == 0)
|
|
success = do_pset("tuples_only", NULL, &pset.popt, quiet);
|
|
|
|
|
|
/* \T -- define html <table ...> attributes */
|
|
else if (strcmp(cmd, "T") == 0)
|
|
{
|
|
char *value = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
success = do_pset("tableattr", value, &pset.popt, quiet);
|
|
free(value);
|
|
}
|
|
|
|
/* \timing -- toggle timing of queries */
|
|
else if (strcmp(cmd, "timing") == 0)
|
|
{
|
|
pset.timing = !pset.timing;
|
|
if (!quiet)
|
|
{
|
|
if (pset.timing)
|
|
puts(gettext("Timing is on."));
|
|
else
|
|
puts(gettext("Timing is off."));
|
|
}
|
|
}
|
|
|
|
/* \unset */
|
|
else if (strcmp(cmd, "unset") == 0)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, false);
|
|
|
|
if (!opt)
|
|
{
|
|
psql_error("\\%s: missing required argument\n", cmd);
|
|
success = false;
|
|
}
|
|
else 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 is_pipe = false;
|
|
char *fname = NULL;
|
|
|
|
if (!query_buf)
|
|
{
|
|
psql_error("no query buffer\n");
|
|
status = CMD_ERROR;
|
|
}
|
|
else
|
|
{
|
|
fname = psql_scan_slash_option(scan_state,
|
|
OT_FILEPIPE, NULL, true);
|
|
expand_tilde(&fname);
|
|
|
|
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)
|
|
{
|
|
int result;
|
|
|
|
if (query_buf && query_buf->len > 0)
|
|
fprintf(fd, "%s\n", query_buf->data);
|
|
|
|
if (is_pipe)
|
|
result = pclose(fd);
|
|
else
|
|
result = fclose(fd);
|
|
|
|
if (result == EOF)
|
|
{
|
|
psql_error("%s: %s\n", fname, strerror(errno));
|
|
success = false;
|
|
}
|
|
}
|
|
|
|
free(fname);
|
|
}
|
|
|
|
/* \x -- toggle expanded table representation */
|
|
else if (strcmp(cmd, "x") == 0)
|
|
success = do_pset("expanded", NULL, &pset.popt, quiet);
|
|
|
|
|
|
/* \z -- list table rights (equivalent to \dp) */
|
|
else if (strcmp(cmd, "z") == 0)
|
|
{
|
|
char *pattern = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true);
|
|
|
|
success = permissionsList(pattern);
|
|
if (pattern)
|
|
free(pattern);
|
|
}
|
|
|
|
/* \! -- shell escape */
|
|
else if (strcmp(cmd, "!") == 0)
|
|
{
|
|
char *opt = psql_scan_slash_option(scan_state,
|
|
OT_WHOLE_LINE, NULL, false);
|
|
|
|
success = do_shell(opt);
|
|
free(opt);
|
|
}
|
|
|
|
/* \? -- slash command help */
|
|
else if (strcmp(cmd, "?") == 0)
|
|
slashUsage(pset.popt.topt.pager);
|
|
|
|
#if 0
|
|
|
|
/*
|
|
* 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;
|
|
|
|
while ((value = psql_scan_slash_option(scan_state,
|
|
OT_NORMAL, NULL, true)))
|
|
{
|
|
fprintf(stderr, "+ opt(%d) = |%s|\n", i++, value);
|
|
free(value);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
else
|
|
status = CMD_UNKNOWN;
|
|
|
|
if (!success)
|
|
status = CMD_ERROR;
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
|
|
/* 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.
|
|
* The old connection will be kept if the session is interactive.
|
|
*/
|
|
static bool
|
|
do_connect(const char *new_dbname, const char *new_user)
|
|
{
|
|
PGconn *oldconn = pset.db;
|
|
const char *dbparam = NULL;
|
|
const char *userparam = NULL;
|
|
const char *pwparam = NULL;
|
|
char *prompted_password = NULL;
|
|
bool need_pass;
|
|
bool success = false;
|
|
|
|
/* Delete variables (in case we fail before setting them anew) */
|
|
UnsyncVariables();
|
|
|
|
/* 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;
|
|
|
|
/* If user is "" then use the old one */
|
|
if (new_user && PQuser(oldconn) && strcmp(new_user, "") == 0)
|
|
userparam = PQuser(oldconn);
|
|
else
|
|
userparam = new_user;
|
|
|
|
/* need to prompt for password? */
|
|
if (pset.getPassword)
|
|
pwparam = prompted_password = simple_prompt("Password: ", 100, false);
|
|
|
|
/*
|
|
* Use old password (if any) if no new one given and we are
|
|
* reconnecting as same user
|
|
*/
|
|
if (!pwparam && oldconn && PQuser(oldconn) && userparam &&
|
|
strcmp(PQuser(oldconn), userparam) == 0)
|
|
pwparam = PQpass(oldconn);
|
|
|
|
do
|
|
{
|
|
need_pass = false;
|
|
pset.db = PQsetdbLogin(PQhost(oldconn), PQport(oldconn),
|
|
NULL, NULL, dbparam, userparam, pwparam);
|
|
|
|
if (PQstatus(pset.db) == CONNECTION_BAD &&
|
|
strcmp(PQerrorMessage(pset.db), "fe_sendauth: no password supplied\n") == 0 &&
|
|
!feof(stdin))
|
|
{
|
|
PQfinish(pset.db);
|
|
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)
|
|
{
|
|
if (pset.cur_cmd_interactive)
|
|
{
|
|
psql_error("%s", PQerrorMessage(pset.db));
|
|
PQfinish(pset.db);
|
|
if (oldconn)
|
|
{
|
|
fputs(gettext("Previous connection kept\n"), stderr);
|
|
pset.db = oldconn;
|
|
}
|
|
else
|
|
pset.db = NULL;
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* we don't want unpredictable things to happen in scripting
|
|
* mode
|
|
*/
|
|
psql_error("\\connect: %s", PQerrorMessage(pset.db));
|
|
PQfinish(pset.db);
|
|
if (oldconn)
|
|
PQfinish(oldconn);
|
|
pset.db = NULL;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!QUIET())
|
|
{
|
|
if (userparam != new_user) /* no new user */
|
|
printf(gettext("You are now connected to database \"%s\".\n"), dbparam);
|
|
else if (dbparam != new_dbname) /* no new db */
|
|
printf(gettext("You are now connected as new user \"%s\".\n"), new_user);
|
|
else
|
|
/* both new */
|
|
printf(gettext("You are now connected to database \"%s\" as user \"%s\".\n"),
|
|
PQdb(pset.db), PQuser(pset.db));
|
|
}
|
|
|
|
if (oldconn)
|
|
PQfinish(oldconn);
|
|
|
|
success = true;
|
|
}
|
|
|
|
PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
|
|
|
|
/* Update variables */
|
|
SyncVariables();
|
|
|
|
return success;
|
|
}
|
|
|
|
|
|
/*
|
|
* SyncVariables
|
|
*
|
|
* Make psql's internal variables agree with connection state upon
|
|
* establishing a new connection.
|
|
*/
|
|
void
|
|
SyncVariables(void)
|
|
{
|
|
/* get stuff from connection */
|
|
pset.encoding = PQclientEncoding(pset.db);
|
|
pset.popt.topt.encoding = pset.encoding;
|
|
|
|
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));
|
|
SetVariable(pset.vars, "ENCODING", pg_encoding_to_char(pset.encoding));
|
|
|
|
/* send stuff to it, too */
|
|
SyncVerbosityVariable();
|
|
}
|
|
|
|
/*
|
|
* UnsyncVariables
|
|
*
|
|
* Clear variables that should be not be set when there is no connection.
|
|
*/
|
|
void
|
|
UnsyncVariables(void)
|
|
{
|
|
SetVariable(pset.vars, "DBNAME", NULL);
|
|
SetVariable(pset.vars, "USER", NULL);
|
|
SetVariable(pset.vars, "HOST", NULL);
|
|
SetVariable(pset.vars, "PORT", NULL);
|
|
SetVariable(pset.vars, "ENCODING", NULL);
|
|
}
|
|
|
|
/*
|
|
* Update connection state from VERBOSITY variable
|
|
*/
|
|
void
|
|
SyncVerbosityVariable(void)
|
|
{
|
|
switch (SwitchVariable(pset.vars, "VERBOSITY",
|
|
"default", "terse", "verbose", NULL))
|
|
{
|
|
case 1: /* default */
|
|
pset.verbosity = PQERRORS_DEFAULT;
|
|
break;
|
|
case 2: /* terse */
|
|
pset.verbosity = PQERRORS_TERSE;
|
|
break;
|
|
case 3: /* verbose */
|
|
pset.verbosity = PQERRORS_VERBOSE;
|
|
break;
|
|
default: /* not set or unrecognized value */
|
|
pset.verbosity = PQERRORS_DEFAULT;
|
|
break;
|
|
}
|
|
|
|
PQsetErrorVerbosity(pset.db, pset.verbosity);
|
|
}
|
|
|
|
|
|
/*
|
|
* 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;
|
|
char *sys;
|
|
int result;
|
|
|
|
psql_assert(fname);
|
|
|
|
/* 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 = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
|
|
sprintf(sys,
|
|
#ifndef WIN32
|
|
"exec "
|
|
#endif
|
|
"%s '%s'", editorName, fname);
|
|
result = system(sys);
|
|
if (result == -1)
|
|
psql_error("could not start editor \"%s\"\n", editorName);
|
|
else if (result == 127)
|
|
psql_error("could not start /bin/sh\n");
|
|
free(sys);
|
|
|
|
return result == 0;
|
|
}
|
|
|
|
|
|
/* call this one */
|
|
static bool
|
|
do_edit(const char *filename_arg, PQExpBuffer query_buf)
|
|
{
|
|
char fnametmp[MAXPGPATH];
|
|
FILE *stream = NULL;
|
|
const char *fname;
|
|
bool error = false;
|
|
int fd;
|
|
|
|
#ifndef WIN32
|
|
struct stat before,
|
|
after;
|
|
#endif
|
|
|
|
if (filename_arg)
|
|
fname = filename_arg;
|
|
|
|
else
|
|
{
|
|
/* make a temp file to edit */
|
|
#ifndef WIN32
|
|
const char *tmpdirenv = getenv("TMPDIR");
|
|
|
|
snprintf(fnametmp, sizeof(fnametmp), "%s/psql.edit.%ld.%ld",
|
|
tmpdirenv ? tmpdirenv : "/tmp",
|
|
(long) geteuid(), (long) getpid());
|
|
#else
|
|
GetTempFileName(".", "psql", 0, fnametmp);
|
|
#endif
|
|
fname = (const char *) fnametmp;
|
|
|
|
fd = open(fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
|
|
if (fd != -1)
|
|
stream = fdopen(fd, "w");
|
|
|
|
if (fd == -1 || !stream)
|
|
{
|
|
psql_error("could not open temporary file \"%s\": %s\n", fname, strerror(errno));
|
|
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)
|
|
{
|
|
psql_error("%s: %s\n", fname, strerror(errno));
|
|
fclose(stream);
|
|
remove(fname);
|
|
error = true;
|
|
}
|
|
else if (fclose(stream) != 0)
|
|
{
|
|
psql_error("%s: %s\n", fname, strerror(errno));
|
|
remove(fname);
|
|
error = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef WIN32
|
|
if (!error && stat(fname, &before) != 0)
|
|
{
|
|
psql_error("%s: %s\n", fname, strerror(errno));
|
|
error = true;
|
|
}
|
|
#endif
|
|
|
|
/* call editor */
|
|
if (!error)
|
|
error = !editFile(fname);
|
|
|
|
#ifndef WIN32
|
|
if (!error && stat(fname, &after) != 0)
|
|
{
|
|
psql_error("%s: %s\n", fname, strerror(errno));
|
|
error = true;
|
|
}
|
|
|
|
if (!error && before.st_mtime != after.st_mtime)
|
|
{
|
|
#else
|
|
if (!error)
|
|
{
|
|
#endif
|
|
stream = fopen(fname, "r");
|
|
if (!stream)
|
|
{
|
|
psql_error("%s: %s\n", fname, strerror(errno));
|
|
error = true;
|
|
}
|
|
else
|
|
{
|
|
/* read file back in */
|
|
char line[1024];
|
|
|
|
resetPQExpBuffer(query_buf);
|
|
while (fgets(line, sizeof(line), stream) != NULL)
|
|
appendPQExpBufferStr(query_buf, line);
|
|
|
|
if (ferror(stream))
|
|
{
|
|
psql_error("%s: %s\n", fname, strerror(errno));
|
|
error = true;
|
|
}
|
|
|
|
#ifdef USE_READLINE
|
|
#ifdef HAVE_REPLACE_HISTORY_ENTRY
|
|
|
|
replace_history_entry(where_history(), query_buf->data, NULL);
|
|
#else
|
|
add_history(query_buf->data);
|
|
#endif
|
|
#endif
|
|
fclose(stream);
|
|
}
|
|
|
|
}
|
|
|
|
/* remove temp file */
|
|
if (!filename_arg)
|
|
{
|
|
if (remove(fname) == -1)
|
|
{
|
|
psql_error("%s: %s\n", fname, strerror(errno));
|
|
error = true;
|
|
}
|
|
}
|
|
|
|
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.
|
|
*/
|
|
int
|
|
process_file(char *filename)
|
|
{
|
|
FILE *fd;
|
|
int result;
|
|
char *oldfilename;
|
|
|
|
if (!filename)
|
|
return false;
|
|
|
|
fd = fopen(filename, "r");
|
|
|
|
if (!fd)
|
|
{
|
|
psql_error("%s: %s\n", filename, strerror(errno));
|
|
return false;
|
|
}
|
|
|
|
oldfilename = pset.inputfile;
|
|
pset.inputfile = filename;
|
|
result = MainLoop(fd);
|
|
fclose(fd);
|
|
pset.inputfile = oldfilename;
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* do_pset
|
|
*
|
|
*/
|
|
static const char *
|
|
_align2string(enum printFormat in)
|
|
{
|
|
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
|
|
do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
|
|
{
|
|
size_t vallen = 0;
|
|
|
|
psql_assert(param);
|
|
|
|
if (value)
|
|
vallen = strlen(value);
|
|
|
|
/* set format */
|
|
if (strcmp(param, "format") == 0)
|
|
{
|
|
if (!value)
|
|
;
|
|
else if (pg_strncasecmp("unaligned", value, vallen) == 0)
|
|
popt->topt.format = PRINT_UNALIGNED;
|
|
else if (pg_strncasecmp("aligned", value, vallen) == 0)
|
|
popt->topt.format = PRINT_ALIGNED;
|
|
else if (pg_strncasecmp("html", value, vallen) == 0)
|
|
popt->topt.format = PRINT_HTML;
|
|
else if (pg_strncasecmp("latex", value, vallen) == 0)
|
|
popt->topt.format = PRINT_LATEX;
|
|
else
|
|
{
|
|
psql_error("\\pset: allowed formats are unaligned, aligned, html, latex\n");
|
|
return false;
|
|
}
|
|
|
|
if (!quiet)
|
|
printf(gettext("Output format is %s.\n"), _align2string(popt->topt.format));
|
|
}
|
|
|
|
/* set border style/width */
|
|
else if (strcmp(param, "border") == 0)
|
|
{
|
|
if (value)
|
|
popt->topt.border = atoi(value);
|
|
|
|
if (!quiet)
|
|
printf(gettext("Border style is %d.\n"), popt->topt.border);
|
|
}
|
|
|
|
/* 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(popt->topt.expanded
|
|
? gettext("Expanded display is on.\n")
|
|
: gettext("Expanded display is off.\n"));
|
|
}
|
|
|
|
/* null display */
|
|
else if (strcmp(param, "null") == 0)
|
|
{
|
|
if (value)
|
|
{
|
|
free(popt->nullPrint);
|
|
popt->nullPrint = pg_strdup(value);
|
|
}
|
|
if (!quiet)
|
|
printf(gettext("Null display is \"%s\".\n"), popt->nullPrint ? popt->nullPrint : "");
|
|
}
|
|
|
|
/* field separator for unaligned text */
|
|
else if (strcmp(param, "fieldsep") == 0)
|
|
{
|
|
if (value)
|
|
{
|
|
free(popt->topt.fieldSep);
|
|
popt->topt.fieldSep = pg_strdup(value);
|
|
}
|
|
if (!quiet)
|
|
printf(gettext("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 = pg_strdup(value);
|
|
}
|
|
if (!quiet)
|
|
{
|
|
if (strcmp(popt->topt.recordSep, "\n") == 0)
|
|
printf(gettext("Record separator is <newline>."));
|
|
else
|
|
printf(gettext("Record separator is \"%s\".\n"), popt->topt.recordSep);
|
|
}
|
|
}
|
|
|
|
/* 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(gettext("Showing only tuples."));
|
|
else
|
|
puts(gettext("Tuples only is off."));
|
|
}
|
|
}
|
|
|
|
/* set title override */
|
|
else if (strcmp(param, "title") == 0)
|
|
{
|
|
free(popt->title);
|
|
if (!value)
|
|
popt->title = NULL;
|
|
else
|
|
popt->title = pg_strdup(value);
|
|
|
|
if (!quiet)
|
|
{
|
|
if (popt->title)
|
|
printf(gettext("Title is \"%s\".\n"), popt->title);
|
|
else
|
|
printf(gettext("Title is unset.\n"));
|
|
}
|
|
}
|
|
|
|
/* 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 = pg_strdup(value);
|
|
|
|
if (!quiet)
|
|
{
|
|
if (popt->topt.tableAttr)
|
|
printf(gettext("Table attribute is \"%s\".\n"), popt->topt.tableAttr);
|
|
else
|
|
printf(gettext("Table attributes unset.\n"));
|
|
}
|
|
}
|
|
|
|
/* toggle use of pager */
|
|
else if (strcmp(param, "pager") == 0)
|
|
{
|
|
if (value && pg_strcasecmp(value, "always") == 0)
|
|
popt->topt.pager = 2;
|
|
else if (popt->topt.pager == 1)
|
|
popt->topt.pager = 0;
|
|
else
|
|
popt->topt.pager = 1;
|
|
if (!quiet)
|
|
{
|
|
if (popt->topt.pager == 1)
|
|
puts(gettext("Pager is used for long output."));
|
|
else if (popt->topt.pager == 2)
|
|
puts(gettext("Pager is always used."));
|
|
else
|
|
puts(gettext("Pager usage is off."));
|
|
}
|
|
}
|
|
|
|
/* disable "(x rows)" footer */
|
|
else if (strcmp(param, "footer") == 0)
|
|
{
|
|
popt->default_footer = !popt->default_footer;
|
|
if (!quiet)
|
|
{
|
|
if (popt->default_footer)
|
|
puts(gettext("Default footer is on."));
|
|
else
|
|
puts(gettext("Default footer is off."));
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
psql_error("\\pset: unknown option: %s\n", param);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
|
|
#define DEFAULT_SHELL "/bin/sh"
|
|
|
|
static bool
|
|
do_shell(const char *command)
|
|
{
|
|
int result;
|
|
|
|
if (!command)
|
|
{
|
|
char *sys;
|
|
const char *shellName;
|
|
|
|
shellName = getenv("SHELL");
|
|
if (shellName == NULL)
|
|
shellName = DEFAULT_SHELL;
|
|
|
|
sys = pg_malloc(strlen(shellName) + 16);
|
|
sprintf(sys,
|
|
#ifndef WIN32
|
|
"exec "
|
|
#endif
|
|
"%s", shellName);
|
|
result = system(sys);
|
|
free(sys);
|
|
}
|
|
else
|
|
result = system(command);
|
|
|
|
if (result == 127 || result == -1)
|
|
{
|
|
psql_error("\\!: failed\n");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|