]> granicus.if.org Git - postgresql/commitdiff
Major psql overhaul by Peter Eisentraut.
authorBruce Momjian <bruce@momjian.us>
Thu, 4 Nov 1999 21:56:02 +0000 (21:56 +0000)
committerBruce Momjian <bruce@momjian.us>
Thu, 4 Nov 1999 21:56:02 +0000 (21:56 +0000)
30 files changed:
src/bin/psql/Makefile.in
src/bin/psql/command.c [new file with mode: 0644]
src/bin/psql/command.h [new file with mode: 0644]
src/bin/psql/common.c [new file with mode: 0644]
src/bin/psql/common.h [new file with mode: 0644]
src/bin/psql/copy.c [new file with mode: 0644]
src/bin/psql/copy.h [new file with mode: 0644]
src/bin/psql/create_help.pl [new file with mode: 0644]
src/bin/psql/describe.c [new file with mode: 0644]
src/bin/psql/describe.h [new file with mode: 0644]
src/bin/psql/help.c [new file with mode: 0644]
src/bin/psql/help.h [new file with mode: 0644]
src/bin/psql/input.c [new file with mode: 0644]
src/bin/psql/input.h [new file with mode: 0644]
src/bin/psql/large_obj.c [new file with mode: 0644]
src/bin/psql/large_obj.h [new file with mode: 0644]
src/bin/psql/mainloop.c [new file with mode: 0644]
src/bin/psql/mainloop.h [new file with mode: 0644]
src/bin/psql/print.c [new file with mode: 0644]
src/bin/psql/print.h [new file with mode: 0644]
src/bin/psql/prompt.c [new file with mode: 0644]
src/bin/psql/prompt.h [new file with mode: 0644]
src/bin/psql/settings.h [new file with mode: 0644]
src/bin/psql/sql_help.h [new file with mode: 0644]
src/bin/psql/startup.c [new file with mode: 0644]
src/bin/psql/stringutils.c
src/bin/psql/stringutils.h
src/bin/psql/variables.c [new file with mode: 0644]
src/bin/psql/variables.h [new file with mode: 0644]
src/bin/psql/win32.mak-old [new file with mode: 0644]

