diff --git a/src/bin/psql/Makefile.in b/src/bin/psql/Makefile.in index 4e19ed1984..200a96ac7d 100644 --- a/src/bin/psql/Makefile.in +++ b/src/bin/psql/Makefile.in @@ -1,13 +1,13 @@ #------------------------------------------------------------------------- # -# Makefile.inc-- +# Makefile.in-- # Makefile for bin/psql # # Copyright (c) 1994, Regents of the University of California # # # IDENTIFICATION -# $Header: /cvsroot/pgsql/src/bin/psql/Attic/Makefile.in,v 1.15 1999/01/17 06:19:19 momjian Exp $ +# $Header: /cvsroot/pgsql/src/bin/psql/Attic/Makefile.in,v 1.16 1999/11/04 21:56:01 momjian Exp $ # #------------------------------------------------------------------------- @@ -28,7 +28,9 @@ ifdef MULTIBYTE CFLAGS+= $(MBFLAGS) endif -OBJS= psql.o stringutils.o @STRDUP@ @STRERROR2@ +OBJS=command.o common.o help.o input.o stringutils.o mainloop.o \ +copy.o startup.o prompt.o variables.o large_obj.o print.o describe.o \ +@STRDUP@ @STRERROR2@ all: submake psql @@ -38,6 +40,18 @@ psql: $(OBJS) $(LIBPQDIR)/libpq.a ../../utils/strdup.o: $(MAKE) -C ../../utils strdup.o +OBJS: + $(CC) $(CFLAGS) -c $< -o $@ + +help.o: sql_help.h + +ifneq ($(strip $(PERL)),) +sql_help.h: ../../../doc/src/sgml/ref/*.sgml create_help.pl + $(PERL) create_help.pl sql_help.h +else +sql_help.h: +endif + .PHONY: submake submake: $(MAKE) -C $(LIBPQDIR) libpq.a @@ -46,14 +60,18 @@ install: psql $(INSTALL) $(INSTL_EXE_OPTS) psql$(X) $(BINDIR)/psql$(X) depend dep: - $(CC) -MM $(CFLAGS) *.c >depend + $(CC) -MM -MG $(CFLAGS) *.c >depend clean: - rm -f psql$(X) $(OBJS) + rm -f psql$(X) $(OBJS) + +# Some people might get in trouble if they do a make clean and the +# sql_help.h is gone, for it needs the docs in the right place to be +# regenerated. -- (pe) + +distclean: clean + rm -f sql_help.h ifeq (depend,$(wildcard depend)) include depend endif - - - diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c new file mode 100644 index 0000000000..6bcf96c9cd --- /dev/null +++ b/src/bin/psql/command.c @@ -0,0 +1,1228 @@ +#include +#include +#include "command.h" + +#include +#include +#include +#include +#ifndef WIN32 +#include /* for umask() */ +#include /* for umask(), stat() */ +#include /* for geteuid(), getpid(), stat() */ +#endif +#include + +#include +#include + +#include "stringutils.h" +#include "mainloop.h" +#include "copy.h" +#include "help.h" +#include "settings.h" +#include "common.h" +#include "large_obj.h" +#include "print.h" +#include "describe.h" +#include "input.h" + +#ifdef WIN32 +#define popen(x,y) _popen(x,y) +#define pclose(x) _pclose(x) +#endif + + +/* functions for use in this file only */ + +static backslashResult +exec_command(const char * cmd, + char * const * options, + const char * options_string, + PQExpBuffer query_buf, + PsqlSettings * pset); + +static bool +do_edit(const char *filename_arg, PQExpBuffer query_buf); + +static char * +unescape(const char * source, PsqlSettings * pset); + +static bool +do_shell(const char *command); + + + +/*---------- + * HandleSlashCmds: + * + * Handles all the different commands that start with '\', + * ordinarily called by MainLoop(). + * + * 'line' is the current input line, which must start with a '\' + * (that is taken care of by MainLoop) + * + * 'query_buf' contains the query-so-far, which may be modified by + * execution of the backslash command (for example, \r clears it) + * query_buf can be NULL if there is no query-so-far. + * + * Returns a status code indicating what action is desired, see command.h. + *---------- + */ + +backslashResult +HandleSlashCmds(PsqlSettings *pset, + const char *line, + PQExpBuffer query_buf, + const char ** end_of_cmd) +{ + backslashResult status = CMD_SKIP_LINE; + char * my_line; + char * options[17] ={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; + char * token; + const char * options_string = NULL; + const char * cmd; + size_t blank_loc; + int i; + const char * continue_parse = NULL; /* tell the mainloop where the backslash command ended */ + + my_line = xstrdup(line); + + /* Find the first whitespace (or backslash) + line[blank_loc] will now be the whitespace character + or the \0 at the end */ + blank_loc = strcspn(my_line, " \t"); + + /* do we have an option string? */ + if (my_line[blank_loc] != '\0') { + options_string = &my_line[blank_loc+1]; + + my_line[blank_loc] = '\0'; + } + + if (options_string) { + char quote; + unsigned int pos; + options_string = &options_string[strspn(options_string, " \t")]; /* skip leading whitespace */ + + i = 0; + token = strtokx(options_string, " \t", "\"'`", '\\', "e, &pos); + + for (i = 0; token && i<16; i++) { + switch(quote) { + case '"': + options[i] = unescape(token, pset); + break; + case '\'': + options[i] = xstrdup(token); + break; + case '`': + { + bool error = false; + FILE * fd = NULL; + char * file = unescape(token, pset); + PQExpBufferData output; + char buf[512]; + size_t result; + + fd = popen(file, "r"); + if (!fd) { + perror(file); + error = true; + } + + if (!error) { + initPQExpBuffer(&output); + + do { + result = fread(buf, 1, 512, fd); + if (ferror(fd)) { + perror(file); + error = true; + break; + } + appendBinaryPQExpBuffer(&output, buf, result); + } while (!feof(fd)); + appendPQExpBufferChar(&output, '\0'); + + if (pclose(fd) == -1) { + perror(file); + error = true; + } + } + + if (!error) { + if (output.data[strlen(output.data)-1] == '\n') + output.data[strlen(output.data)-1] = '\0'; + } + + free(file); + if (!error) + options[i] = output.data; + else { + options[i] = xstrdup(""); + termPQExpBuffer(&output); + } + break; + } + case 0: + default: + if (token[0] == '\\') + continue_parse = options_string + pos; + else if (token[0] == '$') + options[i] = xstrdup(interpolate_var(token+1, pset)); + else + options[i] = xstrdup(token); + break; + } + + if (continue_parse) + break; + + token = strtokx(NULL, " \t", "\"'`", '\\', "e, &pos); + } + } + + cmd = my_line; + + status = exec_command(cmd, options, options_string, query_buf, pset); + + if (status == CMD_UNKNOWN) { + /* If the command was not recognized, try inserting a space after + the first letter and call again. The one letter commands + allow arguments to start immediately after the command, + but that is no longer encouraged. */ + const char * new_options[17]; + char new_cmd[2]; + int i; + + for (i=1; i<17; i++) + new_options[i] = options[i-1]; + new_options[0] = cmd+1; + + new_cmd[0] = cmd[0]; + new_cmd[1] = '\0'; + + status = exec_command(new_cmd, (char * const *)new_options, my_line+2, query_buf, pset); + } + + if (status == CMD_UNKNOWN) { + fprintf(stderr, "Unrecognized command: \\%s. Try \\? for help.\n", cmd); + status = CMD_ERROR; + } + + if (continue_parse && *(continue_parse+1) == '\\') + continue_parse+=2; + + + if (end_of_cmd) { + if (continue_parse) + *end_of_cmd = line + (continue_parse - my_line); + else + *end_of_cmd = NULL; + } + + /* clean up */ + for (i = 0; i<16 && options[i]; i++) + free(options[i]); + + free(my_line); + + return status; +} + + + + +static backslashResult +exec_command(const char * cmd, + char * const * options, + const char * options_string, + PQExpBuffer query_buf, + PsqlSettings * pset) +{ + bool success = true; /* indicate here if the command ran ok or failed */ + bool quiet = GetVariableBool(pset->vars, "quiet"); + + backslashResult status = CMD_SKIP_LINE; + + + /* \a -- toggle field alignment + This is deprecated and makes no sense, but we keep it around. */ + if (strcmp(cmd, "a") == 0) { + if (pset->popt.topt.format != PRINT_ALIGNED) + success = do_pset("format", "aligned", &pset->popt, quiet); + else + success = do_pset("format", "unaligned", &pset->popt, quiet); + } + + + /* \C -- override table title + (formerly change HTML caption) This is deprecated. */ + else if (strcmp(cmd, "C") == 0) + success = do_pset("title", options[0], &pset->popt, quiet); + + + + /* \c or \connect -- connect to new database or as different user + * + * \c foo bar : connect to db "foo" as user "bar" + * \c foo [-] : connect to db "foo" as current user + * \c - bar : connect to current db as user "bar" + * \c : connect to default db as default user + */ + else if (strcmp(cmd, "c")==0 || strcmp(cmd, "connect")==0) + { + if (options[1]) + /* gave username */ + success = do_connect(options[0], options[1], pset); + else { + if (options[0]) + /* gave database name */ + success = do_connect(options[0], "", pset); /* empty string is same username as before, + NULL would mean libpq default */ + else + /* connect to default db as default user */ + success = do_connect(NULL, NULL, pset); + } + } + + + /* \copy */ + else if (strcmp(cmd, "copy") == 0) + success = do_copy(options_string, pset); + + /* \copyright */ + else if (strcmp(cmd, "copyright") == 0) + print_copyright(); + + /* \d* commands */ + else if (cmd[0] == 'd') { + switch(cmd[1]) { + case '\0': + if (options[0]) + success = describeTableDetails(options[0], pset); + else + success = listTables("tvs", NULL, pset); /* standard listing of interesting things */ + break; + case 'a': + success = describeAggregates(options[0], pset); + break; + case 'd': + success = objectDescription(options[0], pset); + break; + case 'f': + success = describeFunctions(options[0], pset); + break; + case 'l': + success = do_lo_list(pset); + break; + case 'o': + success = describeOperators(options[0], pset); + break; + case 'p': + success = permissionsList(options[0], pset); + break; + case 'T': + success = describeTypes(options[0], pset); + break; + case 't': case 'v': case 'i': case 's': case 'S': + if (cmd[1] == 'S' && cmd[2] == '\0') + success = listTables("Stvs", NULL, pset); + else + success = listTables(&cmd[1], options[0], pset); + break; + default: + status = CMD_UNKNOWN; + } + } + + + /* \e or \edit -- edit the current query buffer (or a file and make it the + query buffer */ + else if (strcmp(cmd, "e") == 0 || strcmp(cmd, "edit") == 0) + status = do_edit(options[0], query_buf) ? CMD_NEWEDIT : CMD_ERROR; + + + /* \echo */ + else if (strcmp(cmd, "echo") == 0) { + int i; + for (i=0; i<16 && options[i]; i++) + fputs(options[i], stdout); + fputs("\n", stdout); + } + + /* \f -- change field separator + (This is deprecated in favour of \pset.) */ + else if (strcmp(cmd, "f") == 0) + success = do_pset("fieldsep", options[0], &pset->popt, quiet); + + + /* \g means send query */ + else if (strcmp(cmd, "g") == 0) { + if (!options[0]) + pset->gfname = NULL; + else + pset->gfname = xstrdup(options[0]); + status = CMD_SEND; + } + + /* help */ + else if (strcmp(cmd, "h") == 0 || strcmp(cmd, "help") == 0) + helpSQL(options_string); + + + /* HTML mode */ + else if (strcmp(cmd, "H") == 0 || strcmp(cmd, "html") == 0) + success = do_pset("format", "html", &pset->popt, quiet); + + + /* \i is include file */ + else if (strcmp(cmd, "i") == 0 || strcmp(cmd, "include") == 0) + { + if (!options[0]) { + fputs("Usage: \\i \n", stderr); + success = false; + } + else + success = process_file(options[0], pset); + } + + /* \l is list databases */ + else if (strcmp(cmd, "l") == 0 || strcmp(cmd, "list") == 0) + success = listAllDbs(pset); + + + /* large object things */ + else if (strncmp(cmd, "lo_", 3)==0) { + if (strcmp(cmd+3, "export") == 0) { + if (!options[1]) { + fputs("Usage: \\lo_export \n", stderr); + success = false; + } + else + success = do_lo_export(pset, options[0], options[1]); + } + + else if (strcmp(cmd+3, "import") == 0) { + if (!options[0]) { + fputs("Usage: \\lo_import []\n", stderr); + success = false; + } + else + success = do_lo_import(pset, options[0], options[1]); + } + + else if (strcmp(cmd+3, "list") == 0) + success = do_lo_list(pset); + + else if (strcmp(cmd+3, "unlink") == 0) { + if (!options[0]) { + fputs("Usage: \\lo_unlink \n", stderr); + success = false; + } + else + success = do_lo_unlink(pset, options[0]); + } + + else + status = CMD_UNKNOWN; + } + + /* \o -- set query output */ + else if (strcmp(cmd, "o") == 0 || strcmp(cmd, "out") == 0) + success = setQFout(options[0], pset); + + + /* \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 (!GetVariableBool(pset->vars, "quiet")) + puts("Query buffer is empty."); + } + + /* \pset -- set printing parameters */ + else if (strcmp(cmd, "pset")==0) { + if (!options[0]) { + fputs("Usage: \\pset []\n", stderr); + success = false; + } + else + success = do_pset(options[0], options[1], &pset->popt, quiet); + } + + /* \q or \quit */ + else if (strcmp(cmd, "q")==0 || strcmp(cmd, "quit")==0) + status = CMD_TERMINATE; + + /* \qecho */ + else if (strcmp(cmd, "qecho") == 0) { + int i; + for (i=0; i<16 && options[i]; i++) + fputs(options[i], pset->queryFout); + fputs("\n", pset->queryFout); + } + + /* reset(clear) the buffer */ + else if (strcmp(cmd, "r")==0 || strcmp(cmd, "reset")==0) + { + resetPQExpBuffer(query_buf); + if (!quiet) puts("Query buffer reset (cleared)."); + } + + + /* \s save history in a file or show it on the screen */ + else if (strcmp(cmd, "s")==0) + { + const char * fname; + if (!options[0]) + fname = "/dev/tty"; + else + fname = options[0]; + + success = saveHistory(fname); + + if (success && !quiet && options[0]) + printf("Wrote history to %s.\n", fname); + } + + + /* \set -- generalized set option command */ + else if (strcmp(cmd, "set")==0) + { + if (!options[0]) { + /* list all variables */ + /* (This is in utter violation of the GetVariable abstraction, but + I have not dreamt up a better way.) */ + struct _variable * ptr; + for (ptr = pset->vars; ptr->next; ptr = ptr->next) + fprintf(stdout, "%s = '%s'\n", ptr->next->name, ptr->next->value); + success = true; + } + else { + if (!SetVariable(pset->vars, options[0], options[1])) { + fprintf(stderr, "Set variable failed.\n"); + success = false; + } + } + } + + /* \t -- turn off headers and row count */ + else if (strcmp(cmd, "t")==0) + success = do_pset("tuples_only", NULL, &pset->popt, quiet); + + + /* \T -- define html attributes */ + else if (strcmp(cmd, "T")==0) + success = do_pset("tableattr", options[0], &pset->popt, quiet); + + + /* \w -- write query buffer to file */ + else if (strcmp(cmd, "w") == 0 || strcmp(cmd, "write") == 0 ) + { + FILE *fd = NULL; + bool pipe = false; + + if (!options[0]) { + fprintf(stderr, "Usage \\%s \n", cmd); + success = false; + } + else { + if (options[0][0] == '|') { + pipe = true; +#ifndef __CYGWIN32__ + fd = popen(&options[0][1], "w"); +#else + fd = popen(&options[0][1], "wb"); +#endif + } + else { +#ifndef __CYGWIN32__ + fd = fopen(options[0], "w"); +#else + fd = fopen(options[0], "wb"); +#endif + } + + if (!fd) { + perror(options[0]); + success = false; + } + } + + if (fd) { + int result; + + if (query_buf && query_buf->len > 0) + fprintf(fd, "%s\n", query_buf->data); + + if (pipe) + result = pclose(fd); + else + result = fclose(fd); + + if (result == EOF) { + perror("close"); + success = false; + } + } + } + + /* \x -- toggle expanded table representation */ + else if (strcmp(cmd, "x")==0) + success = do_pset("expanded", NULL, &pset->popt, quiet); + + + /* list table rights (grant/revoke) */ + else if (strcmp(cmd, "z")==0) + success = permissionsList(options[0], pset); + + + else if (strcmp(cmd, "!")==0) + success = do_shell(options_string); + + else if (strcmp(cmd, "?")==0) + slashUsage(pset); + + +#ifdef NOT_USED + /* These commands don't do anything. I just use them to test the parser. */ + else if (strcmp(cmd, "void")==0 || strcmp(cmd, "#")==0) + { + int i; + fprintf(stderr, "+ optline = |%s|\n", options_string); + for(i=0; options[i]; i++) + fprintf(stderr, "+ opt%d = |%s|\n", i, options[i]); + } +#endif + + else { + status = CMD_UNKNOWN; + } + + if (!success) status = CMD_ERROR; + return status; +} + + + +/* + * unescape + * + * Replaces \n, \t, and the like. + * Also interpolates ${variables}. + * + * The return value is malloc()'ed. + */ +static char * +unescape(const char * source, PsqlSettings * pset) +{ + unsigned char *p; + bool esc = false; /* Last character we saw was the + escape character */ + char *destination, *tmp; + size_t length; + +#ifdef USE_ASSERT_CHECKING + assert(source); +#endif + + length = strlen(source)+1; + + tmp = destination = (char *) malloc(length); + if (!tmp) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + for (p = (char *) source; *p; p += PQmblen(p)) { + if (esc) { + char c; + + switch (*p) + { + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case 'f': + c = '\f'; + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + long int l; + char * end; + l = strtol(p, &end, 0); + c = l; + p = end-1; + break; + } + default: + c = *p; + } + *tmp++ = c; + esc = false; + } + + else if (*p == '\\') { + esc = true; + } + + else if (*p == '$') + { + if (*(p+1) == '{') { + unsigned int len; + char *copy; + const char *value; + void * new; + len = strcspn(p+2, "}"); + copy = xstrdup(p+2); + copy[len] = '\0'; + value = interpolate_var(copy, pset); + + length += strlen(value) - (len+3); + new = realloc(destination, length); + if (!new) { + perror("realloc"); + exit(EXIT_FAILURE); + } + tmp = new + (tmp - destination); + destination = new; + + strcpy(tmp, value); + tmp += strlen(value); + p += len + 2; + free(copy); + } + else { + *tmp++ = '$'; + } + } + + else { + *tmp++ = *p; + esc = false; + } + } + + *tmp = '\0'; + return destination; +} + + + + +/* do_connect + * -- handler for \connect + * + * Connects to a database (new_dbname) as a certain user (new_user). + * The new user can be NULL. A db name of "-" is the same as the old one. + * (That is, the one currently in pset. But pset->db can also be NULL. A NULL + * dbname is handled by libpq.) + * Returns true if all ok, false if the new connection couldn't be established + * but the old one was set back. Otherwise it terminates the program. + */ +bool +do_connect(const char *new_dbname, const char *new_user, PsqlSettings * pset) +{ + PGconn *oldconn = pset->db; + const char *dbparam = NULL; + const char *userparam = NULL; + char *pwparam = NULL; + char * prompted_password = NULL; + char * prompted_user = NULL; + bool need_pass; + bool success = false; + + /* If dbname is "-" then use old name, else new one (even if NULL) */ + if (new_dbname && PQdb(oldconn) && (strcmp(new_dbname, "-") == 0 || strcmp(new_dbname, PQdb(oldconn))==0)) + dbparam = PQdb(oldconn); + else + dbparam = new_dbname; + + /* If user is "" or "-" then use the old one */ + if ( new_user && PQuser(oldconn) && ( strcmp(new_user, "")==0 || strcmp(new_user, "-")==0 || strcmp(new_user, PQuser(oldconn))==0 )) { + userparam = PQuser(oldconn); + } + /* If username is "?" then prompt */ + else if (new_user && strcmp(new_user, "?")==0) + userparam = prompted_user = simple_prompt("Username: ", 100, true); /* save for free() */ + else + userparam = new_user; + + /* need to prompt for password? */ + if (pset->getPassword) + pwparam = prompted_password = simple_prompt("Password: ", 100, false); /* need to save for free() */ + + /* Use old password if no new one given (if you didn't have an old one, fine) */ + if (!pwparam) + pwparam = PQpass(oldconn); + + +#ifdef MULTIBYTE + /* + * PGCLIENTENCODING may be set by the previous connection. if a + * user does not explicitly set PGCLIENTENCODING, we should + * discard PGCLIENTENCODING so that libpq could get the backend + * encoding as the default PGCLIENTENCODING value. -- 1998/12/12 + * Tatsuo Ishii + */ + + if (!pset->has_client_encoding) + putenv("PGCLIENTENCODING="); +#endif + + do { + need_pass = false; + pset->db = PQsetdbLogin(PQhost(oldconn), PQport(oldconn), + NULL, NULL, dbparam, userparam, pwparam); + + if (PQstatus(pset->db)==CONNECTION_BAD && + strcmp(PQerrorMessage(pset->db), "fe_sendauth: no password supplied\n")==0) { + need_pass = true; + free(prompted_password); + prompted_password = NULL; + pwparam = prompted_password = simple_prompt("Password: ", 100, false); + } + } while (need_pass); + + free(prompted_password); + free(prompted_user); + + /* If connection failed, try at least keep the old one. + That's probably more convenient than just kicking you out of the + program. */ + if (!pset->db || PQstatus(pset->db) == CONNECTION_BAD) + { + fprintf(stderr, "Could not establish database connection.\n%s", PQerrorMessage(pset->db)); + PQfinish(pset->db); + if (!oldconn || !pset->cur_cmd_interactive) { /* we don't want unpredictable things to happen + in scripting mode */ + fputs("Terminating.\n", stderr); + if (oldconn) + PQfinish(oldconn); + pset->db = NULL; + } + else { + fputs("Keeping old connection.\n", stderr); + pset->db = oldconn; + } + } + else { + if (!GetVariable(pset->vars, "quiet")) { + 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)); + } + + if (oldconn) + PQfinish(oldconn); + + success = true; + } + + return success; +} + + + +/* + * 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) +{ + char *editorName; + char *sys; + int result; + +#ifdef USE_ASSERT_CHECKING + assert(fname); +#else + if (!fname) return false; +#endif + + /* Find an editor to use */ + editorName = getenv("PSQL_EDITOR"); + if (!editorName) + editorName = getenv("EDITOR"); + if (!editorName) + editorName = getenv("VISUAL"); + if (!editorName) + editorName = DEFAULT_EDITOR; + + sys = malloc(strlen(editorName) + strlen(fname) + 32 + 1); + if (!sys) + return false; + sprintf(sys, "exec %s %s", editorName, fname); + result = system(sys); + if (result == -1 || result == 127) + perror(sys); + free(sys); + + return result==0; +} + + +/* call this one */ +static bool +do_edit(const char *filename_arg, PQExpBuffer query_buf) +{ + char fnametmp[64]; + FILE * stream; + const char *fname; + bool error = false; +#ifndef WIN32 + struct stat before, after; +#endif + +#ifdef USE_ASSERT_CHECKING + assert(query_buf); +#else + if (!query_buf) return false; +#endif + + + if (filename_arg) + fname = filename_arg; + + else { + /* make a temp file to edit */ +#ifndef WIN32 + mode_t oldumask; + + sprintf(fnametmp, "/tmp/psql.edit.%ld.%ld", (long) geteuid(), (long) getpid()); +#else + GetTempFileName(".", "psql", 0, fnametmp); +#endif + fname = (const char *)fnametmp; + +#ifndef WIN32 + oldumask = umask(0177); +#endif + stream = fopen(fname, "w"); +#ifndef WIN32 + umask(oldumask); +#endif + + if (!stream) { + perror(fname); + 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) { + perror(fname); + fclose(stream); + remove(fname); + error = true; + } + else + fclose(stream); + } + } + +#ifndef WIN32 + if (!error && stat(fname, &before) != 0) { + perror(fname); + error = true; + } +#endif + + /* call editor */ + if (!error) + error = !editFile(fname); + +#ifndef WIN32 + if (!error && stat(fname, &after) !=0) { + perror(fname); + error = true; + } + + if (!error && before.st_mtime != after.st_mtime) { +#else + if (!error) { +#endif + stream = fopen(fname, "r"); + if (!stream) { + perror(fname); + error = true; + } + else { + /* read file back in */ + char line[1024]; + size_t result; + + resetPQExpBuffer(query_buf); + do { + result = fread(line, 1, 1024, stream); + if (ferror(stream)) { + perror(fname); + error = true; + break; + } + appendBinaryPQExpBuffer(query_buf, line, result); + } while (!feof(stream)); + appendPQExpBufferChar(query_buf, '\0'); + + fclose(stream); + } + + /* remove temp file */ + if (!filename_arg) + remove(fname); + } + + return !error; +} + + + +/* + * process_file + * + * Read commands from filename and then them to the main processing loop + * Handler for \i, but can be used for other things as well. + */ +bool +process_file(const char *filename, PsqlSettings *pset) +{ + FILE *fd; + int result; + + if (!filename) + return false; + +#ifdef __CYGWIN32__ + fd = fopen(filename, "rb"); +#else + fd = fopen(filename, "r"); +#endif + + if (!fd) { + perror(filename); + return false; + } + + result = MainLoop(pset, fd); + fclose(fd); + return (result == EXIT_SUCCESS); +} + + + +/* + * do_pset + * + */ +static const char * +_align2string(enum printFormat in) +{ + switch (in) { + case PRINT_NOTHING: + return "nothing"; + break; + case PRINT_UNALIGNED: + return "unaligned"; + break; + case PRINT_ALIGNED: + return "aligned"; + break; + case PRINT_HTML: + return "html"; + break; + case PRINT_LATEX: + return "latex"; + break; + } + return "unknown"; +} + + +bool +do_pset(const char * param, const char * value, printQueryOpt * popt, bool quiet) +{ + size_t vallen = 0; +#ifdef USE_ASSERT_CHECKING + assert(param); +#else + if (!param) return false; +#endif + + if (value) + vallen = strlen(value); + + /* set format */ + if (strcmp(param, "format")==0) { + if (!value) + ; + else if (strncasecmp("unaligned", value, vallen)==0) + popt->topt.format = PRINT_UNALIGNED; + else if (strncasecmp("aligned", value, vallen)==0) + popt->topt.format = PRINT_ALIGNED; + else if (strncasecmp("html", value, vallen)==0) + popt->topt.format = PRINT_HTML; + else if (strncasecmp("latex", value, vallen)==0) + popt->topt.format = PRINT_LATEX; + else { + fprintf(stderr, "Allowed formats are unaligned, aligned, html, latex.\n"); + return false; + } + + if (!quiet) + printf("Output format is %s.\n", _align2string(popt->topt.format)); + } + + /* set border style/width */ + else if (strcmp(param, "border")==0) { + if (value) + popt->topt.border = atoi(value); + + if (!quiet) + printf("Border style is %d.\n", popt->topt.border); + } + + /* set expanded/vertical mode */ + else if (strcmp(param, "x")==0 || strcmp(param, "expanded")==0 || strcmp(param, "vertical")==0) { + popt->topt.expanded = !popt->topt.expanded; + if (!quiet) + printf("Expanded display is %s.\n", popt->topt.expanded ? "on" : "off"); + } + + /* null display */ + else if (strcmp(param, "null")==0) { + if (value) { + free(popt->nullPrint); + popt->nullPrint = xstrdup(value); + } + if (!quiet) + printf("Null display is \"%s\".\n", popt->nullPrint ? popt->nullPrint : ""); + } + + /* field separator for unaligned text */ + else if (strcmp(param, "fieldsep")==0) { + if (value) { + free(popt->topt.fieldSep); + popt->topt.fieldSep = xstrdup(value); + } + if (!quiet) + printf("Field separator is \"%s\".\n", popt->topt.fieldSep); + } + + /* toggle between full and barebones format */ + else if (strcmp(param, "t")==0 || strcmp(param, "tuples_only")==0) { + popt->topt.tuples_only = !popt->topt.tuples_only; + if (!quiet) { + if (popt->topt.tuples_only) + puts("Showing only tuples."); + else + puts("Tuples only is off."); + } + } + + /* set title override */ + else if (strcmp(param, "title")==0) { + free(popt->title); + if (!value) + popt->title = NULL; + else + popt->title = xstrdup(value); + + if (!quiet) { + if (popt->title) + printf("Title is \"%s\".\n", popt->title); + else + printf("Title is unset.\n"); + } + } + + /* set HTML table tag options */ + else if (strcmp(param, "T")==0 || strcmp(param, "tableattr")==0) { + free(popt->topt.tableAttr); + if (!value) + popt->topt.tableAttr = NULL; + else + popt->topt.tableAttr = xstrdup(value); + + if (!quiet) { + if (popt->topt.tableAttr) + printf("Table attribute is \"%s\".\n", popt->topt.tableAttr); + else + printf("Table attributes unset.\n"); + } + } + + /* toggle use of pager */ + else if (strcmp(param, "pager")==0) { + popt->topt.pager = !popt->topt.pager; + if (!quiet) { + if (popt->topt.pager) + puts("Using pager is on."); + else + puts("Using pager is off."); + } + } + + + else { + fprintf(stderr, "Unknown option: %s\n", param); + return false; + } + + return true; +} + + + +#define DEFAULT_SHELL "/bin/sh" + +static bool +do_shell(const char *command) +{ + int result; + + if (!command) { + char *sys; + char *shellName; + + shellName = getenv("SHELL"); + if (shellName == NULL) + shellName = DEFAULT_SHELL; + + sys = malloc(strlen(shellName) + 16); + if (!sys) + return false; + sprintf(sys, "exec %s", shellName); + result = system(sys); + free(sys); + } + else + result = system(command); + + if (result==127 || result==-1) { + perror("system"); + return false; + } + return true; +} diff --git a/src/bin/psql/command.h b/src/bin/psql/command.h new file mode 100644 index 0000000000..eeaf0859ba --- /dev/null +++ b/src/bin/psql/command.h @@ -0,0 +1,49 @@ +#ifndef COMMAND_H +#define COMMAND_H + +#include +#include + +#include + +#include "settings.h" +#include "print.h" + + + +typedef enum _backslashResult { + CMD_UNKNOWN = 0, /* not done parsing yet (internal only) */ + CMD_SEND, /* query complete; send off */ + CMD_SKIP_LINE, /* keep building query */ + CMD_TERMINATE, /* quit program */ + CMD_NEWEDIT, /* query buffer was changed (e.g., via \e) */ + CMD_ERROR /* the execution of the backslash command resulted + in an error */ +} backslashResult; + + + +backslashResult +HandleSlashCmds(PsqlSettings *pset, + const char *line, + PQExpBuffer query_buf, + const char ** end_of_cmd); + +bool +do_connect(const char *new_dbname, + const char *new_user, + PsqlSettings *pset); + +bool +process_file(const char *filename, + PsqlSettings *pset); + + +bool +do_pset(const char * param, + const char * value, + printQueryOpt * popt, + bool quiet); + + +#endif diff --git a/src/bin/psql/common.c b/src/bin/psql/common.c new file mode 100644 index 0000000000..71df7bd098 --- /dev/null +++ b/src/bin/psql/common.c @@ -0,0 +1,518 @@ +#include +#include +#include "common.h" + +#include +#ifdef HAVE_TERMIOS_H +#include +#endif +#include +#include +#ifndef HAVE_STRDUP +#include +#endif +#include +#include +#ifndef WIN32 +#include /* for write() */ +#endif + +#include +#include +#include + +#include "settings.h" +#include "variables.h" +#include "copy.h" +#include "prompt.h" +#include "print.h" + +#ifdef WIN32 +#define popen(x,y) _popen(x,y) +#define pclose(x) _pclose(x) +#endif + + + +/* xstrdup() + * + * "Safe" wrapper around strdup() + * (Using this also avoids writing #ifdef HAVE_STRDUP in every file :) + */ +char * xstrdup(const char * string) +{ + char * tmp; + if (!string) { + fprintf(stderr, "xstrdup: Cannot duplicate null pointer.\n"); + exit(EXIT_FAILURE); + } + tmp = strdup(string); + if (!tmp) { + perror("strdup"); + exit(EXIT_FAILURE); + } + return tmp; +} + + + +/* + * setQFout + * -- handler for -o command line option and \o command + * + * Tries to open file fname (or pipe if fname starts with '|') + * and stores the file handle in pset) + * Upon failure, sets stdout and returns false. + */ +bool +setQFout(const char *fname, PsqlSettings *pset) +{ + bool status = true; + +#ifdef USE_ASSERT_CHECKING + assert(pset); +#else + if (!pset) return false; +#endif + + /* Close old file/pipe */ + if (pset->queryFout && pset->queryFout != stdout && pset->queryFout != stderr) + { + if (pset->queryFoutPipe) + pclose(pset->queryFout); + else + fclose(pset->queryFout); + } + + /* If no filename, set stdout */ + if (!fname || fname[0]=='\0') + { + pset->queryFout = stdout; + pset->queryFoutPipe = false; + } + else if (*fname == '|') + { + const char * pipename = fname+1; + + +#ifndef __CYGWIN32__ + pset->queryFout = popen(pipename, "w"); +#else + pset->queryFout = popen(pipename, "wb"); +#endif + pset->queryFoutPipe = true; + } + else + { +#ifndef __CYGWIN32__ + pset->queryFout = fopen(fname, "w"); +#else + pset->queryFout = fopen(fname, "wb"); +#endif + pset->queryFoutPipe = false; + } + + if (!pset->queryFout) + { + perror(fname); + pset->queryFout = stdout; + pset->queryFoutPipe = false; + status = false; + } + + /* Direct signals */ + if (pset->queryFoutPipe) + pqsignal(SIGPIPE, SIG_IGN); + else + pqsignal(SIGPIPE, SIG_DFL); + + return status; +} + + + +/* + * simple_prompt + * + * Generalized function especially intended for reading in usernames and + * password interactively. Reads from stdin. + * + * prompt: The prompt to print + * maxlen: How many characters to accept + * echo: Set to false if you want to hide what is entered (for passwords) + * + * Returns a malloc()'ed string with the input (w/o trailing newline). + */ +char * +simple_prompt(const char *prompt, int maxlen, bool echo) +{ + int length; + char * destination; + +#ifdef HAVE_TERMIOS_H + struct termios t_orig, t; +#endif + + destination = (char *) malloc(maxlen+2); + if (!destination) + return NULL; + if (prompt) fputs(prompt, stdout); + +#ifdef HAVE_TERMIOS_H + if (!echo) + { + tcgetattr(0, &t); + t_orig = t; + t.c_lflag &= ~ECHO; + tcsetattr(0, TCSADRAIN, &t); + } +#endif + + fgets(destination, maxlen, stdin); + +#ifdef HAVE_TERMIOS_H + if (!echo) { + tcsetattr(0, TCSADRAIN, &t_orig); + puts(""); + } +#endif + + length = strlen(destination); + if (length > 0 && destination[length - 1] != '\n') { + /* eat rest of the line */ + char buf[512]; + do { + fgets(buf, 512, stdin); + } while (buf[strlen(buf) - 1] != '\n'); + } + + if (length > 0 && destination[length - 1] == '\n') + /* remove trailing newline */ + destination[length - 1] = '\0'; + + return destination; +} + + + +/* + * interpolate_var() + * + * If the variable is a regular psql variable, just return its value. + * If it's a magic variable, return that value. + * + * This function only returns NULL if you feed in NULL. Otherwise it's ready for + * immediate consumption. + */ +const char * +interpolate_var(const char * name, PsqlSettings * pset) +{ + const char * var; + +#ifdef USE_ASSERT_CHECKING + assert(name); + assert(pset); +#else + if (!name || !pset) return NULL; +#endif + + if (strspn(name, VALID_VARIABLE_CHARS) == strlen(name)) { + var = GetVariable(pset->vars, name); + if (var) + return var; + else + return ""; + } + + /* otherwise return magic variable */ + /* (by convention these should be capitalized (but not all caps), to not be + shadowed by regular vars or to shadow env vars) */ + if (strcmp(name, "Version")==0) + return PG_VERSION_STR; + + if (strcmp(name, "Database")==0) { + if (PQdb(pset->db)) + return PQdb(pset->db); + else + return ""; + } + + if (strcmp(name, "User")==0) { + if (PQuser(pset->db)) + return PQuser(pset->db); + else + return ""; + } + + if (strcmp(name, "Host")==0) { + if (PQhost(pset->db)) + return PQhost(pset->db); + else + return ""; + } + + if (strcmp(name, "Port")==0) { + if (PQport(pset->db)) + return PQport(pset->db); + else + return ""; + } + + /* env vars (if env vars are all caps there should be no prob, otherwise + you're on your own */ + + if ((var = getenv(name))) + return var; + + return ""; +} + + + +/* + * Code to support command cancellation. + * + * If interactive, we enable a SIGINT signal catcher before we start a + * query that sends a cancel request to the backend. + * Note that sending the cancel directly from the signal handler is safe + * only because PQrequestCancel is carefully written to make it so. We + * have to be very careful what else we do in the signal handler. + * + * Writing on stderr is potentially dangerous, if the signal interrupted + * some stdio operation on stderr. On Unix we can avoid trouble by using + * write() instead; on Windows that's probably not workable, but we can + * at least avoid trusting printf by using the more primitive fputs(). + */ + +PGconn * cancelConn; + +#ifdef WIN32 +#define safe_write_stderr(String) fputs(s, stderr) +#else +#define safe_write_stderr(String) write(fileno(stderr), String, strlen(String)) +#endif + + +static void +handle_sigint(SIGNAL_ARGS) +{ + /* accept signal if no connection */ + if (cancelConn == NULL) + exit(1); + /* Try to send cancel request */ + if (PQrequestCancel(cancelConn)) + safe_write_stderr("\nCANCEL request sent\n"); + else { + safe_write_stderr("\nCould not send cancel request: "); + safe_write_stderr(PQerrorMessage(cancelConn)); + } +} + + + +/* + * PSQLexec + * + * This is the way to send "backdoor" queries (those not directly entered + * by the user). It is subject to -E (echo_secret) but not -e (echo). + */ +PGresult * +PSQLexec(PsqlSettings *pset, const char *query) +{ + PGresult *res; + const char * var; + + if (!pset->db) { + fputs("You are not currently connected to a database.\n", stderr); + return NULL; + } + + var = GetVariable(pset->vars, "echo_secret"); + if (var) { + printf("********* QUERY *********\n%s\n*************************\n\n", query); + fflush(stdout); + } + + if (var && strcmp(var, "noexec")==0) + return NULL; + + cancelConn = pset->db; + pqsignal(SIGINT, handle_sigint); /* control-C => cancel */ + + res = PQexec(pset->db, query); + + pqsignal(SIGINT, SIG_DFL); /* no control-C is back to normal */ + + if (PQstatus(pset->db) == CONNECTION_BAD) + { + fputs("The connection to the server was lost. Attempting reset: ", stderr); + PQreset(pset->db); + if (PQstatus(pset->db) == CONNECTION_BAD) { + fputs("Failed.\n", stderr); + PQfinish(pset->db); + PQclear(res); + pset->db = NULL; + return NULL; + } + else + fputs("Succeeded.\n", stderr); + } + + if (res && (PQresultStatus(res) == PGRES_COMMAND_OK || + PQresultStatus(res) == PGRES_TUPLES_OK || + PQresultStatus(res) == PGRES_COPY_IN || + PQresultStatus(res) == PGRES_COPY_OUT) + ) + return res; + else { + fprintf(stderr, "%s", PQerrorMessage(pset->db)); + PQclear(res); + return NULL; + } +} + + + +/* + * SendQuery: send the query string to the backend + * (and print out results) + * + * Note: This is the "front door" way to send a query. That is, use it to + * send queries actually entered by the user. These queries will be subject to + * single step mode. + * To send "back door" queries (generated by slash commands, etc.) in a + * controlled way, use PSQLexec(). + * + * Returns true if the query executed successfully, false otherwise. + */ +bool +SendQuery(PsqlSettings *pset, const char *query) +{ + bool success = false; + PGresult *results; + PGnotify *notify; + + if (!pset->db) { + fputs("You are not currently connected to a database.\n", stderr); + return false; + } + + if (GetVariableBool(pset->vars, "singlestep")) { + char buf[3]; + fprintf(stdout, "***(Single step mode: Verify query)*********************************************\n" + "QUERY: %s\n" + "***(press return to proceed or enter x and return to cancel)********************\n", + query); + fflush(stdout); + fgets(buf, 3, stdin); + if (buf[0]=='x') + return false; + fflush(stdin); + } + + cancelConn = pset->db; + pqsignal(SIGINT, handle_sigint); + + results = PQexec(pset->db, query); + + pqsignal(SIGINT, SIG_DFL); + + if (results == NULL) + { + fputs(PQerrorMessage(pset->db), pset->queryFout); + success = false; + } + else + { + switch (PQresultStatus(results)) + { + case PGRES_TUPLES_OK: + if (pset->gfname) + { + PsqlSettings settings_copy = *pset; + + settings_copy.queryFout = stdout; + if (!setQFout(pset->gfname, &settings_copy)) { + success = false; + break; + } + + printQuery(results, &settings_copy.popt, settings_copy.queryFout); + + /* close file/pipe */ + setQFout(NULL, &settings_copy); + + free(pset->gfname); + pset->gfname = NULL; + + success = true; + break; + } + else + { + success = true; + printQuery(results, &pset->popt, pset->queryFout); + fflush(pset->queryFout); + } + break; + case PGRES_EMPTY_QUERY: + success = true; + break; + case PGRES_COMMAND_OK: + success = true; + fprintf(pset->queryFout, "%s\n", PQcmdStatus(results)); + break; + + case PGRES_COPY_OUT: + if (pset->cur_cmd_interactive && !GetVariable(pset->vars, "quiet")) + puts("Copy command returns:"); + + success = handleCopyOut(pset->db, pset->queryFout); + break; + + case PGRES_COPY_IN: + if (pset->cur_cmd_interactive && !GetVariable(pset->vars, "quiet")) + puts("Enter data to be copied followed by a newline.\n" + "End with a backslash and a period on a line by itself."); + + success = handleCopyIn(pset->db, pset->cur_cmd_source, + pset->cur_cmd_interactive ? get_prompt(pset, PROMPT_COPY) : NULL); + break; + + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + case PGRES_BAD_RESPONSE: + success = false; + fputs(PQerrorMessage(pset->db), pset->queryFout); + break; + } + + if (PQstatus(pset->db) == CONNECTION_BAD) + { + fputs("The connection to the server was lost. Attempting reset: ", stderr); + PQreset(pset->db); + if (PQstatus(pset->db) == CONNECTION_BAD) { + fputs("Failed.\n", stderr); + PQfinish(pset->db); + PQclear(results); + pset->db = NULL; + return false; + } + else + fputs("Succeeded.\n", stderr); + } + + /* check for asynchronous notification returns */ + while ((notify = PQnotifies(pset->db)) != NULL) + { + fprintf(pset->queryFout, "Asynchronous NOTIFY '%s' from backend with pid '%d' received.\n", + notify->relname, notify->be_pid); + free(notify); + } + + if (results) + PQclear(results); + } + + return success; +} diff --git a/src/bin/psql/common.h b/src/bin/psql/common.h new file mode 100644 index 0000000000..25bda2c0f8 --- /dev/null +++ b/src/bin/psql/common.h @@ -0,0 +1,25 @@ +#ifndef COMMON_H +#define COMMON_H + +#include +#include "settings.h" + +char * +xstrdup(const char * string); + +bool +setQFout(const char *fname, PsqlSettings *pset); + +char * +simple_prompt(const char *prompt, int maxlen, bool echo); + +const char * +interpolate_var(const char * name, PsqlSettings * pset); + +PGresult * +PSQLexec(PsqlSettings *pset, const char *query); + +bool +SendQuery(PsqlSettings *pset, const char *query); + +#endif /* COMMON_H */ diff --git a/src/bin/psql/copy.c b/src/bin/psql/copy.c new file mode 100644 index 0000000000..d8d11e3267 --- /dev/null +++ b/src/bin/psql/copy.c @@ -0,0 +1,390 @@ +#include +#include +#include "copy.h" + +#include +#include +#include +#include +#include +#ifndef WIN32 +#include /* for isatty */ +#else +#include /* I think */ +#endif + +#include + +#include "settings.h" +#include "common.h" +#include "stringutils.h" + +#ifdef WIN32 +#define strcasecmp(x,y) stricmp(x,y) +#endif + +/* + * parse_slash_copy + * -- parses \copy command line + * + * Accepted syntax: \copy [binary] table|"table" [with oids] from|to filename|'filename' using delimiters [''] + * (binary is not here yet) + * + * returns a malloc'ed structure with the options, or NULL on parsing error + */ + +struct copy_options { + char * table; + char * file; + bool from; + bool binary; + bool oids; + char * delim; +}; + + +static void +free_copy_options(struct copy_options * ptr) +{ + if (!ptr) + return; + free(ptr->table); + free(ptr->file); + free(ptr->delim); + free(ptr); +} + + +static struct copy_options * +parse_slash_copy(const char *args) +{ + struct copy_options * result; + char * line; + char * token; + bool error = false; + char quote; + + line = xstrdup(args); + + if (!(result = calloc(1, sizeof (struct copy_options)))) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + token = strtokx(line, " \t", "\"", '\\', "e, NULL); + if (!token) + error = true; + else { + if (!quote && strcasecmp(token, "binary")==0) { + result->binary = true; + token = strtokx(NULL, " \t", "\"", '\\', "e, NULL); + if (!token) + error = true; + } + if (token) + result->table = xstrdup(token); + } + +#ifdef USE_ASSERT_CHECKING + assert(error || result->table); +#endif + + if (!error) { + token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL); + if (!token) + error = true; + else { + if (strcasecmp(token, "with")==0) { + token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL); + if (!token || strcasecmp(token, "oids")!=0) + error = true; + else + result->oids = true; + + if (!error) { + token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL); + if (!token) + error = true; + } + } + + if (!error && strcasecmp(token, "from")==0) + result->from = true; + else if (!error && strcasecmp(token, "to")==0) + result->from = false; + else + error = true; + } + } + + if (!error) { + token = strtokx(NULL, " \t", "'", '\\', NULL, NULL); + if (!token) + error = true; + else + result->file=xstrdup(token); + } + +#ifdef USE_ASSERT_CHECKING + assert(error || result->file); +#endif + + if (!error) { + token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL); + if (token) { + if (strcasecmp(token, "using")!=0) + error = true; + else { + token = strtokx(NULL, " \t", NULL, '\\', NULL, NULL); + if (!token || strcasecmp(token, "delimiters")!=0) + error = true; + else { + token = strtokx(NULL, " \t", "'", '\\', NULL, NULL); + if (token) + result->delim = xstrdup(token); + else + error = true; + } + } + } + } + + free(line); + + if (error) { + fputs("Parse error at ", stderr); + if (!token) + fputs("end of line.", stderr); + else + fprintf(stderr, "'%s'.", token); + fputs("\n", stderr); + free(result); + return NULL; + } + else + return result; +} + + + +/* + * Execute a \copy command (frontend copy). We have to open a file, then + * submit a COPY query to the backend and either feed it data from the + * file or route its response into the file. + */ +bool +do_copy(const char * args, PsqlSettings *pset) +{ + char query[128 + NAMEDATALEN]; + FILE *copystream; + struct copy_options *options; + PGresult *result; + bool success; + + /* parse options */ + options = parse_slash_copy(args); + + if (!options) + return false; + + strcpy(query, "COPY "); + if (options->binary) + fputs("Warning: \\copy binary is not implemented. Resorting to text output.\n", stderr); +/* strcat(query, "BINARY "); */ + + strcat(query, "\""); + strncat(query, options->table, NAMEDATALEN); + strcat(query, "\" "); + if (options->oids) + strcat(query, "WITH OIDS "); + + if (options->from) + strcat(query, "FROM stdin"); + else + strcat(query, "TO stdout"); + + + if (options->delim) { + /* backend copy only uses the first character here, + but that might be the escape backslash + (makes me wonder though why it's called delimiterS) */ + strncat(query, " USING DELIMITERS '", 2); + strcat(query, options->delim); + strcat(query, "'"); + } + + + if (options->from) +#ifndef __CYGWIN32__ + copystream = fopen(options->file, "r"); +#else + copystream = fopen(options->file, "rb"); +#endif + else +#ifndef __CYGWIN32__ + copystream = fopen(options->file, "w"); +#else + copystream = fopen(options->file, "wb"); +#endif + + if (!copystream) { + fprintf(stderr, + "Unable to open file %s which to copy: %s\n", + options->from ? "from" : "to", strerror(errno)); + free_copy_options(options); + return false; + } + + result = PSQLexec(pset, query); + + switch (PQresultStatus(result)) + { + case PGRES_COPY_OUT: + success = handleCopyOut(pset->db, copystream); + break; + case PGRES_COPY_IN: + success = handleCopyIn(pset->db, copystream, NULL); + break; + case PGRES_NONFATAL_ERROR: + case PGRES_FATAL_ERROR: + case PGRES_BAD_RESPONSE: + success = false; + fputs(PQerrorMessage(pset->db), stderr); + break; + default: + success = false; + fprintf(stderr, "Unexpected response (%d)\n", PQresultStatus(result)); + } + + PQclear(result); + + if (!GetVariable(pset->vars, "quiet")) { + if (success) + puts("Successfully copied."); + else + puts("Copy failed."); + } + + fclose(copystream); + free_copy_options(options); + return success; +} + + +#define COPYBUFSIZ BLCKSZ + + +/* + * handeCopyOut + * receives data as a result of a COPY ... TO stdout command + * + * If you want to use COPY TO in your application, this is the code to steal :) + * + * conn should be a database connection that you just called COPY TO on + * (and which gave you PGRES_COPY_OUT back); + * copystream is the file stream you want the output to go to + */ +bool +handleCopyOut(PGconn *conn, FILE *copystream) +{ + bool copydone = false; /* haven't started yet */ + char copybuf[COPYBUFSIZ]; + int ret; + + while (!copydone) + { + ret = PQgetline(conn, copybuf, COPYBUFSIZ); + + if (copybuf[0] == '\\' && + copybuf[1] == '.' && + copybuf[2] == '\0') + { + copydone = true; /* we're at the end */ + } + else + { + fputs(copybuf, copystream); + switch (ret) + { + case EOF: + copydone = true; + /* FALLTHROUGH */ + case 0: + fputc('\n', copystream); + break; + case 1: + break; + } + } + } + fflush(copystream); + return !PQendcopy(conn); +} + + + +/* + * handeCopyOut + * receives data as a result of a COPY ... FROM stdin command + * + * Again, if you want to use COPY FROM in your application, copy this. + * + * conn should be a database connection that you just called COPY FROM on + * (and which gave you PGRES_COPY_IN back); + * copystream is the file stream you want the input to come from + * prompt is something to display to request user input (only makes sense + * if stdin is an interactive tty) + */ + +bool +handleCopyIn(PGconn *conn, FILE *copystream, const char * prompt) +{ + bool copydone = false; + bool firstload; + bool linedone; + char copybuf[COPYBUFSIZ]; + char *s; + int buflen; + int c = 0; + + while (!copydone) + { /* for each input line ... */ + if (prompt && isatty(fileno(stdin))) + { + fputs(prompt, stdout); + fflush(stdout); + } + firstload = true; + linedone = false; + while (!linedone) + { /* for each buffer ... */ + s = copybuf; + for (buflen = COPYBUFSIZ; buflen > 1; buflen--) + { + c = getc(copystream); + if (c == '\n' || c == EOF) + { + linedone = true; + break; + } + *s++ = c; + } + *s = '\0'; + if (c == EOF) + { + PQputline(conn, "\\."); + copydone = true; + break; + } + PQputline(conn, copybuf); + if (firstload) + { + if (!strcmp(copybuf, "\\.")) + copydone = true; + firstload = false; + } + } + PQputline(conn, "\n"); + } + return !PQendcopy(conn); +} diff --git a/src/bin/psql/copy.h b/src/bin/psql/copy.h new file mode 100644 index 0000000000..50fe0afcf5 --- /dev/null +++ b/src/bin/psql/copy.h @@ -0,0 +1,22 @@ +#ifndef COPY_H +#define COPY_H + +#include +#include +#include +#include "settings.h" + +/* handler for \copy */ +bool +do_copy(const char *args, PsqlSettings *pset); + + +/* lower level processors for copy in/out streams */ + +bool +handleCopyOut(PGconn *conn, FILE *copystream); + +bool +handleCopyIn(PGconn *conn, FILE *copystream, const char * prompt); + +#endif diff --git a/src/bin/psql/create_help.pl b/src/bin/psql/create_help.pl new file mode 100644 index 0000000000..c877a8d1d4 --- /dev/null +++ b/src/bin/psql/create_help.pl @@ -0,0 +1,91 @@ +#!/usr/bin/perl + +# +# This script automatically generates the help on SQL in psql from the +# SGML docs. So far the format of the docs was consistent enough that +# this worked, but this here is my no means an SGML parser. +# +# It might be a good idea that this is just done once before distribution +# so people that don't have the docs or have slightly messed up docs or +# don't have perl, etc. won't have to bother. +# +# Call: perl create_help.pl sql_help.h +# (Do not rely on this script to be executable.) +# The name of the header file doesn't matter to this script, but it sure +# does matter to the rest of the source. +# +# A rule for this is also in the psql makefile. +# + +$docdir = "./../../../doc/src/sgml/ref"; +$outputfile = $ARGV[0] or die "Missing required argument.\n"; + +$define = $outputfile; +$define =~ tr/a-z/A-Z/; +$define =~ s/\W/_/g; + +opendir DIR, $docdir or die "Couldn't open documentation sources: $!\n"; +open OUT, ">$outputfile" or die "Couldn't open output file '$outputfile': $!\n"; + +print OUT +"/* + * This file is automatically generated from the SGML documentation. + * Direct changes here will be overwritten. + */ +#ifndef $define +#define $define + +struct _helpStruct +{ + char *cmd; /* the command name */ + char *help; /* the help associated with it */ + char *syntax; /* the syntax associated with it */ +}; + + +static struct _helpStruct QL_HELP[] = { +"; + +foreach $file (readdir DIR) { + my ($cmdname, $cmddesc, $cmdsynopsis); + $file =~ /\.sgml$/ || next; + + open FILE, "$docdir/$file" or next; + $filecontent = join('', ); + close FILE; + + $filecontent =~ m!\s*SQL - Language Statements\s*!i + or next; + + $filecontent =~ m!\s*([a-z ]+?)\s*!i && ($cmdname = $1); + $filecontent =~ m!\s*(.+?)\s*!i && ($cmddesc = $1); + + $filecontent =~ m!\s*(.+?)\s*!is && ($cmdsynopsis = $1); + + if ($cmdname && $cmddesc && $cmdsynopsis) { + $cmdname =~ s/\"/\\"/g; + + $cmddesc =~ s/<\/?.+?>//sg; + $cmddesc =~ s/\n/ /g; + $cmddesc =~ s/\"/\\"/g; + + $cmdsynopsis =~ s/<\/?.+?>//sg; + $cmdsynopsis =~ s/\n/\\n/g; + $cmdsynopsis =~ s/\"/\\"/g; + + print OUT " { \"$cmdname\",\n \"$cmddesc\",\n \"$cmdsynopsis\" },\n\n"; + } + else { + print STDERR "Couldn't parse file '$file'. (N='$cmdname' D='$cmddesc')\n"; + } +} + +print OUT " + { NULL, NULL, NULL } /* End of list marker */ +}; + +#endif /* $define */ +"; + +close OUT; +closedir DIR; diff --git a/src/bin/psql/describe.c b/src/bin/psql/describe.c new file mode 100644 index 0000000000..6304bc14ee --- /dev/null +++ b/src/bin/psql/describe.c @@ -0,0 +1,816 @@ +#include +#include +#include "describe.h" + +#include + +#include /* for VARHDRSZ, int4 type */ +#include +#include + +#include "common.h" +#include "settings.h" +#include "print.h" +#include "variables.h" + + +/*---------------- + * Handlers for various slash commands displaying some sort of list + * of things in the database. + * + * If you add something here, consider this: + * - If (and only if) the variable "description" is set, the description/ + * comment for the object should be displayed. + * - Try to format the query to look nice in -E output. + *---------------- + */ + +/* the maximal size of regular expression we'll accept here */ +/* (it is save to just change this here) */ +#define REGEXP_CUTOFF 10 * NAMEDATALEN + + +/* \da + * takes an optional regexp to match specific aggregates by name + */ +bool +describeAggregates(const char * name, PsqlSettings * pset) +{ + char descbuf[384 + 2*REGEXP_CUTOFF]; /* observe/adjust this if you change the query */ + PGresult * res; + bool description = GetVariableBool(pset->vars, "description"); + printQueryOpt myopt = pset->popt; + + descbuf[0] = '\0'; + + /* There are two kinds of aggregates: ones that work on particular types + ones that work on all */ + strcat(descbuf, + "SELECT a.aggname AS \"Name\", t.typname AS \"Type\""); + if (description) + strcat(descbuf, + ",\n obj_description(a.oid) as \"Description\""); + strcat(descbuf, + "\nFROM pg_aggregate a, pg_type t\n" + "WHERE a.aggbasetype = t.oid\n"); + if (name) { + strcat(descbuf, " AND a.aggname ~* '^"); + strncat(descbuf, name, REGEXP_CUTOFF); + strcat(descbuf, "'\n"); + } + + strcat(descbuf, + "UNION\n" + "SELECT a.aggname AS \"Name\", '(all types)' as \"Type\""); + if (description) + strcat(descbuf, + ",\n obj_description(a.oid) as \"Description\""); + strcat(descbuf, + "\nFROM pg_aggregate a\n" + "WHERE a.aggbasetype = 0\n"); + if (name) + { + strcat(descbuf, " AND a.aggname ~* '^"); + strncat(descbuf, name, REGEXP_CUTOFF); + strcat(descbuf, "'\n"); + } + + strcat(descbuf, "ORDER BY \"Name\", \"Type\""); + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + myopt.topt.tuples_only = false; + myopt.nullPrint = NULL; + myopt.title = "List of aggregates"; + + printQuery(res, &myopt, pset->queryFout); + + PQclear(res); + return true; +} + + +/* \df + * takes an optional regexp to narrow down the function name + */ +bool +describeFunctions(const char * name, PsqlSettings * pset) +{ + char descbuf[384 + REGEXP_CUTOFF]; + PGresult * res; + printQueryOpt myopt = pset->popt; + + /* + * we skip in/out funcs by excluding functions that take + * some arguments, but have no types defined for those + * arguments + */ + descbuf[0] = '\0'; + + strcat(descbuf, "SELECT t.typname as \"Result\", p.proname as \"Function\",\n" + " oid8types(p.proargtypes) as \"Arguments\""); + if (GetVariableBool(pset->vars, "description")) + strcat(descbuf, "\n, obj_description(p.oid) as \"Description\""); + strcat(descbuf, "\nFROM pg_proc p, pg_type t\n" + "WHERE p.prorettype = t.oid and (pronargs = 0 or oid8types(p.proargtypes) != '')\n"); + if (name) + { + strcat(descbuf, " AND p.proname ~* '^"); + strncat(descbuf, name, REGEXP_CUTOFF); + strcat(descbuf, "'\n"); + } + strcat(descbuf, "ORDER BY \"Function\", \"Result\", \"Arguments\""); + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + myopt.topt.tuples_only = false; + myopt.nullPrint = NULL; + myopt.title = "List of functions"; + + printQuery(res, &myopt, pset->queryFout); + + PQclear(res); + return true; +} + + + +/* + * describeTypes + * + * for \dT + */ +bool +describeTypes(const char * name, PsqlSettings * pset) +{ + char descbuf[256 + REGEXP_CUTOFF]; + PGresult * res; + printQueryOpt myopt = pset->popt; + + descbuf[0] = '\0'; + strcat(descbuf, "SELECT typname AS \"Type\""); + if (GetVariableBool(pset->vars, "description")) + strcat(descbuf, ", obj_description(p.oid) as \"Description\""); + strcat(descbuf, "\nFROM pg_type\n" + "WHERE typrelid = 0 AND typname !~ '^_.*'\n"); + + if (name) { + strcat(descbuf, " AND typname ~* '^"); + strncat(descbuf, name, REGEXP_CUTOFF); + strcat(descbuf, "' "); + } + strcat(descbuf, "ORDER BY typname;"); + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + myopt.topt.tuples_only = false; + myopt.nullPrint = NULL; + myopt.title = "List of types"; + + printQuery(res, &myopt, pset->queryFout); + + PQclear(res); + return true; +} + + + +/* \do + * NOTE: The (optional) argument here is _not_ a regexp since with all the + * funny chars floating around that would probably confuse people. It's an + * exact match string. + */ +bool +describeOperators(const char * name, PsqlSettings * pset) +{ + char descbuf[1536 + 3 * 32]; /* 32 is max length for operator name */ + PGresult * res; + bool description = GetVariableBool(pset->vars, "description"); + printQueryOpt myopt = pset->popt; + + descbuf[0] = '\0'; + + strcat(descbuf, "SELECT o.oprname AS \"Op\",\n" + " t1.typname AS \"Left arg\",\n" + " t2.typname AS \"Right arg\",\n" + " t0.typname AS \"Result\""); + if (description) + strcat(descbuf, ",\n obj_description(p.oid) as \"Description\""); + strcat(descbuf, "\nFROM pg_proc p, pg_type t0,\n" + " pg_type t1, pg_type t2,\n" + " pg_operator o\n" + "WHERE p.prorettype = t0.oid AND\n" + " RegprocToOid(o.oprcode) = p.oid AND\n" + " p.pronargs = 2 AND\n" + " o.oprleft = t1.oid AND\n" + " o.oprright = t2.oid\n"); + if (name) + { + strcat(descbuf, " AND o.oprname = '"); + strncat(descbuf, name, 32); + strcat(descbuf, "'\n"); + } + + strcat(descbuf, "\nUNION\n\n" + "SELECT o.oprname as \"Op\",\n" + " ''::name AS \"Left arg\",\n" + " t1.typname AS \"Right arg\",\n" + " t0.typname AS \"Result\""); + if (description) + strcat(descbuf, ",\n obj_description(p.oid) as \"Description\""); + strcat(descbuf, "\nFROM pg_operator o, pg_proc p, pg_type t0, pg_type t1\n" + "WHERE RegprocToOid(o.oprcode) = p.oid AND\n" + " o.oprresult = t0.oid AND\n" + " o.oprkind = 'l' AND\n" + " o.oprright = t1.oid\n"); + if (name) + { + strcat(descbuf, "AND o.oprname = '"); + strncat(descbuf, name, 32); + strcat(descbuf, "'\n"); + } + + strcat(descbuf, "\nUNION\n\n" + "SELECT o.oprname as \"Op\",\n" + " t1.typname AS \"Left arg\",\n" + " ''::name AS \"Right arg\",\n" + " t0.typname AS \"Result\""); + if (description) + strcat(descbuf, ",\n obj_description(p.oid) as \"Description\""); + strcat(descbuf, "\nFROM pg_operator o, pg_proc p, pg_type t0, pg_type t1\n" + "WHERE RegprocToOid(o.oprcode) = p.oid AND\n" + " o.oprresult = t0.oid AND\n" + " o.oprkind = 'r' AND\n" + " o.oprleft = t1.oid\n"); + if (name) + { + strcat(descbuf, "AND o.oprname = '"); + strncat(descbuf, name, 32); + strcat(descbuf, "'\n"); + } + strcat(descbuf, "\nORDER BY \"Op\", \"Left arg\", \"Right arg\", \"Result\""); + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + myopt.topt.tuples_only = false; + myopt.nullPrint = NULL; + myopt.title = "List of operators"; + + printQuery(res, &myopt, pset->queryFout); + + PQclear(res); + return true; +} + + +/* + * listAllDbs + * + * for \l, \list, and -l switch + */ +bool +listAllDbs(PsqlSettings *pset) +{ + PGresult *res; + char descbuf[256]; + printQueryOpt myopt = pset->popt; + + descbuf[0] = '\0'; + strcat(descbuf, "SELECT pg_database.datname as \"Database\",\n" + " pg_user.usename as \"Owner\"" +#ifdef MULTIBYTE + ",\n pg_database.encoding as \"Encoding\"" +#endif + ); + if (GetVariableBool(pset->vars, "description")) + strcat(descbuf, ",\n obj_description(pg_database.oid) as \"Description\"\n"); + strcat(descbuf, "FROM pg_database, pg_user\n" + "WHERE pg_database.datdba = pg_user.usesysid\n" + "ORDER BY \"Database\""); + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + myopt.topt.tuples_only = false; + myopt.nullPrint = NULL; + myopt.title = "List of databases"; + + printQuery(res, &myopt, pset->queryFout); + + PQclear(res); + return true; +} + + +/* List Tables Grant/Revoke Permissions + * \z (now also \dp -- perhaps more mnemonic) + * + */ +bool +permissionsList(const char * name, PsqlSettings *pset) +{ + char descbuf[256 + REGEXP_CUTOFF]; + PGresult *res; + printQueryOpt myopt = pset->popt; + + descbuf[0] = '\0'; + /* Currently, we ignore indexes since they have no meaningful rights */ + strcat(descbuf, "SELECT relname as \"Relation\",\n" + " relacl as \"Access permissions\"\n" + "FROM pg_class\n" + "WHERE ( relkind = 'r' OR relkind = 'S') AND\n" + " relname !~ '^pg_'\n"); + if (name) { + strcat(descbuf, " AND rename ~ '^"); + strncat(descbuf, name, REGEXP_CUTOFF); + strcat(descbuf, "'\n"); + } + strcat (descbuf, "ORDER BY relname"); + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + if (PQntuples(res) == 0) { + fputs("Couldn't find any tables.\n", pset->queryFout); + } + else { + myopt.topt.tuples_only = false; + myopt.nullPrint = NULL; + sprintf(descbuf, "Access permissions for database \"%s\"", PQdb(pset->db)); + myopt.title = descbuf; + + printQuery(res, &myopt, pset->queryFout); + } + + PQclear(res); + return true; +} + + + + +/* + * Get object comments + * + * \dd [foo] + * + * Note: This only lists things that actually have a description. For complete + * lists of things, there are other \d? commands. + */ +bool +objectDescription(const char * object, PsqlSettings *pset) +{ + char descbuf[2048 + 7*REGEXP_CUTOFF]; + PGresult *res; + printQueryOpt myopt = pset->popt; + + descbuf[0] = '\0'; + + /* Aggregate descriptions */ + strcat(descbuf, "SELECT DISTINCT a.aggname as \"Name\", 'aggregate'::text as \"What\", d.description as \"Description\"\n" + "FROM pg_aggregate a, pg_description d\n" + "WHERE a.oid = d.objoid\n"); + if (object) { + strcat(descbuf," AND a.aggname ~* '^"); + strncat(descbuf, object, REGEXP_CUTOFF); + strcat(descbuf,"'\n"); + } + + /* Function descriptions (except in/outs for datatypes) */ + strcat(descbuf, "\nUNION ALL\n\n"); + strcat(descbuf, "SELECT DISTINCT p.proname as \"Name\", 'function'::text as \"What\", d.description as \"Description\"\n" + "FROM pg_proc p, pg_description d\n" + "WHERE p.oid = d.objoid AND (p.pronargs = 0 or oid8types(p.proargtypes) != '')\n"); + if (object) { + strcat(descbuf," AND p.proname ~* '^"); + strncat(descbuf, object, REGEXP_CUTOFF); + strcat(descbuf,"'\n"); + } + + /* Operator descriptions */ + strcat(descbuf, "\nUNION ALL\n\n"); + strcat(descbuf, "SELECT DISTINCT o.oprname as \"Name\", 'operator'::text as \"What\", d.description as \"Description\"\n" + "FROM pg_operator o, pg_description d\n" + // must get comment via associated function + "WHERE RegprocToOid(o.oprcode) = d.objoid\n"); + if (object) { + strcat(descbuf," AND o.oprname = '"); + strncat(descbuf, object, REGEXP_CUTOFF); + strcat(descbuf,"'\n"); + } + + /* Type description */ + strcat(descbuf, "\nUNION ALL\n\n"); + strcat(descbuf, "SELECT DISTINCT t.typname as \"Name\", 'type'::text as \"What\", d.description as \"Description\"\n" + "FROM pg_type t, pg_description d\n" + "WHERE t.oid = d.objoid\n"); + if (object) { + strcat(descbuf," AND t.typname ~* '^"); + strncat(descbuf, object, REGEXP_CUTOFF); + strcat(descbuf,"'\n"); + } + + /* Relation (tables, views, indices, sequences) descriptions */ + strcat(descbuf, "\nUNION ALL\n\n"); + strcat(descbuf, "SELECT DISTINCT c.relname as \"Name\", 'relation'::text||'('||c.relkind||')' as \"What\", d.description as \"Description\"\n" + "FROM pg_class c, pg_description d\n" + "WHERE c.oid = d.objoid\n"); + if (object) { + strcat(descbuf," AND c.relname ~* '^"); + strncat(descbuf, object, REGEXP_CUTOFF); + strcat(descbuf,"'\n"); + } + + /* Rule description (ignore rules for views) */ + strcat(descbuf, "\nUNION ALL\n\n"); + strcat(descbuf, "SELECT DISTINCT r.rulename as \"Name\", 'rule'::text as \"What\", d.description as \"Description\"\n" + "FROM pg_rewrite r, pg_description d\n" + "WHERE r.oid = d.objoid AND r.rulename !~ '^_RET'\n"); + if (object) { + strcat(descbuf," AND r.rulename ~* '^"); + strncat(descbuf, object, REGEXP_CUTOFF); + strcat(descbuf,"'\n"); + } + + /* Trigger description */ + strcat(descbuf, "\nUNION ALL\n\n"); + strcat(descbuf, "SELECT DISTINCT t.tgname as \"Name\", 'trigger'::text as \"What\", d.description as \"Description\"\n" + "FROM pg_trigger t, pg_description d\n" + "WHERE t.oid = d.objoid\n"); + if (object) { + strcat(descbuf," AND t.tgname ~* '^"); + strncat(descbuf, object, REGEXP_CUTOFF); + strcat(descbuf,"'\n"); + } + + strcat(descbuf, "\nORDER BY \"Name\""); + + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + myopt.topt.tuples_only = false; + myopt.nullPrint = NULL; + myopt.title = "Object descriptions"; + + printQuery(res, &myopt, pset->queryFout); + + PQclear(res); + return true; +} + + + +/* + * describeTableDetails (for \d) + * + * Unfortunately, the information presented here is so complicated that it + * be done in a single query. So we have to assemble the printed table by hand + * and pass it to the underlying printTable() function. + * + */ +static void * xmalloc(size_t size) +{ + void * tmp; + tmp = malloc(size); + if (!tmp) { + perror("malloc"); + exit(EXIT_FAILURE); + } + return tmp; +} + + +bool +describeTableDetails(const char * name, PsqlSettings * pset) +{ + char descbuf[512 + NAMEDATALEN]; + PGresult *res = NULL, *res2 = NULL, *res3 = NULL; + printTableOpt myopt = pset->popt.topt; + bool description = GetVariableBool(pset->vars, "description"); + int i; + char * view_def = NULL; + char * headers[5]; + char ** cells = NULL; + char * title = NULL; + char ** footers = NULL; + char ** ptr; + unsigned int cols; + + cols = 3 + (description ? 1 : 0); + + headers[0] = "Attribute"; + headers[1] = "Type"; + headers[2] = "Info"; + if (description) { + headers[3] = "Description"; + headers[4] = NULL; + } + else + headers[3] = NULL; + + /* Get general table info */ + strcpy(descbuf, "SELECT a.attname, t.typname, a.attlen, a.atttypmod, a.attnotnull, a.atthasdef, a.attnum"); + if (description) + strcat(descbuf, ", obj_description(a.oid)"); + strcat(descbuf, "\nFROM pg_class c, pg_attribute a, pg_type t\n" + "WHERE c.relname = '"); + strncat(descbuf, name, NAMEDATALEN); + strcat(descbuf, "'\n AND a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid\n" + "ORDER BY a.attnum"); + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + /* Did we get anything? */ + if (PQntuples(res)==0) { + if (!GetVariableBool(pset->vars, "quiet")) + fprintf(stdout, "Did not find any class named \"%s\".\n", name); + PQclear(res); + return false; + } + + /* Check if table is a view */ + strcpy(descbuf, "SELECT definition FROM pg_views WHERE viewname = '"); + strncat(descbuf, name, NAMEDATALEN); + strcat(descbuf, "'"); + res2 = PSQLexec(pset, descbuf); + if (!res2) + return false; + + if (PQntuples(res2) > 0) + view_def = PQgetvalue(res2,0,0); + + + + /* Generate table cells to be printed */ + cells = calloc(PQntuples(res) * cols + 1, sizeof(*cells)); + if (!cells) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + for (i = 0; i < PQntuples(res); i++) { + int4 attypmod = atoi(PQgetvalue(res, i, 3)); + char * attype = PQgetvalue(res, i, 1); + + /* Name */ + cells[i*cols + 0] = PQgetvalue(res, i, 0); /* don't free this afterwards */ + + /* Type */ + cells[i*cols + 1] = xmalloc(NAMEDATALEN + 16); + if (strcmp(attype, "bpchar")==0) + sprintf(cells[i*cols + 1], "char(%d)", attypmod != -1 ? attypmod - VARHDRSZ : 0); + else if (strcmp(attype, "varchar")==0) + sprintf(cells[i*cols + 1], "varchar(%d)", attypmod != -1 ? attypmod - VARHDRSZ : 0); + else if (strcmp(attype, "numeric")==0) + sprintf(cells[i*cols + 1], "numeric(%d,%d)", ((attypmod - VARHDRSZ) >> 16) & 0xffff, + (attypmod - VARHDRSZ) & 0xffff ); + else if (attype[0] == '_') + sprintf(cells[i*cols + 1], "%s[]", attype+1); + else + strcpy(cells[i*cols + 1], attype); + + /* Info */ + cells[i*cols + 2] = xmalloc(128 + 128); /* I'm cutting off the default string at 128 */ + cells[i*cols + 2][0] = '\0'; + if (strcmp(PQgetvalue(res, i, 4), "t") == 0) + strcat(cells[i*cols + 2], "not null"); + if (strcmp(PQgetvalue(res, i, 5), "t") == 0) { + /* handle "default" here */ + strcpy(descbuf, "SELECT substring(d.adsrc for 128) FROM pg_attrdef d, pg_class c\n" + "WHERE c.relname = '"); + strncat(descbuf, name, NAMEDATALEN); + strcat(descbuf, "' AND c.oid = d.adrelid AND d.adnum = "); + strcat(descbuf, PQgetvalue(res, i, 6)); + + res3 = PSQLexec(pset, descbuf); + if (!res) return false; + if (cells[i*cols+2][0]) strcat(cells[i*cols+2], " "); + strcat(cells[i*cols + 2], "default "); + strcat(cells[i*cols + 2], PQgetvalue(res3, 0, 0)); + } + + /* Description */ + if (description) + cells[i*cols + 3] = PQgetvalue(res, i, 7); + } + + /* Make title */ + title = xmalloc(10 + strlen(name)); + if (view_def) + sprintf(title, "View \"%s\"", name); + else + sprintf(title, "Table \"%s\"", name); + + /* Make footers */ + if (view_def) { + footers = xmalloc(2 * sizeof(*footers)); + footers[0] = xmalloc(20 + strlen(view_def)); + sprintf(footers[0], "View definition: %s", view_def); + footers[1] = NULL; + } + else { + /* display indices */ + strcpy(descbuf, "SELECT c2.relname\n" + "FROM pg_class c, pg_class c2, pg_index i\n" + "WHERE c.relname = '"); + strncat(descbuf, name, NAMEDATALEN); + strcat(descbuf, "' AND c.oid = i.indrelid AND i.indexrelid = c2.oid\n" + "ORDER BY c2.relname"); + res3 = PSQLexec(pset, descbuf); + if (!res3) + return false; + + if (PQntuples(res3) > 0) { + footers = xmalloc((PQntuples(res3) + 1) * sizeof(*footers)); + + for (i=0; iqueryFout); + + /* clean up */ + free(title); + + for (i = 0; ivars, "description"); + + char descbuf[1536 + 4 * REGEXP_CUTOFF]; + PGresult *res; + printQueryOpt myopt = pset->popt; + + descbuf[0] = '\0'; + + /* tables */ + if (showTables) { + strcat(descbuf, "SELECT u.usename as \"Owner\", c.relname as \"Name\", 'table'::text as \"Type\""); + if (description) + strcat(descbuf, ", obj_description(c.oid) as \"Description\""); + strcat(descbuf, "\nFROM pg_class c, pg_user u\n" + "WHERE c.relowner = u.usesysid AND c.relkind = 'r'\n" + " AND not exists (select 1 from pg_views where viewname = c.relname)\n"); + strcat(descbuf, showSystem ? " AND c.relname ~ '^pg_'\n" : " AND c.relname !~ '^pg_'\n"); + if (name) { + strcat(descbuf, " AND c.relname ~ '^"); + strncat(descbuf, name, REGEXP_CUTOFF); + strcat(descbuf, "'\n"); + } + } + + /* views */ + if (showViews) { + if (descbuf[0]) + strcat(descbuf, "\nUNION\n\n"); + + strcat(descbuf, "SELECT u.usename as \"Owner\", c.relname as \"Name\", 'view'::text as \"Type\""); + if (description) + strcat(descbuf, ", obj_description(c.oid) as \"Description\""); + strcat(descbuf, "\nFROM pg_class c, pg_user u\n" + "WHERE c.relowner = u.usesysid AND c.relkind = 'r'\n" + " AND exists (select 1 from pg_views where viewname = c.relname)\n"); + strcat(descbuf, showSystem ? " AND c.relname ~ '^pg_'\n" : " AND c.relname !~ '^pg_'\n"); + if (name) { + strcat(descbuf, " AND c.relname ~ '^"); + strncat(descbuf, name, REGEXP_CUTOFF); + strcat(descbuf, "'\n"); + } + } + + /* indices, sequences */ + if (showIndices || showSeq) { + if (descbuf[0]) + strcat(descbuf, "\nUNION\n\n"); + + strcat(descbuf, "SELECT u.usename as \"Owner\", c.relname as \"Name\",\n" + " (CASE WHEN relkind = 'S' THEN 'sequence'::text ELSE 'index'::text END) as \"Type\""); + if (description) + strcat(descbuf, ", obj_description(c.oid) as \"Description\""); + strcat(descbuf, "\nFROM pg_class c, pg_user u\n" + "WHERE c.relowner = u.usesysid AND relkind in ("); + if (showIndices && showSeq) + strcat(descbuf, "'i', 'S'"); + else if (showIndices) + strcat(descbuf, "'i'"); + else + strcat(descbuf, "'S'"); + strcat(descbuf, ")\n"); + + /* ignore large-obj indices */ + if (showIndices) + strcat(descbuf, " AND (c.relkind != 'i' OR c.relname !~ '^xinx')\n"); + + strcat(descbuf, showSystem ? " AND c.relname ~ '^pg_'\n" : " AND c.relname !~ '^pg_'\n"); + if (name) { + strcat(descbuf, " AND c.relname ~ '^"); + strncat(descbuf, name, REGEXP_CUTOFF); + strcat(descbuf, "'\n"); + } + } + + /* real system catalogue tables */ + if (showSystem && showTables) { + if (descbuf[0]) + strcat(descbuf, "\nUNION\n\n"); + + strcat(descbuf, "SELECT u.usename as \"Owner\", c.relname as \"Name\", 'system'::text as \"Type\""); + if (description) + strcat(descbuf, ", obj_description(c.oid) as \"Description\""); + strcat(descbuf, "\nFROM pg_class c, pg_user u\n" + "WHERE c.relowner = u.usesysid AND c.relkind = 's'\n"); + if (name) { + strcat(descbuf, " AND c.relname ~ '^"); + strncat(descbuf, name, REGEXP_CUTOFF); + strcat(descbuf, "'\n"); + } + } + + strcat(descbuf, "\nORDER BY \"Name\""); + + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + if (PQntuples(res) == 0) + fprintf(pset->queryFout, "No matching classes found.\n"); + + else { + myopt.topt.tuples_only = false; + myopt.nullPrint = NULL; + myopt.title = "List of classes"; + + printQuery(res, &myopt, pset->queryFout); + } + + PQclear(res); + return true; +} + + +/* the end */ diff --git a/src/bin/psql/describe.h b/src/bin/psql/describe.h new file mode 100644 index 0000000000..8f165b4f80 --- /dev/null +++ b/src/bin/psql/describe.h @@ -0,0 +1,42 @@ +#ifndef DESCRIBE_H +#define DESCRIBE_H + +#include "settings.h" + +/* \da */ +bool +describeAggregates(const char * name, PsqlSettings * pset); + +/* \df */ +bool +describeFunctions(const char * name, PsqlSettings * pset); + +/* \dT */ +bool +describeTypes(const char * name, PsqlSettings * pset); + +/* \do */ +bool +describeOperators(const char * name, PsqlSettings * pset); + +/* \dp (formerly \z) */ +bool +permissionsList(const char * name, PsqlSettings *pset); + +/* \dd */ +bool +objectDescription(const char * object, PsqlSettings *pset); + +/* \d foo */ +bool +describeTableDetails(const char * name, PsqlSettings * pset); + +/* \l */ +bool +listAllDbs(PsqlSettings *pset); + +/* \dt, \di, \dS, etc. */ +bool +listTables(const char * infotype, const char * name, PsqlSettings * pset); + +#endif /* DESCRIBE_H */ diff --git a/src/bin/psql/help.c b/src/bin/psql/help.c new file mode 100644 index 0000000000..0caab0f0d0 --- /dev/null +++ b/src/bin/psql/help.c @@ -0,0 +1,306 @@ +#include +#include +#include "help.h" + +#include +#include +#include + +#ifndef WIN32 +#include /* for ioctl() */ +#ifdef HAVE_PWD_H +#include /* for getpwuid() */ +#endif +#include /* (ditto) */ +#include /* for getuid() */ +#else +#define strcasecmp(x,y) stricmp(x,y) +#define popen(x,y) _popen(x,y) +#define pclose(x) _pclose(x) +#endif + +#include +#include + +#include "settings.h" +#include "common.h" +#include "sql_help.h" + + +/* + * usage + * + * print out command line arguments and exit + */ +#define ON(var) (var ? "on" : "off") + +void usage(void) +{ + const char *env; + const char *user; +#ifndef WIN32 + struct passwd *pw = NULL; +#endif + + /* Find default user, in case we need it. */ + user = getenv("USER"); + if (!user) { +#ifndef WIN32 + pw = getpwuid(getuid()); + if (pw) user = pw->pw_name; + else { + perror("getpwuid()"); + exit(EXIT_FAILURE); + } +#else + user = "?"; +#endif + } + +/* If string begins " here, then it ought to end there to fit on an 80 column terminal> > > > > > > " */ + fprintf(stderr, "Usage: psql [options] [dbname [username]] \n"); + fprintf(stderr, " -A Unaligned table output mode (-P format=unaligned)\n"); + fprintf(stderr, " -c query Run single query (slash commands, too) and exit\n"); + + /* Display default database */ + env = getenv("PGDATABASE"); + if (!env) env=user; + fprintf(stderr, " -d dbname Specify database name to connect to (default: %s)\n", env); + + fprintf(stderr, " -e Echo all input in non-interactive mode\n"); + fprintf(stderr, " -E Display queries that internal commands generate\n"); + fprintf(stderr, " -f filename Execute queries from file, then exit\n"); + fprintf(stderr, " -F sep Set field separator (default: '" DEFAULT_FIELD_SEP "') (-P fieldsep=)\n"); + + /* Display default host */ + env = getenv("PGHOST"); + fprintf(stderr, " -h host Specify database server host (default: "); + if (env) + fprintf(stderr, env); + else + fprintf(stderr, "domain socket"); + fprintf(stderr, ")\n"); + + fprintf(stderr, " -H HTML table output mode (-P format=html)\n"); + fprintf(stderr, " -l List available databases, then exit\n"); + fprintf(stderr, " -n Do not use readline and history\n"); + fprintf(stderr, " -o filename Send query output to filename (or |pipe)\n"); + + /* Display default port */ + env = getenv("PGPORT"); + fprintf(stderr, " -p port Specify database server port (default: %s)\n", + env ? env : "hardwired"); + + fprintf(stderr, " -P var[=arg] Set printing option 'var' to 'arg'. (see \\pset command)\n"); + fprintf(stderr, " -q Run quietly (no messages, no prompts)\n"); + fprintf(stderr, " -s Single step mode (confirm each query)\n"); + fprintf(stderr, " -S Single line mode (newline sends query)\n"); + fprintf(stderr, " -t Don't print headings and row count (-P tuples_only)\n"); + fprintf(stderr, " -T text Set HTML table tag options (e.g., width, border)\n"); + fprintf(stderr, " -u Prompt for username and password (same as \"-U ? -W\")\n"); + + /* Display default user */ + env = getenv("PGUSER"); + if (!env) env=user; + fprintf(stderr, " -U [username] Specifiy username, \"?\"=prompt (default user: %s)\n", env); + + fprintf(stderr, " -x Turn on expanded table output (-P expanded)\n"); + fprintf(stderr, " -v name=val Set psql variable 'name' to 'value'\n"); + fprintf(stderr, " -V Show version information and exit\n"); + fprintf(stderr, " -W Prompt for password (should happen automatically)\n"); + + fprintf(stderr, "Consult the documentation for the complete details.\n"); + +#ifndef WIN32 + if (pw) free(pw); +#endif +} + + + +/* + * slashUsage + * + * print out help for the backslash commands + */ + +#ifndef TIOCGWINSZ +struct winsize { + int ws_row; + int ws_col; +}; +#endif + +void +slashUsage(PsqlSettings *pset) +{ + bool usePipe = false; + const char *pagerenv; + FILE *fout; + struct winsize screen_size; + +#ifdef TIOCGWINSZ + if (pset->notty == 0 && + (ioctl(fileno(stdout), TIOCGWINSZ, &screen_size) == -1 || + screen_size.ws_col == 0 || + screen_size.ws_row == 0)) + { +#endif + screen_size.ws_row = 24; + screen_size.ws_col = 80; +#ifdef TIOCGWINSZ + } +#endif + + if (pset->notty == 0 && + (pagerenv = getenv("PAGER")) && + (pagerenv[0] != '\0') && + screen_size.ws_row <= 36 && + (fout = popen(pagerenv, "w"))) + { + usePipe = true; + pqsignal(SIGPIPE, SIG_IGN); + } + else + fout = stdout; + + /* if you add/remove a line here, change the row test above */ + fprintf(fout, " \\? -- help\n"); + fprintf(fout, " \\c[onnect] [|- [|?]] -- connect to new database (currently '%s')\n", PQdb(pset->db)); + fprintf(fout, " \\copy [binary]
[with oids] {from|to} [with delimiters '']\n"); + fprintf(fout, " \\copyright -- show PostgreSQL copyright\n"); + fprintf(fout, " \\d -- list tables, views, and sequences\n"); + fprintf(fout, " \\distvS -- list only indices/sequences/tables/views/system tables\n"); + fprintf(fout, " \\da -- list aggregates\n"); + fprintf(fout, " \\dd []- list comment for table, type, function, or operator\n"); + fprintf(fout, " \\df -- list functions\n"); + fprintf(fout, " \\do -- list operators\n"); + fprintf(fout, " \\dT -- list data types\n"); + fprintf(fout, " \\e [] -- edit the current query buffer or with external editor\n"); + fprintf(fout, " \\echo -- write text to stdout\n"); + fprintf(fout, " \\g [] -- send query to backend (and results in or |pipe)\n"); + fprintf(fout, " \\h [] -- help on syntax of sql commands, * for all commands\n"); + fprintf(fout, " \\i -- read and execute queries from filename\n"); + fprintf(fout, " \\l -- list all databases\n"); + fprintf(fout, " \\lo_export, \\lo_import, \\lo_list, \\lo_unlink -- large object operations\n"); + fprintf(fout, " \\o [] -- send all query results to , or |pipe\n"); + fprintf(fout, " \\p -- print the content of the current query buffer\n"); + fprintf(fout, " \\pset -- set table output options\n"); + fprintf(fout, " \\q -- quit\n"); + fprintf(fout, " \\qecho -- write text to query output stream (see \\o)\n"); + fprintf(fout, " \\r -- reset (clear) the query buffer\n"); + fprintf(fout, " \\s [] -- print history or save it in \n"); + fprintf(fout, " \\set [] -- set/unset internal variable\n"); + fprintf(fout, " \\t -- don't show table headers or footers (currently %s)\n", ON(pset->popt.topt.tuples_only)); + fprintf(fout, " \\x -- toggle expanded output (currently %s)\n", ON(pset->popt.topt.expanded)); + fprintf(fout, " \\w -- write current query buffer to a file\n"); + fprintf(fout, " \\z -- list table access permissions\n"); + fprintf(fout, " \\! [] -- shell escape or command\n"); + + if (usePipe) { + pclose(fout); + pqsignal(SIGPIPE, SIG_DFL); + } +} + + + +/* + * helpSQL -- help with SQL commands + * + */ +void +helpSQL(const char *topic) +{ + if (!topic || strlen(topic)==0) + { + char left_center_right; /* Which column we're displaying */ + int i; /* Index into QL_HELP[] */ + + puts("Syntax: \\h or \\help , where is one of the following:"); + + left_center_right = 'L';/* Start with left column */ + i = 0; + while (QL_HELP[i].cmd != NULL) + { + switch (left_center_right) + { + case 'L': + printf(" %-25s", QL_HELP[i].cmd); + left_center_right = 'C'; + break; + case 'C': + printf("%-25s", QL_HELP[i].cmd); + left_center_right = 'R'; + break; + case 'R': + printf("%-25s\n", QL_HELP[i].cmd); + left_center_right = 'L'; + break; + } + i++; + } + if (left_center_right != 'L') + puts("\n"); + puts("Or type \\h * for a complete description of all commands."); + } + + + else + { + int i; + bool help_found = false; + + for (i = 0; QL_HELP[i].cmd; i++) + { + if (strcasecmp(QL_HELP[i].cmd, topic) == 0 || + strcmp(topic, "*") == 0) + { + help_found = true; + printf("Command: %s\nDescription: %s\nSyntax:\n%s\n\n", + QL_HELP[i].cmd, QL_HELP[i].help, QL_HELP[i].syntax); + } + } + + if (!help_found) + printf("No help available for '%s'.\nTry \\h with no arguments to see available help.\n", topic); + } +} + + + + +void +print_copyright(void) +{ + puts( +" +PostgreSQL Data Base Management System + +Copyright (c) 1996-9 PostgreSQL Global Development Group + +This software is based on Postgres95, formerly known as Postgres, which +contains the following notice: + +Copyright (c) 1994-7 Regents of the University of California + +Permission to use, copy, modify, and distribute this software and its +documentation for any purpose, without fee, and without a written agreement +is hereby granted, provided that the above copyright notice and this paragraph +and the following two paragraphs appear in all copies. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR +DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST +PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF +THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGE. + +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, +BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS ON AN \"AS IS\" BASIS, +AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. + +(end of terms)" + ); +} diff --git a/src/bin/psql/help.h b/src/bin/psql/help.h new file mode 100644 index 0000000000..34bb267764 --- /dev/null +++ b/src/bin/psql/help.h @@ -0,0 +1,16 @@ +#ifndef HELP_H +#define HELP_H + +#include "settings.h" + +void usage(void); + +void slashUsage(PsqlSettings *pset); + +void helpSQL(const char *topic); + +void print_copyright(void); + + +#endif + diff --git a/src/bin/psql/input.c b/src/bin/psql/input.c new file mode 100644 index 0000000000..3bb418be48 --- /dev/null +++ b/src/bin/psql/input.c @@ -0,0 +1,162 @@ +#include +#include +#include "input.h" + +#include + +/* Note that this file does not depend on any other files in psql. */ + +/* Runtime options for turning off readline and history */ +/* (of course there is no runtime command for doing that :) */ +#ifdef USE_READLINE +static bool useReadline; +#endif +#ifdef USE_HISTORY +static bool useHistory; +#endif + + +/* + * gets_interactive() + * + * Gets a line of interactive input, using readline of desired. + * The result is malloced. + */ +char * +gets_interactive(const char *prompt) +{ + char * s; + +#ifdef USE_READLINE + if (useReadline) { + s = readline(prompt); + fputc('\r', stdout); + fflush(stdout); + } + else { +#endif + fputs(prompt, stdout); + fflush(stdout); + s = gets_fromFile(stdin); +#ifdef USE_READLINE + } +#endif + +#ifdef USE_HISTORY + if (useHistory && s && s[0] != '\0') + add_history(s); +#endif + + return s; +} + + + +/* + * gets_fromFile + * + * Gets a line of noninteractive input from a file (which could be stdin). + */ +char * +gets_fromFile(FILE *source) +{ + PQExpBufferData buffer; + char line[1024]; + + initPQExpBuffer(&buffer); + + while (fgets(line, 1024, source) != NULL) { + appendPQExpBufferStr(&buffer, line); + if (buffer.data[buffer.len-1] == '\n') { + buffer.data[buffer.len-1] = '\0'; + return buffer.data; + } + } + + if (buffer.len > 0) + return buffer.data; /* EOF after reading some bufferload(s) */ + + /* EOF, so return null */ + termPQExpBuffer(&buffer); + return NULL; +} + + + +/* + * Put any startup stuff related to input in here. It's good to maintain + * abstraction this way. + * + * The only "flag" right now is 1 for use readline & history. + */ +void +initializeInput(int flags) +{ +#ifdef USE_READLINE + if (flags == 1) { + useReadline = true; + rl_readline_name = "psql"; + } +#endif + +#ifdef USE_HISTORY + if (flags == 1) { + const char * home; + + useHistory = true; + using_history(); + home = getenv("HOME"); + if (home) { + char * psql_history = (char *) malloc(strlen(home) + 20); + if (psql_history) { + sprintf(psql_history, "%s/.psql_history", home); + read_history(psql_history); + free(psql_history); + } + } + } +#endif +} + + + +bool +saveHistory(const char *fname) +{ +#ifdef USE_HISTORY + if (useHistory) { + if (write_history(fname) != 0) { + perror(fname); + return false; + } + return true; + } + else + return false; +#else + return false; +#endif +} + + + +void +finishInput(void) +{ +#ifdef USE_HISTORY + if (useHistory) { + char * home; + char * psql_history; + + home = getenv("HOME"); + if (home) { + psql_history = (char *) malloc(strlen(home) + 20); + if (psql_history) { + sprintf(psql_history, "%s/.psql_history", home); + write_history(psql_history); + free(psql_history); + } + } + } +#endif +} diff --git a/src/bin/psql/input.h b/src/bin/psql/input.h new file mode 100644 index 0000000000..272b0574f1 --- /dev/null +++ b/src/bin/psql/input.h @@ -0,0 +1,56 @@ +#ifndef INPUT_H +#define INPUT_H + +#include +#include +#include +#include "settings.h" + + +/* If some other file needs to have access to readline/history, include this + * file and save yourself all this work. + * + * USE_READLINE and USE_HISTORY are the definite pointers regarding existence or not. + */ +#ifdef HAVE_LIBREADLINE +#ifdef HAVE_READLINE_H +#include +#define USE_READLINE 1 +#else +#if defined(HAVE_READLINE_READLINE_H) +#include +#define USE_READLINE 1 +#endif +#endif +#endif + +#if defined(HAVE_LIBHISTORY) || (defined(HAVE_LIBREADLINE) && defined(HAVE_HISTORY_IN_READLINE)) +#if defined(HAVE_HISTORY_H) +#include +#define USE_HISTORY 1 +#else +#if defined(HAVE_READLINE_HISTORY_H) +#include +#define USE_HISTORY 1 +#endif +#endif +#endif + + +char * +gets_interactive(const char *prompt); + +char * +gets_fromFile(FILE *source); + + +void +initializeInput(int flags); + +bool +saveHistory(const char *fname); + +void +finishInput(void); + +#endif diff --git a/src/bin/psql/large_obj.c b/src/bin/psql/large_obj.c new file mode 100644 index 0000000000..dfc39435ab --- /dev/null +++ b/src/bin/psql/large_obj.c @@ -0,0 +1,311 @@ +#include +#include +#include "large_obj.h" + +#include +#include + +#include +#include + +#include "settings.h" +#include "variables.h" +#include "common.h" +#include "print.h" + + +/* + * Since all large object ops must be in a transaction, we must do some magic + * here. You can set the variable lo_transaction to one of commit|rollback| + * nothing to get your favourite behaviour regarding any transaction in + * progress. Rollback is default. + */ + +static char notice[80]; + +static void +_my_notice_handler(void * arg, const char * message) { + (void)arg; + strncpy(notice, message, 79); + notice[79] = '\0'; +} + + +static bool +handle_transaction(PsqlSettings * pset) +{ + const char * var = GetVariable(pset->vars, "lo_transaction"); + PGresult * res; + bool commit; + PQnoticeProcessor old_notice_hook; + + if (var && strcmp(var, "nothing")==0) + return true; + + commit = (var && strcmp(var, "commit")==0); + + notice[0] = '\0'; + old_notice_hook = PQsetNoticeProcessor(pset->db, _my_notice_handler, NULL); + + res = PSQLexec(pset, commit ? "COMMIT" : "ROLLBACK"); + if (!res) + return false; + + if (notice[0]) { + if ( (!commit && strcmp(notice, "NOTICE: UserAbortTransactionBlock and not in in-progress state\n")!=0) || + (commit && strcmp(notice, "NOTICE: EndTransactionBlock and not inprogress/abort state\n")!=0) ) + fputs(notice, stderr); + } + else if (!GetVariableBool(pset->vars, "quiet")) { + if (commit) + puts("Warning: Your transaction in progress has been committed."); + else + puts("Warning: Your transaction in progress has been rolled back."); + } + + PQsetNoticeProcessor(pset->db, old_notice_hook, NULL); + return true; +} + + + +/* + * do_lo_export() + * + * Write a large object to a file + */ +bool +do_lo_export(PsqlSettings * pset, const char * loid_arg, const char * filename_arg) +{ + PGresult * res; + int status; + bool own_transaction = true; + const char * var = GetVariable(pset->vars, "lo_transaction"); + + if (var && strcmp(var, "nothing")==0) + own_transaction = false; + + if (!pset->db) { + fputs("You are not connected to a database.\n", stderr); + return false; + } + + if (own_transaction) { + if (!handle_transaction(pset)) + return false; + + if (!(res = PSQLexec(pset, "BEGIN"))) + return false; + + PQclear(res); + } + + status = lo_export(pset->db, atol(loid_arg), (char *)filename_arg); + if (status != 1) { /* of course this status is documented nowhere :( */ + fputs(PQerrorMessage(pset->db), stderr); + if (own_transaction) { + res = PQexec(pset->db, "ROLLBACK"); + PQclear(res); + } + return false; + } + + if (own_transaction) { + if (!(res = PSQLexec(pset, "COMMIT"))) { + res = PQexec(pset->db, "ROLLBACK"); + PQclear(res); + return false; + } + + PQclear(res); + } + + fprintf(pset->queryFout, "lo_export\n"); + + return true; +} + + + +/* + * do_lo_import() + * + * Copy large object from file to database + */ +bool +do_lo_import(PsqlSettings * pset, const char * filename_arg, const char * comment_arg) +{ + PGresult * res; + Oid loid; + char buf[1024]; + unsigned int i; + bool own_transaction = true; + const char * var = GetVariable(pset->vars, "lo_transaction"); + + if (var && strcmp(var, "nothing")==0) + own_transaction = false; + + if (!pset->db) { + fputs("You are not connected to a database.\n", stderr); + return false; + } + + if (own_transaction) { + if (!handle_transaction(pset)) + return false; + + if (!(res = PSQLexec(pset, "BEGIN"))) + return false; + + PQclear(res); + } + + loid = lo_import(pset->db, (char *)filename_arg); + if (loid == InvalidOid) { + fputs(PQerrorMessage(pset->db), stderr); + if (own_transaction) { + res = PQexec(pset->db, "ROLLBACK"); + PQclear(res); + } + return false; + } + + /* insert description if given */ + if (comment_arg) { + sprintf(buf, "INSERT INTO pg_description VALUES (%d, '", loid); + for (i=0; idb, "ROLLBACK"); + PQclear(res); + } + return false; + } + } + + if (own_transaction) { + if (!(res = PSQLexec(pset, "COMMIT"))) { + res = PQexec(pset->db, "ROLLBACK"); + PQclear(res); + return false; + } + + PQclear(res); + } + + + fprintf(pset->queryFout, "lo_import %d\n", loid); + + return true; +} + + + +/* + * do_lo_unlink() + * + * removes a large object out of the database + */ +bool do_lo_unlink(PsqlSettings * pset, const char * loid_arg) +{ + PGresult * res; + int status; + Oid loid = (Oid)atol(loid_arg); + char buf[256]; + bool own_transaction = true; + const char * var = GetVariable(pset->vars, "lo_transaction"); + + if (var && strcmp(var, "nothing")==0) + own_transaction = false; + + if (!pset->db) { + fputs("You are not connected to a database.\n", stderr); + return false; + } + + if (own_transaction) { + if (!handle_transaction(pset)) + return false; + + if (!(res = PSQLexec(pset, "BEGIN"))) + return false; + + PQclear(res); + } + + status = lo_unlink(pset->db, loid); + if (status == -1) { + fputs(PQerrorMessage(pset->db), stderr); + if (own_transaction) { + res = PQexec(pset->db, "ROLLBACK"); + PQclear(res); + } + return false; + } + + /* remove the comment as well */ + sprintf(buf, "DELETE FROM pg_description WHERE objoid = %d", loid); + if (!(res = PSQLexec(pset, buf))) { + if (own_transaction) { + res = PQexec(pset->db, "ROLLBACK"); + PQclear(res); + } + return false; + } + + + if (own_transaction) { + if (!(res = PSQLexec(pset, "COMMIT"))) { + res = PQexec(pset->db, "ROLLBACK"); + PQclear(res); + return false; + } + PQclear(res); + } + + + fprintf(pset->queryFout, "lo_unlink %d\n", loid); + + return true; +} + + + +/* + * do_lo_list() + * + * Show all large objects in database, with comments if desired + */ +bool do_lo_list(PsqlSettings * pset) +{ + PGresult * res; + char descbuf[512]; + printQueryOpt myopt = pset->popt; + + descbuf[0] = '\0'; + strcat(descbuf, "SELECT usename as \"Owner\", substring(relname from 5) as \"ID\""); + if (GetVariableBool(pset->vars, "description")) + strcat(descbuf, ",\n obj_description(pg_class.oid) as \"Description\""); + strcat(descbuf,"\nFROM pg_class, pg_user\n" + "WHERE usesysid = relowner AND relkind = 'l'\n" + "ORDER BY \"ID\""); + + res = PSQLexec(pset, descbuf); + if (!res) + return false; + + myopt.topt.tuples_only = false; + myopt.nullPrint = NULL; + myopt.title = "Large objects"; + + printQuery(res, &myopt, pset->queryFout); + + PQclear(res); + return true; +} diff --git a/src/bin/psql/large_obj.h b/src/bin/psql/large_obj.h new file mode 100644 index 0000000000..22228e7b11 --- /dev/null +++ b/src/bin/psql/large_obj.h @@ -0,0 +1,11 @@ +#ifndef LARGE_OBJ_H +#define LARGE_OBJ_H + +#include "settings.h" + +bool do_lo_export(PsqlSettings * pset, const char * loid_arg, const char * filename_arg); +bool do_lo_import(PsqlSettings * pset, const char * filename_arg, const char * comment_arg); +bool do_lo_unlink(PsqlSettings * pset, const char * loid_arg); +bool do_lo_list(PsqlSettings * pset); + +#endif /* LARGE_OBJ_H */ diff --git a/src/bin/psql/mainloop.c b/src/bin/psql/mainloop.c new file mode 100644 index 0000000000..2f38ffbcff --- /dev/null +++ b/src/bin/psql/mainloop.c @@ -0,0 +1,368 @@ +#include +#include +#include "mainloop.h" + +#include +#include +#include + +#include + +#include "settings.h" +#include "prompt.h" +#include "input.h" +#include "common.h" +#include "command.h" + + + +/* MainLoop() + * Main processing loop for reading lines of input + * and sending them to the backend. + * + * This loop is re-entrant. May be called by \i command + * which reads input from a file. + */ +int +MainLoop(PsqlSettings *pset, FILE *source) +{ + PQExpBuffer query_buf; /* buffer for query being accumulated */ + char *line; /* current line of input */ + char *xcomment; /* start of extended comment */ + int len; /* length of the line */ + int successResult = EXIT_SUCCESS; + backslashResult slashCmdStatus; + + bool eof = false; /* end of our command input? */ + bool success; + char in_quote; /* == 0 for no in_quote */ + bool was_bslash; /* backslash */ + int paren_level; + unsigned int query_start; + + int i, prevlen, thislen; + + /* Save the prior command source */ + FILE *prev_cmd_source; + bool prev_cmd_interactive; + + bool die_on_error; + const char *interpol_char; + + + /* Save old settings */ + prev_cmd_source = pset->cur_cmd_source; + prev_cmd_interactive = pset->cur_cmd_interactive; + + /* Establish new source */ + pset->cur_cmd_source = source; + pset->cur_cmd_interactive = ((source == stdin) && !pset->notty); + + + query_buf = createPQExpBuffer(); + if (!query_buf) { + perror("createPQExpBuffer"); + exit(EXIT_FAILURE); + } + + xcomment = NULL; + in_quote = 0; + paren_level = 0; + slashCmdStatus = CMD_UNKNOWN; /* set default */ + + + /* main loop to get queries and execute them */ + while (!eof) + { + if (slashCmdStatus == CMD_NEWEDIT) + { + /* + * just returned from editing the line? then just copy to the + * input buffer + */ + line = strdup(query_buf->data); + resetPQExpBuffer(query_buf); + /* reset parsing state since we are rescanning whole query */ + xcomment = NULL; + in_quote = 0; + paren_level = 0; + } + else + { + /* + * otherwise, set interactive prompt if necessary + * and get another line + */ + if (pset->cur_cmd_interactive) + { + int prompt_status; + + if (in_quote && in_quote == '\'') + prompt_status = PROMPT_SINGLEQUOTE; + else if (in_quote && in_quote == '"') + prompt_status= PROMPT_DOUBLEQUOTE; + else if (xcomment != NULL) + prompt_status = PROMPT_COMMENT; + else if (query_buf->len > 0) + prompt_status = PROMPT_CONTINUE; + else + prompt_status = PROMPT_READY; + + line = gets_interactive(get_prompt(pset, prompt_status)); + } + else + line = gets_fromFile(source); + } + + + /* Setting these will not have effect until next line */ + die_on_error = GetVariableBool(pset->vars, "die_on_error"); + interpol_char = GetVariable(pset->vars, "sql_interpol");; + + + /* + * query_buf holds query already accumulated. line is the malloc'd + * new line of input (note it must be freed before looping around!) + * query_start is the next command start location within the line. + */ + + /* No more input. Time to quit, or \i done */ + if (line == NULL || (!pset->cur_cmd_interactive && *line == '\0')) + { + if (GetVariableBool(pset->vars, "echo") && !GetVariableBool(pset->vars, "quiet")) + puts("EOF"); + eof = true; + continue; + } + + /* not currently inside an extended comment? */ + if (xcomment) + xcomment = line; + + + /* strip trailing backslashes, they don't have a clear meaning */ + while (1) { + char * cp = strrchr(line, '\\'); + if (cp && (*(cp + 1) == '\0')) + *cp = '\0'; + else + break; + } + + + /* echo back if input is from file and flag is set */ + if (!pset->cur_cmd_interactive && GetVariableBool(pset->vars, "echo")) + fprintf(stderr, "%s\n", line); + + + /* interpolate variables into SQL */ + len = strlen(line); + thislen = PQmblen(line); + + for (i = 0; line[i]; i += (thislen = PQmblen(&line[i])) ) { + if (interpol_char && interpol_char[0] != '\0' && interpol_char[0] == line[i]) { + size_t in_length, out_length; + const char * value; + char * new; + bool closer; /* did we have a closing delimiter or just an end of line? */ + + in_length = strcspn(&line[i+thislen], interpol_char); + closer = line[i + thislen + in_length] == line[i]; + line[i + thislen + in_length] = '\0'; + value = interpolate_var(&line[i + thislen], pset); + out_length = strlen(value); + + new = malloc(len + out_length - (in_length + (closer ? 2 : 1)) + 1); + if (!new) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + new[0] = '\0'; + strncat(new, line, i); + strcat(new, value); + if (closer) + strcat(new, line + i + 2 + in_length); + + free(line); + line = new; + i += out_length; + } + } + + /* nothing left on line? then ignore */ + if (line[0] == '\0') { + free(line); + continue; + } + + slashCmdStatus = CMD_UNKNOWN; + + len = strlen(line); + query_start = 0; + + /* + * Parse line, looking for command separators. + * + * The current character is at line[i], the prior character at + * line[i - prevlen], the next character at line[i + thislen]. + */ + prevlen = 0; + thislen = (len > 0) ? PQmblen(line) : 0; + +#define ADVANCE_1 (prevlen = thislen, i += thislen, thislen = PQmblen(line+i)) + + success = true; + for (i = 0; i < len; ADVANCE_1) { + if (!success && die_on_error) + break; + + + /* was the previous character a backslash? */ + if (i > 0 && line[i - prevlen] == '\\') + was_bslash = true; + else + was_bslash = false; + + + /* in quote? */ + if (in_quote) { + /* end of quote */ + if (line[i] == in_quote && !was_bslash) + in_quote = '\0'; + } + + /* start of quote */ + else if (line[i] == '\'' || line[i] == '"') + in_quote = line[i]; + + /* in extended comment? */ + else if (xcomment != NULL) { + if (line[i] == '*' && line[i + thislen] == '/') { + xcomment = NULL; + ADVANCE_1; + } + } + + /* start of extended comment? */ + else if (line[i] == '/' && line[i + thislen] == '*') { + xcomment = &line[i]; + ADVANCE_1; + } + + /* single-line comment? truncate line */ + else if ((line[i] == '-' && line[i + thislen] == '-') || + (line[i] == '/' && line[i + thislen] == '/')) + { + line[i] = '\0'; /* remove comment */ + break; + } + + /* count nested parentheses */ + else if (line[i] == '(') + paren_level++; + + else if (line[i] == ')' && paren_level > 0) + paren_level--; + + /* semicolon? then send query */ + else if (line[i] == ';' && !was_bslash && paren_level==0) { + line[i] = '\0'; + /* is there anything else on the line? */ + if (line[query_start + strspn(line + query_start, " \t")]!='\0') { + /* insert a cosmetic newline, if this is not the first line in the buffer */ + if (query_buf->len > 0) + appendPQExpBufferChar(query_buf, '\n'); + /* append the line to the query buffer */ + appendPQExpBufferStr(query_buf, line + query_start); + } + + /* execute query */ + success = SendQuery(pset, query_buf->data); + + resetPQExpBuffer(query_buf); + query_start = i + thislen; + } + + /* backslash command */ + else if (was_bslash) { + const char * end_of_cmd = NULL; + + line[i - prevlen] = '\0'; /* overwrites backslash */ + + /* is there anything else on the line? */ + if (line[query_start + strspn(line + query_start, " \t")]!='\0') { + /* insert a cosmetic newline, if this is not the first line in the buffer */ + if (query_buf->len > 0) + appendPQExpBufferChar(query_buf, '\n'); + /* append the line to the query buffer */ + appendPQExpBufferStr(query_buf, line + query_start); + } + + /* handle backslash command */ + + slashCmdStatus = HandleSlashCmds(pset, &line[i], query_buf, &end_of_cmd); + + success = slashCmdStatus != CMD_ERROR; + + if (slashCmdStatus == CMD_SEND) { + success = SendQuery(pset, query_buf->data); + resetPQExpBuffer(query_buf); + query_start = i + thislen; + } + + /* is there anything left after the backslash command? */ + if (end_of_cmd) { + i += end_of_cmd - &line[i]; + query_start = i; + } + else + break; + } + } + + + if (!success && die_on_error && !pset->cur_cmd_interactive) { + successResult = EXIT_USER; + break; + } + + + if (slashCmdStatus == CMD_TERMINATE) { + successResult = EXIT_SUCCESS; + break; + } + + + /* Put the rest of the line in the query buffer. */ + if (line[query_start + strspn(line + query_start, " \t")]!='\0') { + if (query_buf->len > 0) + appendPQExpBufferChar(query_buf, '\n'); + appendPQExpBufferStr(query_buf, line + query_start); + } + + free(line); + + + /* In single line mode, send off the query if any */ + if (query_buf->data[0] != '\0' && GetVariableBool(pset->vars, "singleline")) { + success = SendQuery(pset, query_buf->data); + resetPQExpBuffer(query_buf); + } + + + /* Have we lost the db connection? */ + if (pset->db == NULL && !pset->cur_cmd_interactive) { + successResult = EXIT_BADCONN; + break; + } + } /* while */ + + destroyPQExpBuffer(query_buf); + + pset->cur_cmd_source = prev_cmd_source; + pset->cur_cmd_interactive = prev_cmd_interactive; + + return successResult; +} /* MainLoop() */ + diff --git a/src/bin/psql/mainloop.h b/src/bin/psql/mainloop.h new file mode 100644 index 0000000000..0e2e5dd06e --- /dev/null +++ b/src/bin/psql/mainloop.h @@ -0,0 +1,10 @@ +#ifndef MAINLOOP_H +#define MAINLOOP_H + +#include +#include "settings.h" + +int +MainLoop(PsqlSettings *pset, FILE *source); + +#endif MAINLOOP_H diff --git a/src/bin/psql/print.c b/src/bin/psql/print.c new file mode 100644 index 0000000000..c710f02f28 --- /dev/null +++ b/src/bin/psql/print.c @@ -0,0 +1,975 @@ +#include +#include +#include "print.h" + +#include +#include +#include +#include +#ifndef WIN32 +#include /* for isatty() */ +#include /* for ioctl() */ +#else +#define popen(x,y) _popen(x,y) +#define pclose(x) _pclose(x) +#endif + +#include +#include +#include /* for Oid type */ + +#define DEFAULT_PAGER "/bin/more" + + + +/*************************/ +/* Unaligned text */ +/*************************/ + + +static void +print_unaligned_text(const char * title, char ** headers, char ** cells, char ** footers, + const char * opt_fieldsep, bool opt_barebones, + FILE * fout) +{ + unsigned int col_count = 0; + unsigned int i; + char ** ptr; + + if (!opt_fieldsep) + opt_fieldsep = ""; + + /* print title */ + if (!opt_barebones && title) + fprintf(fout, "%s\n", title); + + /* print headers and count columns */ + for (ptr = headers; *ptr; ptr++) { + col_count++; + if (!opt_barebones) { + if (col_count>1) + fputs(opt_fieldsep, fout); + fputs(*ptr, fout); + } + } + if (!opt_barebones) + fputs("\n", fout); + + /* print cells */ + i = 0; + for (ptr = cells; *ptr; ptr++) { + fputs(*ptr, fout); + if ((i+1) % col_count) + fputs(opt_fieldsep, fout); + else + fputs("\n", fout); + i++; + } + + /* print footers */ + + if (!opt_barebones && footers) + for (ptr = footers; *ptr; ptr++) + fprintf(fout, "%s\n", *ptr); + +} + + + +static void +print_unaligned_vertical(const char * title, char ** headers, char ** cells, char ** footers, + const char * opt_fieldsep, bool opt_barebones, + FILE * fout) +{ + unsigned int col_count = 0; + unsigned int i; + unsigned int record = 1; + char ** ptr; + + if (!opt_fieldsep) + opt_fieldsep = ""; + + /* print title */ + if (!opt_barebones && title) + fprintf(fout, "%s\n", title); + + /* count columns */ + for (ptr = headers; *ptr; ptr++) { + col_count++; + } + + /* print records */ + for (i=0, ptr = cells; *ptr; i++, ptr++) { + if (i % col_count == 0) { + if (!opt_barebones) + fprintf(fout, "-- RECORD %d\n", record++); + else + fputc('\n', fout); + } + fprintf(fout, "%s%s%s\n", headers[i%col_count], opt_fieldsep, *ptr); + } + + /* print footers */ + + if (!opt_barebones && footers) { + fputs("--- END ---\n", fout); + for (ptr = footers; *ptr; ptr++) + fprintf(fout, "%s\n", *ptr); + } +} + + + +/********************/ +/* Aligned text */ +/********************/ + + +/* draw "line" */ +static void +_print_horizontal_line(const unsigned int col_count, const unsigned int * widths, unsigned short border, FILE * fout) +{ + unsigned int i, j; + if (border == 1) + fputc('-', fout); + else if (border == 2) + fputs("+-", fout); + + for (i=0; i widths[i]) + widths[i] = tmp; /* don't wanna call strlen twice */ + + for (i=0, ptr = cells; *ptr; ptr++, i++) + if ((tmp = strlen(*ptr)) > widths[i % col_count]) + widths[i % col_count] = tmp; + + if (opt_border==0) + total_w = col_count - 1; + else if (opt_border==1) + total_w = col_count*3 - 2; + else + total_w = col_count*3 + 1; + + for (i=0; i=total_w) + fprintf(fout, "%s\n", title); + else + fprintf(fout, "%-*s%s\n", (total_w-strlen(title))/2, "", title); + } + + /* print headers */ + if (!opt_barebones) { + if (opt_border==2) + _print_horizontal_line(col_count, widths, opt_border, fout); + + if (opt_border==2) + fputs("| ", fout); + else if (opt_border==1) + fputc(' ', fout); + + for (i=0; i hwidth) + hwidth = tmp; /* don't wanna call strlen twice */ + } + + /* find longest data cell */ + for (ptr = cells; *ptr; ptr++) + if ((tmp = strlen(*ptr)) > dwidth) + dwidth = tmp; + + /* print title */ + if (!opt_barebones && title) + fprintf(fout, "%s\n", title); + + /* make horizontal border */ + divider = malloc(hwidth + dwidth + 10); + if (!divider) { + perror("malloc"); + exit(EXIT_FAILURE); + } + divider[0] = '\0'; + if (opt_border == 2) + strcat(divider, "+-"); + for (i=0; i 0 ? "-" : " "); + if (opt_border > 0) + strcat(divider, "-+-"); + else + strcat(divider, " "); + for (i=0; i 0 ? "-" : " "); + if (opt_border == 2) + strcat(divider, "-+"); + + + /* print records */ + for (i=0, ptr = cells; *ptr; i++, ptr++) { + if (i % col_count == 0) { + if (!opt_barebones) { + char * div_copy = strdup(divider); + char * record_str = malloc(32); + size_t record_str_len; + + if (!div_copy || !record_str) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + if (opt_border==0) + sprintf(record_str, "* Record %d", record++); + else + sprintf(record_str, "[ RECORD %d ]", record++); + record_str_len = strlen(record_str); + if (record_str_len + opt_border > strlen(div_copy)) { + void * new; + new = realloc(div_copy, record_str_len + opt_border); + if (!new) { + perror("realloc"); + exit(EXIT_FAILURE); + } + div_copy = new; + } + strncpy(div_copy + opt_border, record_str, record_str_len); + fprintf(fout, "%s\n", div_copy); + free(record_str); + free(div_copy); + } + else if (i != 0 && opt_border < 2) + fprintf(fout, "%s\n", divider); + } + if (opt_border == 2) + fputs("| ", fout); + fprintf(fout, "%-*s", hwidth, headers[i%col_count]); + if (opt_border > 0) + fputs(" | ", fout); + else + fputs(" ", fout); + + if (opt_border < 2) + fprintf(fout, "%s\n", *ptr); + else + fprintf(fout, "%-*s |\n", dwidth, *ptr); + } + + if (opt_border == 2) + fprintf(fout, "%s\n", divider); + + + /* print footers */ + + if (!opt_barebones && footers && *footers) { + if (opt_border < 2) + fputc('\n', fout); + for (ptr = footers; *ptr; ptr++) + fprintf(fout, "%s\n", *ptr); + } + + fputc('\n', fout); + free(divider); +} + + + + + +/**********************/ +/* HTML printing ******/ +/**********************/ + + +static void +html_escaped_print(const char * in, FILE * fout) +{ + const char * p; + for (p=in; *p; p++) + switch (*p) { + case '&': + fputs("&", fout); + break; + case '<': + fputs("<", fout); + break; + case '>': + fputs(">", fout); + break; + case '\n': + fputs("
", fout); + break; + default: + fputc(*p, fout); + } +} + + + +static void +print_html_text(const char * title, char ** headers, char ** cells, char ** footers, + const char * opt_align, bool opt_barebones, unsigned short int opt_border, + char * opt_table_attr, + FILE * fout) +{ + unsigned int col_count = 0; + unsigned int i; + char ** ptr; + + fprintf(fout, "
\n", fout); + + /* print title */ + if (!opt_barebones && title) { + fputs(" \n", fout); + } + + /* print headers and count columns */ + if (!opt_barebones) + fputs(" \n", fout); + for (i=0, ptr = headers; *ptr; i++, ptr++) { + col_count++; + if (!opt_barebones) { + fputs(" \n", fout); + } + } + if (!opt_barebones) + fputs(" \n", fout); + + /* print cells */ + for (i=0, ptr = cells; *ptr; i++, ptr++) { + if ( i % col_count == 0 ) + fputs(" \n", fout); + + fprintf(fout, " \n", fout); + + if ( (i+1) % col_count == 0 ) + fputs(" \n", fout); + } + + fputs("
", fout); + html_escaped_print(title, fout); + fputs("
", fout); + html_escaped_print(*ptr, fout); + fputs("
", opt_align[(i)%col_count] == 'r' ? "right" : "left"); + if ( (*ptr)[strspn(*ptr, " \t")] == '\0' ) /* is string only whitespace? */ + fputs(" ", fout); + else + html_escaped_print(*ptr, fout); + fputs("
\n", fout); + + /* print footers */ + + if (footers && !opt_barebones) + for (ptr = footers; *ptr; ptr++) { + html_escaped_print(*ptr, fout); + fputs("
\n", fout); + } + + fputc('\n', fout); +} + + + +static void +print_html_vertical(const char * title, char ** headers, char ** cells, char ** footers, + const char * opt_align, bool opt_barebones, unsigned short int opt_border, + char * opt_table_attr, + FILE * fout) +{ + unsigned int col_count = 0; + unsigned int i; + unsigned int record = 1; + char ** ptr; + + fprintf(fout, "\n", fout); + + /* print title */ + if (!opt_barebones && title) { + fputs(" \n", fout); + } + + /* count columns */ + for (ptr = headers; *ptr; ptr++) + col_count++; + + /* print records */ + for (i=0, ptr = cells; *ptr; i++, ptr++) { + if (i % col_count == 0) { + if (!opt_barebones) + fprintf(fout, "\n \n", record++); + else + fputs("\n \n", fout); + } + fputs(" \n" + " \n", fout); + + fprintf(fout, " \n \n", fout); + } + + fputs("
", fout); + html_escaped_print(title, fout); + fputs("
Record %d
 
