postgresql/src/bin/psql/command.c

3141 lines
72 KiB
C
Raw Normal View History

2000-01-19 00:30:24 +01:00
/*
* psql - the PostgreSQL interactive terminal
*
* Copyright (c) 2000-2015, PostgreSQL Global Development Group
2000-01-19 00:30:24 +01:00
*
2010-09-20 22:08:53 +02:00
* src/bin/psql/command.c
2000-01-19 00:30:24 +01:00
*/
#include "postgres_fe.h"
#include "command.h"
2006-10-04 02:30:14 +02:00
#ifdef __BORLANDC__ /* needed for BCC */
#undef mkdir
#endif
#include <ctype.h>
#include <time.h>
2001-05-07 21:31:33 +02:00
#ifdef HAVE_PWD_H
#include <pwd.h>
#endif
#ifndef WIN32
1999-11-05 00:14:30 +01:00
#include <sys/types.h> /* for umask() */
#include <sys/stat.h> /* for stat() */
#include <fcntl.h> /* open() flags */
1999-11-05 00:14:30 +01:00
#include <unistd.h> /* for geteuid(), getpid(), stat() */
#else
#include <win32.h>
Here is a patch to make the current snapshot compile on Win32 (native, libpq and psql) again. Changes are: 1) psql requires the includes of "io.h" and "fcntl.h" in command.c in order to make a call to open() work (io.h for _open(), fcntl.h for the O_xxx) 2) PG_VERSION is no longer defined in version.h[.in], but in configure.in. Since we don't do configure on native win32, we need to put it in config.h.win32 :-( 3) Added define of SYSCONFDIR to config.h.win32 - libpq won't compile without it. This functionality is *NOT* tested - it's just defined as "" for now. May work, may not. 4) DEF_PGPORT renamed to DEF_PGPORT_STR I have done the "basic tests" on it - it connects to a database, and I can run queries. Haven't tested any of the fancier functions (yet). However, I stepped on a much bigger problem when fixing psql to work. It no longer works when linked against the .DLL version of libpq (which the Makefile does for it). I have left it linked against this version anyway, pending the comments I get on this mail :-) The problem is that there are strings being allocated from libpq.dll using PQExpBuffers (for example, initPQExpBuffer() on line 92 of input.c). These are being allocated using the malloc function used by libpq.dll. This function *may* be different from the malloc function used by psql.exe - only the resulting pointer must be valid. And with the default linking methods, it *WILL* be different. Later, psql.exe tries to free() this string, at which point it crashes because the free() function can't find the allocated block (it's on the allocated blocks list used by the runtime lib of libpq.dll). Shouldn't the right thing to do be to have psql call termPQExpBuffer() on the data instead? As it is now, gets_fromFile() will just return the pointer received from the PQExpBuffer.data (this may well be present at several places - this is the one I was bitten by so far). Isn't that kind of "accessing the internals of the PQExpBuffer structure" wrong? Instead, perhaps it shuold make a copy of the string, adn then termPQExpBuffer() it? In that case, the string will have been allocated from within the same library as the free() is called. I can get it to work just fine by doing this - changing from (around line 100 of input.c): and the same a bit further down in the same function. But, as I said above, this may be at more places in the code? Perhaps someone more familiar to it could comment on that? What do you think shuld be done about this? Personally, I go by the "If you allocate a piece of memory using an interface, use the same interface to free it", but the question is how to make it work :-) Also, AFAIK this only affects psql.exe, so the changes made to the libpq this patch are required no matter how the other issue is handled. Regards, Magnus
2001-01-24 04:42:38 +01:00
#include <io.h>
#include <fcntl.h>
#include <direct.h>
2004-11-04 23:25:14 +01:00
#include <sys/types.h> /* for umask() */
#include <sys/stat.h> /* for stat() */
#endif
#include "portability/instr_time.h"
2000-02-16 14:15:26 +01:00
#include "libpq-fe.h"
#include "pqexpbuffer.h"
#include "dumputils.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"
/* functions for use in this file */
1999-11-05 00:14:30 +01:00
static backslashResult exec_command(const char *cmd,
2004-08-29 07:07:03 +02:00
PsqlScanState scan_state,
PQExpBuffer query_buf);
static bool do_edit(const char *filename_arg, PQExpBuffer query_buf,
int lineno, bool *edited);
static bool do_connect(char *dbname, char *user, char *host, char *port);
static bool do_shell(const char *command);
static bool do_watch(PQExpBuffer query_buf, long sleep);
static bool lookup_function_oid(const char *desc, Oid *foid);
static bool get_create_function_cmd(Oid oid, PQExpBuffer buf);
static int strip_lineno_from_funcdesc(char *func);
static void minimal_error_message(PGresult *res);
static void printSSLInfo(void);
static bool printPsetInfo(const char *param, struct printQueryOpt *popt);
static char *pset_value_string(const char *param, struct printQueryOpt *popt);
#ifdef WIN32
static void checkWin32Codepage(void);
#endif
/*----------
* 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 = PSQL_CMD_SKIP_LINE;
char *cmd;
char *arg;
1999-11-05 00:14:30 +01:00
Assert(scan_state != NULL);
1999-11-05 00:14:30 +01:00
/* Parse off the command name */
cmd = psql_scan_slash_command(scan_state);
1999-11-05 00:14:30 +01:00
/* And try to execute it */
status = exec_command(cmd, scan_state, query_buf);
if (status == PSQL_CMD_UNKNOWN)
1999-11-05 00:14:30 +01:00
{
if (pset.cur_cmd_interactive)
psql_error("Invalid command \\%s. Try \\? for help.\n", cmd);
else
psql_error("invalid command \\%s\n", cmd);
status = PSQL_CMD_ERROR;
1999-11-05 00:14:30 +01:00
}
if (status != PSQL_CMD_ERROR)
{
/* eat any remaining arguments after a valid command */
/* note we suppress evaluation of backticks here */
while ((arg = psql_scan_slash_option(scan_state,
OT_NO_EVAL, NULL, false)))
{
psql_error("\\%s: extra argument \"%s\" ignored\n", cmd, arg);
free(arg);
}
}
else
{
/* silently throw away rest of line after an erroneous command */
while ((arg = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false)))
free(arg);
}
/* if there is a trailing \\, swallow it */
psql_scan_slash_command_end(scan_state);
free(cmd);
/* some commands write to queryFout, so make sure output is sent */
fflush(pset.queryFout);
1999-11-05 00:14:30 +01:00
return status;
}
/*
* Read and interpret an argument to the \connect slash command.
*/
static char *
read_connect_arg(PsqlScanState scan_state)
{
2006-10-04 02:30:14 +02:00
char *result;
char quote;
/*
2006-10-04 02:30:14 +02:00
* 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.
*/
result = psql_scan_slash_option(scan_state, OT_SQLIDHACK, &quote, true);
if (!result)
return NULL;
if (quote)
return result;
if (*result == '\0' || strcmp(result, "-") == 0)
return NULL;
return result;
}
2006-10-04 02:30:14 +02:00
/*
* Subroutine to actually try to execute a backslash command.
*/
static backslashResult
1999-11-05 00:14:30 +01:00
exec_command(const char *cmd,
PsqlScanState scan_state,
PQExpBuffer query_buf)
{
1999-11-05 00:14:30 +01:00
bool success = true; /* indicate here if the command ran ok or
* failed */
backslashResult status = PSQL_CMD_SKIP_LINE;
/*
* \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, pset.quiet);
1999-11-05 00:14:30 +01:00
else
success = do_pset("format", "unaligned", &pset.popt, pset.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 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
success = do_pset("title", opt, &pset.popt, pset.quiet);
free(opt);
}
1999-11-05 00:14:30 +01:00
/*
* \c or \connect -- connect to database using the specified parameters.
*
* \c dbname user host port
*
2006-10-04 02:30:14 +02:00
* If any of these parameters are omitted or specified as '-', the current
* value of the parameter will be used instead. If the parameter has no
* current value, the default value for that parameter will be used. Some
* examples:
1999-11-05 00:14:30 +01:00
*
2006-10-04 02:30:14 +02:00
* \c - - hst Connect to current database on current port of host
* "hst" as current user. \c - usr - prt Connect to current database on
* "prt" port of current host as user "usr". \c dbs Connect to
* "dbs" database on current port of current host as current user.
1999-11-05 00:14:30 +01:00
*/
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
{
char *opt1,
*opt2,
*opt3,
*opt4;
opt1 = read_connect_arg(scan_state);
opt2 = read_connect_arg(scan_state);
opt3 = read_connect_arg(scan_state);
opt4 = read_connect_arg(scan_state);
success = do_connect(opt1, opt2, opt3, opt4);
free(opt1);
free(opt2);
free(opt3);
free(opt4);
}
2001-05-07 21:31:33 +02:00
/* \cd */
else if (strcmp(cmd, "cd") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
char *dir;
2001-05-07 21:31:33 +02:00
if (opt)
dir = opt;
else
{
#ifndef WIN32
struct passwd *pw;
uid_t user_id = geteuid();
2001-05-07 21:31:33 +02:00
errno = 0; /* clear errno before call */
pw = getpwuid(user_id);
2001-05-07 21:31:33 +02:00
if (!pw)
{
2014-08-29 06:01:34 +02:00
psql_error("could not get home directory for user ID %ld: %s\n",
(long) user_id,
errno ? strerror(errno) : _("user does not exist"));
2001-05-07 21:31:33 +02:00
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...
*/
2001-05-07 21:31:33 +02:00
dir = "/";
#endif /* WIN32 */
2001-05-07 21:31:33 +02:00
}
if (chdir(dir) == -1)
{
psql_error("\\%s: could not change directory to \"%s\": %s\n",
2001-05-07 21:31:33 +02:00
cmd, dir, strerror(errno));
success = false;
}
if (opt)
free(opt);
}
/* \conninfo -- display information about the current connection */
else if (strcmp(cmd, "conninfo") == 0)
{
char *db = PQdb(pset.db);
if (db == NULL)
2011-05-21 23:36:14 +02:00
printf(_("You are currently not connected to a database.\n"));
else
{
char *host;
PQconninfoOption *connOptions;
PQconninfoOption *option;
host = PQhost(pset.db);
if (host == NULL)
host = DEFAULT_PGSOCKET_DIR;
/* A usable "hostaddr" overrides the basic sense of host. */
connOptions = PQconninfo(pset.db);
if (connOptions == NULL)
{
psql_error("out of memory\n");
exit(EXIT_FAILURE);
}
for (option = connOptions; option && option->keyword; option++)
if (strcmp(option->keyword, "hostaddr") == 0)
{
if (option->val != NULL && option->val[0] != '\0')
host = option->val;
break;
}
/* If the host is an absolute path, the connection is via socket */
if (is_absolute_path(host))
printf(_("You are connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
db, PQuser(pset.db), host, PQport(pset.db));
else
printf(_("You are connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
db, PQuser(pset.db), host, PQport(pset.db));
printSSLInfo();
PQconninfoFree(connOptions);
}
}
1999-11-05 00:14:30 +01:00
/* \copy */
else if (pg_strcasecmp(cmd, "copy") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
OT_WHOLE_LINE, NULL, false);
2007-11-15 22:14:46 +01:00
success = do_copy(opt);
free(opt);
}
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 *pattern;
bool show_verbose,
show_system;
/* 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;
show_system = strchr(cmd, 'S') ? true : false;
1999-11-05 00:14:30 +01:00
switch (cmd[1])
{
case '\0':
case '+':
case 'S':
if (pattern)
success = describeTableDetails(pattern, show_verbose, show_system);
1999-11-05 00:14:30 +01:00
else
/* standard listing of interesting things */
success = listTables("tvmsE", NULL, show_verbose, show_system);
1999-11-05 00:14:30 +01:00
break;
case 'a':
success = describeAggregates(pattern, show_verbose, show_system);
1999-11-05 00:14:30 +01:00
break;
case 'b':
2004-07-15 05:56:06 +02:00
success = describeTablespaces(pattern, show_verbose);
break;
case 'c':
success = listConversions(pattern, show_verbose, show_system);
break;
case 'C':
success = listCasts(pattern, show_verbose);
break;
1999-11-05 00:14:30 +01:00
case 'd':
if (strncmp(cmd, "ddp", 3) == 0)
success = listDefaultACLs(pattern);
else
success = objectDescription(pattern, show_system);
1999-11-05 00:14:30 +01:00
break;
case 'D':
success = listDomains(pattern, show_verbose, show_system);
break;
case 'f': /* function subsystem */
switch (cmd[2])
{
case '\0':
case '+':
case 'S':
case 'a':
case 'n':
case 't':
case 'w':
success = describeFunctions(&cmd[2], pattern, show_verbose, show_system);
break;
default:
status = PSQL_CMD_UNKNOWN;
break;
}
1999-11-05 00:14:30 +01:00
break;
case 'g':
/* no longer distinct from \du */
success = describeRoles(pattern, show_verbose);
break;
1999-11-05 00:14:30 +01:00
case 'l':
success = do_lo_list();
1999-11-05 00:14:30 +01:00
break;
case 'L':
success = listLanguages(pattern, show_verbose, show_system);
break;
case 'n':
success = listSchemas(pattern, show_verbose, show_system);
break;
1999-11-05 00:14:30 +01:00
case 'o':
success = describeOperators(pattern, show_verbose, show_system);
1999-11-05 00:14:30 +01:00
break;
case 'O':
success = listCollations(pattern, show_verbose, show_system);
break;
1999-11-05 00:14:30 +01:00
case 'p':
success = permissionsList(pattern);
1999-11-05 00:14:30 +01:00
break;
case 'T':
success = describeTypes(pattern, show_verbose, show_system);
1999-11-05 00:14:30 +01:00
break;
case 't':
case 'v':
case 'm':
1999-11-05 00:14:30 +01:00
case 'i':
case 's':
case 'E':
success = listTables(&cmd[1], pattern, show_verbose, show_system);
1999-11-05 00:14:30 +01:00
break;
case 'r':
if (cmd[2] == 'd' && cmd[3] == 's')
{
char *pattern2 = NULL;
if (pattern)
pattern2 = psql_scan_slash_option(scan_state,
2010-02-26 03:01:40 +01:00
OT_NORMAL, NULL, true);
success = listDbRoleSettings(pattern, pattern2);
}
else
success = PSQL_CMD_UNKNOWN;
break;
case 'u':
success = describeRoles(pattern, show_verbose);
break;
case 'F': /* text search subsystem */
switch (cmd[2])
{
case '\0':
case '+':
success = listTSConfigs(pattern, show_verbose);
break;
case 'p':
success = listTSParsers(pattern, show_verbose);
break;
case 'd':
success = listTSDictionaries(pattern, show_verbose);
break;
case 't':
success = listTSTemplates(pattern, show_verbose);
break;
default:
status = PSQL_CMD_UNKNOWN;
break;
}
break;
case 'e': /* SQL/MED subsystem */
switch (cmd[2])
{
case 's':
success = listForeignServers(pattern, show_verbose);
break;
case 'u':
success = listUserMappings(pattern, show_verbose);
break;
case 'w':
success = listForeignDataWrappers(pattern, show_verbose);
break;
case 't':
success = listForeignTables(pattern, show_verbose);
break;
default:
status = PSQL_CMD_UNKNOWN;
break;
}
break;
2011-04-10 17:42:00 +02:00
case 'x': /* Extensions */
if (show_verbose)
success = listExtensionContents(pattern);
else
success = listExtensions(pattern);
break;
case 'y': /* Event Triggers */
success = listEventTriggers(pattern, show_verbose);
break;
1999-11-05 00:14:30 +01:00
default:
status = PSQL_CMD_UNKNOWN;
1999-11-05 00:14:30 +01:00
}
if (pattern)
free(pattern);
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 edit a file and make
* it the query buffer
1999-11-05 00:14:30 +01:00
*/
else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0)
{
if (!query_buf)
{
psql_error("no query buffer\n");
status = PSQL_CMD_ERROR;
}
else
{
char *fname;
char *ln = NULL;
int lineno = -1;
fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
if (fname)
{
/* try to get separate lineno arg */
ln = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
if (ln == NULL)
{
/* only one arg; maybe it is lineno not fname */
if (fname[0] &&
strspn(fname, "0123456789") == strlen(fname))
{
/* all digits, so assume it is lineno */
ln = fname;
fname = NULL;
}
}
}
if (ln)
{
lineno = atoi(ln);
if (lineno < 1)
{
psql_error("invalid line number: %s\n", ln);
status = PSQL_CMD_ERROR;
}
}
if (status != PSQL_CMD_ERROR)
{
expand_tilde(&fname);
if (fname)
canonicalize_path(fname);
if (do_edit(fname, query_buf, lineno, NULL))
status = PSQL_CMD_NEWEDIT;
else
status = PSQL_CMD_ERROR;
}
if (fname)
free(fname);
if (ln)
free(ln);
}
}
/*
* \ef -- edit the named function, or present a blank CREATE FUNCTION
* template if no argument is given
*/
else if (strcmp(cmd, "ef") == 0)
{
int lineno = -1;
if (pset.sversion < 80400)
{
psql_error("The server (version %d.%d) does not support editing function source.\n",
pset.sversion / 10000, (pset.sversion / 100) % 100);
status = PSQL_CMD_ERROR;
}
else if (!query_buf)
{
psql_error("no query buffer\n");
status = PSQL_CMD_ERROR;
}
else
{
char *func;
Oid foid = InvalidOid;
func = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, true);
lineno = strip_lineno_from_funcdesc(func);
if (lineno == 0)
{
/* error already reported */
status = PSQL_CMD_ERROR;
}
else if (!func)
{
/* set up an empty command to fill in */
printfPQExpBuffer(query_buf,
"CREATE FUNCTION ( )\n"
" RETURNS \n"
" LANGUAGE \n"
" -- common options: IMMUTABLE STABLE STRICT SECURITY DEFINER\n"
"AS $function$\n"
"\n$function$\n");
}
else if (!lookup_function_oid(func, &foid))
{
/* error already reported */
status = PSQL_CMD_ERROR;
}
else if (!get_create_function_cmd(foid, query_buf))
{
/* error already reported */
status = PSQL_CMD_ERROR;
}
else if (lineno > 0)
{
/*
* lineno "1" should correspond to the first line of the
* function body. We expect that pg_get_functiondef() will
* emit that on a line beginning with "AS ", and that there
* can be no such line before the real start of the function
* body. Increment lineno by the number of lines before that
* line, so that it becomes relative to the first line of the
* function definition.
*/
const char *lines = query_buf->data;
while (*lines != '\0')
{
if (strncmp(lines, "AS ", 3) == 0)
break;
lineno++;
/* find start of next line */
lines = strchr(lines, '\n');
if (!lines)
break;
lines++;
}
}
if (func)
free(func);
}
if (status != PSQL_CMD_ERROR)
{
bool edited = false;
if (!do_edit(NULL, query_buf, lineno, &edited))
status = PSQL_CMD_ERROR;
else if (!edited)
2009-03-25 14:07:26 +01:00
puts(_("No changes"));
else
status = PSQL_CMD_NEWEDIT;
}
}
/* \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 = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
OT_NORMAL, &quoted, 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);
1999-11-05 00:14:30 +01:00
}
/* \encoding -- set/show client side encoding */
else if (strcmp(cmd, "encoding") == 0)
{
char *encoding = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
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 */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "f") == 0)
{
char *fname = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
OT_NORMAL, NULL, false);
success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
free(fname);
}
/* \g [filename] -- send query, optionally with output to file/pipe */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "g") == 0)
{
char *fname = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
OT_FILEPIPE, NULL, false);
if (!fname)
pset.gfname = NULL;
1999-11-05 00:14:30 +01:00
else
{
expand_tilde(&fname);
pset.gfname = pg_strdup(fname);
}
free(fname);
status = PSQL_CMD_SEND;
}
1999-11-05 00:14:30 +01:00
/* \gset [prefix] -- send query and store result into variables */
else if (strcmp(cmd, "gset") == 0)
{
char *prefix = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (prefix)
pset.gset_prefix = prefix;
else
{
/* we must set a non-NULL prefix to trigger storing */
pset.gset_prefix = pg_strdup("");
}
/* gset_prefix is freed later */
status = PSQL_CMD_SEND;
}
1999-11-05 00:14:30 +01:00
/* help */
else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
OT_WHOLE_LINE, NULL, false);
size_t len;
/* strip any trailing spaces and semicolons */
if (opt)
{
len = strlen(opt);
while (len > 0 &&
(isspace((unsigned char) opt[len - 1])
2010-07-06 21:19:02 +02:00
|| opt[len - 1] == ';'))
opt[--len] = '\0';
}
helpSQL(opt, pset.popt.topt.pager);
free(opt);
}
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, pset.quiet);
else
success = do_pset("format", "aligned", &pset.popt, pset.quiet);
}
1999-11-05 00:14:30 +01:00
/* \i and \ir include files */
else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
|| strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
1999-11-05 00:14:30 +01:00
{
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
if (!fname)
{
psql_error("\\%s: missing required argument\n", cmd);
1999-11-05 00:14:30 +01:00
success = false;
}
else
{
bool include_relative;
include_relative = (strcmp(cmd, "ir") == 0
|| strcmp(cmd, "include_relative") == 0);
expand_tilde(&fname);
success = (process_file(fname, false, include_relative) == EXIT_SUCCESS);
free(fname);
}
}
1999-11-05 00:14:30 +01:00
/* \l is list databases */
else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0 ||
strcmp(cmd, "l+") == 0 || strcmp(cmd, "list+") == 0)
{
char *pattern;
bool show_verbose;
pattern = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
show_verbose = strchr(cmd, '+') ? true : false;
success = listAllDbs(pattern, show_verbose);
if (pattern)
free(pattern);
}
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 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
opt2 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
1999-11-05 00:14:30 +01:00
if (strcmp(cmd + 3, "export") == 0)
{
if (!opt2)
1999-11-05 00:14:30 +01:00
{
psql_error("\\%s: missing required argument\n", cmd);
1999-11-05 00:14:30 +01:00
success = false;
}
else
{
expand_tilde(&opt2);
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
{
psql_error("\\%s: missing required argument\n", cmd);
1999-11-05 00:14:30 +01:00
success = false;
}
else
{
expand_tilde(&opt1);
2001-03-22 05:01:46 +01:00
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
{
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 = PSQL_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 = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
OT_FILEPIPE, NULL, true);
expand_tilde(&fname);
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 (!pset.quiet)
puts(_("Query buffer is empty."));
fflush(stdout);
}
/* \password -- set user password */
else if (strcmp(cmd, "password") == 0)
{
char *pw1;
char *pw2;
pw1 = simple_prompt("Enter new password: ", 100, false);
pw2 = simple_prompt("Enter it again: ", 100, false);
if (strcmp(pw1, pw2) != 0)
{
psql_error("Passwords didn't match.\n");
success = false;
}
else
{
char *opt0 = psql_scan_slash_option(scan_state, OT_SQLID, NULL, true);
char *user;
char *encrypted_password;
if (opt0)
user = opt0;
else
user = PQuser(pset.db);
encrypted_password = PQencryptPassword(pw1, user);
if (!encrypted_password)
{
psql_error("Password encryption failed.\n");
success = false;
}
else
{
PQExpBufferData buf;
PGresult *res;
initPQExpBuffer(&buf);
printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ",
fmtId(user));
appendStringLiteralConn(&buf, encrypted_password, pset.db);
res = PSQLexec(buf.data);
termPQExpBuffer(&buf);
if (!res)
success = false;
else
PQclear(res);
PQfreemem(encrypted_password);
}
if (opt0)
free(opt0);
}
free(pw1);
free(pw2);
}
/* \prompt -- prompt and set variable */
else if (strcmp(cmd, "prompt") == 0)
{
2007-11-15 22:14:46 +01:00
char *opt,
*prompt_text = NULL;
char *arg1,
*arg2;
arg1 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
arg2 = psql_scan_slash_option(scan_state, OT_NORMAL, NULL, false);
if (!arg1)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else
{
2007-11-15 22:14:46 +01:00
char *result;
if (arg2)
{
prompt_text = arg1;
opt = arg2;
}
else
opt = arg1;
if (!pset.inputfile)
result = simple_prompt(prompt_text, 4096, true);
else
{
if (prompt_text)
{
fputs(prompt_text, stdout);
fflush(stdout);
}
result = gets_fromFile(stdin);
}
if (!SetVariable(pset.vars, opt, result))
{
psql_error("\\%s: error while setting variable\n", cmd);
success = false;
}
free(result);
if (prompt_text)
free(prompt_text);
free(opt);
}
}
1999-11-05 00:14:30 +01:00
/* \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)
1999-11-05 00:14:30 +01:00
{
/* list all variables */
int i;
static const char *const my_list[] = {
"border", "columns", "expanded", "fieldsep", "fieldsep_zero",
"footer", "format", "linestyle", "null",
"numericlocale", "pager", "recordsep", "recordsep_zero",
"tableattr", "title", "tuples_only",
"unicode_border_linestyle",
"unicode_column_linestyle",
"unicode_header_linestyle",
NULL
};
for (i = 0; my_list[i] != NULL; i++)
{
char *val = pset_value_string(my_list[i], &pset.popt);
printf("%-24s %s\n", my_list[i], val);
free(val);
}
success = true;
1999-11-05 00:14:30 +01:00
}
else
success = do_pset(opt0, opt1, &pset.popt, pset.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 = PSQL_CMD_TERMINATE;
1999-11-05 00:14:30 +01:00
/* reset(clear) the buffer */
else if (strcmp(cmd, "r") == 0 || strcmp(cmd, "reset") == 0)
{
resetPQExpBuffer(query_buf);
psql_scan_reset(scan_state);
if (!pset.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 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
1999-11-05 00:14:30 +01:00
expand_tilde(&fname);
Fix psql \s to work with recent libedit, and add pager support. psql's \s (print command history) doesn't work at all with recent libedit versions when printing to the terminal, because libedit tries to do an fchmod() on the target file which will fail if the target is /dev/tty. (We'd already noted this in the context of the target being /dev/null.) Even before that, it didn't work pleasantly, because libedit likes to encode the command history file (to ensure successful reloading), which renders it nigh unreadable, not to mention significantly different-looking depending on exactly which libedit version you have. So let's forget using write_history() for this purpose, and instead print the data ourselves, using logic similar to that used to iterate over the history for newline encoding/decoding purposes. While we're at it, insert the ability to use the pager when \s is printing to the terminal. This has been an acknowledged shortcoming of \s for many years, so while you could argue it's not exactly a back-patchable bug fix it still seems like a good improvement. Anyone who's seriously annoyed at this can use "\s /dev/tty" or local equivalent to get the old behavior. Experimentation with this showed that the history iteration logic was actually rather broken when used with libedit. It turns out that with libedit you have to use previous_history() not next_history() to advance to more recent history entries. The easiest and most robust fix for this seems to be to make a run-time test to verify which function to call. We had not noticed this because libedit doesn't really need the newline encoding logic: its own encoding ensures that command entries containing newlines are reloaded correctly (unlike libreadline). So the effective behavior with recent libedits was that only the oldest history entry got newline-encoded or newline-decoded. However, because of yet other bugs in history_set_pos(), some old versions of libedit allowed the existing loop logic to reach entries besides the oldest, which means there may be libedit ~/.psql_history files out there containing encoded newlines in more than just the oldest entry. To ensure we can reload such files, it seems appropriate to back-patch this fix, even though that will result in some incompatibility with older psql versions (ie, multiline history entries written by a psql with this fix will look corrupted to a psql without it, if its libedit is reasonably up to date). Stepan Rutz and Tom Lane
2014-09-08 22:09:45 +02:00
success = printHistory(fname, pset.popt.topt.pager);
if (success && !pset.quiet && fname)
printf(_("Wrote history to file \"%s\".\n"), fname);
if (!fname)
putchar('\n');
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 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (!opt0)
1999-11-05 00:14:30 +01:00
{
/* list all variables */
PrintVariables(pset.vars);
1999-11-05 00:14:30 +01:00
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 = pg_realloc(newval, strlen(newval) + strlen(opt) + 1);
strcat(newval, opt);
free(opt);
}
if (!SetVariable(pset.vars, opt0, newval))
1999-11-05 00:14:30 +01:00
{
psql_error("\\%s: error while setting variable\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
/* \setenv -- set environment command */
else if (strcmp(cmd, "setenv") == 0)
{
char *envvar = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
char *envval = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (!envvar)
{
psql_error("\\%s: missing required argument\n", cmd);
success = false;
}
else if (strchr(envvar, '=') != NULL)
{
2012-06-07 22:35:33 +02:00
psql_error("\\%s: environment variable name must not contain \"=\"\n",
cmd);
success = false;
}
else if (!envval)
{
/* No argument - unset the environment variable */
unsetenv(envvar);
success = true;
}
else
{
/* Set variable to the value of the next argument */
char *newval;
newval = psprintf("%s=%s", envvar, envval);
putenv(newval);
success = true;
/*
* Do not free newval here, it will screw up the environment if
* you do. See putenv man page for details. That means we leak a
* bit of memory here, but not enough to worry about.
*/
}
free(envvar);
free(envval);
}
/* \sf -- show a function's source code */
else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
{
bool show_linenumbers = (strcmp(cmd, "sf+") == 0);
2011-04-10 17:42:00 +02:00
PQExpBuffer func_buf;
char *func;
Oid foid = InvalidOid;
func_buf = createPQExpBuffer();
func = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, true);
if (pset.sversion < 80400)
{
psql_error("The server (version %d.%d) does not support showing function source.\n",
pset.sversion / 10000, (pset.sversion / 100) % 100);
status = PSQL_CMD_ERROR;
}
else if (!func)
{
psql_error("function name is required\n");
status = PSQL_CMD_ERROR;
}
else if (!lookup_function_oid(func, &foid))
{
/* error already reported */
status = PSQL_CMD_ERROR;
}
else if (!get_create_function_cmd(foid, func_buf))
{
/* error already reported */
status = PSQL_CMD_ERROR;
}
else
{
2011-04-10 17:42:00 +02:00
FILE *output;
bool is_pager;
/* Select output stream: stdout, pager, or file */
if (pset.queryFout == stdout)
{
/* count lines in function to see if pager is needed */
int lineno = 0;
const char *lines = func_buf->data;
while (*lines != '\0')
{
lineno++;
/* find start of next line */
lines = strchr(lines, '\n');
if (!lines)
break;
lines++;
}
output = PageOutput(lineno, pset.popt.topt.pager);
is_pager = true;
}
else
{
/* use previously set output file, without pager */
output = pset.queryFout;
is_pager = false;
}
if (show_linenumbers)
{
bool in_header = true;
int lineno = 0;
char *lines = func_buf->data;
/*
* lineno "1" should correspond to the first line of the
* function body. We expect that pg_get_functiondef() will
* emit that on a line beginning with "AS ", and that there
* can be no such line before the real start of the function
* body.
*
* Note that this loop scribbles on func_buf.
*/
while (*lines != '\0')
{
2011-04-10 17:42:00 +02:00
char *eol;
if (in_header && strncmp(lines, "AS ", 3) == 0)
in_header = false;
/* increment lineno only for body's lines */
if (!in_header)
lineno++;
/* find and mark end of current line */
eol = strchr(lines, '\n');
if (eol != NULL)
*eol = '\0';
/* show current line as appropriate */
if (in_header)
fprintf(output, " %s\n", lines);
else
fprintf(output, "%-7d %s\n", lineno, lines);
/* advance to next line, if any */
if (eol == NULL)
break;
lines = ++eol;
}
}
else
{
/* just send the function definition to output */
fputs(func_buf->data, output);
}
if (is_pager)
ClosePager(output);
}
if (func)
free(func);
destroyPQExpBuffer(func_buf);
}
1999-11-05 00:14:30 +01:00
/* \t -- turn off headers and row count */
else if (strcmp(cmd, "t") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
free(opt);
}
1999-11-05 00:14:30 +01:00
/* \T -- define html <table ...> attributes */
else if (strcmp(cmd, "T") == 0)
{
char *value = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
OT_NORMAL, NULL, false);
success = do_pset("tableattr", value, &pset.popt, pset.quiet);
free(value);
}
/* \timing -- toggle timing of queries */
else if (strcmp(cmd, "timing") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (opt)
pset.timing = ParseVariableBool(opt, "\\timing");
else
pset.timing = !pset.timing;
if (!pset.quiet)
{
if (pset.timing)
puts(_("Timing is on."));
else
puts(_("Timing is off."));
}
free(opt);
}
2002-09-04 22:31:48 +02:00
/* \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 while setting variable\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;
if (!query_buf)
{
psql_error("no query buffer\n");
status = PSQL_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
{
canonicalize_path(fname);
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);
}
/* \watch -- execute a query every N seconds */
else if (strcmp(cmd, "watch") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
long sleep = 2;
/* Convert optional sleep-length argument */
if (opt)
{
sleep = strtol(opt, NULL, 10);
if (sleep <= 0)
sleep = 1;
free(opt);
}
success = do_watch(query_buf, sleep);
/* Reset the query buffer as though for \r */
resetPQExpBuffer(query_buf);
psql_scan_reset(scan_state);
}
/* \x -- set or toggle expanded table representation */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "x") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
success = do_pset("expanded", opt, &pset.popt, pset.quiet);
free(opt);
}
/* \z -- list table rights (equivalent to \dp) */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "z") == 0)
{
char *pattern = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
OT_NORMAL, NULL, true);
success = permissionsList(pattern);
if (pattern)
free(pattern);
}
/* \! -- shell escape */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "!") == 0)
{
char *opt = psql_scan_slash_option(scan_state,
2005-10-15 04:49:52 +02:00
OT_WHOLE_LINE, NULL, false);
success = do_shell(opt);
free(opt);
}
1999-11-05 00:14:30 +01:00
/* \? -- slash command help */
1999-11-05 00:14:30 +01:00
else if (strcmp(cmd, "?") == 0)
{
char *opt0 = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
if (!opt0 || strcmp(opt0, "commands") == 0)
slashUsage(pset.popt.topt.pager);
else if (strcmp(opt0, "options") == 0)
usage(pset.popt.topt.pager);
else if (strcmp(opt0, "variables") == 0)
helpVariables(pset.popt.topt.pager);
else
slashUsage(pset.popt.topt.pager);
}
#if 0
/*
2005-10-15 04:49:52 +02:00
* These commands don't do anything. I just use them to test the parser.
1999-11-05 00:14:30 +01:00
*/
else if (strcmp(cmd, "void") == 0 || strcmp(cmd, "#") == 0)
{
int i = 0;
char *value;
1999-11-05 00:14:30 +01:00
while ((value = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true)))
{
psql_error("+ 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 = PSQL_CMD_UNKNOWN;
1999-11-05 00:14:30 +01:00
if (!success)
status = PSQL_CMD_ERROR;
1999-11-05 00:14:30 +01:00
return status;
}
/*
* Ask the user for a password; 'username' is the username the
* password is for, if one has been explicitly specified. Returns a
* malloc'd string.
*/
static char *
prompt_for_password(const char *username)
{
2006-10-04 02:30:14 +02:00
char *result;
1999-11-05 00:14:30 +01:00
if (username == NULL)
result = simple_prompt("Password: ", 100, false);
1999-11-05 00:14:30 +01:00
else
{
2006-10-04 02:30:14 +02:00
char *prompt_text;
1999-11-05 00:14:30 +01:00
prompt_text = psprintf(_("Password for user %s: "), username);
result = simple_prompt(prompt_text, 100, false);
free(prompt_text);
}
return result;
}
static bool
param_is_newly_set(const char *old_val, const char *new_val)
{
if (new_val == NULL)
return false;
if (old_val == NULL || strcmp(old_val, new_val) != 0)
return true;
return false;
}
/*
* do_connect -- handler for \connect
*
* Connects to a database with given parameters. If there exists an
* established connection, NULL values will be replaced with the ones
* in the current connection. Otherwise NULL will be passed for that
* parameter to PQconnectdbParams(), so the libpq defaults will be used.
*
* In interactive mode, if connection fails with the given parameters,
* the old connection will be kept.
*/
static bool
do_connect(char *dbname, char *user, char *host, char *port)
{
2006-10-04 02:30:14 +02:00
PGconn *o_conn = pset.db,
*n_conn;
char *password = NULL;
if (!o_conn && (!dbname || !user || !host || !port))
{
/*
* We don't know the supplied connection parameters and don't want to
* connect to the wrong database by using defaults, so require all
* parameters to be specified.
*/
psql_error("All connection parameters must be supplied because no "
"database connection exists\n");
return false;
}
if (!dbname)
dbname = PQdb(o_conn);
if (!user)
user = PQuser(o_conn);
if (!host)
host = PQhost(o_conn);
if (!port)
port = PQport(o_conn);
1999-11-05 00:14:30 +01:00
/*
2006-10-04 02:30:14 +02:00
* If the user asked to be prompted for a password, ask for one now. If
* not, use the password from the old connection, provided the username
* has not changed. Otherwise, try to connect without a password first,
* and then ask for a password if needed.
*
2007-11-15 22:14:46 +01:00
* XXX: this behavior leads to spurious connection attempts recorded in
* the postmaster's log. But libpq offers no API that would let us obtain
* a password and then continue with the first connection attempt.
1999-11-05 00:14:30 +01:00
*/
if (pset.getPassword == TRI_YES)
{
password = prompt_for_password(user);
}
else if (o_conn && user && strcmp(PQuser(o_conn), user) == 0)
{
password = pg_strdup(PQpass(o_conn));
}
while (true)
1999-11-05 00:14:30 +01:00
{
#define PARAMS_ARRAY_SIZE 8
const char **keywords = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*keywords));
const char **values = pg_malloc(PARAMS_ARRAY_SIZE * sizeof(*values));
2010-02-26 03:01:40 +01:00
keywords[0] = "host";
values[0] = host;
keywords[1] = "port";
values[1] = port;
keywords[2] = "user";
values[2] = user;
keywords[3] = "password";
values[3] = password;
keywords[4] = "dbname";
values[4] = dbname;
keywords[5] = "fallback_application_name";
values[5] = pset.progname;
keywords[6] = "client_encoding";
values[6] = (pset.notty || getenv("PGCLIENTENCODING")) ? NULL : "auto";
keywords[7] = NULL;
values[7] = NULL;
n_conn = PQconnectdbParams(keywords, values, true);
free(keywords);
free(values);
/* We can immediately discard the password -- no longer needed */
if (password)
free(password);
1999-11-05 00:14:30 +01:00
if (PQstatus(n_conn) == CONNECTION_OK)
break;
/*
2006-10-04 02:30:14 +02:00
* Connection attempt failed; either retry the connection attempt with
* a new password, or give up.
*/
if (!password && PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO)
1999-11-05 00:14:30 +01:00
{
PQfinish(n_conn);
password = prompt_for_password(user);
continue;
1999-11-05 00:14:30 +01:00
}
/*
2006-10-04 02:30:14 +02:00
* Failed to connect to the database. In interactive mode, keep the
* previous connection to the DB; in scripting mode, close our
* previous connection as well.
*/
if (pset.cur_cmd_interactive)
{
psql_error("%s", PQerrorMessage(n_conn));
/* pset.db is left unmodified */
if (o_conn)
psql_error("Previous connection kept\n");
}
else
{
psql_error("\\connect: %s", PQerrorMessage(n_conn));
if (o_conn)
{
PQfinish(o_conn);
pset.db = NULL;
}
1999-11-05 00:14:30 +01:00
}
PQfinish(n_conn);
return false;
1999-11-05 00:14:30 +01:00
}
/*
* Replace the old connection with the new one, and update
* connection-dependent variables.
*/
PQsetNoticeProcessor(n_conn, NoticeProcessor, NULL);
pset.db = n_conn;
SyncVariables();
2010-02-26 03:01:40 +01:00
connection_warnings(false); /* Must be after SyncVariables */
/* Tell the user about the new connection */
if (!pset.quiet)
{
if (param_is_newly_set(PQhost(o_conn), PQhost(pset.db)) ||
param_is_newly_set(PQport(o_conn), PQport(pset.db)))
{
2011-04-10 17:42:00 +02:00
char *host = PQhost(pset.db);
if (host == NULL)
host = DEFAULT_PGSOCKET_DIR;
/* If the host is an absolute path, the connection is via socket */
if (is_absolute_path(host))
printf(_("You are now connected to database \"%s\" as user \"%s\" via socket in \"%s\" at port \"%s\".\n"),
PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db));
else
printf(_("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port \"%s\".\n"),
PQdb(pset.db), PQuser(pset.db), host, PQport(pset.db));
}
else
printf(_("You are now connected to database \"%s\" as user \"%s\".\n"),
PQdb(pset.db), PQuser(pset.db));
}
if (o_conn)
PQfinish(o_conn);
return true;
}
void
connection_warnings(bool in_startup)
{
if (!pset.quiet && !pset.notty)
{
int client_ver = PG_VERSION_NUM;
if (pset.sversion != client_ver)
{
const char *server_version;
char server_ver_str[16];
/* Try to get full text form, might include "devel" etc */
server_version = PQparameterStatus(pset.db, "server_version");
if (!server_version)
{
snprintf(server_ver_str, sizeof(server_ver_str),
"%d.%d.%d",
pset.sversion / 10000,
(pset.sversion / 100) % 100,
pset.sversion % 100);
server_version = server_ver_str;
}
printf(_("%s (%s, server %s)\n"),
pset.progname, PG_VERSION, server_version);
}
/* For version match, only print psql banner on startup. */
else if (in_startup)
printf("%s (%s)\n", pset.progname, PG_VERSION);
if (pset.sversion / 100 > client_ver / 100)
printf(_("WARNING: %s major version %d.%d, server major version %d.%d.\n"
" Some psql features might not work.\n"),
pset.progname, client_ver / 10000, (client_ver / 100) % 100,
pset.sversion / 10000, (pset.sversion / 100) % 100);
#ifdef WIN32
checkWin32Codepage();
#endif
printSSLInfo();
}
}
/*
* printSSLInfo
*
* Prints information about the current SSL connection, if SSL is in use
*/
static void
printSSLInfo(void)
{
const char *protocol;
const char *cipher;
const char *bits;
const char *compression;
if (!PQsslInUse(pset.db))
return; /* no SSL */
protocol = PQsslAttribute(pset.db, "protocol");
cipher = PQsslAttribute(pset.db, "cipher");
bits = PQsslAttribute(pset.db, "key_bits");
compression = PQsslAttribute(pset.db, "compression");
printf(_("SSL connection (protocol: %s, cipher: %s, bits: %s, compression: %s)\n"),
protocol ? protocol : _("unknown"),
cipher ? cipher : _("unknown"),
bits ? bits : _("unknown"),
(compression && strcmp(compression, "off") != 0) ? _("on") : _("off"));
}
/*
* checkWin32Codepage
*
* Prints a warning when win32 console codepage differs from Windows codepage
*/
#ifdef WIN32
static void
checkWin32Codepage(void)
{
unsigned int wincp,
concp;
wincp = GetACP();
concp = GetConsoleCP();
if (wincp != concp)
{
printf(_("WARNING: Console code page (%u) differs from Windows code page (%u)\n"
" 8-bit characters might not work correctly. See psql reference\n"
" page \"Notes for Windows users\" for details.\n"),
concp, wincp);
}
}
#endif
/*
* 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;
pset.sversion = PQserverVersion(pset.db);
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 */
PQsetErrorVerbosity(pset.db, pset.verbosity);
}
/*
* 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);
}
/*
* 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, int lineno)
{
const char *editorName;
const char *editor_lineno_arg = NULL;
1999-11-05 00:14:30 +01:00
char *sys;
int result;
Assert(fname != NULL);
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;
/* Get line number argument, if we need it. */
if (lineno > 0)
{
editor_lineno_arg = getenv("PSQL_EDITOR_LINENUMBER_ARG");
#ifdef DEFAULT_EDITOR_LINENUMBER_ARG
if (!editor_lineno_arg)
editor_lineno_arg = DEFAULT_EDITOR_LINENUMBER_ARG;
#endif
if (!editor_lineno_arg)
{
psql_error("environment variable PSQL_EDITOR_LINENUMBER_ARG must be set to specify a line number\n");
return false;
}
}
/*
* On Unix the EDITOR value should *not* be quoted, since it might include
* switches, eg, EDITOR="pico -t"; it's up to the user to put quotes in it
* if necessary. But this policy is not very workable on Windows, due to
* severe brain damage in their command shell plus the fact that standard
* program paths include spaces.
*/
2003-04-04 22:40:45 +02:00
#ifndef WIN32
if (lineno > 0)
sys = psprintf("exec %s %s%d '%s'",
editorName, editor_lineno_arg, lineno, fname);
else
sys = psprintf("exec %s '%s'",
editorName, fname);
#else
if (lineno > 0)
sys = psprintf("\"%s\" %s%d \"%s\"",
editorName, editor_lineno_arg, lineno, fname);
else
sys = psprintf("\"%s\" \"%s\"",
editorName, fname);
2003-04-04 22:40:45 +02:00
#endif
1999-11-05 00:14:30 +01:00
result = system(sys);
2000-01-19 00:30:24 +01:00
if (result == -1)
psql_error("could not start editor \"%s\"\n", editorName);
else if (result == 127)
2000-01-19 00:30:24 +01:00
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,
int lineno, bool *edited)
{
char fnametmp[MAXPGPATH];
2000-11-26 12:09:32 +01:00
FILE *stream = NULL;
1999-11-05 00:14:30 +01:00
const char *fname;
bool error = false;
int fd;
2001-03-22 05:01:46 +01:00
1999-11-05 00:14:30 +01:00
struct stat before,
after;
1999-11-05 00:14:30 +01:00
if (filename_arg)
fname = filename_arg;
else
{
/* make a temp file to edit */
#ifndef WIN32
2004-11-04 23:25:14 +01:00
const char *tmpdir = getenv("TMPDIR");
2004-11-04 23:25:14 +01:00
if (!tmpdir)
tmpdir = "/tmp";
#else
2005-10-15 04:49:52 +02:00
char tmpdir[MAXPGPATH];
int ret;
2004-11-04 23:25:14 +01:00
ret = GetTempPath(MAXPGPATH, tmpdir);
if (ret == 0 || ret > MAXPGPATH)
{
psql_error("could not locate temporary directory: %s\n",
2005-10-15 04:49:52 +02:00
!ret ? strerror(errno) : "");
2004-11-04 23:25:14 +01:00
return false;
}
2005-10-15 04:49:52 +02:00
2004-11-04 23:25:14 +01:00
/*
2005-10-15 04:49:52 +02:00
* No canonicalize_path() here. EDIT.EXE run from CMD.EXE prepends the
* current directory to the supplied path unless we use only
* backslashes, so we do that.
2004-11-04 23:25:14 +01:00
*/
#endif
#ifndef WIN32
snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d.sql", tmpdir,
2005-10-15 04:49:52 +02:00
"/", (int) getpid());
#else
snprintf(fnametmp, sizeof(fnametmp), "%s%spsql.edit.%d.sql", tmpdir,
2005-10-15 04:49:52 +02:00
"" /* trailing separator already present */ , (int) getpid());
#endif
2004-11-04 23:25:14 +01:00
1999-11-05 00:14:30 +01:00
fname = (const char *) fnametmp;
2001-03-22 05:01:46 +01:00
fd = open(fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd != -1)
stream = fdopen(fd, "w");
if (fd == -1 || !stream)
1999-11-05 00:14:30 +01:00
{
psql_error("could not open temporary 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)
{
psql_error("%s: %s\n", fname, strerror(errno));
if (fclose(stream) != 0)
psql_error("%s: %s\n", fname, strerror(errno));
if (remove(fname) != 0)
psql_error("%s: %s\n", fname, strerror(errno));
1999-11-05 00:14:30 +01:00
error = true;
}
else if (fclose(stream) != 0)
{
psql_error("%s: %s\n", fname, strerror(errno));
if (remove(fname) != 0)
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
1999-11-05 00:14:30 +01:00
}
}
1999-11-05 00:14:30 +01:00
if (!error && stat(fname, &before) != 0)
{
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
1999-11-05 00:14:30 +01:00
/* call editor */
if (!error)
error = !editFile(fname, lineno);
1999-11-05 00:14:30 +01:00
if (!error && stat(fname, &after) != 0)
{
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)
{
stream = fopen(fname, PG_BINARY_R);
1999-11-05 00:14:30 +01:00
if (!stream)
{
psql_error("%s: %s\n", fname, strerror(errno));
1999-11-05 00:14:30 +01:00
error = true;
}
else
{
/* read file back into query_buf */
1999-11-05 00:14:30 +01:00
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;
}
else if (edited)
{
*edited = true;
}
1999-11-05 00:14:30 +01:00
fclose(stream);
}
}
2000-04-11 19:35:50 +02:00
/* remove temp file */
2000-04-11 19:35:50 +02:00
if (!filename_arg)
{
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
*
* Reads commands from filename and passes them to the main processing loop.
* Handler for \i and \ir, but can be used for other things as well. Returns
* MainLoop() error code.
*
* If use_relative_path is true and filename is not an absolute path, then open
* the file from where the currently processed file (if any) is located.
*/
int
process_file(char *filename, bool single_txn, bool use_relative_path)
{
1999-11-05 00:14:30 +01:00
FILE *fd;
int result;
char *oldfilename;
char relpath[MAXPGPATH];
2006-10-04 02:30:14 +02:00
PGresult *res;
1999-11-05 00:14:30 +01:00
if (!filename)
{
fd = stdin;
filename = NULL;
}
else if (strcmp(filename, "-") != 0)
{
canonicalize_path(filename);
/*
* If we were asked to resolve the pathname relative to the location
* of the currently executing script, and there is one, and this is a
* relative pathname, then prepend all but the last pathname component
* of the current script to this pathname.
*/
if (use_relative_path && pset.inputfile &&
!is_absolute_path(filename) && !has_drive_prefix(filename))
{
strlcpy(relpath, pset.inputfile, sizeof(relpath));
get_parent_directory(relpath);
join_path_components(relpath, relpath, filename);
canonicalize_path(relpath);
filename = relpath;
}
fd = fopen(filename, PG_BINARY_R);
if (!fd)
{
psql_error("%s: %s\n", filename, strerror(errno));
return EXIT_FAILURE;
}
}
else
1999-11-05 00:14:30 +01:00
{
fd = stdin;
filename = "<stdin>"; /* for future error messages */
1999-11-05 00:14:30 +01:00
}
oldfilename = pset.inputfile;
pset.inputfile = filename;
2006-10-04 02:30:14 +02:00
if (single_txn)
{
if ((res = PSQLexec("BEGIN")) == NULL)
{
if (pset.on_error_stop)
{
result = EXIT_USER;
goto error;
}
}
else
PQclear(res);
}
2000-01-19 00:30:24 +01:00
result = MainLoop(fd);
2006-10-04 02:30:14 +02:00
if (single_txn)
{
if ((res = PSQLexec("COMMIT")) == NULL)
{
if (pset.on_error_stop)
{
result = EXIT_USER;
goto error;
}
}
else
PQclear(res);
}
error:
if (fd != stdin)
fclose(fd);
pset.inputfile = oldfilename;
return result;
}
static const char *
_align2string(enum printFormat in)
{
1999-11-05 00:14:30 +01:00
switch (in)
{
case PRINT_NOTHING:
1999-11-05 00:14:30 +01:00
return "nothing";
break;
case PRINT_UNALIGNED:
return "unaligned";
break;
case PRINT_ALIGNED:
return "aligned";
break;
case PRINT_WRAPPED:
return "wrapped";
break;
1999-11-05 00:14:30 +01:00
case PRINT_HTML:
return "html";
break;
case PRINT_LATEX:
return "latex";
break;
case PRINT_LATEX_LONGTABLE:
return "latex-longtable";
break;
case PRINT_TROFF_MS:
return "troff-ms";
break;
1999-11-05 00:14:30 +01:00
}
return "unknown";
}
/*
* Parse entered unicode linestyle. Returns true, when entered string is
* known linestyle: single, double else returns false.
*/
static bool
set_unicode_line_style(printQueryOpt *popt, const char *value, size_t vallen,
unicode_linestyle *linestyle)
{
if (pg_strncasecmp("single", value, vallen) == 0)
*linestyle = UNICODE_LINESTYLE_SINGLE;
else if (pg_strncasecmp("double", value, vallen) == 0)
*linestyle = UNICODE_LINESTYLE_DOUBLE;
else
return false;
/* input is ok, generate new unicode style */
refresh_utf8format(&(popt->topt));
return true;
}
static const char *
_unicode_linestyle2string(int linestyle)
{
switch (linestyle)
{
case UNICODE_LINESTYLE_SINGLE:
return "single";
break;
case UNICODE_LINESTYLE_DOUBLE:
return "double";
break;
}
return "unknown";
}
/*
* do_pset
*
*/
bool
do_pset(const char *param, const char *value, printQueryOpt *popt, bool quiet)
{
1999-11-05 00:14:30 +01:00
size_t vallen = 0;
Assert(param != NULL);
1999-11-05 00:14:30 +01:00
if (value)
vallen = strlen(value);
/* set format */
if (strcmp(param, "format") == 0)
{
if (!value)
;
else if (pg_strncasecmp("unaligned", value, vallen) == 0)
1999-11-05 00:14:30 +01:00
popt->topt.format = PRINT_UNALIGNED;
else if (pg_strncasecmp("aligned", value, vallen) == 0)
1999-11-05 00:14:30 +01:00
popt->topt.format = PRINT_ALIGNED;
else if (pg_strncasecmp("wrapped", value, vallen) == 0)
popt->topt.format = PRINT_WRAPPED;
else if (pg_strncasecmp("html", value, vallen) == 0)
1999-11-05 00:14:30 +01:00
popt->topt.format = PRINT_HTML;
else if (pg_strncasecmp("latex", value, vallen) == 0)
1999-11-05 00:14:30 +01:00
popt->topt.format = PRINT_LATEX;
else if (pg_strncasecmp("latex-longtable", value, vallen) == 0)
popt->topt.format = PRINT_LATEX_LONGTABLE;
else if (pg_strncasecmp("troff-ms", value, vallen) == 0)
popt->topt.format = PRINT_TROFF_MS;
1999-11-05 00:14:30 +01:00
else
{
psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
1999-11-05 00:14:30 +01:00
return false;
}
}
/* set table line style */
else if (strcmp(param, "linestyle") == 0)
{
if (!value)
;
else if (pg_strncasecmp("ascii", value, vallen) == 0)
popt->topt.line_style = &pg_asciiformat;
else if (pg_strncasecmp("old-ascii", value, vallen) == 0)
popt->topt.line_style = &pg_asciiformat_old;
else if (pg_strncasecmp("unicode", value, vallen) == 0)
popt->topt.line_style = &pg_utf8format;
else
{
psql_error("\\pset: allowed line styles are ascii, old-ascii, unicode\n");
return false;
}
}
/* set unicode border line style */
else if (strcmp(param, "unicode_border_linestyle") == 0)
{
if (!value)
;
else if (!set_unicode_line_style(popt, value, vallen,
&popt->topt.unicode_border_linestyle))
{
psql_error("\\pset: allowed unicode border linestyle are single, double\n");
return false;
}
}
/* set unicode column line style */
else if (strcmp(param, "unicode_column_linestyle") == 0)
{
if (!value)
;
else if (!set_unicode_line_style(popt, value, vallen,
&popt->topt.unicode_column_linestyle))
{
psql_error("\\pset: allowed unicode column linestyle are single, double\n");
return false;
}
}
/* set unicode header line style */
else if (strcmp(param, "unicode_header_linestyle") == 0)
{
if (!value)
;
else if (!set_unicode_line_style(popt, value, vallen,
&popt->topt.unicode_header_linestyle))
{
psql_error("\\pset: allowed unicode header linestyle are single, double\n");
return false;
}
}
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
/* set expanded/vertical mode */
else if (strcmp(param, "x") == 0 ||
strcmp(param, "expanded") == 0 ||
strcmp(param, "vertical") == 0)
1999-11-05 00:14:30 +01:00
{
if (value && pg_strcasecmp(value, "auto") == 0)
popt->topt.expanded = 2;
else if (value)
popt->topt.expanded = ParseVariableBool(value, param);
else
popt->topt.expanded = !popt->topt.expanded;
}
2005-07-18 22:57:53 +02:00
/* locale-aware numeric output */
else if (strcmp(param, "numericlocale") == 0)
{
if (value)
popt->topt.numericLocale = ParseVariableBool(value, param);
else
popt->topt.numericLocale = !popt->topt.numericLocale;
}
1999-11-05 00:14:30 +01:00
/* null display */
else if (strcmp(param, "null") == 0)
{
if (value)
{
free(popt->nullPrint);
popt->nullPrint = pg_strdup(value);
1999-11-05 00:14:30 +01:00
}
}
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.separator);
popt->topt.fieldSep.separator = pg_strdup(value);
popt->topt.fieldSep.separator_zero = false;
1999-11-05 00:14:30 +01:00
}
}
else if (strcmp(param, "fieldsep_zero") == 0)
{
free(popt->topt.fieldSep.separator);
popt->topt.fieldSep.separator = NULL;
popt->topt.fieldSep.separator_zero = true;
2000-01-19 00:30:24 +01:00
}
/* record separator for unaligned text */
else if (strcmp(param, "recordsep") == 0)
{
if (value)
{
free(popt->topt.recordSep.separator);
popt->topt.recordSep.separator = pg_strdup(value);
popt->topt.recordSep.separator_zero = false;
2000-01-19 00:30:24 +01:00
}
1999-11-05 00:14:30 +01:00
}
else if (strcmp(param, "recordsep_zero") == 0)
{
free(popt->topt.recordSep.separator);
popt->topt.recordSep.separator = NULL;
popt->topt.recordSep.separator_zero = true;
}
/* toggle between full and tuples-only format */
1999-11-05 00:14:30 +01:00
else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
{
if (value)
popt->topt.tuples_only = ParseVariableBool(value, param);
else
popt->topt.tuples_only = !popt->topt.tuples_only;
1999-11-05 00:14:30 +01:00
}
/* set title override */
else if (strcmp(param, "title") == 0)
{
free(popt->title);
if (!value)
popt->title = NULL;
else
popt->title = pg_strdup(value);
}
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 = pg_strdup(value);
}
1999-11-05 00:14:30 +01:00
/* toggle use of pager */
else if (strcmp(param, "pager") == 0)
{
if (value && pg_strcasecmp(value, "always") == 0)
2003-08-04 02:43:34 +02:00
popt->topt.pager = 2;
else if (value)
{
if (ParseVariableBool(value, param))
popt->topt.pager = 1;
else
popt->topt.pager = 0;
}
else if (popt->topt.pager == 1)
2003-08-04 02:43:34 +02:00
popt->topt.pager = 0;
else
2003-08-04 02:43:34 +02:00
popt->topt.pager = 1;
1999-11-05 00:14:30 +01:00
}
/* disable "(x rows)" footer */
else if (strcmp(param, "footer") == 0)
{
if (value)
popt->topt.default_footer = ParseVariableBool(value, param);
else
popt->topt.default_footer = !popt->topt.default_footer;
}
1999-11-05 00:14:30 +01:00
/* set border style/width */
else if (strcmp(param, "columns") == 0)
{
if (value)
popt->topt.columns = atoi(value);
}
else
{
psql_error("\\pset: unknown option: %s\n", param);
return false;
}
if (!quiet)
printPsetInfo(param, &pset.popt);
return true;
}
static bool
printPsetInfo(const char *param, struct printQueryOpt *popt)
{
Assert(param != NULL);
/* show border style/width */
if (strcmp(param, "border") == 0)
printf(_("Border style is %d.\n"), popt->topt.border);
/* show the target width for the wrapped format */
else if (strcmp(param, "columns") == 0)
{
if (!popt->topt.columns)
printf(_("Target width is unset.\n"));
else
printf(_("Target width is %d.\n"), popt->topt.columns);
}
/* show expanded/vertical mode */
else if (strcmp(param, "x") == 0 || strcmp(param, "expanded") == 0 || strcmp(param, "vertical") == 0)
{
if (popt->topt.expanded == 1)
printf(_("Expanded display is on.\n"));
else if (popt->topt.expanded == 2)
printf(_("Expanded display is used automatically.\n"));
else
printf(_("Expanded display is off.\n"));
}
/* show field separator for unaligned text */
else if (strcmp(param, "fieldsep") == 0)
{
if (popt->topt.fieldSep.separator_zero)
printf(_("Field separator is zero byte.\n"));
else
printf(_("Field separator is \"%s\".\n"),
popt->topt.fieldSep.separator);
}
else if (strcmp(param, "fieldsep_zero") == 0)
{
printf(_("Field separator is zero byte.\n"));
}
/* show disable "(x rows)" footer */
else if (strcmp(param, "footer") == 0)
{
if (popt->topt.default_footer)
printf(_("Default footer is on.\n"));
else
printf(_("Default footer is off.\n"));
}
/* show format */
else if (strcmp(param, "format") == 0)
{
printf(_("Output format is %s.\n"), _align2string(popt->topt.format));
}
/* show table line style */
else if (strcmp(param, "linestyle") == 0)
{
printf(_("Line style is %s.\n"),
get_line_style(&popt->topt)->name);
}
/* show null display */
else if (strcmp(param, "null") == 0)
{
printf(_("Null display is \"%s\".\n"),
popt->nullPrint ? popt->nullPrint : "");
}
/* show locale-aware numeric output */
else if (strcmp(param, "numericlocale") == 0)
{
if (popt->topt.numericLocale)
printf(_("Locale-adjusted numeric output is on.\n"));
else
printf(_("Locale-adjusted numeric output is off.\n"));
}
/* show toggle use of pager */
else if (strcmp(param, "pager") == 0)
{
if (popt->topt.pager == 1)
printf(_("Pager is used for long output.\n"));
else if (popt->topt.pager == 2)
printf(_("Pager is always used.\n"));
else
printf(_("Pager usage is off.\n"));
}
/* show record separator for unaligned text */
else if (strcmp(param, "recordsep") == 0)
{
if (popt->topt.recordSep.separator_zero)
printf(_("Record separator is zero byte.\n"));
else if (strcmp(popt->topt.recordSep.separator, "\n") == 0)
printf(_("Record separator is <newline>.\n"));
else
printf(_("Record separator is \"%s\".\n"),
popt->topt.recordSep.separator);
}
else if (strcmp(param, "recordsep_zero") == 0)
{
printf(_("Record separator is zero byte.\n"));
}
/* show HTML table tag options */
else if (strcmp(param, "T") == 0 || strcmp(param, "tableattr") == 0)
{
if (popt->topt.tableAttr)
printf(_("Table attributes are \"%s\".\n"),
popt->topt.tableAttr);
else
printf(_("Table attributes unset.\n"));
}
/* show title override */
else if (strcmp(param, "title") == 0)
{
if (popt->title)
printf(_("Title is \"%s\".\n"), popt->title);
else
printf(_("Title is unset.\n"));
}
/* show toggle between full and tuples-only format */
else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
{
if (popt->topt.tuples_only)
printf(_("Tuples only is on.\n"));
else
printf(_("Tuples only is off.\n"));
}
/* unicode style formatting */
else if (strcmp(param, "unicode_border_linestyle") == 0)
{
printf(_("Unicode border linestyle is \"%s\".\n"),
_unicode_linestyle2string(popt->topt.unicode_border_linestyle));
}
else if (strcmp(param, "unicode_column_linestyle") == 0)
{
printf(_("Unicode column linestyle is \"%s\".\n"),
_unicode_linestyle2string(popt->topt.unicode_column_linestyle));
}
else if (strcmp(param, "unicode_header_linestyle") == 0)
{
printf(_("Unicode border linestyle is \"%s\".\n"),
_unicode_linestyle2string(popt->topt.unicode_header_linestyle));
}
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;
}
static const char *
pset_bool_string(bool val)
{
return val ? "on" : "off";
}
static char *
pset_quoted_string(const char *str)
{
char *ret = pg_malloc(strlen(str) * 2 + 3);
char *r = ret;
*r++ = '\'';
for (; *str; str++)
{
if (*str == '\n')
{
*r++ = '\\';
*r++ = 'n';
}
else if (*str == '\'')
{
*r++ = '\\';
*r++ = '\'';
}
else
*r++ = *str;
}
*r++ = '\'';
*r = '\0';
return ret;
}
/*
* Return a malloc'ed string for the \pset value.
*
* Note that for some string parameters, print.c distinguishes between unset
* and empty string, but for others it doesn't. This function should produce
* output that produces the correct setting when fed back into \pset.
*/
static char *
pset_value_string(const char *param, struct printQueryOpt *popt)
{
Assert(param != NULL);
if (strcmp(param, "border") == 0)
return psprintf("%d", popt->topt.border);
else if (strcmp(param, "columns") == 0)
return psprintf("%d", popt->topt.columns);
else if (strcmp(param, "expanded") == 0)
return pstrdup(popt->topt.expanded == 2
? "auto"
: pset_bool_string(popt->topt.expanded));
else if (strcmp(param, "fieldsep") == 0)
return pset_quoted_string(popt->topt.fieldSep.separator
? popt->topt.fieldSep.separator
: "");
else if (strcmp(param, "fieldsep_zero") == 0)
return pstrdup(pset_bool_string(popt->topt.fieldSep.separator_zero));
else if (strcmp(param, "footer") == 0)
return pstrdup(pset_bool_string(popt->topt.default_footer));
else if (strcmp(param, "format") == 0)
return psprintf("%s", _align2string(popt->topt.format));
else if (strcmp(param, "linestyle") == 0)
return psprintf("%s", get_line_style(&popt->topt)->name);
else if (strcmp(param, "null") == 0)
return pset_quoted_string(popt->nullPrint
? popt->nullPrint
: "");
else if (strcmp(param, "numericlocale") == 0)
return pstrdup(pset_bool_string(popt->topt.numericLocale));
else if (strcmp(param, "pager") == 0)
return psprintf("%d", popt->topt.pager);
else if (strcmp(param, "recordsep") == 0)
return pset_quoted_string(popt->topt.recordSep.separator
? popt->topt.recordSep.separator
: "");
else if (strcmp(param, "recordsep_zero") == 0)
return pstrdup(pset_bool_string(popt->topt.recordSep.separator_zero));
else if (strcmp(param, "tableattr") == 0)
return popt->topt.tableAttr ? pset_quoted_string(popt->topt.tableAttr) : pstrdup("");
else if (strcmp(param, "title") == 0)
return popt->title ? pset_quoted_string(popt->title) : pstrdup("");
else if (strcmp(param, "tuples_only") == 0)
return pstrdup(pset_bool_string(popt->topt.tuples_only));
else if (strcmp(param, "unicode_border_linestyle") == 0)
return pstrdup(_unicode_linestyle2string(popt->topt.unicode_border_linestyle));
else if (strcmp(param, "unicode_column_linestyle") == 0)
return pstrdup(_unicode_linestyle2string(popt->topt.unicode_column_linestyle));
else if (strcmp(param, "unicode_header_linestyle") == 0)
return pstrdup(_unicode_linestyle2string(popt->topt.unicode_header_linestyle));
else
return pstrdup("ERROR");
}
#ifndef WIN32
#define DEFAULT_SHELL "/bin/sh"
#else
/*
* CMD.EXE is in different places in different Win32 releases so we
* have to rely on the path to find it.
*/
#define DEFAULT_SHELL "cmd.exe"
#endif
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");
2004-11-04 23:25:14 +01:00
#ifdef WIN32
if (shellName == NULL)
shellName = getenv("COMSPEC");
#endif
1999-11-05 00:14:30 +01:00
if (shellName == NULL)
shellName = DEFAULT_SHELL;
/* See EDITOR handling comment for an explanation */
#ifndef WIN32
sys = psprintf("exec %s", shellName);
2004-11-30 20:01:28 +01:00
#else
sys = psprintf("\"%s\"", shellName);
2004-11-30 20:01:28 +01:00
#endif
1999-11-05 00:14:30 +01:00
result = system(sys);
free(sys);
}
else
result = system(command);
1999-11-05 00:14:30 +01:00
if (result == 127 || result == -1)
{
psql_error("\\!: failed\n");
1999-11-05 00:14:30 +01:00
return false;
}
return true;
}
/*
* do_watch -- handler for \watch
*
* We break this out of exec_command to avoid having to plaster "volatile"
* onto a bunch of exec_command's variables to silence stupider compilers.
*/
static bool
do_watch(PQExpBuffer query_buf, long sleep)
{
printQueryOpt myopt = pset.popt;
char title[50];
if (!query_buf || query_buf->len <= 0)
{
psql_error(_("\\watch cannot be used with an empty query\n"));
return false;
}
/*
* Set up rendering options, in particular, disable the pager, because
* nobody wants to be prompted while watching the output of 'watch'.
*/
myopt.topt.pager = 0;
for (;;)
{
int res;
time_t timer;
long i;
/*
* Prepare title for output. XXX would it be better to use the time
* of completion of the command?
*/
timer = time(NULL);
snprintf(title, sizeof(title), _("Watch every %lds\t%s"),
sleep, asctime(localtime(&timer)));
myopt.title = title;
/* Run the query and print out the results */
res = PSQLexecWatch(query_buf->data, &myopt);
/*
* PSQLexecWatch handles the case where we can no longer
* repeat the query, and returns 0 or -1.
*/
if (res == 0)
break;
if (res == -1)
return false;
/*
* Set up cancellation of 'watch' via SIGINT. We redo this each time
* through the loop since it's conceivable something inside
* PSQLexecWatch could change sigint_interrupt_jmp.
*/
if (sigsetjmp(sigint_interrupt_jmp, 1) != 0)
break;
/*
* Enable 'watch' cancellations and wait a while before running the
* query again. Break the sleep into short intervals since pg_usleep
* isn't interruptible on some platforms.
*/
sigint_interrupt_enabled = true;
for (i = 0; i < sleep; i++)
{
pg_usleep(1000000L);
if (cancel_pressed)
break;
}
sigint_interrupt_enabled = false;
}
return true;
}
/*
* a little code borrowed from PSQLexec() to manage ECHO_HIDDEN output.
* returns true unless we have ECHO_HIDDEN_NOEXEC.
*/
static bool
lookup_function_echo_hidden(char * query)
{
if (pset.echo_hidden != PSQL_ECHO_HIDDEN_OFF)
{
printf(_("********* QUERY **********\n"
"%s\n"
"**************************\n\n"), query);
fflush(stdout);
if (pset.logfile)
{
fprintf(pset.logfile,
_("********* QUERY **********\n"
"%s\n"
"**************************\n\n"), query);
fflush(pset.logfile);
}
if (pset.echo_hidden == PSQL_ECHO_HIDDEN_NOEXEC)
return false;
}
return true;
}
/*
* This function takes a function description, e.g. "x" or "x(int)", and
* issues a query on the given connection to retrieve the function's OID
* using a cast to regproc or regprocedure (as appropriate). The result,
* if there is one, is returned at *foid. Note that we'll fail if the
* function doesn't exist OR if there are multiple matching candidates
* OR if there's something syntactically wrong with the function description;
* unfortunately it can be hard to tell the difference.
*/
static bool
lookup_function_oid(const char *desc, Oid *foid)
{
bool result = true;
PQExpBuffer query;
PGresult *res;
query = createPQExpBuffer();
appendPQExpBufferStr(query, "SELECT ");
appendStringLiteralConn(query, desc, pset.db);
appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
strchr(desc, '(') ? "regprocedure" : "regproc");
if (!lookup_function_echo_hidden(query->data))
{
destroyPQExpBuffer(query);
return false;
}
res = PQexec(pset.db, query->data);
if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
*foid = atooid(PQgetvalue(res, 0, 0));
else
{
minimal_error_message(res);
result = false;
}
PQclear(res);
destroyPQExpBuffer(query);
return result;
}
/*
* Fetches the "CREATE OR REPLACE FUNCTION ..." command that describes the
* function with the given OID. If successful, the result is stored in buf.
*/
static bool
get_create_function_cmd(Oid oid, PQExpBuffer buf)
{
bool result = true;
PQExpBuffer query;
PGresult *res;
query = createPQExpBuffer();
printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
if (!lookup_function_echo_hidden(query->data))
{
destroyPQExpBuffer(query);
return false;
}
res = PQexec(pset.db, query->data);
if (PQresultStatus(res) == PGRES_TUPLES_OK && PQntuples(res) == 1)
{
resetPQExpBuffer(buf);
appendPQExpBufferStr(buf, PQgetvalue(res, 0, 0));
}
else
{
minimal_error_message(res);
result = false;
}
PQclear(res);
destroyPQExpBuffer(query);
return result;
}
/*
* If the given argument of \ef ends with a line number, delete the line
* number from the argument string and return it as an integer. (We need
* this kluge because we're too lazy to parse \ef's function name argument
* carefully --- we just slop it up in OT_WHOLE_LINE mode.)
*
* Returns -1 if no line number is present, 0 on error, or a positive value
* on success.
*/
static int
strip_lineno_from_funcdesc(char *func)
{
char *c;
int lineno;
if (!func || func[0] == '\0')
return -1;
c = func + strlen(func) - 1;
/*
* This business of parsing backwards is dangerous as can be in a
* multibyte environment: there is no reason to believe that we are
* looking at the first byte of a character, nor are we necessarily
* working in a "safe" encoding. Fortunately the bitpatterns we are
2011-04-10 17:42:00 +02:00
* looking for are unlikely to occur as non-first bytes, but beware of
* trying to expand the set of cases that can be recognized. We must
* guard the <ctype.h> macros by using isascii() first, too.
*/
/* skip trailing whitespace */
while (c > func && isascii((unsigned char) *c) && isspace((unsigned char) *c))
c--;
/* must have a digit as last non-space char */
if (c == func || !isascii((unsigned char) *c) || !isdigit((unsigned char) *c))
return -1;
/* find start of digit string */
while (c > func && isascii((unsigned char) *c) && isdigit((unsigned char) *c))
c--;
/* digits must be separated from func name by space or closing paren */
/* notice also that we are not allowing an empty func name ... */
if (c == func || !isascii((unsigned char) *c) ||
!(isspace((unsigned char) *c) || *c == ')'))
return -1;
/* parse digit string */
c++;
lineno = atoi(c);
if (lineno < 1)
{
psql_error("invalid line number: %s\n", c);
return 0;
}
/* strip digit string from func */
*c = '\0';
return lineno;
}
/*
* Report just the primary error; this is to avoid cluttering the output
* with, for instance, a redisplay of the internally generated query
*/
static void
minimal_error_message(PGresult *res)
{
PQExpBuffer msg;
const char *fld;
msg = createPQExpBuffer();
fld = PQresultErrorField(res, PG_DIAG_SEVERITY);
if (fld)
printfPQExpBuffer(msg, "%s: ", fld);
else
printfPQExpBuffer(msg, "ERROR: ");
fld = PQresultErrorField(res, PG_DIAG_MESSAGE_PRIMARY);
if (fld)
appendPQExpBufferStr(msg, fld);
else
appendPQExpBufferStr(msg, "(not available)");
appendPQExpBufferStr(msg, "\n");
psql_error("%s", msg->data);
destroyPQExpBuffer(msg);
}