index 4e19ed1984a701ba9c93a91f7e4ffc311d251d1a..200a96ac7dd482a15f4251c09caa220950da687b 100644 (file)
@@ -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 (file)
index 0000000..6bcf96c
--- /dev/null
@@ -0,0 +1,1228 @@
+#include <config.h>
+#include <c.h>
+#include "command.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#ifndef WIN32
+#include <sys/types.h>   /* for umask() */
+#include <sys/stat.h>    /* for umask(), stat() */
+#include <unistd.h>      /* for geteuid(), getpid(), stat() */
+#endif
+#include <assert.h>
+
+#include <libpq-fe.h>
+#include <pqexpbuffer.h>
+
+#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", "\"'`", '\\', &quote, &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", "\"'`", '\\', &quote, &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 <filename>\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 <loid> <filename>\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 <filename> [<description>]\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 <loid>\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 <parameter> [<value>]\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 <table ...> 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 <filename>\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 (file)
index 0000000..eeaf085
--- /dev/null
@@ -0,0 +1,49 @@
+#ifndef COMMAND_H
+#define COMMAND_H
+
+#include <config.h>
+#include <c.h>
+
+#include <pqexpbuffer.h>
+
+#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 (file)
index 0000000..71df7bd
--- /dev/null
@@ -0,0 +1,518 @@
+#include <config.h>
+#include <c.h>
+#include "common.h"
+
+#include <stdlib.h>
+#ifdef HAVE_TERMIOS_H
+#include <termios.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#ifndef HAVE_STRDUP
+#include <strdup.h>
+#endif
+#include <signal.h>
+#include <assert.h>
+#ifndef WIN32
+#include <unistd.h> /* for write() */
+#endif
+
+#include <libpq-fe.h>
+#include <pqsignal.h>
+#include <version.h>
+
+#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 (file)
index 0000000..25bda2c
--- /dev/null
@@ -0,0 +1,25 @@
+#ifndef COMMON_H
+#define COMMON_H
+
+#include <c.h>
+#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 (file)
index 0000000..d8d11e3
--- /dev/null
@@ -0,0 +1,390 @@
+#include <config.h>
+#include <c.h>
+#include "copy.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#ifndef WIN32
+#include <unistd.h> /* for isatty */
+#else
+#include <io.h> /* I think */
+#endif
+
+#include <libpq-fe.h>
+
+#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 ['<char>']
+ * (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", "\"", '\\', &quote, NULL);
+    if (!token)
+       error = true;
+    else {
+       if (!quote && strcasecmp(token, "binary")==0) {
+           result->binary = true;
+           token = strtokx(NULL, " \t", "\"", '\\', &quote, 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 (file)
index 0000000..50fe0af
--- /dev/null
@@ -0,0 +1,22 @@
+#ifndef COPY_H
+#define COPY_H
+
+#include <c.h>
+#include <stdio.h>
+#include <libpq-fe.h>
+#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 (file)
index 0000000..c877a8d
--- /dev/null
@@ -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('', <FILE>);
+    close FILE;
+
+    $filecontent =~ m!<refmiscinfo>\s*SQL - Language Statements\s*</refmiscinfo>!i
+       or next;
+
+    $filecontent =~ m!<refname>\s*([a-z ]+?)\s*</refname>!i && ($cmdname = $1);
+    $filecontent =~ m!<refpurpose>\s*(.+?)\s*</refpurpose>!i && ($cmddesc = $1);
+
+    $filecontent =~ m!<synopsis>\s*(.+?)\s*</synopsis>!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 (file)
index 0000000..6304bc1
--- /dev/null
@@ -0,0 +1,816 @@
+#include <config.h>
+#include <c.h>
+#include "describe.h"
+
+#include <string.h>
+
+#include <postgres.h>     /* for VARHDRSZ, int4 type */
+#include <postgres_ext.h>
+#include <libpq-fe.h>
+
+#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; i<PQntuples(res3); i++) {
+               footers[i] = xmalloc(10 + NAMEDATALEN);
+               if (PQntuples(res3)==1)
+                   sprintf(footers[i], "Index: %s", PQgetvalue(res3, i, 0));
+               else if (i==0)
+                   sprintf(footers[i], "Indices: %s", PQgetvalue(res3, i, 0));
+               else
+                   sprintf(footers[i], "         %s", PQgetvalue(res3, i, 0));
+           }
+
+           footers[i] = NULL;
+       }
+    }
+
+
+    myopt.tuples_only = false;
+    printTable(title, headers, cells, footers, "llll", &myopt, pset->queryFout);
+
+    /* clean up */
+    free(title);
+
+    for (i = 0; i<PQntuples(res); i++) {
+       free(cells[i*cols + 1]);
+       free(cells[i*cols + 2]);
+    }
+    free(cells);
+
+    for (ptr = footers; footers && *ptr; ptr++)
+       free(*ptr);
+    free(footers);
+
+    PQclear(res);
+    PQclear(res2);
+    PQclear(res3);
+
+    return true;
+}
+
+
+
+/*
+ * listTables()
+ *
+ * handler for \d, \dt, etc.
+ *
+ * The infotype is an array of characters, specifying what info is desired:
+ * t - tables
+ * i - indices
+ * v - views
+ * s - sequences
+ * S - systems tables (~'^pg_')
+ * (any order of the above is fine)
+ */
+bool
+listTables(const char * infotype, const char * name, PsqlSettings * pset)
+{
+    bool showTables = strchr(infotype, 't') != NULL;
+    bool showIndices= strchr(infotype, 'i') != NULL;
+    bool showViews  = strchr(infotype, 'v') != NULL;
+    bool showSeq    = strchr(infotype, 's') != NULL;
+    bool showSystem = strchr(infotype, 'S') != NULL;
+
+    bool description = GetVariableBool(pset->vars, "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 (file)
index 0000000..8f165b4
--- /dev/null
@@ -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 (file)
index 0000000..0caab0f
--- /dev/null
@@ -0,0 +1,306 @@
+#include <config.h>
+#include <c.h>
+#include "help.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#ifndef WIN32
+#include <sys/ioctl.h>     /* for ioctl() */
+#ifdef HAVE_PWD_H
+#include <pwd.h>           /* for getpwuid() */
+#endif
+#include <sys/types.h>     /* (ditto) */
+#include <unistd.h>        /* for getuid() */
+#else
+#define strcasecmp(x,y) stricmp(x,y)
+#define popen(x,y) _popen(x,y)
+#define pclose(x) _pclose(x)
+#endif
+
+#include <pqsignal.h>
+#include <libpq-fe.h>
+
+#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] [<dbname>|- [<user>|?]] -- connect to new database (currently '%s')\n", PQdb(pset->db));
+    fprintf(fout, " \\copy [binary] <table> [with oids] {from|to} <fname> [with delimiters '<char>']\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 [<object>]- 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 [<fname>] -- edit the current query buffer or <fname> with external editor\n");
+    fprintf(fout, " \\echo <text> -- write text to stdout\n");
+    fprintf(fout, " \\g [<fname>] -- send query to backend (and results in <fname> or |pipe)\n");
+    fprintf(fout, " \\h [<cmd>]   -- help on syntax of sql commands, * for all commands\n");
+    fprintf(fout, " \\i <fname>   -- 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 [<fname>] -- send all query results to <fname>, 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 <text>-- write text to query output stream (see \\o)\n");
+    fprintf(fout, " \\r           -- reset (clear) the query buffer\n");
+    fprintf(fout, " \\s [<fname>] -- print history or save it in <fname>\n");
+    fprintf(fout, " \\set <var> [<value>] -- 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 <fname>   -- write current query buffer to a file\n");
+    fprintf(fout, " \\z           -- list table access permissions\n");
+    fprintf(fout, " \\! [<cmd>]   -- 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 <cmd> or \\help <cmd>, where <cmd> 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 (file)
index 0000000..34bb267
--- /dev/null
@@ -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 (file)
index 0000000..3bb418b
--- /dev/null
@@ -0,0 +1,162 @@
+#include <config.h>
+#include <c.h>
+#include "input.h"
+
+#include <pqexpbuffer.h>
+
+/* 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 (file)
index 0000000..272b057
--- /dev/null
@@ -0,0 +1,56 @@
+#ifndef INPUT_H
+#define INPUT_H
+
+#include <config.h>
+#include <c.h>
+#include <stdio.h>
+#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 <readline.h>
+#define USE_READLINE 1
+#else
+#if defined(HAVE_READLINE_READLINE_H)
+#include <readline/readline.h>
+#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 <history.h>
+#define USE_HISTORY 1
+#else
+#if defined(HAVE_READLINE_HISTORY_H)
+#include <readline/history.h>
+#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 (file)
index 0000000..dfc3943
--- /dev/null
@@ -0,0 +1,311 @@
+#include <config.h>
+#include <c.h>
+#include "large_obj.h"
+
+#include <stdio.h>
+#include <string.h>
+
+#include <libpq-fe.h>
+#include <postgres.h>
+
+#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; i<strlen(comment_arg); i++)
+           if (comment_arg[i]=='\'')
+               strcat(buf, "\\'");
+           else
+               strncat(buf, &comment_arg[i], 1);
+       strcat(buf, "')");
+
+       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_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 (file)
index 0000000..22228e7
--- /dev/null
@@ -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 (file)
index 0000000..2f38ffb
--- /dev/null
@@ -0,0 +1,368 @@
+#include <config.h>
+#include <c.h>
+#include "mainloop.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <pqexpbuffer.h>
+
+#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 (file)
index 0000000..0e2e5dd
--- /dev/null
@@ -0,0 +1,10 @@
+#ifndef MAINLOOP_H
+#define MAINLOOP_H
+
+#include <stdio.h>
+#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 (file)
index 0000000..c710f02
--- /dev/null
@@ -0,0 +1,975 @@
+#include <config.h>
+#include <c.h>
+#include "print.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <math.h>
+#include <signal.h>
+#ifndef WIN32
+#include <unistd.h>      /* for isatty() */
+#include <sys/ioctl.h>   /* for ioctl() */
+#else
+#define popen(x,y) _popen(x,y)
+#define pclose(x) _pclose(x)
+#endif
+
+#include <pqsignal.h>
+#include <libpq-fe.h>
+#include <postgres_ext.h> /* 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<col_count; i++) {
+       for (j=0; j<widths[i]; j++)
+           fputc('-', fout);
+       
+       if (i<col_count-1) {
+           if (border == 0)
+               fputc(' ', fout);
+           else
+               fputs("-+-", fout);
+       }
+    }
+
+    if (border == 2)
+       fputs("-+", fout);
+    else if (border == 1)
+       fputc('-', fout);
+
+    fputc('\n', fout);
+}
+
+
+
+static void
+print_aligned_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, tmp;
+    unsigned int * widths, total_w;
+    char ** ptr;
+
+    /* count columns */
+    for (ptr = headers; *ptr; ptr++)
+       col_count++;
+
+    widths = calloc(col_count, sizeof (*widths));
+    if (!widths) {
+       perror("calloc");
+       exit(EXIT_FAILURE);
+    }
+
+    /* calc column widths */
+    for (i=0; i<col_count; i++)
+       if ((tmp = strlen(headers[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<col_count; i++)
+       total_w += widths[i];
+
+    /* print title */
+    if (title && !opt_barebones) {
+       if (strlen(title)>=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<col_count; i++) {
+           /* centered */
+           fprintf(fout, "%-*s%s%-*s", (int)floor((widths[i]-strlen(headers[i]))/2.0), "", headers[i], (int)ceil((widths[i]-strlen(headers[i]))/2.0) , "");
+
+           if (i<col_count-1) {
+               if (opt_border==0)
+                   fputc(' ', fout);
+               else
+                   fputs(" | ", fout);
+           }
+       }
+
+       if (opt_border==2)
+           fputs(" |", fout);
+       else if (opt_border==1)
+           fputc(' ', fout);;
+       fputc('\n', fout);
+
+       _print_horizontal_line(col_count, widths, opt_border, fout);
+    }
+
+    /* print cells */
+    for (i=0, ptr = cells; *ptr; i++, ptr++) {
+       /* beginning of line */
+       if (i % col_count == 0) {
+           if (opt_border==2)
+               fputs("| ", fout);
+           else if (opt_border==1)
+               fputc(' ', fout);
+       }
+
+       /* content */
+       if (opt_align[(i) % col_count ] == 'r')
+           fprintf(fout, "%*s", widths[i%col_count], cells[i]);
+       else {
+           if ((i+1) % col_count == 0 && opt_border != 2)
+               fputs(cells[i], fout);
+           else
+               fprintf(fout, "%-*s", widths[i%col_count], cells[i]);
+       }
+
+       /* divider */
+       if ((i+1) % col_count) {
+           if (opt_border==0)
+               fputc(' ', fout);
+           else
+               fputs(" | ", fout);
+       }
+       /* end of line */
+       else {
+           if (opt_border==2)
+               fputs(" |", fout);
+           fputc('\n', fout);
+       }
+    }
+
+    if (opt_border==2)
+       _print_horizontal_line(col_count, widths, opt_border, fout);
+
+    /* print footers */
+    if (footers && !opt_barebones)
+       for (ptr =  footers; *ptr; ptr++)
+           fprintf(fout, "%s\n", *ptr);
+
+    fputc('\n', fout);
+
+    /* clean up */
+    free(widths);
+}
+
+
+
+static void
+print_aligned_vertical(const char * title, char ** headers, char ** cells, char ** footers,
+                      bool opt_barebones, unsigned short int opt_border,
+                      FILE * fout)
+{
+    unsigned int col_count = 0;
+    unsigned int record = 1;
+    char ** ptr;
+    unsigned int i, tmp, hwidth=0, dwidth=0;
+    char * divider;
+
+    /* count columns and find longest header */
+    for (ptr = headers; *ptr; ptr++) {
+       col_count++;
+       if ((tmp = strlen(*ptr)) > 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<hwidth; i++) strcat(divider, opt_border > 0 ? "-" : " ");
+    if (opt_border > 0)
+       strcat(divider, "-+-");
+    else
+       strcat(divider, " ");
+    for (i=0; i<dwidth; i++) strcat(divider, opt_border > 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("&amp;", fout);
+           break;
+       case '<':
+           fputs("&lt;", fout);
+           break;
+       case '>':
+           fputs("&gt;", fout);
+           break;
+       case '\n':
+           fputs("<br>", 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, "<table border=%d", opt_border);
+    if (opt_table_attr)
+       fprintf(fout, " %s", opt_table_attr);
+    fputs(">\n", fout);
+
+    /* print title */
+    if (!opt_barebones && title) {
+       fputs("  <caption>", fout);
+       html_escaped_print(title, fout);
+       fputs("</caption>\n", fout);
+    }
+
+    /* print headers and count columns */
+    if (!opt_barebones)
+       fputs("  <tr>\n", fout);
+    for (i=0, ptr = headers; *ptr; i++, ptr++) {
+       col_count++;
+       if (!opt_barebones) {
+           fputs("    <th align=center>", fout);
+           html_escaped_print(*ptr, fout);
+           fputs("</th>\n", fout);
+       }
+    }
+    if (!opt_barebones)
+       fputs("  </tr>\n", fout);
+
+    /* print cells */
+    for (i=0, ptr = cells; *ptr; i++, ptr++) {
+       if ( i % col_count == 0 )
+           fputs("  <tr valign=top>\n", fout);
+
+       fprintf(fout, "    <td align=%s>", opt_align[(i)%col_count] == 'r' ? "right" : "left");
+       if ( (*ptr)[strspn(*ptr, " \t")] == '\0' ) /* is string only whitespace? */
+           fputs("&nbsp;", fout);
+       else
+           html_escaped_print(*ptr, fout);
+       fputs("</td>\n", fout);
+
+       if ( (i+1) % col_count == 0 )
+           fputs("  </tr>\n", fout);
+    }
+
+    fputs("</table>\n", fout);
+
+    /* print footers */
+
+    if (footers && !opt_barebones)
+       for (ptr =  footers; *ptr; ptr++) {
+           html_escaped_print(*ptr, fout);
+           fputs("<br>\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, "<table border=%d", opt_border);
+    if (opt_table_attr)
+       fprintf(fout, " %s", opt_table_attr);
+    fputs(">\n", fout);
+
+    /* print title */
+    if (!opt_barebones && title) {
+       fputs("  <caption>", fout);
+       html_escaped_print(title, fout);
+       fputs("</caption>\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  <tr><td colspan=2 align=center>Record %d</td></tr>\n", record++);
+           else
+               fputs("\n  <tr><td colspan=2>&nbsp;</td></tr>\n", fout);
+       }
+       fputs("  <tr valign=top>\n"
+             "    <th>", fout);
+       html_escaped_print(headers[i%col_count], fout);
+       fputs("</th>\n", fout);
+
+       fprintf(fout, "    <td align=%s>", opt_align[i%col_count] == 'r' ? "right" : "left");
+       if ( (*ptr)[strspn(*ptr, " \t")] == '\0' ) /* is string only whitespace? */
+           fputs("&nbsp;", fout);
+       else
+           html_escaped_print(*ptr, fout);
+       fputs("</td>\n  </tr>\n", fout);
+    }
+
+    fputs("</table>\n", fout);
+
+    /* print footers */
+    if (footers && !opt_barebones)
+       for (ptr =  footers; *ptr; ptr++) {
+           html_escaped_print(*ptr, fout);
+           fputs("<br>\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; i<nfields; i++)
+       headers[i] = PQfname(result, i);
+
+    /* set cells */
+
+    cells = calloc(nfields * PQntuples(result) + 1, sizeof(*cells));
+    if (!cells) {
+       perror("calloc");
+       exit(EXIT_FAILURE);
+    }
+
+    for (i=0; i< nfields * PQntuples(result); i++) {
+       if (PQgetisnull(result, i / nfields, i % nfields))
+           cells[i] = opt->nullPrint ? 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<nfields; i++) {
+       Oid ftype = PQftype(result, i);
+       if ( ftype == 20 ||  /* int8 */
+            ftype == 21 ||  /* int2 */
+            ftype == 23 ||  /* int4 */
+            (ftype >=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 (file)
index 0000000..d24e41e
--- /dev/null
@@ -0,0 +1,66 @@
+#ifndef PRINT_H
+#define PRINT_H
+
+#include <config.h>
+#include <c.h>
+
+#include <stdio.h>
+#include <libpq-fe.h>
+
+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 <table ...> */
+} 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 (file)
index 0000000..a0fa797
--- /dev/null
@@ -0,0 +1,256 @@
+#include <config.h>
+#include <c.h>
+#include "prompt.h"
+
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libpq-fe.h>
+
+#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)<MAX_PROMPT_SIZE;
+        p++)
+    {
+       MemSet(buf, 0, MAX_PROMPT_SIZE+1);
+       if (esc)
+       {
+           switch (*p)
+           {
+           case '%':
+               strcpy(buf, "%");
+               break;
+
+               /* Current database */
+           case '/':
+               if (pset->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 (file)
index 0000000..f38a2d6
--- /dev/null
@@ -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 (file)
index 0000000..6ceefd8
--- /dev/null
@@ -0,0 +1,57 @@
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <libpq-fe.h>
+#include <c.h>
+
+#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 (file)
index 0000000..e356888
--- /dev/null
@@ -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 (file)
index 0000000..480e1bc
--- /dev/null
@@ -0,0 +1,543 @@
+#include <config.h>
+#include <c.h>
+
+#include <signal.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <assert.h>
+
+#ifdef WIN32
+#include <io.h>
+#include <window.h>
+#else
+#include <unistd.h>
+#endif
+
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+
+#include <libpq-fe.h>
+#include <pqsignal.h>
+#include <version.h>
+
+#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);
+}
index f3cedd39a684987cdf8deb43fb1840f00f49f35a..c495fd496601376c46430f4b6444d6c3b3c558a8 100644 (file)
-/*-------------------------------------------------------------------------
- *
- * 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 <ctype.h>
+#include <config.h>
+#include <c.h>
+#include "stringutils.h"
+
+//#include <ctype.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+//#include <stdio.h>
 
-#include "postgres.h"
+#include <postgres.h>
 #ifndef HAVE_STRDUP
-#include "strdup.h"
+#include <strdup.h>
 #endif
+#include <libpq-fe.h>
+
 
-#include "stringutils.h"
 
-/* all routines assume null-terminated strings! */
+static void
+unescape_quotes(char *source, char quote, char escape);
 
-/* 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 */
 
-#ifdef NOT_USED
-static char *
-leftTrim(char *s)
+/*
+ * 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;
-
-       while (isspace(*s))
-       {
-               s++;
-               shift++;
+    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;
+
+    if (s) {
+       free(storage);
+       storage = strdup(s);
+       string = storage;
+    }
+
+    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;
        }
-       if (shift > 0)
-       {
-               while ((s2[j] = s2[j + shift]) != '\0')
-                       j++;
+       else {
+           if (was_quoted)
+               *was_quoted = *cp;
+           string = p;
+
+           unescape_quotes (start, *cp, escape);
+           return start;
        }
+    }
 
-       return s2;
-}
+    /* otherwise no quoting character. scan till next delimiter */
+    start = &string[offset];
 
-#endif
+    if (token_pos)
+       *token_pos = start - storage;
 
-char *
-rightTrim(char *s)
-{
-       char       *sEnd,
-                          *bsEnd;
-       bool            in_bs = false;
-
-       sEnd = s + strlen(s) - 1;
-       while (sEnd >= s && isspace(*sEnd))
-               sEnd--;
-       bsEnd = sEnd;
-       while (bsEnd >= s && *bsEnd == '\\')
-       {
-               in_bs = (in_bs == false);
-               bsEnd--;
-       }
-       if (in_bs && *sEnd)
-               sEnd++;
-       if (sEnd < s)
-               s[0] = '\0';
-       else
-               s[sEnd - s + 1] = '\0';
-       return s;
+    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;
+    }
 }
 
-#ifdef NOT_USED
-static char *
-doubleTrim(char *s)
+
+
+
+/*
+ * unescape_quotes
+ *
+ * Resolves escaped quotes. Used by strtokx above.
+ */
+static void
+unescape_quotes(char *source, char quote, char escape)
 {
-       strcpy(s, leftTrim(rightTrim(s)));
-       return s;
-}
+    char *p;
+    char *destination, *tmp;
 
+#ifdef USE_ASSERT_CHECKING
+    assert(source);
 #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++;
+    destination = (char *) calloc(1, strlen(source)+1);
+    if (!destination) {
+       perror("calloc");
+       exit(EXIT_FAILURE);
+    }
+
+    tmp = destination;
+
+    for (p = source; *p; p++)
+    {
+       char c;
+
+       if (*p == escape && *(p+1) && quote == *(p+1)) {
+           c = *(p+1);
+           p++;
        }
+       else
+           c = *p;
 
-}
+       *tmp = c;
+       tmp ++;
+    }
 
-#endif
+    /* Terminating null character */
+    *tmp = '\0';
+
+    strcpy(source, destination);
+}
index 84c8c77777f4645e267ddb68825d5c893b5f1ab9..f505a7b2c14ee532c22d857f4b2e8991d266945b 100644 (file)
@@ -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 <tcl.h>
-#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 (file)
index 0000000..ea824cf
--- /dev/null
@@ -0,0 +1,132 @@
+#include <config.h>
+#include <c.h>
+#include "variables.h"
+
+#include <stdlib.h>
+#include <assert.h>
+
+
+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 (file)
index 0000000..9e2dd2e
--- /dev/null
@@ -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 <c.h>
+
+#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 (file)
index 0000000..0ebc3e5
--- /dev/null
@@ -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) $< 
+<<