/*
* psql - the PostgreSQL interactive terminal
*
- * Copyright (c) 2000-2006, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2011, PostgreSQL Global Development Group
*
- * $PostgreSQL: pgsql/src/bin/psql/command.c,v 1.164 2006/03/05 15:58:51 momjian Exp $
+ * src/bin/psql/command.c
*/
#include "postgres_fe.h"
#include "command.h"
-#ifdef WIN32_CLIENT_ONLY /* needed for BCC */
+#ifdef __BORLANDC__ /* needed for BCC */
#undef mkdir
#endif
#include <io.h>
#include <fcntl.h>
#include <direct.h>
-#ifndef WIN32_CLIENT_ONLY
#include <sys/types.h> /* for umask() */
#include <sys/stat.h> /* for stat() */
#endif
+#ifdef USE_SSL
+#include <openssl/ssl.h>
#endif
+#include "portability/instr_time.h"
+
#include "libpq-fe.h"
#include "pqexpbuffer.h"
#include "dumputils.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, const char *new_host, const char *new_port);
+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 lookup_function_oid(PGconn *conn, const char *desc, Oid *foid);
+static bool get_create_function_cmd(PGconn *conn, Oid oid, PQExpBuffer buf);
+static int strip_lineno_from_funcdesc(char *func);
+static void minimal_error_message(PGresult *res);
+
+static void printSSLInfo(void);
+
+#ifdef WIN32
+static void checkWin32Codepage(void);
+#endif
+
/*----------
* HandleSlashCmds:
*
- * Handles all the different commands that start with '\',
- * ordinarily called by MainLoop().
+ * 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
/* And try to execute it */
status = exec_command(cmd, scan_state, query_buf);
- if (status == PSQL_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 != PSQL_CMD_UNKNOWN)
- {
- /* adjust cmd for possible messages below */
- cmd[1] = '\0';
- }
- }
-
if (status == PSQL_CMD_UNKNOWN)
{
if (pset.cur_cmd_interactive)
free(cmd);
+ /* some commands write to queryFout, so make sure output is sent */
+ fflush(pset.queryFout);
+
return status;
}
+/*
+ * Read and interpret an argument to the \connect slash command.
+ */
+static char *
+read_connect_arg(PsqlScanState scan_state)
+{
+ char *result;
+ char quote;
+
+ /*
+ * 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, "e, true);
+
+ if (!result)
+ return NULL;
+
+ if (quote)
+ return result;
+
+ if (*result == '\0' || strcmp(result, "-") == 0)
+ return NULL;
+
+ return result;
+}
+
+
/*
* Subroutine to actually try to execute a backslash command.
*/
{
bool success = true; /* indicate here if the command ran ok or
* failed */
- bool quiet = QUIET();
backslashResult status = PSQL_CMD_SKIP_LINE;
/*
if (strcmp(cmd, "a") == 0)
{
if (pset.popt.topt.format != PRINT_ALIGNED)
- success = do_pset("format", "aligned", &pset.popt, quiet);
+ success = do_pset("format", "aligned", &pset.popt, pset.quiet);
else
- success = do_pset("format", "unaligned", &pset.popt, quiet);
+ success = do_pset("format", "unaligned", &pset.popt, pset.quiet);
}
/* \C -- override table title (formerly change HTML caption) */
char *opt = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
- success = do_pset("title", opt, &pset.popt, quiet);
+ success = do_pset("title", opt, &pset.popt, pset.quiet);
free(opt);
}
- /*----------
- * \c or \connect -- connect to new database or as different user,
- * and/or new host and/or port
+ /*
+ * \c or \connect -- connect to database using the specified parameters.
+ *
+ * \c dbname user host port
*
- * \c foo bar [-] [-] connect to db "foo" as user "bar" on current host and port
- * \c foo [-] [-] [-] connect to db "foo" as current user on current host and port
- * \c - bar [-] [-] connect to current db as user "bar" on current host and port
- * \c - - host.domain.tld [-] connect to default db as default user on host.domain.tld on default port
- * \c - - - 5555 connect to default db as default user on default host at port 5555
- * \c connect to default db as default user
- *----------
+ * 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:
+ *
+ * \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.
*/
else if (strcmp(cmd, "c") == 0 || strcmp(cmd, "connect") == 0)
{
*opt2,
*opt3,
*opt4;
- char opt1q,
- opt2q,
- opt3q,
- opt4q;
- /*
- * 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);
- opt3 = psql_scan_slash_option(scan_state,
- OT_SQLIDHACK, &opt3q, true);
- opt4 = psql_scan_slash_option(scan_state,
- OT_SQLIDHACK, &opt4q, true);
-
- if (opt4)
- /* gave port */
- success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 ||
- strcmp(opt1, "") == 0) ? "" : opt1,
- !opt2q && (strcmp(opt2, "-") == 0 ||
- strcmp(opt2, "") == 0) ? "" : opt2,
- !opt3q && (strcmp(opt3, "-") == 0 ||
- strcmp(opt3, "") == 0) ? "" : opt3,
- !opt3q && (strcmp(opt3, "-") == 0 ||
- strcmp(opt3, "") == 0) ? "" : opt3);
- if (opt3)
- /* gave host */
- success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 ||
- strcmp(opt1, "") == 0) ? "" : opt1,
- !opt2q && (strcmp(opt2, "-") == 0 ||
- strcmp(opt2, "") == 0) ? "" : opt2,
- !opt3q && (strcmp(opt3, "-") == 0 ||
- strcmp(opt3, "") == 0) ? "" : opt3,
- NULL);
- if (opt2)
- /* gave username */
- success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 ||
- strcmp(opt1, "") == 0) ? "" : opt1,
- !opt2q && (strcmp(opt2, "-") == 0 ||
- strcmp(opt2, "") == 0) ? "" : opt2,
- NULL,
- NULL);
- else if (opt1)
- /* gave database name */
- success = do_connect(!opt1q && (strcmp(opt1, "-") == 0 ||
- strcmp(opt1, "") == 0) ? "" : opt1,
- "",
- NULL,
- NULL);
- else
- /* connect to default db as default user */
- success = do_connect(NULL, NULL, NULL, NULL);
+ 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);
}
if (pset.dirname)
- pfree(pset.dirname);
+ free(pset.dirname);
pset.dirname = pg_strdup(dir);
canonicalize_path(pset.dirname);
free(opt);
}
+ /* \conninfo -- display information about the current connection */
+ else if (strcmp(cmd, "conninfo") == 0)
+ {
+ char *db = PQdb(pset.db);
+ char *host = PQhost(pset.db);
+
+ if (db == NULL)
+ printf(_("You are currently not connected to a database.\n"));
+ else
+ {
+ 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 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));
+ }
+ }
+
/* \copy */
else if (pg_strcasecmp(cmd, "copy") == 0)
{
+ /* Default fetch-it-all-and-print mode */
+ instr_time before,
+ after;
+
char *opt = psql_scan_slash_option(scan_state,
OT_WHOLE_LINE, NULL, false);
+ if (pset.timing)
+ INSTR_TIME_SET_CURRENT(before);
+
success = do_copy(opt);
+
+ if (pset.timing && success)
+ {
+ INSTR_TIME_SET_CURRENT(after);
+ INSTR_TIME_SUBTRACT(after, before);
+ printf(_("Time: %.3f ms\n"), INSTR_TIME_GET_MILLISEC(after));
+ }
+
free(opt);
}
else if (cmd[0] == 'd')
{
char *pattern;
- bool show_verbose;
+ 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;
switch (cmd[1])
{
case '\0':
case '+':
+ case 'S':
if (pattern)
- success = describeTableDetails(pattern, show_verbose);
+ success = describeTableDetails(pattern, show_verbose, show_system);
else
/* standard listing of interesting things */
- success = listTables("tvs", NULL, show_verbose);
+ success = listTables("tvsE", NULL, show_verbose, show_system);
break;
case 'a':
- success = describeAggregates(pattern, show_verbose);
+ success = describeAggregates(pattern, show_verbose, show_system);
break;
case 'b':
success = describeTablespaces(pattern, show_verbose);
break;
case 'c':
- success = listConversions(pattern);
+ success = listConversions(pattern, show_system);
break;
case 'C':
success = listCasts(pattern);
break;
case 'd':
- success = objectDescription(pattern);
+ if (strncmp(cmd, "ddp", 3) == 0)
+ success = listDefaultACLs(pattern);
+ else
+ success = objectDescription(pattern, show_system);
break;
case 'D':
- success = listDomains(pattern);
+ success = listDomains(pattern, show_system);
break;
- case 'f':
- success = describeFunctions(pattern, show_verbose);
+ 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;
+ }
break;
case 'g':
/* no longer distinct from \du */
case 'l':
success = do_lo_list();
break;
+ case 'L':
+ success = listLanguages(pattern, show_verbose, show_system);
+ break;
case 'n':
- success = listSchemas(pattern, show_verbose);
+ success = listSchemas(pattern, show_verbose, show_system);
break;
case 'o':
- success = describeOperators(pattern);
+ success = describeOperators(pattern, show_system);
+ break;
+ case 'O':
+ success = listCollations(pattern, show_verbose, show_system);
break;
case 'p':
success = permissionsList(pattern);
break;
case 'T':
- success = describeTypes(pattern, show_verbose);
+ success = describeTypes(pattern, show_verbose, show_system);
break;
case 't':
case 'v':
case 'i':
case 's':
- case 'S':
- success = listTables(&cmd[1], pattern, show_verbose);
+ case 'E':
+ success = listTables(&cmd[1], pattern, show_verbose, show_system);
+ break;
+ case 'r':
+ if (cmd[2] == 'd' && cmd[3] == 's')
+ {
+ char *pattern2 = NULL;
+
+ if (pattern)
+ pattern2 = psql_scan_slash_option(scan_state,
+ 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;
+ case 'x': /* Extensions */
+ if (show_verbose)
+ success = listExtensionContents(pattern);
+ else
+ success = listExtensions(pattern);
+ break;
default:
status = PSQL_CMD_UNKNOWN;
}
/*
- * \e or \edit -- edit the current query buffer (or a file and make it the
- * query buffer
+ * \e or \edit -- edit the current query buffer, or edit 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");
}
else
{
+ char *fname;
+ char *ln = NULL;
+ int lineno = -1;
+
fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
- expand_tilde(&fname);
if (fname)
- canonicalize_path(fname);
- status = do_edit(fname, query_buf) ? PSQL_CMD_NEWEDIT : PSQL_CMD_ERROR;
- free(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 (!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(pset.db, func, &foid))
+ {
+ /* error already reported */
+ status = PSQL_CMD_ERROR;
+ }
+ else if (!get_create_function_cmd(pset.db, 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)
+ puts(_("No changes"));
+ else
+ status = PSQL_CMD_NEWEDIT;
}
}
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, false);
- success = do_pset("fieldsep", fname, &pset.popt, quiet);
+ success = do_pset("fieldsep", fname, &pset.popt, pset.quiet);
free(fname);
}
{
char *opt = psql_scan_slash_option(scan_state,
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])
+ || opt[len - 1] == ';'))
+ opt[--len] = '\0';
+ }
helpSQL(opt, pset.popt.topt.pager);
free(opt);
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);
+ success = do_pset("format", "html", &pset.popt, pset.quiet);
else
- success = do_pset("format", "aligned", &pset.popt, quiet);
+ success = do_pset("format", "aligned", &pset.popt, pset.quiet);
}
- /* \i is include file */
- else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0)
+ /* \i and \ir include files */
+ else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0
+ || strcmp(cmd, "ir") == 0 || strcmp(cmd, "include_relative") == 0)
{
char *fname = psql_scan_slash_option(scan_state,
OT_NORMAL, NULL, true);
}
else
{
+ bool include_relative;
+
+ include_relative = (strcmp(cmd, "ir") == 0
+ || strcmp(cmd, "include_relative") == 0);
expand_tilde(&fname);
- success = (process_file(fname, false) == EXIT_SUCCESS);
+ success = (process_file(fname, false, include_relative) == EXIT_SUCCESS);
free(fname);
}
}
{
if (query_buf && query_buf->len > 0)
puts(query_buf->data);
- else if (!quiet)
+ else if (!pset.quiet)
puts(_("Query buffer is empty."));
fflush(stdout);
}
PGresult *res;
initPQExpBuffer(&buf);
- printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD '%s';",
- fmtId(user), encrypted_password);
+ printfPQExpBuffer(&buf, "ALTER USER %s PASSWORD ",
+ fmtId(user));
+ appendStringLiteralConn(&buf, encrypted_password, pset.db);
res = PSQLexec(buf.data, false);
termPQExpBuffer(&buf);
if (!res)
free(pw2);
}
+ /* \prompt -- prompt and set variable */
+ else if (strcmp(cmd, "prompt") == 0)
+ {
+ 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
+ {
+ 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\n", cmd);
+ success = false;
+ }
+
+ free(result);
+ if (prompt_text)
+ free(prompt_text);
+ free(opt);
+ }
+ }
+
/* \pset -- set printing parameters */
else if (strcmp(cmd, "pset") == 0)
{
success = false;
}
else
- success = do_pset(opt0, opt1, &pset.popt, quiet);
+ success = do_pset(opt0, opt1, &pset.popt, pset.quiet);
free(opt0);
free(opt1);
{
resetPQExpBuffer(query_buf);
psql_scan_reset(scan_state);
- if (!quiet)
+ if (!pset.quiet)
puts(_("Query buffer reset (cleared)."));
}
expand_tilde(&fname);
/* This scrolls off the screen when using /dev/tty */
- success = saveHistory(fname ? fname : DEVTTY);
- if (success && !quiet && fname)
+ success = saveHistory(fname ? fname : DEVTTY, -1, false, false);
+ if (success && !pset.quiet && fname)
printf(gettext("Wrote history to file \"%s/%s\".\n"),
pset.dirname ? pset.dirname : ".", fname);
if (!fname)
free(opt);
}
- if (SetVariable(pset.vars, opt0, newval))
- {
- /* Check for special variables */
- if (strcmp(opt0, "VERBOSITY") == 0)
- SyncVerbosityVariable();
- }
- else
+ if (!SetVariable(pset.vars, opt0, newval))
{
psql_error("\\%s: error\n", cmd);
success = false;
free(opt0);
}
+ /* \sf -- show a function's source code */
+ else if (strcmp(cmd, "sf") == 0 || strcmp(cmd, "sf+") == 0)
+ {
+ bool show_linenumbers = (strcmp(cmd, "sf+") == 0);
+ PQExpBuffer func_buf;
+ char *func;
+ Oid foid = InvalidOid;
+
+ func_buf = createPQExpBuffer();
+ func = psql_scan_slash_option(scan_state,
+ OT_WHOLE_LINE, NULL, true);
+ if (!func)
+ {
+ psql_error("function name is required\n");
+ status = PSQL_CMD_ERROR;
+ }
+ else if (!lookup_function_oid(pset.db, func, &foid))
+ {
+ /* error already reported */
+ status = PSQL_CMD_ERROR;
+ }
+ else if (!get_create_function_cmd(pset.db, foid, func_buf))
+ {
+ /* error already reported */
+ status = PSQL_CMD_ERROR;
+ }
+ else
+ {
+ 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')
+ {
+ 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);
+ }
+
/* \t -- turn off headers and row count */
else if (strcmp(cmd, "t") == 0)
- success = do_pset("tuples_only", NULL, &pset.popt, quiet);
+ {
+ char *opt = psql_scan_slash_option(scan_state,
+ OT_NORMAL, NULL, true);
+ success = do_pset("tuples_only", opt, &pset.popt, pset.quiet);
+ free(opt);
+ }
/* \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);
+ success = do_pset("tableattr", value, &pset.popt, pset.quiet);
free(value);
}
/* \timing -- toggle timing of queries */
else if (strcmp(cmd, "timing") == 0)
{
- pset.timing = !pset.timing;
- if (!quiet)
+ char *opt = psql_scan_slash_option(scan_state,
+ OT_NORMAL, NULL, false);
+
+ if (opt)
+ pset.timing = ParseVariableBool(opt);
+ else
+ pset.timing = !pset.timing;
+ if (!pset.quiet)
{
if (pset.timing)
puts(_("Timing is on."));
else
puts(_("Timing is off."));
}
+ free(opt);
}
/* \unset */
/* \x -- toggle expanded table representation */
else if (strcmp(cmd, "x") == 0)
- success = do_pset("expanded", NULL, &pset.popt, quiet);
+ {
+ 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) */
else if (strcmp(cmd, "z") == 0)
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.
+/*
+ * 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 bool
-do_connect(const char *new_dbname, const char *new_user, const char *new_host, const char *new_port)
+static char *
+prompt_for_password(const char *username)
{
- PGconn *oldconn = pset.db;
- const char *dbparam = NULL;
- const char *userparam = NULL;
- const char *hostparam = NULL;
- const char *portparam = NULL;
- const char *pwparam = NULL;
- char *password_prompt = 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;
+ char *result;
- /* If user is "" then use the old one */
- if (new_user && PQuser(oldconn) && strcmp(new_user, "") == 0)
- userparam = PQuser(oldconn);
+ if (username == NULL)
+ result = simple_prompt("Password: ", 100, false);
else
- userparam = new_user;
+ {
+ char *prompt_text;
- /* If host is "" then use the old one */
- if (new_host && PQhost(oldconn) && strcmp(new_host, "") == 0)
- hostparam = PQhost(oldconn);
- else
- hostparam = new_host;
+ prompt_text = malloc(strlen(username) + 100);
+ snprintf(prompt_text, strlen(username) + 100,
+ _("Password for user %s: "), username);
+ result = simple_prompt(prompt_text, 100, false);
+ free(prompt_text);
+ }
- /* If port is "" then use the old one */
- if (new_port && PQport(oldconn) && strcmp(new_port, "") == 0)
- portparam = PQport(oldconn);
- else
- portparam = new_port;
+ return result;
+}
- if (userparam == NULL)
- password_prompt = strdup("Password: ");
- else
- {
- password_prompt = malloc(strlen(_("Password for user %s: ")) - 2 +
- strlen(userparam) + 1);
- sprintf(password_prompt, _("Password for user %s: "), userparam);
- }
+static bool
+param_is_newly_set(const char *old_val, const char *new_val)
+{
+ if (new_val == NULL)
+ return false;
- /* need to prompt for password? */
- if (pset.getPassword)
- pwparam = prompted_password = simple_prompt(password_prompt, 100, 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)
+{
+ PGconn *o_conn = pset.db,
+ *n_conn;
+ char *password = NULL;
+
+ if (!dbname)
+ dbname = PQdb(o_conn);
+ if (!user)
+ user = PQuser(o_conn);
+ if (!host)
+ host = PQhost(o_conn);
+ if (!port)
+ port = PQport(o_conn);
/*
- * Use old password (if any) if no new one given and we are reconnecting
- * as same user
+ * 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.
+ *
+ * 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.
*/
- if (!pwparam && oldconn && PQuser(oldconn) && userparam &&
- strcmp(PQuser(oldconn), userparam) == 0)
- pwparam = PQpass(oldconn);
+ if (pset.getPassword == TRI_YES)
+ {
+ password = prompt_for_password(user);
+ }
+ else if (o_conn && user && strcmp(PQuser(o_conn), user) == 0)
+ {
+ password = strdup(PQpass(o_conn));
+ }
- do
+ while (true)
{
- need_pass = false;
- pset.db = PQsetdbLogin(hostparam, portparam,
- NULL, NULL, dbparam, userparam, pwparam);
+#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));
+
+ 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);
+
+ if (PQstatus(n_conn) == CONNECTION_OK)
+ break;
+
+ /*
+ * Connection attempt failed; either retry the connection attempt with
+ * a new password, or give up.
+ */
+ if (!password && PQconnectionNeedsPassword(n_conn) && pset.getPassword != TRI_NO)
+ {
+ PQfinish(n_conn);
+ password = prompt_for_password(user);
+ continue;
+ }
- if (PQstatus(pset.db) == CONNECTION_BAD &&
- strcmp(PQerrorMessage(pset.db), PQnoPasswordSupplied) == 0 &&
- !feof(stdin))
+ /*
+ * 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)
{
- PQfinish(pset.db);
- need_pass = true;
- free(prompted_password);
- prompted_password = NULL;
- pwparam = prompted_password = simple_prompt(password_prompt, 100, false);
+ psql_error("%s", PQerrorMessage(n_conn));
+
+ /* pset.db is left unmodified */
+ if (o_conn)
+ fputs(_("Previous connection kept\n"), stderr);
+ }
+ else
+ {
+ psql_error("\\connect: %s", PQerrorMessage(n_conn));
+ if (o_conn)
+ {
+ PQfinish(o_conn);
+ pset.db = NULL;
+ }
}
- } while (need_pass);
- free(prompted_password);
- free(password_prompt);
+ PQfinish(n_conn);
+ return false;
+ }
/*
- * If connection failed, try at least keep the old one. That's probably
- * more convenient than just kicking you out of the program.
+ * Replace the old connection with the new one, and update
+ * connection-dependent variables.
*/
- if (!pset.db || PQstatus(pset.db) == CONNECTION_BAD)
+ PQsetNoticeProcessor(n_conn, NoticeProcessor, NULL);
+ pset.db = n_conn;
+ SyncVariables();
+ connection_warnings(false); /* Must be after SyncVariables */
+
+ /* Tell the user about the new connection */
+ if (!pset.quiet)
{
- if (pset.cur_cmd_interactive)
+ if (param_is_newly_set(PQhost(o_conn), PQhost(pset.db)) ||
+ param_is_newly_set(PQport(o_conn), PQport(pset.db)))
{
- psql_error("%s", PQerrorMessage(pset.db));
- PQfinish(pset.db);
- if (oldconn)
- {
- fputs(_("Previous connection kept\n"), stderr);
- pset.db = oldconn;
- }
+ 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
- pset.db = NULL;
+ 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
- {
- /*
- * 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;
- }
+ printf(_("You are now connected to database \"%s\" as user \"%s\".\n"),
+ PQdb(pset.db), PQuser(pset.db));
}
- else
+
+ if (o_conn)
+ PQfinish(o_conn);
+ return true;
+}
+
+
+void
+connection_warnings(bool in_startup)
+{
+ if (!pset.quiet && !pset.notty)
{
- if (!QUIET())
+ int client_ver = parse_version(PG_VERSION);
+
+ if (pset.sversion != client_ver)
{
- if ((hostparam == new_host) && (portparam == new_port)) /* no new host or port */
- {
- if (userparam != new_user) /* no new user */
- printf(_("You are now connected to database \"%s\".\n"), dbparam);
- else if (dbparam != new_dbname) /* no new db */
- printf(_("You are now connected as new user \"%s\".\n"), new_user);
- else
- /* both new */
- printf(_("You are now connected to database \"%s\" as user \"%s\".\n"),
- PQdb(pset.db), PQuser(pset.db));
- }
- else /* At least one of host and port are new */
+ 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)
{
- printf(
- _("You are now connected to database \"%s\" as user \"%s\" on host \"%s\" at port %s.\n"),
- PQdb(pset.db), PQuser(pset.db), PQhost(pset.db),
- PQport(pset.db));
+ 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 (oldconn)
- PQfinish(oldconn);
+ if (pset.sversion / 100 != client_ver / 100)
+ printf(_("WARNING: %s version %d.%d, server 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);
- success = true;
+#ifdef WIN32
+ checkWin32Codepage();
+#endif
+ printSSLInfo();
}
+}
- PQsetNoticeProcessor(pset.db, NoticeProcessor, NULL);
- /* Update variables */
- SyncVariables();
+/*
+ * printSSLInfo
+ *
+ * Prints information about the current SSL connection, if SSL is in use
+ */
+static void
+printSSLInfo(void)
+{
+#ifdef USE_SSL
+ int sslbits = -1;
+ SSL *ssl;
+
+ ssl = PQgetssl(pset.db);
+ if (!ssl)
+ return; /* no SSL */
+
+ SSL_get_cipher_bits(ssl, &sslbits);
+ printf(_("SSL connection (cipher: %s, bits: %d)\n"),
+ SSL_get_cipher(ssl), sslbits);
+#else
+
+ /*
+ * If psql is compiled without SSL but is using a libpq with SSL, we
+ * cannot figure out the specifics about the connection. But we know it's
+ * SSL secured.
+ */
+ if (PQgetssl(pset.db))
+ printf(_("SSL connection (unknown cipher)\n"));
+#endif
+}
+
+
+/*
+ * checkWin32Codepage
+ *
+ * Prints a warning when win32 console codepage differs from Windows codepage
+ */
+#ifdef WIN32
+static void
+checkWin32Codepage(void)
+{
+ unsigned int wincp,
+ concp;
- return success;
+ 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
/*
/* 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, "ENCODING", pg_encoding_to_char(pset.encoding));
/* send stuff to it, too */
- SyncVerbosityVariable();
+ PQsetErrorVerbosity(pset.db, pset.verbosity);
}
/*
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)
+editFile(const char *fname, int lineno)
{
const char *editorName;
+ const char *editor_lineno_arg = NULL;
char *sys;
int result;
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;
+ }
+ }
+
+ /* Allocate sufficient memory for command line. */
+ if (lineno > 0)
+ sys = pg_malloc(strlen(editorName)
+ + strlen(editor_lineno_arg) + 10 /* for integer */
+ + 1 + strlen(fname) + 10 + 1);
+ else
+ sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
+
/*
* 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
* severe brain damage in their command shell plus the fact that standard
* program paths include spaces.
*/
- sys = pg_malloc(strlen(editorName) + strlen(fname) + 10 + 1);
#ifndef WIN32
- sprintf(sys, "exec %s '%s'", editorName, fname);
+ if (lineno > 0)
+ sprintf(sys, "exec %s %s%d '%s'",
+ editorName, editor_lineno_arg, lineno, fname);
+ else
+ sprintf(sys, "exec %s '%s'",
+ editorName, fname);
#else
- sprintf(sys, "%s\"%s\" \"%s\"%s",
- SYSTEMQUOTE, editorName, fname, SYSTEMQUOTE);
+ if (lineno > 0)
+ sprintf(sys, SYSTEMQUOTE "\"%s\" %s%d \"%s\"" SYSTEMQUOTE,
+ editorName, editor_lineno_arg, lineno, fname);
+ else
+ sprintf(sys, SYSTEMQUOTE "\"%s\" \"%s\"" SYSTEMQUOTE,
+ editorName, fname);
#endif
result = system(sys);
if (result == -1)
/* call this one */
static bool
-do_edit(const char *filename_arg, PQExpBuffer query_buf)
+do_edit(const char *filename_arg, PQExpBuffer query_buf,
+ int lineno, bool *edited)
{
char fnametmp[MAXPGPATH];
FILE *stream = NULL;
bool error = false;
int fd;
-#ifndef WIN32_CLIENT_ONLY
struct stat before,
after;
-#endif
if (filename_arg)
fname = filename_arg;
ret = GetTempPath(MAXPGPATH, tmpdir);
if (ret == 0 || ret > MAXPGPATH)
{
- psql_error("cannot locate temporary directory: %s",
+ psql_error("could not locate temporary directory: %s\n",
!ret ? strerror(errno) : "");
return false;
}
}
}
-#ifndef WIN32_CLIENT_ONLY
if (!error && stat(fname, &before) != 0)
{
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
-#endif
/* call editor */
if (!error)
- error = !editFile(fname);
+ error = !editFile(fname, lineno);
-#ifndef WIN32_CLIENT_ONLY
if (!error && stat(fname, &after) != 0)
{
psql_error("%s: %s\n", fname, strerror(errno));
if (!error && before.st_mtime != after.st_mtime)
{
-#else
- if (!error)
- {
-#endif
stream = fopen(fname, PG_BINARY_R);
if (!stream)
{
}
else
{
- /* read file back in */
+ /* read file back into query_buf */
char line[1024];
resetPQExpBuffer(query_buf);
psql_error("%s: %s\n", fname, strerror(errno));
error = true;
}
+ else if (edited)
+ {
+ *edited = 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 */
* 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. Returns
+ * 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)
+process_file(char *filename, bool single_txn, bool use_relative_path)
{
FILE *fd;
int result;
char *oldfilename;
- PGresult *res;
+ char relpath[MAXPGPATH];
+ PGresult *res;
if (!filename)
return EXIT_FAILURE;
- canonicalize_path(filename);
- fd = fopen(filename, PG_BINARY_R);
+ 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))
+ {
+ snprintf(relpath, MAXPGPATH, "%s", pset.inputfile);
+ get_parent_directory(relpath);
+ join_path_components(relpath, relpath, filename);
+ canonicalize_path(relpath);
+
+ filename = relpath;
+ }
+
+ fd = fopen(filename, PG_BINARY_R);
+ }
+ else
+ fd = stdin;
if (!fd)
{
oldfilename = pset.inputfile;
pset.inputfile = filename;
- if (single_txn)
- res = PSQLexec("BEGIN", false);
+ if (single_txn)
+ {
+ if ((res = PSQLexec("BEGIN", false)) == NULL)
+ {
+ if (pset.on_error_stop)
+ {
+ result = EXIT_USER;
+ goto error;
+ }
+ }
+ else
+ PQclear(res);
+ }
+
result = MainLoop(fd);
- if (single_txn)
- res = PSQLexec("COMMIT", false);
- fclose(fd);
+ if (single_txn)
+ {
+ if ((res = PSQLexec("COMMIT", false)) == 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;
}
case PRINT_ALIGNED:
return "aligned";
break;
+ case PRINT_WRAPPED:
+ return "wrapped";
+ break;
case PRINT_HTML:
return "html";
break;
popt->topt.format = PRINT_UNALIGNED;
else if (pg_strncasecmp("aligned", value, vallen) == 0)
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)
popt->topt.format = PRINT_HTML;
else if (pg_strncasecmp("latex", value, vallen) == 0)
popt->topt.format = PRINT_TROFF_MS;
else
{
- psql_error("\\pset: allowed formats are unaligned, aligned, html, latex, troff-ms\n");
+ psql_error("\\pset: allowed formats are unaligned, aligned, wrapped, html, latex, troff-ms\n");
return false;
}
printf(_("Output format is %s.\n"), _align2string(popt->topt.format));
}
+ /* 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;
+ }
+
+ if (!quiet)
+ printf(_("Line style is %s.\n"),
+ get_line_style(&popt->topt)->name);
+ }
+
/* set border style/width */
else if (strcmp(param, "border") == 0)
{
/* 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 (value)
+ popt->topt.expanded = ParseVariableBool(value);
+ else
+ popt->topt.expanded = !popt->topt.expanded;
if (!quiet)
printf(popt->topt.expanded
? _("Expanded display is on.\n")
/* locale-aware numeric output */
else if (strcmp(param, "numericlocale") == 0)
{
- popt->topt.numericLocale = !popt->topt.numericLocale;
+ if (value)
+ popt->topt.numericLocale = ParseVariableBool(value);
+ else
+ popt->topt.numericLocale = !popt->topt.numericLocale;
if (!quiet)
{
if (popt->topt.numericLocale)
/* toggle between full and tuples-only format */
else if (strcmp(param, "t") == 0 || strcmp(param, "tuples_only") == 0)
{
- popt->topt.tuples_only = !popt->topt.tuples_only;
+ if (value)
+ popt->topt.tuples_only = ParseVariableBool(value);
+ else
+ popt->topt.tuples_only = !popt->topt.tuples_only;
if (!quiet)
{
if (popt->topt.tuples_only)
{
if (value && pg_strcasecmp(value, "always") == 0)
popt->topt.pager = 2;
+ else if (value)
+ if (ParseVariableBool(value))
+ popt->topt.pager = 1;
+ else
+ popt->topt.pager = 0;
else if (popt->topt.pager == 1)
popt->topt.pager = 0;
else
/* disable "(x rows)" footer */
else if (strcmp(param, "footer") == 0)
{
- popt->default_footer = !popt->default_footer;
+ if (value)
+ popt->default_footer = ParseVariableBool(value);
+ else
+ popt->default_footer = !popt->default_footer;
if (!quiet)
{
if (popt->default_footer)
}
}
+ /* set border style/width */
+ else if (strcmp(param, "columns") == 0)
+ {
+ if (value)
+ popt->topt.columns = atoi(value);
+
+ if (!quiet)
+ printf(_("Target width for \"wrapped\" format is %d.\n"), popt->topt.columns);
+ }
+
else
{
psql_error("\\pset: unknown option: %s\n", param);
/* See EDITOR handling comment for an explaination */
"exec %s", shellName);
#else
- sprintf(sys,
/* See EDITOR handling comment for an explaination */
- "%s\"%s\"%s", SYSTEMQUOTE, shellName, SYSTEMQUOTE);
+ sprintf(sys, SYSTEMQUOTE "\"%s\"" SYSTEMQUOTE, shellName);
#endif
result = system(sys);
free(sys);
}
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(PGconn *conn, const char *desc, Oid *foid)
+{
+ bool result = true;
+ PQExpBuffer query;
+ PGresult *res;
+
+ query = createPQExpBuffer();
+ printfPQExpBuffer(query, "SELECT ");
+ appendStringLiteralConn(query, desc, conn);
+ appendPQExpBuffer(query, "::pg_catalog.%s::pg_catalog.oid",
+ strchr(desc, '(') ? "regprocedure" : "regproc");
+
+ res = PQexec(conn, 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(PGconn *conn, Oid oid, PQExpBuffer buf)
+{
+ bool result = true;
+ PQExpBuffer query;
+ PGresult *res;
+
+ query = createPQExpBuffer();
+ printfPQExpBuffer(query, "SELECT pg_catalog.pg_get_functiondef(%u)", oid);
+
+ res = PQexec(conn, 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
+ * 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);
+}