", fout); + html_escaped_print(headers[i%col_count], fout); + fputs("", opt_align[i%col_count] == 'r' ? "right" : "left"); + if ( (*ptr)[strspn(*ptr, " \t")] == '\0' ) /* is string only whitespace? */ + fputs(" ", fout); + else + html_escaped_print(*ptr, fout); + fputs("
\n", fout); + + /* print footers */ + if (footers && !opt_barebones) + for (ptr = footers; *ptr; ptr++) { + html_escaped_print(*ptr, fout); + fputs("
\n", fout); + } + + fputc('\n', fout); +} + + + +/*************************/ +/* LaTeX */ +/*************************/ + + +static void +latex_escaped_print(const char * in, FILE * fout) +{ + const char * p; + for (p=in; *p; p++) + switch (*p) { + case '&': + fputs("\\&", fout); + break; + case '%': + fputs("\\%", fout); + break; + case '$': + fputs("\\$", fout); + break; + case '{': + fputs("\\{", fout); + break; + case '}': + fputs("\\}", fout); + break; + case '\\': + fputs("\\backslash", fout); + break; + case '\n': + fputs("\\\\", fout); + break; + default: + fputc(*p, fout); + } +} + + + +static void +print_latex_text(const char * title, char ** headers, char ** cells, char ** footers, + const char * opt_align, bool opt_barebones, unsigned short int opt_border, + FILE * fout) +{ + unsigned int col_count = 0; + unsigned int i; + const char * cp; + char ** ptr; + + + /* print title */ + if (!opt_barebones && title) { + fputs("\begin{center}\n", fout); + latex_escaped_print(title, fout); + fputs("\n\end{center}\n\n", fout); + } + + /* begin environment and set alignments and borders */ + fputs("\\begin{tabular}{", fout); + if (opt_border==0) + fputs(opt_align, fout); + else if (opt_border==1) { + for (cp = opt_align; *cp; cp++) { + if (cp != opt_align) fputc('|', fout); + fputc(*cp, fout); + } + } + else if (opt_border==2) { + for (cp = opt_align; *cp; cp++) { + fputc('|', fout); + fputc(*cp, fout); + } + fputc('|', fout); + } + fputs("}\n", fout); + + if (!opt_barebones && opt_border==2) + fputs("\\hline\n", fout); + + /* print headers and count columns */ + for (i=0, ptr = headers; *ptr; i++, ptr++) { + col_count++; + if (!opt_barebones) { + if (i!=0) + fputs(" & ", fout); + latex_escaped_print(*ptr, fout); + } + } + + if (!opt_barebones) { + fputs(" \\\\\n", fout); + fputs("\\hline\n", fout); + } + + /* print cells */ + for (i=0, ptr = cells; *ptr; i++, ptr++) { + latex_escaped_print(*ptr, fout); + + if ( (i+1) % col_count == 0 ) + fputs(" \\\\\n", fout); + else + fputs(" & ", fout); + } + + if (opt_border==2) + fputs("\\hline\n", fout); + + fputs("\\end{tabular}\n\n", fout); + + + /* print footers */ + + if (footers && !opt_barebones) + for (ptr = footers; *ptr; ptr++) { + latex_escaped_print(*ptr, fout); + fputs(" \\\\\n", fout); + } + + fputc('\n', fout); +} + + + +static void +print_latex_vertical(const char * title, char ** headers, char ** cells, char ** footers, + const char * opt_align, bool opt_barebones, unsigned short int opt_border, + FILE * fout) +{ + unsigned int col_count = 0; + unsigned int i; + char ** ptr; + unsigned int record = 1; + + (void)opt_align; /* currently unused parameter */ + + /* print title */ + if (!opt_barebones && title) { + fputs("\begin{center}\n", fout); + latex_escaped_print(title, fout); + fputs("\n\end{center}\n\n", fout); + } + + /* begin environment and set alignments and borders */ + fputs("\\begin{tabular}{", fout); + if (opt_border==0) + fputs("cl", fout); + else if (opt_border==1) + fputs("c|l", fout); + else if (opt_border==2) + fputs("|c|l|", fout); + fputs("}\n", fout); + + + /* count columns */ + for (ptr = headers; *ptr; ptr++) + col_count++; + + + /* print records */ + for (i=0, ptr = cells; *ptr; i++, ptr++) { + /* new record */ + if (i % col_count == 0) { + if (!opt_barebones) { + if (opt_border == 2) + fputs("\\hline\n", fout); + fprintf(fout, "\\multicolumn{2}{c}{Record %d} \\\\\n", record++); + } + if (opt_border >= 1) + fputs("\\hline\n", fout); + } + + latex_escaped_print(headers[i%col_count], fout); + fputs(" & ", fout); + latex_escaped_print(*ptr, fout); + fputs(" \\\\\n", fout); + } + + if (opt_border==2) + fputs("\\hline\n", fout); + + fputs("\\end{tabular}\n\n", fout); + + + /* print footers */ + + if (footers && !opt_barebones) + for (ptr = footers; *ptr; ptr++) { + latex_escaped_print(*ptr, fout); + fputs(" \\\\\n", fout); + } + + fputc('\n', fout); +} + + + + + + +/********************************/ +/* Public functions */ +/********************************/ + + +void +printTable(const char * title, char ** headers, char ** cells, char ** footers, + const char * align, + const printTableOpt * opt, FILE * fout) +{ + char * default_footer[] = { NULL }; + unsigned short int border = opt->border; + FILE * pager = NULL, + * output; + + + if (opt->format == PRINT_NOTHING) + return; + + if (!footers) + footers = default_footer; + + if (opt->format != PRINT_HTML && border > 2) + border = 2; + + + /* check whether we need / can / are supposed to use pager */ + if (fout == stdout && opt->pager +#ifndef WIN32 + && + isatty(fileno(stdin)) && + isatty(fileno(stdout)) +#endif + ) + { + const char * pagerprog; +#ifdef TIOCGWINSZ + unsigned int col_count=0, row_count=0, lines; + char ** ptr; + int result; + struct winsize screen_size; + + /* rough estimate of columns and rows */ + if (headers) + for (ptr=headers; *ptr; ptr++) + col_count++; + if (cells) + for (ptr=cells; *ptr; ptr++) + row_count++; + row_count /= col_count; + + if (opt->expanded) + lines = (col_count+1) * row_count; + else + lines = row_count+1; + if (!opt->tuples_only) + lines += 5; + + result = ioctl(fileno(stdout), TIOCGWINSZ, &screen_size); + if (result==-1 || lines > screen_size.ws_row) { +#endif + pagerprog = getenv("PAGER"); + if (!pagerprog) pagerprog = DEFAULT_PAGER; + pager = popen(pagerprog, "w"); +#ifdef TIOCGWINSZ + } +#endif + } + + if (pager) { + output = pager; + pqsignal(SIGPIPE, SIG_IGN); + } + else + output = fout; + + + /* print the stuff */ + + switch (opt->format) { + case PRINT_UNALIGNED: + if (opt->expanded) + print_unaligned_vertical(title, headers, cells, footers, opt->fieldSep, opt->tuples_only, output); + else + print_unaligned_text(title, headers, cells, footers, opt->fieldSep, opt->tuples_only, output); + break; + case PRINT_ALIGNED: + if (opt->expanded) + print_aligned_vertical(title, headers, cells, footers, opt->tuples_only, border, output); + else + print_aligned_text(title, headers, cells, footers, align, opt->tuples_only, border, output); + break; + case PRINT_HTML: + if (opt->expanded) + print_html_vertical(title, headers, cells, footers, align, opt->tuples_only, border, opt->tableAttr, output); + else + print_html_text(title, headers, cells, footers, align, opt->tuples_only, border, opt->tableAttr, output); + break; + case PRINT_LATEX: + if (opt->expanded) + print_latex_vertical(title, headers, cells, footers, align, opt->tuples_only, border, output); + else + print_latex_text(title, headers, cells, footers, align, opt->tuples_only, border, output); + break; + default: + fprintf(stderr, "+ Oops, you shouldn't see this!\n"); + } + + if (pager) { + pclose(pager); + pqsignal(SIGPIPE, SIG_DFL); + } +} + + + +void +printQuery(PGresult * result, const printQueryOpt * opt, FILE * fout) +{ + int nfields; + char ** headers; + char ** cells; + char ** footers; + char * align; + int i; + + /* extract headers */ + + nfields = PQnfields(result); + + headers = calloc(nfields+1, sizeof(*headers)); + if (!headers) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + for (i=0; inullPrint ? opt->nullPrint : ""; + else + cells[i] = PQgetvalue(result, i / nfields, i % nfields); + } + + /* set footers */ + + if (opt->footers) + footers = opt->footers; + else if (!opt->topt.expanded) { + footers = calloc(2, sizeof(*footers)); + if (!footers) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + footers[0] = malloc(100); + if (PQntuples(result)==1) + strcpy(footers[0], "(1 row)"); + else + sprintf(footers[0], "(%d rows)", PQntuples(result)); + } + else + footers = NULL; + + /* set alignment */ + + align = calloc(nfields+1, sizeof(*align)); + if (!align) { + perror("calloc"); + exit(EXIT_FAILURE); + } + + for (i=0; i=26 && ftype <=30) || /* ?id */ + ftype == 700 || /* float4 */ + ftype == 701 || /* float8 */ + ftype == 790 || /* money */ + ftype == 1700 /* numeric */ + ) + align[i] = 'r'; + else + align[i] = 'l'; + } + + /* call table printer */ + + printTable(opt->title, headers, cells, footers ? footers : opt->footers, align, + &opt->topt, fout); + + free(headers); + free(cells); + if (footers) { + free(footers[0]); + free(footers); + } +} + + +/* the end */ diff --git a/src/bin/psql/print.h b/src/bin/psql/print.h new file mode 100644 index 0000000000..d24e41e853 --- /dev/null +++ b/src/bin/psql/print.h @@ -0,0 +1,66 @@ +#ifndef PRINT_H +#define PRINT_H + +#include +#include + +#include +#include + +enum printFormat { + PRINT_NOTHING = 0, /* to make sure someone initializes this */ + PRINT_UNALIGNED, + PRINT_ALIGNED, + PRINT_HTML, + PRINT_LATEX + /* add your favourite output format here ... */ +}; + + +typedef struct _printTableOpt { + enum printFormat format; /* one of the above */ + bool expanded; /* expanded/vertical output (if supported by output format) */ + bool pager; /* use pager for output (if to stdout and stdout is a tty) */ + bool tuples_only; /* don't output headers, row counts, etc. */ + unsigned short int border; /* Print a border around the table. 0=none, 1=dividing lines, 2=full */ + char *fieldSep; /* field separator for unaligned text mode */ + char *tableAttr; /* attributes for HTML */ +} printTableOpt; + + +/* + * Use this to print just any table in the supported formats. + * - title is just any string (NULL is fine) + * - headers is the column headings (NULL ptr terminated). It must be given and + * complete since the column count is generated from this. + * - cells are the data cells to be printed. Now you know why the correct + * column count is important + * - footers are lines to be printed below the table + * - align is an 'l' or an 'r' for every column, if the output format needs it. + * (You must specify this long enough. Otherwise anything could happen.) +*/ +void +printTable(const char * title, char ** headers, char ** cells, char ** footers, + const char * align, + const printTableOpt * opt, FILE * fout); + + + +typedef struct _printQueryOpt { + printTableOpt topt; /* the options above */ + char * nullPrint; /* how to print null entities */ + bool quote; /* quote all values as much as possible */ + char * title; /* override title */ + char ** footers; /* override footer (default is "(xx rows)") */ +} printQueryOpt; + +/* + * Use this to print query results + * + * It calls the printTable above with all the things set straight. + */ +void +printQuery(PGresult * result, const printQueryOpt * opt, FILE * fout); + + +#endif /* PRINT_H */ diff --git a/src/bin/psql/prompt.c b/src/bin/psql/prompt.c new file mode 100644 index 0000000000..a0fa797595 --- /dev/null +++ b/src/bin/psql/prompt.c @@ -0,0 +1,256 @@ +#include +#include +#include "prompt.h" + +#include +#include +#include + +#include + +#include "settings.h" +#include "common.h" + +#ifdef WIN32 +#define popen(x,y) _popen(x,y) +#define pclose(x) _pclose(x) +#endif + + + +/*-------------------------- + * get_prompt + * + * Returns a statically allocated prompt made by interpolating certain + * tcsh style escape sequences into pset->vars "prompt1|2|3". + * (might not be completely multibyte safe) + * + * Defined interpolations are: + * %M - database server hostname (or "." if not TCP/IP) + * %m - like %M but hostname truncated after first dot + * %> - database server port number (or "." if not TCP/IP) + * %n - database user name + * %/ - current database + * %~ - like %/ but "~" when database name equals user name + * %# - "#" if the username is postgres, ">" otherwise + * %R - in prompt1 normally =, or ^ if single line mode, + * or a ! if session is not connected to a database; + * in prompt2 -, *, ', or "; + * in prompt3 nothing + * %? - the error code of the last query (not yet implemented) + * %% - a percent sign + * + * %[0-9] - the character with the given decimal code + * %0[0-7] - the character with the given octal code + * %0x[0-9A-Fa-f] - the character with the given hexadecimal code + * + * %`command` - The result of executing command in /bin/sh with trailing + * newline stripped. + * %$name$ - The value of the psql/environment/magic varible 'name' + * (same rules as for, e.g., \echo $foo) + * (those will not be rescanned for more escape sequences!) + * + * + * If the application-wide prompts became NULL somehow, the returned string + * will be empty (not NULL!). Do not free() the result of this function unless + * you want trouble. + *-------------------------- + */ +const char * +get_prompt(PsqlSettings *pset, promptStatus_t status) +{ +#define MAX_PROMPT_SIZE 256 + static char destination[MAX_PROMPT_SIZE+1]; + char buf[MAX_PROMPT_SIZE+1]; + bool esc = false; + const char *p; + const char * prompt_string; + + if (GetVariable(pset->vars, "quiet")) + return ""; + + if (status == PROMPT_READY) + prompt_string = GetVariable(pset->vars, "prompt1"); + else if (status == PROMPT_CONTINUE || status == PROMPT_SINGLEQUOTE || status == PROMPT_DOUBLEQUOTE || status == PROMPT_COMMENT) + prompt_string = GetVariable(pset->vars, "prompt2"); + else if (status == PROMPT_COPY) + prompt_string = GetVariable(pset->vars, "prompt3"); + else + prompt_string = "? "; + + + destination[0] = '\0'; + + for (p = prompt_string; + p && *p && strlen(destination)db) + strncpy(buf, PQdb(pset->db), MAX_PROMPT_SIZE); + break; + case '~': { + const char * var; + if (pset->db) { + if ( strcmp(PQdb(pset->db), PQuser(pset->db))==0 || + ( (var = getenv("PGDATABASE")) && strcmp(var, PQdb(pset->db))==0) ) + strcpy(buf, "~"); + else + strncpy(buf, PQdb(pset->db), MAX_PROMPT_SIZE); + } + break; + } + /* DB server hostname (long/short) */ + case 'M': + case 'm': + if (pset->db) { + if (PQhost(pset->db)) { + strncpy(buf, PQhost(pset->db), MAX_PROMPT_SIZE); + if (*p == 'm') + buf[strcspn(buf,".")] = '\0'; + } + else + buf[0] = '.'; + } + break; + /* DB server port number */ + case '>': + if (pset->db) { + if (PQhost(pset->db)) + strncpy(buf, PQport(pset->db), MAX_PROMPT_SIZE); + else + buf[0] = '.'; + } + break; + /* DB server user name */ + case 'n': + if (pset->db) + strncpy(buf, PQuser(pset->db), MAX_PROMPT_SIZE); + break; + + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + { + long int l; + char * end; + l = strtol(p, &end, 0); + sprintf(buf, "%c", (unsigned char)l); + p = end-1; + break; + } + + case 'R': + switch(status) { + case PROMPT_READY: + if (!pset->db) + buf[0] = '!'; + else if (!GetVariableBool(pset->vars, "singleline")) + buf[0] = '='; + else + buf[0] = '^'; + break; + case PROMPT_CONTINUE: + buf[0] = '-'; + break; + case PROMPT_SINGLEQUOTE: + buf[0] = '\''; + break; + case PROMPT_DOUBLEQUOTE: + buf[0] = '"'; + break; + case PROMPT_COMMENT: + buf[0] = '*'; + break; + default: + buf[0] = '\0'; + break; + } + + case '?': + /* not here yet */ + break; + + case '#': + { + if (pset->db && strcmp(PQuser(pset->db), "postgres")==0) + buf[0] = '#'; + else + buf[0] = '>'; + + break; + } + + /* execute command */ + case '`': + { + FILE * fd = NULL; + char * file = strdup(p+1); + int cmdend; + cmdend = strcspn(file, "`"); + file[cmdend] = '\0'; + if (file) + fd = popen(file, "r"); + if (fd) { + fgets(buf, MAX_PROMPT_SIZE-1, fd); + pclose(fd); + } + if (buf[strlen(buf)-1] == '\n') + buf[strlen(buf)-1] = '\0'; + free(file); + p += cmdend+1; + break; + } + + /* interpolate variable */ + case '$': + { + char *name; + const char *val; + int nameend; + name = strdup(p+1); + nameend = strcspn(name, "$"); + name[nameend] = '\0'; + val = interpolate_var(name, pset); + if (val) + strncpy(buf, val, MAX_PROMPT_SIZE); + free(name); + p += nameend+1; + break; + } + + + default: + buf[0] = *p; + buf[1] = '\0'; + + } + esc = false; + } + else if (*p == '%') + esc = true; + else + { + buf[0] = *p; + buf[1] = '\0'; + esc = false; + } + + if (!esc) { + strncat(destination, buf, MAX_PROMPT_SIZE-strlen(destination)); + } + } + + destination[MAX_PROMPT_SIZE] = '\0'; + return destination; +} + diff --git a/src/bin/psql/prompt.h b/src/bin/psql/prompt.h new file mode 100644 index 0000000000..f38a2d6f31 --- /dev/null +++ b/src/bin/psql/prompt.h @@ -0,0 +1,19 @@ +#ifndef PROMPT_H +#define PROMPT_H + +#include "settings.h" + +typedef enum _promptStatus { + PROMPT_READY, + PROMPT_CONTINUE, + PROMPT_COMMENT, + PROMPT_SINGLEQUOTE, + PROMPT_DOUBLEQUOTE, + PROMPT_COPY +} promptStatus_t; + +const char * +get_prompt(PsqlSettings *pset, promptStatus_t status); + + +#endif /* PROMPT_H */ diff --git a/src/bin/psql/settings.h b/src/bin/psql/settings.h new file mode 100644 index 0000000000..6ceefd8b44 --- /dev/null +++ b/src/bin/psql/settings.h @@ -0,0 +1,57 @@ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include + +#include +#include + +#include "variables.h" +#include "print.h" + +#define DEFAULT_FIELD_SEP "|" +#define DEFAULT_EDITOR "/bin/vi" + +#define DEFAULT_PROMPT1 "%/%R%# " +#define DEFAULT_PROMPT2 "%/%R%# " +#define DEFAULT_PROMPT3 ">> " + + +typedef struct _psqlSettings +{ + PGconn *db; /* connection to backend */ + FILE *queryFout; /* where to send the query results */ + bool queryFoutPipe; /* queryFout is from a popen() */ + + printQueryOpt popt; + VariableSpace vars; /* "shell variable" repository */ + + char *gfname; /* one-shot file output argument for \g */ + + bool notty; /* stdin or stdout is not a tty (as determined on startup) */ + bool useReadline; /* use libreadline routines */ + bool useHistory; + bool getPassword; /* prompt the user for a username and + password */ + FILE * cur_cmd_source; /* describe the status of the current main loop */ + bool cur_cmd_interactive; + + bool has_client_encoding; /* was PGCLIENTENCODING set on startup? */ +} PsqlSettings; + + + +#ifndef EXIT_SUCCESS +#define EXIT_SUCCESS 0 +#endif + +#ifndef EXIT_FAILURE +#define EXIT_FAILURE 1 +#endif + +#define EXIT_BADCONN 2 + +#define EXIT_USER 3 + +#endif diff --git a/src/bin/psql/sql_help.h b/src/bin/psql/sql_help.h new file mode 100644 index 0000000000..e356888e77 --- /dev/null +++ b/src/bin/psql/sql_help.h @@ -0,0 +1,253 @@ +/* + * This file is automatically generated from the SGML documentation. + * Direct changes here will be overwritten. + */ +#ifndef SQL_HELP_H +#define SQL_HELP_H + +struct _helpStruct +{ + char *cmd; /* the command name */ + char *help; /* the help associated with it */ + char *syntax; /* the syntax associated with it */ +}; + + +static struct _helpStruct QL_HELP[] = { + { "ABORT", + "Aborts the current transaction", + "ABORT [ WORK | TRANSACTION ]" }, + + { "ALTER TABLE", + "Modifies table properties", + "ALTER TABLE table\n [ * ] ADD [ COLUMN ] ER\">coBLE> type\nALTER TABLE table\n [ * ] RENAME [ COLUMN ] ER\">coBLE> TO newcolumn\nALTER TABLE table\n RENAME TO newtable" }, + + { "ALTER USER", + "Modifies user account information", + "ALTER USER username [ WITH PASSWORD password ]\n [ CREATEDB | NOCREATEDB ] [ CREATEUSER | NOCREATEUSER ]\n [ IN GROUP groupname [, ...] ]\n [ VALID UNTIL 'abstime' ]" }, + + { "BEGIN", + "Begins a transaction in chained mode", + "BEGIN [ WORK | TRANSACTION ]" }, + + { "CLOSE", + "Close a cursor", + "CLOSE cursor" }, + + { "CLUSTER", + "Gives storage clustering advice to the server", + "CLUSTER indexname ON table" }, + + { "COMMIT", + "Commits the current transaction", + "COMMIT [ WORK | TRANSACTION ]" }, + + { "COPY", + "Copies data between files and tables", + "COPY [ BINARY ] table [ WITH OIDS ]\n FROM { 'filename' | stdin }\n [ USING DELIMITERS 'delimiter' ]\nCOPY [ BINARY ] table [ WITH OIDS ]\n TO { 'filename' | stdout }\n [ USING DELIMITERS 'delimiter' ]" }, + + { "CREATE AGGREGATE", + "Defines a new aggregate function", + "CREATE AGGREGATE name [ AS ] ( BASETYPE = data_type\n [ , SFUNC1 = sfunc1, STYPE1 = sfunc1_return_type ]\n [ , SFUNC2 = sfunc2, STYPE2 = sfunc2_return_type ]\n [ , FINALFUNC = ffunc ]\n [ , INITCOND1 = initial_condition1 ]\n [ , INITCOND2 = initial_condition2 ] )" }, + + { "CREATE DATABASE", + "Creates a new database", + "CREATE DATABASE name [ WITH LOCATION = 'dbpath' ]" }, + + { "CREATE FUNCTION", + "Defines a new function", + "CREATE FUNCTION name ( [ ftype [, ...] ] )\n RETURNS rtype\n AS definition\n LANGUAGE 'langname'" }, + + { "CREATE INDEX", + "Constructs a secondary index", + "CREATE [ UNIQUE ] INDEX index_name ON table\n [ USING acc_name ] ( column [ ops_name] [, ...] )\nCREATE [ UNIQUE ] INDEX index_name ON table\n [ USING acc_name ] ( func_name( r\">colle> [, ... ]) ops_name )" }, + + { "CREATE LANGUAGE", + "Defines a new language for functions", + "CREATE [ TRUSTED ] PROCEDURAL LANGUAGE 'langname'\n HANDLER call_handler\n LANCOMPILER 'comment'" }, + + { "CREATE OPERATOR", + "Defines a new user operator", + "CREATE OPERATOR name ( PROCEDURE = func_name\n [, LEFTARG = type1 ] [, RIGHTARG = type2 ]\n [, COMMUTATOR = com_op ] [, NEGATOR = neg_op ]\n [, RESTRICT = res_proc ] [, JOIN = join_proc ]\n [, HASHES ] [, SORT1 = left_sort_op ] [, SORT2 = right_sort_op ] )" }, + + { "CREATE RULE", + "Defines a new rule", + "CREATE RULE name AS ON event\n TO object [ WHERE condition ]\n DO [ INSTEAD ] [ action | NOTHING ]" }, + + { "CREATE SEQUENCE", + "Creates a new sequence number generator", + "CREATE SEQUENCE seqname [ INCREMENT increment ]\n [ MINVALUE minvalue ] [ MAXVALUE maxvalue ]\n [ START start ] [ CACHE cache ] [ CYCLE ]" }, + + { "CREATE TABLE", + "Creates a new table", + "CREATE [ TEMPORARY | TEMP ] TABLE table (\n column type\n [ NULL | NOT NULL ] [ UNIQUE ] [ DEFAULT value ]\n [column_constraint_clause | PRIMARY KEY } [ ... ] ]\n [, ... ]\n [, PRIMARY KEY ( column [, ...] ) ]\n [, CHECK ( condition ) ]\n [, table_constraint_clause ]\n ) [ INHERITS ( inherited_table [, ...] ) ]" }, + + { "CREATE TABLE AS", + "Creates a new table", + "CREATE TABLE table [ (column [, ...] ) ]\n AS select_clause" }, + + { "CREATE TRIGGER", + "Creates a new trigger", + "CREATE TRIGGER name { BEFORE | AFTER } { event [OR ...] }\n ON table FOR EACH { ROW | STATEMENT }\n EXECUTE PROCEDURE ER\">funcBLE> ( arguments )" }, + + { "CREATE TYPE", + "Defines a new base data type", + "CREATE TYPE typename ( INPUT = input_function, OUTPUT = output_function\n , INTERNALLENGTH = { internallength | VARIABLE } [ , EXTERNALLENGTH = { externallength | VARIABLE } ]\n [ , DEFAULT = \"default\" ]\n [ , ELEMENT = element ] [ , DELIMITER = delimiter ]\n [ , SEND = send_function ] [ , RECEIVE = receive_function ]\n [ , PASSEDBYVALUE ] )" }, + + { "CREATE USER", + "Creates account information for a new user", + "CREATE USER username\n [ WITH PASSWORD password ]\n [ CREATEDB | NOCREATEDB ] [ CREATEUSER | NOCREATEUSER ]\n [ IN GROUP groupname [, ...] ]\n [ VALID UNTIL 'abstime' ]" }, + + { "CREATE VIEW", + "Constructs a virtual table", + "CREATE VIEW view AS SELECT query" }, + + { "DECLARE", + "Defines a cursor for table access", + "DECLARE cursor [ BINARY ] [ INSENSITIVE ] [ SCROLL ]\n CURSOR FOR query\n [ FOR { READ ONLY | UPDATE [ OF column [, ...] ] ]" }, + + { "DELETE", + "Deletes rows from a table", + "DELETE FROM table [ WHERE condition ]" }, + + { "DROP AGGREGATE", + "Removes the definition of an aggregate function", + "DROP AGGREGATE name type" }, + + { "DROP DATABASE", + "Destroys an existing database", + "DROP DATABASE name" }, + + { "END", + "Commits the current transaction", + "END [ WORK | TRANSACTION ]" }, + + { "DROP FUNCTION", + "Removes a user-defined C function", + "DROP FUNCTION name ( [ type [, ...] ] )" }, + + { "DROP INDEX", + "Removes an index from a database", + "DROP INDEX index_name" }, + + { "DROP LANGUAGE", + "Removes a user-defined procedural language", + "DROP PROCEDURAL LANGUAGE 'name'" }, + + { "DROP OPERATOR", + "Removes an operator from the database", + "DROP OPERATOR id ( type | NONE [,...] )" }, + + { "DROP RULE", + "Removes an existing rule from the database", + "DROP RULE name" }, + + { "DROP SEQUENCE", + "Removes an existing sequence", + "DROP SEQUENCE name [, ...]" }, + + { "DROP TABLE", + "Removes existing tables from a database", + "DROP TABLE name [, ...]" }, + + { "DROP TRIGGER", + "Removes the definition of a trigger", + "DROP TRIGGER name ON table" }, + + { "DROP TYPE", + "Removes a user-defined type from the system catalogs", + "DROP TYPE typename" }, + + { "DROP USER", + "Removes an user account information", + "DROP USER name" }, + + { "DROP VIEW", + "Removes an existing view from a database", + "DROP VIEW name" }, + + { "EXPLAIN", + "Shows statement execution details", + "EXPLAIN [ VERBOSE ] query" }, + + { "FETCH", + "Gets rows using a cursor", + "FETCH [ selector ] [ count ] { IN | FROM } cursor\nFETCH [ RELATIVE ] [ { [ # | ALL | NEXT | PRIOR ] } ] FROM ] cursor" }, + + { "GRANT", + "Grants access privilege to a user, a group or all users", + "GRANT privilege [, ...] ON object [, ...]\n TO { PUBLIC | GROUP group | username }" }, + + { "INSERT", + "Inserts new rows into a table", + "INSERT INTO table [ ( column [, ...] ) ]\n { VALUES ( expression [, ...] ) | SELECT query }" }, + + { "LISTEN", + "Listen for a response on a notify condition", + "LISTEN name" }, + + { "LOAD", + "Dynamically loads an object file", + "LOAD 'filename'" }, + + { "LOCK", + "Explicit lock of a table inside a transaction", + "LOCK [ TABLE ] table\nLOCK [ TABLE ] table IN [ ROW | ACCESS ] { SHARE | EXCLUSIVE } MODE\nLOCK [ TABLE ] table IN SHARE ROW EXCLUSIVE MODE" }, + + { "MOVE", + "Moves cursor position", + "MOVE [ selector ] [ count ] \n { IN | FROM } cursor\n FETCH [ RELATIVE ] [ { [ # | ALL | NEXT | PRIOR ] } ] FROM ] cursor" }, + + { "NOTIFY", + "Signals all frontends and backends listening on a notify condition", + "NOTIFY name" }, + + { "RESET", + "Restores run-time parameters for session to default values", + "RESET variable" }, + + { "REVOKE", + "Revokes access privilege from a user, a group or all users.", + "REVOKE privilege [, ...]\n ON object [, ...]\n FROM { PUBLIC | GROUP ER\">gBLE> | username }" }, + + { "ROLLBACK", + "Aborts the current transaction", + "ROLLBACK [ WORK | TRANSACTION ]" }, + + { "SELECT", + "Retrieve rows from a table or view.", + "SELECT [ ALL | DISTINCT [ ON column ] ]\n expression [ AS name ] [, ...]\n [ INTO [ TEMPORARY | TEMP ] [ TABLE ] new_table ]\n [ FROM table [ alias ] [, ...] ]\n [ WHERE condition ]\n [ GROUP BY column [, ...] ]\n [ HAVING condition [, ...] ]\n [ { UNION [ ALL ] | INTERSECT | EXCEPT } select ]\n [ ORDER BY column [ ASC | DESC ] [, ...] ]\n [ FOR UPDATE [ OF class_name... ] ]\n [ LIMIT { count | ALL } [ { OFFSET | , } count ] ]" }, + + { "SELECT INTO", + "Create a new table from an existing table or view", + "SELECT [ ALL | DISTINCT ] expression [ AS name ] [, ...]\n INTO [TEMP] [ TABLE ] new_table ]\n [ FROM table [alias] [, ...] ]\n [ WHERE condition ]\n [ GROUP BY column [, ...] ]\n [ HAVING condition [, ...] ]\n [ { UNION [ALL] | INTERSECT | EXCEPT } select]\n [ ORDER BY column [ ASC | DESC ] [, ...] ]\n [ FOR UPDATE [OF class_name...]]\n [ LIMIT count [OFFSET|, count]]" }, + + { "SET", + "Set run-time parameters for session", + "SET variable { TO | = } { 'value' | DEFAULT }\nSET TIME ZONE { 'timezone' | LOCAL | DEFAULT }\nSET TRANSACTION ISOLATION LEVEL { READ COMMITTED | SERIALIZABLE }" }, + + { "SHOW", + "Shows run-time parameters for session", + "SHOW keyword" }, + + { "TRUNCATE", + "Close a cursor", + "TRUNCATE TABLE table" }, + + { "UPDATE", + "Replaces values of columns in a table", + "UPDATE table SET R\">colle> = expression [, ...]\n [ FROM fromlist ]\n [ WHERE condition ]" }, + + { "UNLISTEN", + "Stop listening for notification", + "UNLISTEN { notifyname | * }" }, + + { "VACUUM", + "Clean and analyze a Postgres database", + "VACUUM [ VERBOSE ] [ ANALYZE ] [ table ]\nVACUUM [ VERBOSE ] ANALYZE [ ER\">tBLE> [ (column [, ...] ) ] ]" }, + + + { NULL, NULL, NULL } /* End of list marker */ +}; + +#endif /* SQL_HELP_H */ diff --git a/src/bin/psql/startup.c b/src/bin/psql/startup.c new file mode 100644 index 0000000000..480e1bc01b --- /dev/null +++ b/src/bin/psql/startup.c @@ -0,0 +1,543 @@ +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#else +#include +#endif + +#ifdef HAVE_GETOPT_H +#include +#endif + +#include +#include +#include + +#include "settings.h" +#include "command.h" +#include "help.h" +#include "mainloop.h" +#include "common.h" +#include "input.h" +#include "variables.h" +#include "print.h" +#include "describe.h" + + + +static void +process_psqlrc(PsqlSettings * pset); + +static void +showVersion(PsqlSettings *pset, bool verbose); + + +/* Structures to pass information between the option parsing routine + * and the main function + */ +enum _actions { ACT_NOTHING = 0, + ACT_SINGLE_SLASH, + ACT_LIST_DB, + ACT_SHOW_VER, + ACT_SINGLE_QUERY, + ACT_FILE +}; + +struct adhoc_opts { + char * dbname; + char * host; + char * port; + char * username; + enum _actions action; + char * action_string; + bool no_readline; +}; + +static void +parse_options(int argc, char *argv[], PsqlSettings * pset, struct adhoc_opts * options); + + + +/* + * + * main() + * + */ +int +main(int argc, char **argv) +{ + PsqlSettings settings; + struct adhoc_opts options; + int successResult; + + char * username = NULL; + char * password = NULL; + bool need_pass; + + MemSet(&settings, 0, sizeof settings); + + settings.cur_cmd_source = stdin; + settings.cur_cmd_interactive = false; + + settings.vars = CreateVariableSpace(); + settings.popt.topt.format = PRINT_ALIGNED; + settings.queryFout = stdout; + settings.popt.topt.fieldSep = strdup(DEFAULT_FIELD_SEP); + settings.popt.topt.border = 1; + + SetVariable(settings.vars, "prompt1", DEFAULT_PROMPT1); + SetVariable(settings.vars, "prompt2", DEFAULT_PROMPT2); + SetVariable(settings.vars, "prompt3", DEFAULT_PROMPT3); + + settings.notty = (!isatty(fileno(stdin)) || !isatty(fileno(stdout))); + + /* This is obsolete and will be removed very soon. */ +#ifdef PSQL_ALWAYS_GET_PASSWORDS + settings.getPassword = true; +#else + settings.getPassword = false; +#endif + +#ifdef MULTIBYTE + settings.has_client_encoding = (getenv("PGCLIENTENCODING") != NULL); +#endif + + parse_options(argc, argv, &settings, &options); + + if (options.action==ACT_LIST_DB || options.action==ACT_SHOW_VER) + options.dbname = "template1"; + + if (options.username) { + if (strcmp(options.username, "?")==0) + username = simple_prompt("Username: ", 100, true); + else + username = strdup(options.username); + } + + if (settings.getPassword) + password = simple_prompt("Password: ", 100, false); + + /* loop until we have a password if requested by backend */ + do { + need_pass = false; + settings.db = PQsetdbLogin(options.host, options.port, NULL, NULL, options.dbname, username, password); + + if (PQstatus(settings.db)==CONNECTION_BAD && + strcmp(PQerrorMessage(settings.db), "fe_sendauth: no password supplied\n")==0) { + need_pass = true; + free(password); + password = NULL; + password = simple_prompt("Password: ", 100, false); + } + } while (need_pass); + + free(username); + free(password); + + if (PQstatus(settings.db) == CONNECTION_BAD) { + fprintf(stderr, "Connection to database '%s' failed.\n%s\n", PQdb(settings.db), PQerrorMessage(settings.db)); + PQfinish(settings.db); + exit(EXIT_BADCONN); + } + + if (options.action == ACT_LIST_DB) { + int success = listAllDbs(&settings); + PQfinish(settings.db); + exit (!success); + } + + if (options.action == ACT_SHOW_VER) { + showVersion(&settings, true); + PQfinish(settings.db); + exit (EXIT_SUCCESS); + } + + + if (!GetVariable(settings.vars, "quiet") && !settings.notty && !options.action) + { + puts("Welcome to psql, the PostgreSQL interactive terminal.\n" + "(Please type \\copyright to see the distribution terms of PostgreSQL.)"); + +// showVersion(&settings, false); + + puts("\n" + "Type \\h for help with SQL commands,\n" + " \\? for help on internal slash commands,\n" + " \\q to quit,\n" + " \\g or terminate with semicolon to execute query."); + } + + process_psqlrc(&settings); + + initializeInput(options.no_readline ? 0 : 1); + + /* Now find something to do */ + + /* process file given by -f */ + if (options.action == ACT_FILE) + successResult = process_file(options.action_string, &settings) ? 0 : 1; + /* process slash command if one was given to -c */ + else if (options.action == ACT_SINGLE_SLASH) + successResult = HandleSlashCmds(&settings, options.action_string, NULL, NULL) != CMD_ERROR ? 0 : 1; + /* If the query given to -c was a normal one, send it */ + else if (options.action == ACT_SINGLE_QUERY) + successResult = SendQuery(&settings, options.action_string) ? 0 : 1; + /* or otherwise enter interactive main loop */ + else + successResult = MainLoop(&settings, stdin); + + /* clean up */ + finishInput(); + PQfinish(settings.db); + setQFout(NULL, &settings); + DestroyVariableSpace(settings.vars); + + return successResult; +} + + + +/* + * Parse command line options + */ + +#ifdef WIN32 +/* getopt is not in the standard includes on Win32 */ +int getopt(int, char *const[], const char *); +#endif + +static void +parse_options(int argc, char *argv[], PsqlSettings * pset, struct adhoc_opts * options) +{ +#ifdef HAVE_GETOPT_LONG + static struct option long_options[] = { + { "no-align", no_argument, NULL, 'A' }, + { "command", required_argument, NULL, 'c' }, + { "database", required_argument, NULL, 'd' }, + { "dbname", required_argument, NULL, 'd' }, + { "echo", no_argument, NULL, 'e' }, + { "echo-queries", no_argument, NULL, 'e' }, + { "echo-all", no_argument, NULL, 'E' }, + { "echo-all-queries", no_argument, NULL, 'E' }, + { "file", required_argument, NULL, 'f' }, + { "field-sep", required_argument, NULL, 'F' }, + { "host", required_argument, NULL, 'h' }, + { "html", no_argument, NULL, 'H' }, + { "list", no_argument, NULL, 'l' }, + { "no-readline", no_argument, NULL, 'n' }, + { "out", required_argument, NULL, 'o' }, + { "to-file", required_argument, NULL, 'o' }, + { "port", required_argument, NULL, 'p' }, + { "pset", required_argument, NULL, 'P' }, + { "quiet", no_argument, NULL, 'q' }, + { "single-step", no_argument, NULL, 's' }, + { "single-line", no_argument, NULL, 'S' }, + { "tuples-only", no_argument, NULL, 't' }, + { "table-attr", required_argument, NULL, 'T' }, + { "username", required_argument, NULL, 'U' }, + { "expanded", no_argument, NULL, 'x' }, + { "set", required_argument, NULL, 'v' }, + { "variable", required_argument, NULL, 'v' }, + { "version", no_argument, NULL, 'V' }, + { "password", no_argument, NULL, 'W' }, + { "help", no_argument, NULL, '?' }, + }; + + int optindex; +#endif + + extern char *optarg; + extern int optind; + int c; + + MemSet(options, 0, sizeof *options); + +#ifdef HAVE_GETOPT_LONG + while ((c = getopt_long(argc, argv, "Ac:d:eEf:F:lh:Hno:p:P:qsStT:uU:v:VWx?", long_options, &optindex)) != -1) +#else + /* Be sure to leave the '-' in here, so we can catch accidental long options. */ + while ((c = getopt(argc, argv, "Ac:d:eEf:F:lh:Hno:p:P:qsStT:uU:v:VWx?-")) != -1) +#endif + { + switch (c) + { + case 'A': + pset->popt.topt.format = PRINT_UNALIGNED; + break; + case 'c': + options->action_string = optarg; + if (optarg[0] == '\\') + options->action = ACT_SINGLE_SLASH; + else + options->action = ACT_SINGLE_QUERY; + break; + case 'd': + options->dbname = optarg; + break; + case 'e': + SetVariable(pset->vars, "echo", ""); + break; + case 'E': + SetVariable(pset->vars, "echo_secret", ""); + break; + case 'f': + options->action = ACT_FILE; + options->action_string = optarg; + break; + case 'F': + pset->popt.topt.fieldSep = strdup(optarg); + break; + case 'h': + options->host = optarg; + break; + case 'H': + pset->popt.topt.format = PRINT_HTML; + break; + case 'l': + options->action = ACT_LIST_DB; + break; + case 'n': + options->no_readline = true; + break; + case 'o': + setQFout(optarg, pset); + break; + case 'p': + options->port = optarg; + break; + case 'P': + { + char *value; + char *equal_loc; + bool result; + + value = xstrdup(optarg); + equal_loc = strchr(value, '='); + if (!equal_loc) + result = do_pset(value, NULL, &pset->popt, true); + else { + *equal_loc = '\0'; + result = do_pset(value, equal_loc+1, &pset->popt, true); + } + + if (!result) { + fprintf(stderr, "Couldn't set printing paramter %s.\n", value); + exit(EXIT_FAILURE); + } + + free(value); + break; + } + case 'q': + SetVariable(pset->vars, "quiet", ""); + break; + case 's': + SetVariable(pset->vars, "singlestep", ""); + break; + case 'S': + SetVariable(pset->vars, "singleline", ""); + break; + case 't': + pset->popt.topt.tuples_only = true; + break; + case 'T': + pset->popt.topt.tableAttr = xstrdup(optarg); + break; + case 'u': + pset->getPassword = true; + options->username = "?"; + break; + case 'U': + options->username = optarg; + break; + case 'x': + pset->popt.topt.expanded = true; + break; + case 'v': + { + char *value; + char *equal_loc; + + value = xstrdup(optarg); + equal_loc = strchr(value, '='); + if (!equal_loc) { + if (!DeleteVariable(pset->vars, value)) { + fprintf(stderr, "Couldn't delete variable %s.\n", value); + exit(EXIT_FAILURE); + } + } + else { + *equal_loc = '\0'; + if (!SetVariable(pset->vars, value, equal_loc+1)) { + fprintf(stderr, "Couldn't set variable %s to %s.\n", value, equal_loc); + exit(EXIT_FAILURE); + } + } + + free(value); + break; + } + case 'V': + options->action = ACT_SHOW_VER; + break; + case 'W': + pset->getPassword = true; + break; + case '?': + usage(); + exit(EXIT_SUCCESS); + break; +#ifndef HAVE_GETOPT_LONG + case '-': + fprintf(stderr, "This version of psql was compiled without support for long options.\n" + "Use -? for help on invocation options.\n"); + exit(EXIT_FAILURE); + break; +#endif + default: + usage(); + exit(EXIT_FAILURE); + break; + } + } + + /* if we still have arguments, use it as the database name and username */ + while (argc - optind >= 1) { + if (!options->dbname) + options->dbname = argv[optind]; + else if (!options->username) + options->username = argv[optind]; + else + fprintf(stderr, "Warning: extra option %s ignored.\n", argv[optind]); + + optind++; + } +} + + + +/* + * Load /etc/psqlrc or .psqlrc file, if found. + */ +static void +process_psqlrc(PsqlSettings * pset) +{ + char *psqlrc; + char * home; + +#ifdef WIN32 +#define R_OK 0 +#endif + + /* System-wide startup file */ + if (access("/etc/psqlrc-"PG_RELEASE"."PG_VERSION"."PG_SUBVERSION, R_OK) == 0) + process_file("/etc/psqlrc-"PG_RELEASE"."PG_VERSION"."PG_SUBVERSION, pset); + else if (access("/etc/psqlrc", R_OK) == 0) + process_file("/etc/psqlrc", pset); + + /* Look for one in the home dir */ + home = getenv("HOME"); + + if (home) { + psqlrc = (char *) malloc(strlen(home) + 20); + if (!psqlrc) { + perror("malloc"); + exit(EXIT_FAILURE); + } + + sprintf(psqlrc, "%s/.psqlrc-"PG_RELEASE"."PG_VERSION"."PG_SUBVERSION, home); + if (access(psqlrc, R_OK) == 0) + process_file(psqlrc, pset); + else { + sprintf(psqlrc, "%s/.psqlrc", home); + if (access(psqlrc, R_OK) == 0) + process_file(psqlrc, pset); + } + free(psqlrc); + } +} + + + +/* showVersion + * + * Displays the database backend version. + * Also checks against the version psql was compiled for and makes + * sure that there are no problems. + * + * Returns false if there was a problem retrieving the information + * or a mismatch was detected. + */ +static void +showVersion(PsqlSettings *pset, bool verbose) +{ + PGresult *res; + char *versionstr = NULL; + long int release = 0, version = 0, subversion = 0; + + /* get backend version */ + res = PSQLexec(pset, "SELECT version()"); + if (PQresultStatus(res) == PGRES_TUPLES_OK) + versionstr = PQgetvalue(res, 0, 0); + + if (!verbose) { + if (versionstr) puts(versionstr); + PQclear(res); + return; + } + + if (strncmp(versionstr, "PostgreSQL ", 11) == 0) { + char *tmp; + release = strtol(&versionstr[11], &tmp, 10); + version = strtol(tmp+1, &tmp, 10); + subversion = strtol(tmp+1, &tmp, 10); + } + + printf("Server: %s\npsql", versionstr ? versionstr : "(could not connected)"); + + if (strcmp(versionstr, PG_VERSION_STR) != 0) + printf(&PG_VERSION_STR[strcspn(PG_VERSION_STR, " ")]); + printf(" ("__DATE__" "__TIME__")"); + +#ifdef MULTIBYTE + printf(", multibyte"); +#endif +#ifdef HAVE_GETOPT_LONG + printf(", long options"); +#endif +#ifdef USE_READLINE + printf(", readline"); +#endif +#ifdef USE_HISTORY + printf(", history"); +#endif +#ifdef USE_LOCALE + printf(", locale"); +#endif +#ifdef PSQL_ALWAYS_GET_PASSWORDS + printf(", always password"); +#endif +#ifdef USE_ASSERT_CHECKING + printf(", assert checks"); +#endif + + puts(""); + + if (release < 6 || (release == 6 && version < 5)) + puts ("\nWarning: The server you are connected to is potentially too old for this client\n" + "version. You should ideally be using clients and servers from the same\n" + "distribution."); + + PQclear(res); +} diff --git a/src/bin/psql/stringutils.c b/src/bin/psql/stringutils.c index f3cedd39a6..c495fd4966 100644 --- a/src/bin/psql/stringutils.c +++ b/src/bin/psql/stringutils.c @@ -1,119 +1,179 @@ -/*------------------------------------------------------------------------- - * - * stringutils.c - * simple string manipulation routines - * - * Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * $Header: /cvsroot/pgsql/src/bin/psql/stringutils.c,v 1.17 1999/07/17 20:18:24 momjian Exp $ - * - *------------------------------------------------------------------------- - */ -#include - -#include "postgres.h" -#ifndef HAVE_STRDUP -#include "strdup.h" -#endif - +#include +#include #include "stringutils.h" -/* all routines assume null-terminated strings! */ +//#include +#include +#include +#include +//#include -/* The following routines remove whitespaces from the left, right - and both sides of a string */ -/* MODIFIES the string passed in and returns the head of it */ +#include +#ifndef HAVE_STRDUP +#include +#endif +#include -#ifdef NOT_USED -static char * -leftTrim(char *s) + + +static void +unescape_quotes(char *source, char quote, char escape); + + +/* + * Replacement for strtok() (a.k.a. poor man's flex) + * + * The calling convention is similar to that of strtok. + * s - string to parse, if NULL continue parsing the last string + * delim - set of characters that delimit tokens (usually whitespace) + * quote - set of characters that quote stuff, they're not part of the token + * escape - character than can quote quotes + * was_quoted - if not NULL, stores the quoting character if any was encountered + * token_pos - if not NULL, receives a count to the start of the token in the + * parsed string + * + * Note that the string s is _not_ overwritten in this implementation. + */ +char * strtokx(const char *s, + const char *delim, + const char *quote, + char escape, + char * was_quoted, + unsigned int * token_pos) { - char *s2 = s; - int shift = 0; - int j = 0; + static char * storage = NULL; /* store the local copy of the users string here */ + static char * string = NULL; /* pointer into storage where to continue on next call */ + /* variously abused variables: */ + unsigned int offset; + char * start; + char *cp = NULL; - while (isspace(*s)) - { - s++; - shift++; - } - if (shift > 0) - { - while ((s2[j] = s2[j + shift]) != '\0') - j++; - } + if (s) { + free(storage); + storage = strdup(s); + string = storage; + } - return s2; + if (!storage) + return NULL; + + /* skip leading "whitespace" */ + offset = strspn(string, delim); + + /* end of string reached */ + if (string[offset] == '\0') { + /* technically we don't need to free here, but we're nice */ + free(storage); + storage = NULL; + string = NULL; + return NULL; + } + + /* test if quoting character */ + if (quote) + cp = strchr(quote, string[offset]); + + if (cp) { + /* okay, we have a quoting character, now scan for the closer */ + char *p; + start = &string[offset+1]; + + if (token_pos) + *token_pos = start - storage; + + for(p = start; + *p && (*p != *cp || *(p-1) == escape) ; +#ifdef MULTIBYTE + p += PQmblen(p) +#else + p++ +#endif + ); + + /* not yet end of string? */ + if (*p != '\0') { + *p = '\0'; + string = p + 1; + if (was_quoted) + *was_quoted = *cp; + unescape_quotes (start, *cp, escape); + return start; + } + else { + if (was_quoted) + *was_quoted = *cp; + string = p; + + unescape_quotes (start, *cp, escape); + return start; + } + } + + /* otherwise no quoting character. scan till next delimiter */ + start = &string[offset]; + + if (token_pos) + *token_pos = start - storage; + + offset = strcspn(start, delim); + if (was_quoted) + *was_quoted = 0; + + if (start[offset] != '\0') { + start[offset] = '\0'; + string = &start[offset]+1; + + return start; + } + else { + string = &start[offset]; + return start; + } } + + + +/* + * unescape_quotes + * + * Resolves escaped quotes. Used by strtokx above. + */ +static void +unescape_quotes(char *source, char quote, char escape) +{ + char *p; + char *destination, *tmp; + +#ifdef USE_ASSERT_CHECKING + assert(source); #endif -char * -rightTrim(char *s) -{ - char *sEnd, - *bsEnd; - bool in_bs = false; + destination = (char *) calloc(1, strlen(source)+1); + if (!destination) { + perror("calloc"); + exit(EXIT_FAILURE); + } - sEnd = s + strlen(s) - 1; - while (sEnd >= s && isspace(*sEnd)) - sEnd--; - bsEnd = sEnd; - while (bsEnd >= s && *bsEnd == '\\') - { - in_bs = (in_bs == false); - bsEnd--; + tmp = destination; + + for (p = source; *p; p++) + { + char c; + + if (*p == escape && *(p+1) && quote == *(p+1)) { + c = *(p+1); + p++; } - if (in_bs && *sEnd) - sEnd++; - if (sEnd < s) - s[0] = '\0'; else - s[sEnd - s + 1] = '\0'; - return s; + c = *p; + + *tmp = c; + tmp ++; + } + + /* Terminating null character */ + *tmp = '\0'; + + strcpy(source, destination); } - -#ifdef NOT_USED -static char * -doubleTrim(char *s) -{ - strcpy(s, leftTrim(rightTrim(s))); - return s; -} - -#endif - -#ifdef STRINGUTILS_TEST -void -testStringUtils() -{ - static char *tests[] = {" goodbye \n", /* space on both ends */ - "hello world", /* no spaces to trim */ - "", /* empty string */ - "a", /* string with one char */ - " ", /* string with one whitespace */ - NULL_STR}; - - int i = 0; - - while (tests[i] != NULL_STR) - { - char *t; - - t = strdup(tests[i]); - printf("leftTrim(%s) = ", t); - printf("%sEND\n", leftTrim(t)); - t = strdup(tests[i]); - printf("rightTrim(%s) = ", t); - printf("%sEND\n", rightTrim(t)); - t = strdup(tests[i]); - printf("doubleTrim(%s) = ", t); - printf("%sEND\n", doubleTrim(t)); - i++; - } - -} - -#endif diff --git a/src/bin/psql/stringutils.h b/src/bin/psql/stringutils.h index 84c8c77777..f505a7b2c1 100644 --- a/src/bin/psql/stringutils.h +++ b/src/bin/psql/stringutils.h @@ -1,45 +1,14 @@ -/*------------------------------------------------------------------------- - * - * stringutils.h - * - * - * Copyright (c) 1994, Regents of the University of California - * - * $Id: stringutils.h,v 1.8 1999/02/13 23:20:42 momjian Exp $ - * - *------------------------------------------------------------------------- - */ #ifndef STRINGUTILS_H #define STRINGUTILS_H -/* use this for memory checking of alloc and free using Tcl's memory check - package*/ -#ifdef TCL_MEM_DEBUG -#include -#define malloc(x) ckalloc(x) -#define free(x) ckfree(x) -#define realloc(x,y) ckrealloc(x,y) -#endif - -/* string fiddling utilties */ - -/* all routines assume null-terminated strings! as arguments */ - -/* removes whitespaces from the left, right and both sides of a string */ -/* MODIFIES the string passed in and returns the head of it */ -extern char *rightTrim(char *s); - -#ifdef STRINGUTILS_TEST -extern void testStringUtils(); - -#endif - -#ifndef NULL_STR -#define NULL_STR (char*)0 -#endif - -#ifndef NULL -#define NULL 0 -#endif +/* The cooler version of strtok() which knows about quotes and doesn't + * overwrite your input */ +extern char * +strtokx(const char *s, + const char *delim, + const char *quote, + char escape, + char * was_quoted, + unsigned int * token_pos); #endif /* STRINGUTILS_H */ diff --git a/src/bin/psql/variables.c b/src/bin/psql/variables.c new file mode 100644 index 0000000000..ea824cff69 --- /dev/null +++ b/src/bin/psql/variables.c @@ -0,0 +1,132 @@ +#include +#include +#include "variables.h" + +#include +#include + + +VariableSpace CreateVariableSpace(void) +{ + struct _variable *ptr; + + ptr = calloc(1, sizeof *ptr); + if (!ptr) return NULL; + + ptr->name = strdup("@"); + ptr->value = strdup(""); + if (!ptr->name || !ptr->value) { + free(ptr->name); + free(ptr->value); + free(ptr); + return NULL; + } + + return ptr; +} + + + +const char * GetVariable(VariableSpace space, const char * name) +{ + struct _variable *current; + + if (!space) + return NULL; + + if (strspn(name, VALID_VARIABLE_CHARS) != strlen(name)) return NULL; + + for (current = space; current; current = current->next) { +#ifdef USE_ASSERT_CHECKING + assert(current->name); + assert(current->value); +#endif + if (strcmp(current->name, name)==0) + return current->value; + } + + return NULL; +} + + + +bool GetVariableBool(VariableSpace space, const char * name) +{ + return GetVariable(space, name)!=NULL ? true : false; +} + + + +bool SetVariable(VariableSpace space, const char * name, const char * value) +{ + struct _variable *current, *previous; + + if (!space) + return false; + + if (!value) + return DeleteVariable(space, name); + + if (strspn(name, VALID_VARIABLE_CHARS) != strlen(name)) return false; + + for (current = space; current; previous = current, current = current->next) { +#ifdef USE_ASSERT_CHECKING + assert(current->name); + assert(current->value); +#endif + if (strcmp(current->name, name)==0) { + free (current->value); + current->value = strdup(value); + return current->value ? true : false; + } + } + + previous->next = calloc(1, sizeof *(previous->next)); + if (!previous->next) + return false; + previous->next->name = strdup(name); + if (!previous->next->name) + return false; + previous->next->value = strdup(value); + return previous->next->value ? true : false; +} + + + +bool DeleteVariable(VariableSpace space, const char * name) +{ + struct _variable *current, *previous; + + if (!space) + return false; + + if (strspn(name, VALID_VARIABLE_CHARS) != strlen(name)) return false; + + for (current = space, previous = NULL; current; previous = current, current = current->next) { +#ifdef USE_ASSERT_CHECKING + assert(current->name); + assert(current->value); +#endif + if (strcmp(current->name, name)==0) { + free (current->name); + free (current->value); + if (previous) + previous->next = current->next; + free(current); + return true; + } + } + + return true; +} + + + +void DestroyVariableSpace(VariableSpace space) +{ + if (!space) + return; + + DestroyVariableSpace(space->next); + free(space); +} diff --git a/src/bin/psql/variables.h b/src/bin/psql/variables.h new file mode 100644 index 0000000000..9e2dd2e3a7 --- /dev/null +++ b/src/bin/psql/variables.h @@ -0,0 +1,33 @@ +/* This implements a sort of variable repository. One could also think of it + * as cheap version of an associative array. In each one of these + * datastructures you can store name/value pairs. + * + * All functions (should) follow the Shit-In-Shit-Out (SISO) principle, i.e., + * you can pass them NULL pointers and the like and they will return something + * appropriate. + */ + +#ifndef VARIABLES_H +#define VARIABLES_H +#include + +#define VALID_VARIABLE_CHARS "abcdefghijklmnopqrstuvwxyz0123456789_" + +struct _variable { + char * name; + char * value; + struct _variable * next; +}; + +typedef struct _variable * VariableSpace; + + +VariableSpace CreateVariableSpace(void); +const char * GetVariable(VariableSpace space, const char * name); +bool GetVariableBool(VariableSpace space, const char * name); +bool SetVariable(VariableSpace space, const char * name, const char * value); +bool DeleteVariable(VariableSpace space, const char * name); +void DestroyVariableSpace(VariableSpace space); + + +#endif /* VARIABLES_H */ diff --git a/src/bin/psql/win32.mak-old b/src/bin/psql/win32.mak-old new file mode 100644 index 0000000000..0ebc3e512c --- /dev/null +++ b/src/bin/psql/win32.mak-old @@ -0,0 +1,72 @@ +# Makefile for Microsoft Visual C++ 5.0 (or compat) + +!IF "$(OS)" == "Windows_NT" +NULL= +!ELSE +NULL=nul +!ENDIF + +CPP=cl.exe + +OUTDIR=.\Release +INTDIR=.\Release +# Begin Custom Macros +OutDir=.\Release +# End Custom Macros + +ALL : "$(OUTDIR)\psql.exe" + +CLEAN : + -@erase "$(INTDIR)\psql.obj" + -@erase "$(INTDIR)\stringutils.obj" + -@erase "$(INTDIR)\getopt.obj" + -@erase "$(INTDIR)\vc50.idb" + -@erase "$(OUTDIR)\psql.exe" + +"$(OUTDIR)" : + if not exist "$(OUTDIR)/$(NULL)" mkdir "$(OUTDIR)" + +CPP_PROJ=/nologo /ML /W3 /GX /O2 /D "WIN32" /D "NDEBUG" /D "_CONSOLE" /D\ + "_MBCS" /Fp"$(INTDIR)\psql.pch" /YX /Fo"$(INTDIR)\\" /Fd"$(INTDIR)\\" /FD /c \ + /I ..\..\include /I ..\..\interfaces\libpq /D "HAVE_STRDUP" /D "BLCKSZ=8192" + +!IFDEF MULTIBYTE +!IFNDEF MBFLAGS +MBFLAGS="-DMULTIBYTE=$(MULTIBYTE)" +!ENDIF +CPP_PROJ=$(MBFLAGS) $(CPP_PROJ) +!ENDIF + +CPP_OBJS=.\Release/ +CPP_SBRS=. + +LINK32=link.exe +LINK32_FLAGS=kernel32.lib user32.lib gdi32.lib winspool.lib comdlg32.lib\ + advapi32.lib shell32.lib ole32.lib oleaut32.lib uuid.lib odbc32.lib\ + odbccp32.lib wsock32.lib /nologo /subsystem:console /incremental:no\ + /pdb:"$(OUTDIR)\psql.pdb" /machine:I386 /out:"$(OUTDIR)\psql.exe" +LINK32_OBJS= \ + "$(INTDIR)\psql.obj" \ + "$(INTDIR)\stringutils.obj" \ + "$(INTDIR)\getopt.obj" \ + "..\..\interfaces\libpq\Release\libpqdll.lib" + +"$(OUTDIR)\psql.exe" : "$(OUTDIR)" $(DEF_FILE) $(LINK32_OBJS) + $(LINK32) @<< + $(LINK32_FLAGS) $(LINK32_OBJS) +<< + +"$(OUTDIR)\getopt.obj" : "$(OUTDIR)" ..\..\utils\getopt.c + $(CPP) @<< + $(CPP_PROJ) ..\..\utils\getopt.c +<< + +.c{$(CPP_OBJS)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<< + +.cpp{$(CPP_OBJS)}.obj:: + $(CPP) @<< + $(CPP_PROJ) $< +<<