]> granicus.if.org Git - postgresql/commitdiff
Add frontend \copy command.
authorBryan Henderson <bryanh@giraffe.netgate.net>
Mon, 4 Nov 1996 09:17:55 +0000 (09:17 +0000)
committerBryan Henderson <bryanh@giraffe.netgate.net>
Mon, 4 Nov 1996 09:17:55 +0000 (09:17 +0000)
src/bin/psql/psql.c

index 98d678290d91291fe7816d904663763e23afdc29..5cf3762f411e3d365a1191c0232c4711bc7e1b79 100644 (file)
@@ -1,4 +1,4 @@
-/*-------------------------------------------------------------------------
+*-------------------------------------------------------------------------
  *
  * psql.c--
  *    an interactive front-end to postgres95
@@ -7,7 +7,7 @@
  *
  *
  * IDENTIFICATION
- *    $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.23 1996/10/14 00:33:47 momjian Exp $
+ *    $Header: /cvsroot/pgsql/src/bin/psql/Attic/psql.c,v 1.24 1996/11/04 09:17:55 bryanh Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -24,7 +24,7 @@
 #include "psqlHelp.h"
 
 #ifdef NOREADLINE
-extern char *readline(char *); /* in rlstubs.c */
+extern char *readline(char *);  /* in rlstubs.c */
 #else
 /* from the GNU readline library */
 #ifdef OLD_READLINE
@@ -38,20 +38,20 @@ extern char *readline(char *);      /* in rlstubs.c */
 
 #define MAX_QUERY_BUFFER 20000
 
-#define COPYBUFSIZ     8192
+#define COPYBUFSIZ  8192
 
 #define DEFAULT_FIELD_SEP "|"
 #define DEFAULT_EDITOR  "vi"
 #define DEFAULT_SHELL  "/bin/sh"
 
 typedef struct _psqlSettings {
-    PGconn *db;                   /* connection to backend */
+    PGconn *db;        /* connection to backend */
     FILE *queryFout;       /* where to send the query results */
     PQprintOpt opt;        /* options to be passed to PQprint */
-    char *prompt;         /* prompt to display */
-    char *gfname;         /* one-shot file output argument for \g */
-    bool notty;                   /* input or output is not a tty */
-    bool pipe;            /* queryFout is from a popen() */
+    char *prompt;      /* prompt to display */
+    char *gfname;      /* one-shot file output argument for \g */
+    bool notty;        /* input or output is not a tty */
+    bool pipe;         /* queryFout is from a popen() */
     bool echoQuery;        /* echo the query before sending it */
     bool quiet;            /* run quietly, no messages, no promt */
     bool singleStep;       /* prompt before for each query */ 
@@ -62,8 +62,9 @@ typedef struct _psqlSettings {
 /* declarations for functions in this file */
 static void usage(char *progname);
 static void slashUsage();
-static void handleCopyOut(PGresult *res, bool quiet);
-static void handleCopyIn(PGresult *res, bool quiet);
+static void handleCopyOut(PGresult *res, bool quiet, FILE *copystream);
+static void handleCopyIn(PGresult *res, const bool mustprompt,
+                         FILE *copystream);
 static int tableList(PsqlSettings *ps, bool deep_tablelist);
 static int tableDesc(PsqlSettings *ps, char *table);
 
@@ -71,16 +72,17 @@ char *gets_noreadline(char *prompt, FILE *source);
 char *gets_readline(char *prompt, FILE *source);
 char *gets_fromFile(char *prompt, FILE *source);
 int listAllDbs(PsqlSettings *settings);
-int SendQuery(PsqlSettings *settings, char *query);
+void SendQuery(bool *success_p, PsqlSettings *settings, const char *query,
+               const bool copy_in, const bool copy_out, FILE *copystream);
 int HandleSlashCmds(PsqlSettings *settings,
-                   char *line,
-                   char *query);
+            char *line,
+            char *query);
 int MainLoop(PsqlSettings *settings, FILE *source);
 /* probably should move this into libpq */
 void PQprint(FILE *fp,
                      PGresult *res, 
-                    PQprintOpt *po
-                    );
+             PQprintOpt *po
+             );
 
 FILE *setFout(PsqlSettings *ps, char *fname);
 
@@ -122,7 +124,7 @@ usage(char *progname)
 
 char *on(bool f)
 {
-       return f? "on": "off";
+    return f? "on": "off";
 }
 
 static void
@@ -131,7 +133,8 @@ slashUsage(PsqlSettings *ps)
   fprintf(stderr,"\t \\?           -- help\n");
   fprintf(stderr,"\t \\a           -- toggle field-alignment (currenty %s)\n", on(ps->opt.align));
   fprintf(stderr,"\t \\C [<captn>] -- set html3 caption (currently '%s')\n", ps->opt.caption? ps->opt.caption: "");
-  fprintf(stderr,"\t \\c <dbname>  -- connect to new database (currently '%s')\n", PQdb(ps->db));
+  fprintf(stderr,"\t \\connect <dbname>  -- connect to new database (currently '%s')\n", PQdb(ps->db));
+  fprintf(stderr,"\t \\copy <dbname>     -- copy table to/from a file\n");
   fprintf(stderr,"\t \\d [<table>] -- list tables in database or columns in <table>,* for all\n");
   fprintf(stderr,"\t \\e [<fname>] -- edit the current query buffer or <fname>,\\E execute too\n");
   fprintf(stderr,"\t \\f [<sep>]   -- change field separater (currently '%s')\n", ps->opt.fieldSep);
@@ -157,19 +160,19 @@ slashUsage(PsqlSettings *ps)
 PGresult *
 PSQLexec(PsqlSettings *ps, char *query)
 {
-       PGresult *res = PQexec(ps->db, query);
-       if (!res)
-               fputs(PQerrorMessage(ps->db), stderr);
-       else
-       {
-               if (PQresultStatus(res)==PGRES_COMMAND_OK ||
-                   PQresultStatus(res)==PGRES_TUPLES_OK)
-                       return res;
-               if (!ps->quiet)
-                       fputs(PQerrorMessage(ps->db), stderr);
-               PQclear(res);
-       }
-       return NULL;
+    PGresult *res = PQexec(ps->db, query);
+    if (!res)
+        fputs(PQerrorMessage(ps->db), stderr);
+    else
+    {
+        if (PQresultStatus(res)==PGRES_COMMAND_OK ||
+            PQresultStatus(res)==PGRES_TUPLES_OK)
+            return res;
+        if (!ps->quiet)
+                fputs(PQerrorMessage(ps->db), stderr);
+        PQclear(res);
+    }
+    return NULL;
 }
 /*
  * listAllDbs
@@ -192,7 +195,7 @@ listAllDbs(PsqlSettings *ps)
     {
       PQprint(ps->queryFout,
                       results,
-                     &ps->opt);
+              &ps->opt);
       PQclear(results);
       return 0;
     }
@@ -226,55 +229,55 @@ tableList (PsqlSettings *ps, bool deep_tablelist)
   strcat(listbuf,"  and usesysid = relowner");
   strcat(listbuf,"  ORDER BY relname ");
   if (!(res=PSQLexec(ps, listbuf)))
-       return -1;
+    return -1;
 
   /* first, print out the attribute names */
   nColumns = PQntuples(res);
   if (nColumns > 0)
   {
       if ( deep_tablelist ) {
-         /* describe everything here */
-         char **table;
-         table = (char**)malloc(nColumns * sizeof(char*));
-         if ( table == NULL )
-             perror("malloc");
-         
-         /* load table table */
-         for (i=0; i < nColumns; i++) {
-             table[i] = (char *) malloc(PQgetlength(res,i,1) * sizeof(char) + 1);
-             if ( table[i] == NULL )
-                 perror("malloc");
-             strcpy(table[i],PQgetvalue(res,i,1));
-         }
-
-       PQclear(res);
-       for (i=0; i < nColumns; i++) {
-          tableDesc(ps, table[i]);
-       }
-       free(table);
+      /* describe everything here */
+      char **table;
+      table = (char**)malloc(nColumns * sizeof(char*));
+      if ( table == NULL )
+          perror("malloc");
+      
+      /* load table table */
+      for (i=0; i < nColumns; i++) {
+          table[i] = (char *) malloc(PQgetlength(res,i,1) * sizeof(char) + 1);
+          if ( table[i] == NULL )
+          perror("malloc");
+          strcpy(table[i],PQgetvalue(res,i,1));
+      }
+
+    PQclear(res);
+    for (i=0; i < nColumns; i++) {
+       tableDesc(ps, table[i]);
+    }
+    free(table);
       }
       else {
-       /* Display the information */
-
-       printf ("\nDatabase    = %s\n", PQdb(ps->db));
-       printf (" +------------------+----------------------------------+----------+\n");
-       printf (" |  Owner           |             Relation             |   Type   |\n");
-       printf (" +------------------+----------------------------------+----------+\n");
-
-       /* next, print out the instances */
-       for (i=0; i < PQntuples(res); i++) {
-           printf (" | %-16.16s", PQgetvalue(res,i,0));
-           printf (" | %-32.32s | ", PQgetvalue(res,i,1));
-           rk =  PQgetvalue(res,i,2);
-           rr =  PQgetvalue(res,i,3);
-           if (strcmp(rk, "r") == 0)
-               printf ("%-8.8s |", (rr[0] == 't') ? "view?" : "table" );
-           else
-               printf ("%-8.8s |", "index");
-           printf("\n");
-       }
-       printf (" +------------------+----------------------------------+----------+\n");
-       PQclear(res);
+    /* Display the information */
+
+    printf ("\nDatabase    = %s\n", PQdb(ps->db));
+    printf (" +------------------+----------------------------------+----------+\n");
+    printf (" |  Owner           |             Relation             |   Type   |\n");
+    printf (" +------------------+----------------------------------+----------+\n");
+
+    /* next, print out the instances */
+    for (i=0; i < PQntuples(res); i++) {
+        printf (" | %-16.16s", PQgetvalue(res,i,0));
+        printf (" | %-32.32s | ", PQgetvalue(res,i,1));
+        rk =  PQgetvalue(res,i,2);
+        rr =  PQgetvalue(res,i,3);
+        if (strcmp(rk, "r") == 0)
+        printf ("%-8.8s |", (rr[0] == 't') ? "view?" : "table" );
+        else
+        printf ("%-8.8s |", "index");
+        printf("\n");
+    }
+    printf (" +------------------+----------------------------------+----------+\n");
+    PQclear(res);
       }
       return (0);
   
@@ -316,7 +319,7 @@ tableDesc (PsqlSettings *ps, char *table)
   strcat(descbuf,"    and a.atttypid = t.oid ");
   strcat(descbuf,"  ORDER BY attnum ");
   if (!(res = PSQLexec(ps, descbuf)))
-       return -1;
+    return -1;
   /* first, print out the attribute names */
   nColumns = PQntuples(res);
   if (nColumns > 0)
@@ -348,21 +351,21 @@ tableDesc (PsqlSettings *ps, char *table)
         printf ("%6i |", rsize > 0 ? rsize - 4 : 0 );
       }
       else {
-         /* array types start with an underscore */
-         if (rtype[0] != '_')
-             printf ("%-32.32s |", rtype);
-         else  {
-             char *newname;
-             newname = malloc(strlen(rtype) + 2);
-             strcpy(newname, rtype+1);
-             strcat(newname, "[]");
-             printf ("%-32.32s |", newname);
-             free(newname);
-         }
-       if (rsize > 0) 
-           printf ("%6i |", rsize);
-       else
-           printf ("%6s |", "var");
+      /* array types start with an underscore */
+      if (rtype[0] != '_')
+          printf ("%-32.32s |", rtype);
+      else  {
+          char *newname;
+          newname = malloc(strlen(rtype) + 2);
+          strcpy(newname, rtype+1);
+          strcat(newname, "[]");
+          printf ("%-32.32s |", newname);
+          free(newname);
+      }
+    if (rsize > 0) 
+        printf ("%6i |", rsize);
+    else
+        printf ("%6s |", "var");
       }
       printf("\n");
     }
@@ -423,7 +426,8 @@ gets_fromFile(char *prompt, FILE *source)
   len = strlen(line);
   if (len == MAX_QUERY_BUFFER)
     {
-      fprintf(stderr, "line read exceeds maximum length.  Truncating at %d\n", MAX_QUERY_BUFFER);
+      fprintf(stderr, "line read exceeds maximum length.  Truncating at %d\n",
+              MAX_QUERY_BUFFER);
     }
   
   return line;
@@ -431,18 +435,19 @@ gets_fromFile(char *prompt, FILE *source)
 
 /*
  *  SendQuery: send the query string to the backend 
- *  return 0 if the query executed successfully
- *  returns 1 otherwise
+ *  return *success_p = 1 if the query executed successfully
+ *  returns *success_p = 0 otherwise
  */
-int
-SendQuery(PsqlSettings *settings, char *query)
-{
+void
+SendQuery(bool *success_p, PsqlSettings *settings, const char *query,
+          const bool copy_in, const bool copy_out, FILE *copystream) {
+
   PGresult *results;
   PGnotify *notify;
-  int status = 0;
 
   if (settings->singleStep)
-       fprintf(stdout, "\n*******************************************************************************\n");
+    fprintf(stdout, "\n**************************************"
+            "*****************************************\n");
 
   if (settings->echoQuery || settings->singleStep) {
       fprintf(stderr,"QUERY: %s\n",query);
@@ -450,81 +455,102 @@ SendQuery(PsqlSettings *settings, char *query)
   }
 
   if (settings->singleStep) {
-       fprintf(stdout, "\n*******************************************************************************\n");
-       fflush(stdout);
-       printf("\npress return to continue ..\n");
-       gets_fromFile("",stdin);
+    fprintf(stdout, "\n**************************************"
+            "*****************************************\n");
+    fflush(stdout);
+    printf("\npress return to continue ..\n");
+    gets_fromFile("",stdin);
   }
 
   results = PQexec(settings->db, query);
   if (results == NULL) {
     fprintf(stderr,"%s",PQerrorMessage(settings->db));
-    return 1;
-  }
-
-  switch (PQresultStatus(results)) {
-  case PGRES_TUPLES_OK:
-      if (settings->gfname)
-      {
-               PsqlSettings ps=*settings;
-               FILE *fp;
-               ps.queryFout=stdout;
-               fp=setFout(&ps, settings->gfname);
-               if (!fp || fp==stdout)
-               {
-                       status = 1;
-                       break;
-               }
-               PQprint(fp,
-                       results,
-                       &(settings->opt));
-               if (ps.pipe)
-                       pclose(fp);
-               else
-                       fclose(fp);
-               settings->gfname=NULL;
-               break;
-       } else 
-       {
-             PQprint(settings->queryFout,
-                             results,
-                             &(settings->opt));
-             fflush(settings->queryFout);
-       }
-      PQclear(results);
-      break;
-  case PGRES_EMPTY_QUERY:
-    /* do nothing */
-    break;
-  case PGRES_COMMAND_OK:
-    if (!settings->quiet)
-      fprintf(stdout,"%s\n", PQcmdStatus(results));
-    break;
-  case PGRES_COPY_OUT:
-    handleCopyOut(results, settings->quiet);
-    break;
-  case PGRES_COPY_IN:
-    handleCopyIn(results, settings->quiet);
-    break;
-  case PGRES_NONFATAL_ERROR:
-  case PGRES_FATAL_ERROR:
-  case PGRES_BAD_RESPONSE:
-    status = 1;
-    fprintf(stderr,"%s",PQerrorMessage(settings->db));
-    break;
-  } 
-
-  /* check for asynchronous returns */
-  notify = PQnotifies(settings->db);
-  if (notify) {
-      fprintf(stderr,"ASYNC NOTIFY of '%s' from backend pid '%d' received\n",
-             notify->relname, notify->be_pid);
-      free(notify);
+    *success_p = false;
+  } else {
+      switch (PQresultStatus(results)) {
+        case PGRES_TUPLES_OK:
+          if (settings->gfname) {
+              PsqlSettings ps=*settings;
+              FILE *fp;
+              ps.queryFout=stdout;
+              fp=setFout(&ps, settings->gfname);
+              if (!fp || fp==stdout) {
+                  *success_p = false;
+                  break;
+              } else *success_p = true;
+              PQprint(fp,
+                      results,
+                      &(settings->opt));
+              if (ps.pipe)
+                pclose(fp);
+              else
+                fclose(fp);
+              settings->gfname=NULL;
+              break;
+          } else {
+              *success_p = true;
+              PQprint(settings->queryFout,
+                      results,
+                      &(settings->opt));
+              fflush(settings->queryFout);
+          }
+          PQclear(results);
+          break;
+        case PGRES_EMPTY_QUERY:
+          *success_p = true;
+          break;
+        case PGRES_COMMAND_OK:
+          *success_p = true;
+          if (!settings->quiet)
+            fprintf(stdout,"%s\n", PQcmdStatus(results));
+          break;
+        case PGRES_COPY_OUT: 
+          *success_p = true;
+          if (copy_out) {
+              handleCopyOut(results, settings->quiet, copystream);
+          } else {
+              if (!settings->quiet)
+                fprintf(stdout, "Copy command returns...\n");
+              
+              handleCopyOut(results, settings->quiet, stdout);
+          }
+          break;
+        case PGRES_COPY_IN:
+          *success_p = true;
+          if (copy_in) {
+              handleCopyIn(results, false, copystream);
+          } else {
+              char c;
+              /*
+               * eat extra newline still in input buffer
+               *
+               */
+              fflush(stdin);
+              if ((c = getc(stdin)) != '\n' && c != EOF) 
+                (void) ungetc(c, stdin);
+              handleCopyIn(results, !settings->quiet, stdin);
+          }
+          break;
+        case PGRES_NONFATAL_ERROR:
+        case PGRES_FATAL_ERROR:
+        case PGRES_BAD_RESPONSE:
+          *success_p = false;
+          fprintf(stderr,"%s",PQerrorMessage(settings->db));
+          break;
+      } 
+      
+      /* check for asynchronous returns */
+      notify = PQnotifies(settings->db);
+      if (notify) {
+          fprintf(stderr,
+                  "ASYNC NOTIFY of '%s' from backend pid '%d' received\n",
+                  notify->relname, notify->be_pid);
+          free(notify);
+      }
   }
+}
 
-  return status;
 
-}
 
 void
 editFile(char *fname)
@@ -533,12 +559,12 @@ editFile(char *fname)
     char *sys;
     editorName = getenv("EDITOR");
     if (!editorName)
-       editorName = DEFAULT_EDITOR;
+    editorName = DEFAULT_EDITOR;
     sys=malloc(strlen(editorName)+strlen(fname)+32+1);
     if (!sys)
     {
-       perror("malloc");
-       exit(1);
+    perror("malloc");
+    exit(1);
     }
     sprintf(sys, "exec '%s' '%s'", editorName, fname);
     system(sys);
@@ -548,49 +574,360 @@ editFile(char *fname)
 bool
 toggle(PsqlSettings *settings, bool *sw, char *msg)
 {
-       *sw= !*sw;
-       if (!settings->quiet)
-           fprintf(stderr, "turned %s %s\n", on(*sw), msg);
-       return *sw;
+    *sw= !*sw;
+    if (!settings->quiet)
+        fprintf(stderr, "turned %s %s\n", on(*sw), msg);
+    return *sw;
 }
 
+
+
 void
-decode(char *s)
-{
-       char *p, *d;
-       bool esc=0;
-       for (d=p=s; *p; p++)
-       {
-               char c=*p;
-               if (esc)
-               {
-                       switch(*p)
-                       {
-                       case 'n':
-                               c='\n';
-                               break;
-                       case 'r':
-                               c='\r';
-                               break;
-                       case 't':
-                               c='\t';
-                               break;
-                       case 'f':
-                               c='\f';
-                               break;
-                       }
-                       esc=0;
-               } else
-                       if (c=='\\')
-                       {
-                               esc=1;
-                               continue;
-                       }
-               *d++=c;
-       }
-       *d='\0';
+unescape(char *dest, const char *source) {
+/*-----------------------------------------------------------------------------
+  Return as the string <dest> the value of string <source> with escape
+  sequences turned into the bytes they represent.
+-----------------------------------------------------------------------------*/
+    char *p;
+    bool esc; /* Last character we saw was the escape character (/) */
+
+    esc = false;  /* Haven't seen escape character yet */
+    for (p = (char *)source; *p; p++) {
+        char c;  /* Our output character */
+
+        if (esc) {
+            switch(*p) {
+              case 'n':
+                c = '\n';
+                break;
+              case 'r':
+                c = '\r';
+                break;
+              case 't':
+                c = '\t';
+                break;
+              case 'f':
+                c = '\f';
+                break;
+              case '\\':
+                c = '\\';
+                break;
+              default:
+                c = *p;
+            }
+            esc = false;
+        } else
+            if (*p == '\\') {
+                esc = true;
+                c = ' '; /* meaningless, but compiler doesn't know that */
+            } else {
+                c = *p;
+                esc = false;
+            }
+        if (!esc) *dest ++= c;
+    }
+    *dest = '\0';   /* Terminating null character */
 }
 
+
+
+void
+parse_slash_copy(const char *args, char *table, const int table_len,
+                 char *file, const int file_len, 
+                 bool *from_p, bool *error_p) {
+    
+    char work_args[200];
+      /* A copy of the \copy command arguments, except that we modify it
+         as we parse to suit our parsing needs.
+         */
+    char *table_tok, *fromto_tok;
+
+    strncpy(work_args, args, sizeof(work_args));
+    work_args[sizeof(work_args)-1] = '\0';
+
+    *error_p = false;   /* initial assumption */
+
+    table_tok = strtok(work_args, " ");
+    if (table_tok == NULL) {
+        fprintf(stderr, "\\copy needs arguments.\n");
+        *error_p = true;
+    } else {
+        strncpy(table, table_tok, table_len);
+        file[table_len-1] = '\0';
+
+        fromto_tok = strtok(NULL, "  ");
+        if (fromto_tok == NULL) {
+            fprintf(stderr, "'FROM' or 'TO' must follow table name.\n");
+            *error_p = true;
+        } else {
+            if (strcasecmp(fromto_tok, "from") == 0) *from_p = true;
+            else if (strcasecmp(fromto_tok, "to") == 0) *from_p = false;
+            else {
+                fprintf(stderr,
+                        "Unrecognized token found where "
+                        "'FROM' or 'TO' expected: '%s'.\n", 
+                        fromto_tok);
+                *error_p = true;
+            }
+            if (!*error_p) {
+                char *file_tok;
+                
+                file_tok = strtok(NULL, " ");
+                if (file_tok == NULL) {
+                    fprintf(stderr, "A file pathname must follow '%s'.\n",
+                            fromto_tok);
+                    *error_p = true;
+                } else {
+                    strncpy(file, file_tok, file_len);
+                    file[file_len-1] = '\0';
+                    if (strtok(NULL, " ") != NULL) {
+                        fprintf(stderr, 
+                                "You have extra tokens after the filename.\n");
+                        *error_p = true;
+                    }
+                }
+            }            
+        }
+    }
+}
+
+
+
+void
+do_copy(const char *args, PsqlSettings *settings) {
+/*---------------------------------------------------------------------------
+  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.
+
+  We do a text copy with default (tab) column delimiters.  Some day, we
+  should do all the things a backend copy can do.
+
+----------------------------------------------------------------------------*/
+    char query[200];
+      /* The COPY command we send to the back end */
+    bool from;
+      /* The direction of the copy is from a file to a table. */
+    char file[MAXPATHLEN+1];
+      /* The pathname of the file from/to which we copy */
+    char table[NAMEDATALEN+1];
+      /* The name of the table from/to which we copy */
+    bool syntax_error;
+      /* The \c command has invalid syntax */
+    FILE *copystream;
+
+    parse_slash_copy(args, table, sizeof(table), file, sizeof(file),
+                     &from, &syntax_error);
+
+    if (!syntax_error) {
+        strcpy(query, "COPY ");
+        strcat(query, table);
+        
+        if (from) 
+          strcat(query, " FROM stdin");
+        else 
+          strcat(query, " TO stdout");
+        
+        if (from) {
+            copystream = fopen(file, "r");
+        } else {
+            copystream = fopen(file, "w");
+        }
+        if (copystream < 0) 
+        fprintf(stderr, 
+                "Unable to open file %s which to copy, errno = %s (%d).", 
+                from ? "from" : "to", strerror(errno), errno);
+        else {
+            bool success;  /* The query succeeded at the backend */
+
+            SendQuery(&success, settings, query, from, !from, copystream);
+            fclose(copystream);
+            if (!settings->quiet) {
+                if (success)
+                  fprintf(stdout, "Successfully copied.\n");
+                else
+                  fprintf(stdout, "Copy failed.\n");
+            }
+        }
+    }
+}
+
+
+void
+do_connect(const char *new_dbname, PsqlSettings *settings) {
+
+    char *dbname=PQdb(settings->db);
+    if (!new_dbname) 
+        fprintf(stderr,"\\connect must be followed by a database name\n");
+    else {
+        PGconn *olddb=settings->db;
+          
+        printf("closing connection to database: %s\n", dbname);
+        settings->db = PQsetdb(PQhost(olddb), PQport(olddb), 
+                               NULL, NULL, new_dbname);
+        printf("connecting to new database: %s\n", new_dbname);
+        if (PQstatus(settings->db) == CONNECTION_BAD) {
+            fprintf(stderr,"%s\n", PQerrorMessage(settings->db));
+            printf("reconnecting to %s\n", dbname);
+            settings->db = PQsetdb(PQhost(olddb), PQport(olddb), 
+                                   NULL, NULL, dbname);
+            if (PQstatus(settings->db) == CONNECTION_BAD) {
+                fprintf(stderr, 
+                        "could not reconnect to %s. exiting\n", dbname);
+                exit(2);
+            }
+        } else {
+            PQfinish(olddb);
+            free(settings->prompt);
+            settings->prompt = malloc(strlen(PQdb(settings->db)) + 10);
+            sprintf(settings->prompt,"%s=> ", PQdb(settings->db));
+        }
+    }
+}
+
+
+void
+do_edit(const char *filename_arg, char *query, int *retcode_p) {
+
+    int fd;
+    char tmp[64];
+    char *fname;
+    int cc;
+    const int ql = strlen(query);
+    bool error;
+
+    if (filename_arg) {
+        fname=(char *)filename_arg;
+        error=false;
+    } else {
+        sprintf(tmp, "/tmp/psql.%d.%d", geteuid(), getpid());
+        fname=tmp;
+        unlink(tmp);
+        if (ql > 0) {
+            if ((fd=open(tmp, O_EXCL|O_CREAT|O_WRONLY, 0600)) == -1) {
+                perror(tmp);
+                error=true; 
+            } else {
+                if (query[ql-1] != '\n')
+                  strcat(query, "\n");
+                if (write(fd, query, ql) != ql) {
+                    perror(tmp);
+                    close(fd);
+                    unlink(tmp);
+                    error=true;
+                } else error=false;
+                close(fd);
+            }
+        } else error=false;
+    }
+
+    if (error) *retcode_p=1;
+    else {
+        editFile(fname);
+        if ((fd=open(fname, O_RDONLY))==-1) {
+            perror(fname);
+            if (!filename_arg)
+              unlink(fname);
+            *retcode_p=1;
+        } else {
+            if ((cc=read(fd, query, MAX_QUERY_BUFFER))==-1) {
+                perror(fname);
+                close(fd);
+                if (!filename_arg)
+                  unlink(fname);
+                *retcode_p=1;
+            } else {
+                query[cc]='\0';
+                close(fd);
+                if (!filename_arg)
+                  unlink(fname);
+                rightTrim(query);
+                if (query[strlen(query)-1]==';') *retcode_p=0;
+                else *retcode_p=1;
+            }
+        }
+    }
+}
+
+
+
+void
+do_help(const char *topic) {
+
+    if (!topic) {
+        char left_center_right; /* Which column we're displaying */
+        int i;  /* Index into QL_HELP[] */
+
+        printf("type \\h <cmd> where <cmd> is one of the following:\n");
+
+        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");
+        printf("type \\h * for a complete description of all commands\n");
+    } else {
+        int i;   /* Index into QL_HELP[] */
+        bool help_found;  /* We found the help he asked for */
+
+        help_found = false;  /* Haven't found it yet */
+        for (i=0; QL_HELP[i].cmd; i++)  {
+            if (strcmp(QL_HELP[i].cmd, topic) == 0 || 
+                    strcmp(topic, "*") == 0 ) {
+                help_found = true;
+                printf("Command: %s\n",QL_HELP[i].cmd);
+                printf("Description: %s\n", QL_HELP[i].help);
+                printf("Syntax:\n");
+                printf("%s\n", QL_HELP[i].syntax);
+                printf("\n");
+            }
+        }
+        if (!help_found)
+          printf("command not found, "
+                 "try \\h with no arguments to see available help\n");
+    }
+}
+
+
+
+void
+do_shell(const char *command) {
+
+    if (!command) {
+        char *sys;
+        char *shellName;
+
+        shellName = getenv("SHELL");
+        if (shellName == NULL) 
+          shellName = DEFAULT_SHELL;
+        sys = malloc(strlen(shellName)+16);
+        if (!sys) {
+            perror("malloc");
+            exit(1);
+        }
+        sprintf(sys,"exec %s", shellName);
+        system(sys);
+        free(sys);
+    } else system(command);
+}
+
+
+
 /*
   HandleSlashCmds:
 
@@ -599,7 +936,7 @@ decode(char *s)
      line is the current input line
      prompt_ptr is a pointer to the prompt string,
                   a pointer is used because the prompt can be used with 
-                 a connection to a new database
+          a connection to a new database
   returns a status:
        0 - send currently constructed query to backend (i.e. we got a \g)
        1 - skip processing of this line, continue building up query
@@ -607,287 +944,179 @@ decode(char *s)
 */
 int
 HandleSlashCmds(PsqlSettings *settings,
-               char *line, 
-               char *query)
+        char *line, 
+        char *query)
 {
   int status = 1;
-  char *optarg = NULL;
-  int len;
+  char *optarg;
+    /* Pointer inside the <cmd> string to the argument of the slash 
+       command, assuming it is a one-character slash command.  If it's
+       not a one-character command, this is meaningless.
+       */
+  char *optarg2;
+    /* Pointer inside the <cmd> string to the argument of the slash
+       command assuming it's not a one-character command.  If it's a 
+       one-character command, this is meaningless.
+       */
+  char *cmd;
+    /* String: value of the slash command, less the slash and with escape
+       sequences decoded.
+       */
+  int blank_loc;
+    /* Offset within <cmd> of first blank */
+
+  cmd = malloc(strlen(line));  /* unescaping better not make string grow. */
+
+  unescape(cmd, line+1);   /* sets cmd string */
+
+  /* Originally, there were just single character commands.  Now,
+     we define some longer, friendly commands, but we have to keep
+     the old single character commands too.  \c used to be what
+     \connect is now.  Complicating matters is the fact that with
+     the single-character commands, you can start the argument 
+     right after the single character, so "\copy" would mean
+     "connect to database named 'opy'".
+     */
 
-  len = strlen(line);
-  if (len > 2)
-  {
-      optarg = leftTrim(line+2);
-      decode(optarg);
-  }
-  switch (line[1])
+  if (strlen(cmd) > 1) optarg = cmd+1 + strspn(cmd+1, " \t");
+  else optarg = NULL;
+
+  blank_loc = strcspn(cmd, " \t");
+  if (blank_loc == 0) optarg2 = NULL;
+  else optarg2 = cmd + blank_loc + strspn(cmd+blank_loc, " \t");
+      
+
+  switch (cmd[0])
   {
     case 'a': /* toggles to align fields on output */
         toggle(settings, &settings->opt.align, "field alignment");
-       break;
+    break;
     case 'C': /* define new caption */
-       if (settings->opt.caption)
-               free(settings->opt.caption);
-       if (!optarg)
-               settings->opt.caption=NULL;
-       else
-               if (!(settings->opt.caption=strdup(optarg)))
-               {
-                       perror("malloc");
-                       exit(1);
-               }
-       break;
-    case 'c':  /* \c means connect to new database */
-    {
-         char *dbname=PQdb(settings->db);
-         if (!optarg) {
-             fprintf(stderr,"\\c must be followed by a database name\n");
-             break;
-         }
-         {
-             PGconn *olddb=settings->db;
-             
-             printf("closing connection to database: %s\n", dbname);
-             settings->db = PQsetdb(PQhost(olddb), PQport(olddb), NULL, NULL, optarg);
-             printf("connecting to new database: %s\n", optarg);
-             if (PQstatus(settings->db) == CONNECTION_BAD) {
-                 fprintf(stderr,"%s\n", PQerrorMessage(settings->db));
-                 printf("reconnecting to %s\n", dbname);
-                 settings->db = PQsetdb(PQhost(olddb), PQport(olddb), 
-                              NULL, NULL, dbname);
-                 if (PQstatus(settings->db) == CONNECTION_BAD) {
-                     fprintf(stderr, 
-                             "could not reconnect to %s. exiting\n", dbname);
-                     exit(2);
-                 }
-                 break;
-             }
-             PQfinish(olddb);
-             free(settings->prompt);
-             settings->prompt = malloc(strlen(PQdb(settings->db)) + 10);
-             sprintf(settings->prompt,"%s=> ", PQdb(settings->db));
-           break;
-         }
+        if (settings->opt.caption)
+        free(settings->opt.caption);
+    if (!optarg)
+        settings->opt.caption=NULL;
+    else
+        if (!(settings->opt.caption=strdup(optarg)))
+        {
+            perror("malloc");
+            exit(1);
+        }
+    break;
+    case 'c':   {
+        if (strncmp(cmd, "copy ", strlen("copy ")) == 0) 
+            do_copy(optarg2, settings);
+        else if (strncmp(cmd, "connect ", strlen("connect ")) == 0)
+            do_connect(optarg2, settings);
+        else
+            do_connect(optarg, settings);
       }
       break;
     case 'd':     /* \d describe tables or columns in a table */
-       if (!optarg) {
+    if (!optarg) {
           tableList(settings, 0);
-         break;
-       
-       if (strcmp(optarg, "*") == 0 ) {
-          tableList(settings, 0);
-          tableList(settings, 1);
-       }
-       else {
-          tableDesc(settings, optarg);
-       }
-       break;
-    case 'e':
+      break;
+    } 
+    if (strcmp(optarg, "*") == 0 ) {
+       tableList(settings, 0);
+       tableList(settings, 1);
+    }
+    else {
+        tableDesc(settings, optarg);
+    }
+    break;
+    case 'e':  /* edit */
     {
-       int fd;
-       char tmp[64];
-       char *fname;
-       int cc;
-       int ql = strlen(query);
-        if (optarg)
-               fname=optarg;
-        else
-       {
-               sprintf(tmp, "/tmp/psql.%d.%d", geteuid(), getpid());
-               fname=tmp;
-               unlink(tmp);
-               if (ql)
-               {
-                       if ((fd=open(tmp, O_EXCL|O_CREAT|O_WRONLY, 0600))==-1)
-                       {
-                               perror(tmp);
-                               break;
-                       }
-                       if (query[ql-1]!='\n')
-                               strcat(query, "\n");
-                       if (write(fd, query, ql)!=ql)
-                       {
-                               perror(tmp);
-                               close(fd);
-                               unlink(tmp);
-                               break;
-                       }
-                       close(fd);
-               }
-       }
-       editFile(fname);
-       if ((fd=open(fname, O_RDONLY))==-1)
-       {
-               perror(fname);
-               if (!optarg)
-                       unlink(fname);
-               break;
-       }
-       if ((cc=read(fd, query, MAX_QUERY_BUFFER))==-1)
-        {
-               perror(fname);
-               close(fd);
-               if (!optarg)
-                       unlink(fname);
-               break;
-       }       
-       query[cc]='\0';
-       close(fd);
-       if (!optarg)
-               unlink(fname);
-       rightTrim(query);
-       if (query[strlen(query)-1]==';')
-               return 0;
-       break;
+        do_edit(optarg, query, &status);
+        break;
     }
     case 'E':
     {
-       FILE *fd;
-       static char *lastfile;
-       struct stat st, st2;
-       if (optarg)
-       {
-               if (lastfile)
-                       free(lastfile);
-               lastfile=malloc(strlen(optarg+1));
-               if (!lastfile)
-               {
-                       perror("malloc");
-                       exit(1);
-               }
-               strcpy(lastfile, optarg);
-       } else if (!lastfile)
-               {
-                         fprintf(stderr,"\\r must be followed by a file name initially\n");
-                         break;
-               }
-       stat(lastfile, &st);
-       editFile(lastfile);
-       if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "r")) == NULL))
-         {
-           perror(lastfile);
-           break;
-         }
-       if (st2.st_mtime==st.st_mtime)
-       {
-               if (!settings->quiet)
-                       fprintf(stderr, "warning: %s not modified. query not executed\n", lastfile);
-               fclose(fd);
-               break;
-       }
-       MainLoop(settings, fd);
-       fclose(fd);
-       break;
+    FILE *fd;
+    static char *lastfile;
+    struct stat st, st2;
+    if (optarg)
+    {
+        if (lastfile)
+            free(lastfile);
+        lastfile=malloc(strlen(optarg+1));
+        if (!lastfile)
+        {
+            perror("malloc");
+            exit(1);
+        }
+        strcpy(lastfile, optarg);
+    } else if (!lastfile)
+        {
+              fprintf(stderr,"\\r must be followed by a file name initially\n");
+              break;
+        }
+    stat(lastfile, &st);
+    editFile(lastfile);
+    if ((stat(lastfile, &st2) == -1) || ((fd = fopen(lastfile, "r")) == NULL))
+      {
+        perror(lastfile);
+        break;
+      }
+    if (st2.st_mtime==st.st_mtime)
+    {
+        if (!settings->quiet)
+            fprintf(stderr, "warning: %s not modified. query not executed\n", lastfile);
+        fclose(fd);
+        break;
+    }
+    MainLoop(settings, fd);
+    fclose(fd);
+    break;
     }
     case 'f':
     {
         char *fs=DEFAULT_FIELD_SEP;
-       if (optarg)
-               fs=optarg;
+        if (optarg)
+        fs=optarg;
         if (settings->opt.fieldSep);
-               free(settings->opt.fieldSep);
-       if (!(settings->opt.fieldSep=strdup(fs)))
-       {
-               perror("malloc");
-               exit(1);
-       }
-       if (!settings->quiet)
-               fprintf(stderr, "field separater changed to '%s'\n", settings->opt.fieldSep);
-       break;
+        free(settings->opt.fieldSep);
+    if (!(settings->opt.fieldSep=strdup(fs)))
+    {
+        perror("malloc");
+        exit(1);
+    }
+    if (!settings->quiet)
+        fprintf(stderr, "field separater changed to '%s'\n", settings->opt.fieldSep);
+    break;
     }
     case 'g':  /* \g means send query */
       settings->gfname = optarg;
       status = 0;     
       break;
-    case 'h':
+    case 'h':  /* help */
     {
-       char *cmd;
-       int i, numCmds;
-       int all_help = 0;
-       char left_center_right = 'L';
-
-       if (!optarg) {
-           printf("type \\h <cmd> where <cmd> is one of the following:\n");
-           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");
-            printf("type \\h * for a complete description of all commands\n");
-         }
-       else
-         {
-         cmd = optarg;
-
-         numCmds = 0;
-         while (QL_HELP[numCmds++].cmd != NULL);
-
-         numCmds = numCmds - 1;
-
-         if (strcmp(cmd, "*") == 0 ) {
-            all_help=1;
-         }
-
-         for (i=0; i<numCmds;i++)  {
-             if (strcmp(QL_HELP[i].cmd, cmd) == 0 || all_help)    {
-               printf("Command: %s\n",QL_HELP[i].cmd);
-               printf("Description: %s\n", QL_HELP[i].help);
-               printf("Syntax:\n");
-               printf("%s\n", QL_HELP[i].syntax);
-               if ( all_help ) {
-                  printf("\n");
-               }
-               else {
-                  break;
-              }
-           }
-         }
-         if (i == numCmds && ! all_help)
-           printf("command not found,  try \\h with no arguments to see available help\n");
-       }
-       break;
+        do_help(optarg);
+        break;
     }
     case 'i':     /* \i is include file */
       {
-       FILE *fd;
-
-       if (!optarg) {
-         fprintf(stderr,"\\i must be followed by a file name\n");
-         break;
-       }
-
-       if ((fd = fopen(optarg, "r")) == NULL)
-         {
-           fprintf(stderr,"file named %s could not be opened\n",optarg);
-           break;
-         }
-       MainLoop(settings, fd);
-       fclose(fd);
-       break;
+    FILE *fd;
+
+    if (!optarg) {
+      fprintf(stderr,"\\i must be followed by a file name\n");
+      break;
+    }
+
+    if ((fd = fopen(optarg, "r")) == NULL)
+      {
+        fprintf(stderr,"file named %s could not be opened\n",optarg);
+        break;
+      }
+    MainLoop(settings, fd);
+    fclose(fd);
+    break;
     }
     case 'l':     /* \l is list database */
       listAllDbs(settings);
       break;
     case 'H':
-      if (toggle(settings, &settings->opt.html3, "HTML3.0 tablular output"))
+      if (toggle(settings, &settings->opt.html3, "HTML3.0 tabular output"))
           settings->opt.standard = 0;
       break;
     case 'o':
@@ -896,8 +1125,8 @@ HandleSlashCmds(PsqlSettings *settings,
     case 'p':
       if (query)
       {
-             fputs(query, stdout);
-             fputc('\n', stdout);
+          fputs(query, stdout);
+          fputc('\n', stdout);
       }
       break;
     case 'q': /* \q is quit */
@@ -906,74 +1135,58 @@ HandleSlashCmds(PsqlSettings *settings,
     case 'r': /* reset(clear) the buffer */
       query[0]='\0';
       if (!settings->quiet)
-         fprintf(stderr, "buffer reset(cleared)\n");
+          fprintf(stderr, "buffer reset(cleared)\n");
       break;
     case 's': /* \s is save history to a file */
-       if (!optarg)
-               optarg="/dev/tty";
-       if (write_history(optarg) != 0)
-           fprintf(stderr,"cannot write history to %s\n",optarg);
-       break;
+    if (!optarg)
+        optarg="/dev/tty";
+    if (write_history(optarg) != 0)
+        fprintf(stderr,"cannot write history to %s\n",optarg);
+    break;
     case 'm': /* monitor like type-setting */
       if (toggle(settings, &settings->opt.standard, "standard SQL separaters and padding"))
       {
           settings->opt.html3 = settings->opt.expanded = 0;
           settings->opt.align = settings->opt.header = 1;
-         free(settings->opt.fieldSep);
-         settings->opt.fieldSep=strdup("|");
-         if (!settings->quiet)
-             fprintf(stderr, "field separater changed to '%s'\n", settings->opt.fieldSep);
+      free(settings->opt.fieldSep);
+      settings->opt.fieldSep=strdup("|");
+      if (!settings->quiet)
+          fprintf(stderr, "field separater changed to '%s'\n", settings->opt.fieldSep);
       } else
       {
-         free(settings->opt.fieldSep);
-         settings->opt.fieldSep=strdup(DEFAULT_FIELD_SEP);
-         if (!settings->quiet)
-             fprintf(stderr, "field separater changed to '%s'\n", settings->opt.fieldSep);
+      free(settings->opt.fieldSep);
+      settings->opt.fieldSep=strdup(DEFAULT_FIELD_SEP);
+      if (!settings->quiet)
+          fprintf(stderr, "field separater changed to '%s'\n", settings->opt.fieldSep);
       }
       break;
     case 't': /* toggle headers */
       toggle(settings, &settings->opt.header, "output headings and row count");
       break;
     case 'T': /* define html <table ...> option */
-       if (settings->opt.tableOpt)
-               free(settings->opt.tableOpt);
-       if (!optarg)
-               settings->opt.tableOpt=NULL;
-       else
-               if (!(settings->opt.tableOpt=strdup(optarg)))
-               {
-                       perror("malloc");
-                       exit(1);
-               }
-       break;
+        if (settings->opt.tableOpt)
+        free(settings->opt.tableOpt);
+    if (!optarg)
+        settings->opt.tableOpt=NULL;
+    else
+        if (!(settings->opt.tableOpt=strdup(optarg)))
+        {
+            perror("malloc");
+            exit(1);
+        }
+    break;
     case 'x':
       toggle(settings, &settings->opt.expanded, "expanded table representation");
       break;
     case '!':
-      if (!optarg) {
-         char *sys;
-         char *shellName;
-         shellName = getenv("SHELL");
-         if (shellName == NULL) 
-             shellName = DEFAULT_SHELL;
-         sys = malloc(strlen(shellName)+16);
-         if (!sys)
-         {
-               perror("malloc");
-               exit(1);
-         }
-         sprintf(sys,"exec %s", shellName);
-         system(sys);
-         free(sys);
-      }
-      else
-         system(optarg);
+      do_shell(optarg);
       break;
     default:
     case '?':     /* \? is help */
       slashUsage(settings);
       break;
     }
+  free(cmd);
   return status;
 }
 
@@ -990,152 +1203,151 @@ HandleSlashCmds(PsqlSettings *settings,
 int
 MainLoop(PsqlSettings *settings, FILE *source)
 {
-  char *line;                   /* line of input */
-  int len;                      /* length of the line */
-  char query[MAX_QUERY_BUFFER]; /* multi-line query storage */
-  int exitStatus = 0;
-  int slashCmdStatus = 0;
- /* slashCmdStatus can be:
+    char *line;                   /* line of input */
+    int len;                      /* length of the line */
+    char query[MAX_QUERY_BUFFER]; /* multi-line query storage */
+    int exitStatus = 0;
+    int slashCmdStatus = 0;
   /* slashCmdStatus can be:
        0 - send currently constructed query to backend (i.e. we got a \g)
        1 - skip processing of this line, continue building up query
        2 - terminate processing of this query entirely
-  */
-
-  bool sendQuery = 0;
-  bool querySent = 0;
-  bool interactive;
-  READ_ROUTINE GetNextLine;
-  bool connected = 1;  
-    /* We are connected to the backend (last time we looked) */
-  bool eof = 0;
-    /* We've reached the end of our command input. */
-
-  interactive = ((source == stdin) && !settings->notty);
+       */
+
+    bool sendQuery = 0;
+    bool querySent = 0;
+    bool interactive;
+    READ_ROUTINE GetNextLine;
+    bool connected = 1;  
+        /* We are connected to the backend (last time we looked) */
+    bool eof = 0;
+        /* We've reached the end of our command input. */
+
+    interactive = ((source == stdin) && !settings->notty);
 #define PROMPT "=> "
-  if (interactive) {
-    if (settings->prompt)
-       free(settings->prompt);
-    settings->prompt = malloc(strlen(PQdb(settings->db)) + strlen(PROMPT) + 1);
-    if (settings->quiet)
-      settings->prompt[0] = '\0';
-    else
-      sprintf(settings->prompt,"%s%s", PQdb(settings->db), PROMPT);
-    if (settings->useReadline) {
-       using_history();
-       GetNextLine = gets_readline;
-    } else
-       GetNextLine = gets_noreadline;
-
-  }
-  else
-    GetNextLine = gets_fromFile;
-
-  query[0] = '\0';
+    if (interactive) {
+        if (settings->prompt)
+          free(settings->prompt);
+        settings->prompt = 
+          malloc(strlen(PQdb(settings->db)) + strlen(PROMPT) + 1);
+        if (settings->quiet)
+          settings->prompt[0] = '\0';
+        else
+          sprintf(settings->prompt,"%s%s", PQdb(settings->db), PROMPT);
+        if (settings->useReadline) {
+            using_history();
+            GetNextLine = gets_readline;
+        } else GetNextLine = gets_noreadline;
+    } else GetNextLine = gets_fromFile;
+
+    query[0] = '\0';
   
-  /* main loop for getting queries and executing them */
-  while (connected && !eof) {
-    line = GetNextLine(settings->prompt, source);
-    if (line == NULL) {   /* No more input.  Time to quit */
-      printf("EOF\n");  /* Goes on prompt line */
-      eof = 1;
-    } else {
-      exitStatus = 0;
-      line = rightTrim(line); /* remove whitespaces on the right, incl. \n's */
-
-      if (line[0] == '\0') {
-         free(line);
-         continue;
-      }
-
-      /* filter out comment lines that begin with --,
-         this could be incorrect if -- is part of a quoted string.
-         But we won't go through the trouble of detecting that.  If you have
-        -- in your quoted string, be careful and don't start a line with it */
-      if (line[0] == '-' && line[1] == '-') {
-         if (settings->singleStep) /* in single step mode, show comments */
-             fprintf(stdout,"%s\n",line);
-         free(line);
-         continue;
-      }
-      if (line[0] != '\\' && querySent)
-      {
-         query[0]='\0';
-          querySent = 0;
-      }
-
-      len = strlen(line);
-
-      if (interactive && settings->useReadline)
-         add_history(line);      /* save non-empty lines in history */
+    /* main loop for getting queries and executing them */
+    while (connected && !eof) {
+        line = GetNextLine(settings->prompt, source);
+        if (line == NULL) {   /* No more input.  Time to quit */
+            printf("EOF\n");  /* Goes on prompt line */
+            eof = true;
+        } else {
+            exitStatus = 0;
+            line = rightTrim(line); 
+                /* remove whitespaces on the right, incl. \n's */
+
+            if (line[0] == '\0') {
+                free(line);
+                continue;
+            }
+
+            /* filter out comment lines that begin with --,
+               this could be incorrect if -- is part of a quoted string.
+               But we won't go through the trouble of detecting that.  
+               If you have -- in your quoted string, be careful and don't 
+               start a line with it 
+               */
+            if (line[0] == '-' && line[1] == '-') {
+                if (settings->singleStep) 
+                    /* in single step mode, show comments */
+                  fprintf(stdout,"%s\n",line);
+                free(line);
+                continue;
+            }
+            if (line[0] != '\\' && querySent) {
+                query[0]='\0';
+                querySent = 0;
+            }
+
+            len = strlen(line);
+
+            if (interactive && settings->useReadline)
+              add_history(line);      /* save non-empty lines in history */
       
-      /* do the query immediately if we are doing single line queries 
-       or if the last character is a semicolon */
-      sendQuery = settings->singleLineMode || (line[len-1] == ';') ;
-
-      /* normally, \ commands have to be start the line,
-        but for backwards compatibility with monitor,
-        check for \g at the end of line */
-      if (len > 2 && !sendQuery) 
-       {
-         if (line[len-1]=='g' && line[len-2]=='\\')
-           {
-           sendQuery = 1;
-           line[len-2]='\0';
-         }
-       }
+            /* do the query immediately if we are doing single line queries 
+               or if the last character is a semicolon 
+               */
+            sendQuery = settings->singleLineMode || (line[len-1] == ';') ;
+
+            /* normally, \ commands have to be start the line,
+               but for backwards compatibility with monitor,
+               check for \g at the end of line */
+            if (len > 2 && !sendQuery) {
+                if (line[len-1]=='g' && line[len-2]=='\\') {
+                    sendQuery = 1;
+                    line[len-2]='\0';
+                }
+            }
       
-      /* slash commands have to be on their own line */
-      if (line[0] == '\\') {
-         slashCmdStatus = HandleSlashCmds(settings,
-                                          line, 
-                                          query);
-       if (slashCmdStatus == 1) {
-         free(line);
-         continue;
-       }
-       if (slashCmdStatus == 2) {
-         free(line);
-         break;
-       }
-       if (slashCmdStatus == 0)
-         sendQuery = 1;
-      }
-      else
-       if (strlen(query) + len > MAX_QUERY_BUFFER)
-         {
-           fprintf(stderr,"query buffer max length of %d exceeded\n",MAX_QUERY_BUFFER);
-           fprintf(stderr,"query line ignored\n");
-         }
-      else
-       if (query[0]!='\0') {
-           strcat(query,"\n");
-           strcat(query,line);
-       }
-      else
-       strcpy(query,line);
-      
-      if (sendQuery && query[0] != '\0')
-       {
-           /* echo the line read from the file,
-            unless we are in single_step mode, because single_step mode
-            will echo anyway */
-         if (!interactive && !settings->singleStep && !settings->quiet) 
-           fprintf(stderr,"%s\n", query);
-
-         exitStatus = SendQuery(settings, query);
-          querySent = 1;
-          if (PQstatus(settings->db) == CONNECTION_BAD) {
-            connected = 0;
-            fprintf(stderr, "We have lost the connection to the backend, so "
-                    "further processing is impossible.  Terminating.\n");
-          }
-       }
-      free(line); /* free storage malloc'd by GetNextLine */
-    }      
-  } /* while */
-  return exitStatus;
+            /* slash commands have to be on their own line */
+            if (line[0] == '\\') {
+                slashCmdStatus = HandleSlashCmds(settings,
+                                                 line, 
+                                                 query);
+                if (slashCmdStatus == 1) {
+                    free(line);
+                    continue;
+                }
+                if (slashCmdStatus == 2) {
+                    free(line);
+                    break;
+                }
+                if (slashCmdStatus == 0)
+                  sendQuery = 1;
+            } else if (strlen(query) + len > MAX_QUERY_BUFFER) {
+                fprintf(stderr,"query buffer max length of %d exceeded\n",
+                        MAX_QUERY_BUFFER);
+                fprintf(stderr,"query line ignored\n");
+            } else if (query[0]!='\0') {
+                strcat(query,"\n");
+                strcat(query,line);
+            } else strcpy(query,line);
+            if (sendQuery && query[0] != '\0') {
+                /* echo the line read from the file,
+                   unless we are in single_step mode, because single_step mode
+                   will echo anyway 
+                   */
+                bool success;  /* The query succeeded at the backend */
+                
+                if (!interactive && !settings->singleStep && !settings->quiet) 
+                  fprintf(stderr,"%s\n", query);
+
+                SendQuery(&success, settings, query, false, false, 0);
+                exitStatus = success ? 0 : 1;
+                querySent = 1;
+                if (PQstatus(settings->db) == CONNECTION_BAD) {
+                    connected = 0;
+                    fprintf(stderr, 
+                            "We have lost the connection to the backend, so "
+                            "further processing is impossible.  "
+                            "Terminating.\n");
+                }
+            }
+            free(line); /* free storage malloc'd by GetNextLine */
+        }      
+    } /* while */
+    return exitStatus;
 } 
 
+
+
 int
 main(int argc, char **argv)
 {
@@ -1164,26 +1376,26 @@ main(int argc, char **argv)
   settings.opt.fieldSep=strdup(DEFAULT_FIELD_SEP);
   settings.opt.pager = 1;
   if (!isatty(0) || !isatty(1))
-       settings.quiet = settings.notty = 1;
+    settings.quiet = settings.notty = 1;
 #ifndef NOREADLINE
   else
-       settings.useReadline = 1;
+    settings.useReadline = 1;
 #endif
 
   while ((c = getopt(argc, argv, "Aa:c:d:ef:F:lh:Hnso:p:qStT:x")) != EOF) {
     switch (c) {
     case 'A':
-       settings.opt.align = 0;
-       break;
+    settings.opt.align = 0;
+    break;
     case 'a':
-       fe_setauthsvc(optarg, errbuf);
-       break;
+    fe_setauthsvc(optarg, errbuf);
+    break;
     case 'c':
-       singleQuery = optarg;
-       if ( singleQuery[0] == '\\' ) {
-           singleSlashCmd=1;
-       }
-       break;
+    singleQuery = optarg;
+    if ( singleQuery[0] == '\\' ) {
+        singleSlashCmd=1;
+    }
+    break;
     case 'd':
       dbname = optarg;
       break;
@@ -1248,92 +1460,89 @@ main(int argc, char **argv)
   dbname = PQdb(settings.db);
 
   if (PQstatus(settings.db) == CONNECTION_BAD) {
-    fprintf(stderr,"Connection to database '%s' failed.\n", dbname);
-    fprintf(stderr,"%s",PQerrorMessage(settings.db));
-    exit(1);
+      fprintf(stderr,"Connection to database '%s' failed.\n", dbname);
+      fprintf(stderr,"%s",PQerrorMessage(settings.db));
+      exit(1);
   }
   if (listDatabases) {
       exit(listAllDbs(&settings));
-    }
+  }
 
   if (!settings.quiet && !singleQuery && !qfilename) {
-    printf("Welcome to the POSTGRES95 interactive sql monitor:\n");
-    printf("  Please read the file COPYRIGHT for copyright terms of POSTGRES95\n\n");
-    printf("   type \\? for help on slash commands\n");
-    printf("   type \\q to quit\n");
-    printf("   type \\g or terminate with semicolon to execute query\n");
-    printf(" You are currently connected to the database: %s\n\n", dbname);
-     }
-
+      printf("Welcome to the POSTGRES95 interactive sql monitor:\n");
+      printf("  Please read the file COPYRIGHT for copyright terms "
+             "of POSTGRES95\n\n");
+      printf("   type \\? for help on slash commands\n");
+      printf("   type \\q to quit\n");
+      printf("   type \\g or terminate with semicolon to execute query\n");
+      printf(" You are currently connected to the database: %s\n\n", dbname);
+  }
+  
   if (qfilename || singleSlashCmd) {
       /* read in a file full of queries instead of reading in queries
-        interactively */
+         interactively */
       char *line;
 
       if ( singleSlashCmd ) {
-       /* Not really a query, but "Do what I mean, not what I say." */
-       line = singleQuery;
-      }
-      else {
-       line = malloc(strlen(qfilename) + 5);
-       sprintf(line,"\\i %s", qfilename);
+          /* Not really a query, but "Do what I mean, not what I say." */
+          line = singleQuery;
+      } else {
+          line = malloc(strlen(qfilename) + 5);
+          sprintf(line,"\\i %s", qfilename);
       }
       HandleSlashCmds(&settings, line, "");
-      
-   } else {
-       if (singleQuery) {
-          exitStatus = SendQuery(&settings, singleQuery);
-       }
-       else 
-          exitStatus = MainLoop(&settings, stdin);
-   }
+  } else {
+      if (singleQuery) {
+          bool success;  /* The query succeeded at the backend */
+          SendQuery(&success, &settings, singleQuery, false, false, 0);
+          exitStatus = success ? 0 : 1;
+      } else exitStatus = MainLoop(&settings, stdin);
+  }
 
   PQfinish(settings.db);
 
   return exitStatus;
 }
 
-#define COPYBUFSIZ     8192
+#define COPYBUFSIZ  8192
 
 static void
-handleCopyOut(PGresult *res, bool quiet)
-{
-    bool copydone = false;
+handleCopyOut(PGresult *res, bool quiet, FILE *copystream) {
+    bool copydone;
     char copybuf[COPYBUFSIZ];
     int ret;
 
-    if (!quiet)
-       fprintf(stdout, "Copy command returns...\n");
-    
+    copydone = false;  /* Can't be done; haven't started. */
+
     while (!copydone) {
-       ret = PQgetline(res->conn, copybuf, COPYBUFSIZ);
-       
-       if (copybuf[0] == '\\' &&
-           copybuf[1] == '.' &&
-           copybuf[2] =='\0') {
-           copydone = true;    /* don't print this... */
-       } else {
-           fputs(copybuf, stdout);
-           switch (ret) {
-           case EOF:
-               copydone = true;
-               /*FALLTHROUGH*/
-           case 0:
-               fputc('\n', stdout);
-               break;
-           case 1:
-               break;
-           }
-       }
+        ret = PQgetline(res->conn, copybuf, COPYBUFSIZ);
+    
+        if (copybuf[0] == '\\' &&
+            copybuf[1] == '.' &&
+            copybuf[2] =='\0') {
+            copydone = true;    /* don't print this... */
+        } else {
+            fputs(copybuf, copystream);
+            switch (ret) {
+              case EOF:
+                copydone = true;
+                /*FALLTHROUGH*/
+              case 0:
+                fputc('\n', copystream);
+                break;
+              case 1:
+                break;
+            }
+        }
     }
-    fflush(stdout);
+    fflush(copystream);
     PQendcopy(res->conn);
 }
 
 
+
 static void
-handleCopyIn(PGresult *res, bool quiet)
-{
+handleCopyIn(PGresult *res, const bool mustprompt, FILE *copystream) {
     bool copydone = false;
     bool firstload;
     bool linedone;
@@ -1341,56 +1550,49 @@ handleCopyIn(PGresult *res, bool quiet)
     char *s;
     int buflen;
     int c;
-    
-    if (!quiet) {
-       fputs("Enter info followed by a newline\n", stdout);
-       fputs("End with a backslash and a period on a line by itself.\n", stdout);
+
+    if (mustprompt) {
+        fputs("Enter info followed by a newline\n", stdout);
+        fputs("End with a backslash and a "
+              "period on a line by itself.\n", stdout);
     }
     
-    /*
-     * eat extra newline still in input buffer
-     *
-     */
-    fflush(stdin);
-    if ((c = getc(stdin)) != '\n' && c != EOF) {
-       (void) ungetc(c, stdin);
+    while (!copydone) {         /* for each input line ... */
+    if (mustprompt) {
+        fputs(">> ", stdout);
+        fflush(stdout);
     }
-    
-    while (!copydone) {                        /* for each input line ... */
-       if (!quiet) {
-           fputs(">> ", stdout);
-           fflush(stdout);
-       }
-       firstload = true;
-       linedone = false;
-       while (!linedone) {             /* for each buffer ... */
-           s = copybuf;
-           buflen = COPYBUFSIZ;
-           for (; buflen > 1 &&
-                !(linedone = (c = getc(stdin)) == '\n' || c == EOF);
-                --buflen) {
-               *s++ = c;
-           }
-           if (c == EOF) {
-               /* reading from stdin, but from a file */
-               PQputline(res->conn, "\\.");
-               copydone = true;
-               break;
-           }
-           *s = '\0';
-           PQputline(res->conn, copybuf);
-           if (firstload) {
-               if (!strcmp(copybuf, "\\.")) {
-                   copydone = true;
-               }
-               firstload = false;
-           }
-       }
-       PQputline(res->conn, "\n");
+    firstload = true;
+    linedone = false;
+    while (!linedone) {     /* for each buffer ... */
+        s = copybuf;
+        buflen = COPYBUFSIZ;
+        for (; buflen > 1 &&
+             !(linedone = (c = getc(copystream)) == '\n' || c == EOF);
+             --buflen) {
+            *s++ = c;
+        }
+        if (c == EOF) {
+            PQputline(res->conn, "\\.");
+            copydone = true;
+            break;
+        }
+        *s = '\0';
+        PQputline(res->conn, copybuf);
+        if (firstload) {
+            if (!strcmp(copybuf, "\\.")) {
+                copydone = true;
+            }
+            firstload = false;
+        }
+    }
+    PQputline(res->conn, "\n");
     }
     PQendcopy(res->conn);
 }
 
+
+
 /* try to open fname and return a FILE *,
    if it fails, use stdout, instead */
 
@@ -1398,31 +1600,31 @@ FILE *
 setFout(PsqlSettings *ps, char *fname)
 {
         if (ps->queryFout && ps->queryFout != stdout)
-       {
-               if (ps->pipe)
-                       pclose(ps->queryFout);
-               else
-                       fclose(ps->queryFout);
-       }
-       if (!fname)
-               ps->queryFout = stdout;
-       else
-       {
-               if (*fname == '|')
-               {
-                       signal(SIGPIPE, SIG_IGN);
-                       ps->queryFout = popen(fname+1, "w");
-                       ps->pipe = 1;
-               }
-               else
-               {
-                       ps->queryFout = fopen(fname, "w");
-                       ps->pipe = 0;
-               }
-               if (!ps->queryFout) {
-                   perror(fname);
-                   ps->queryFout = stdout;
-               }
-       }
+    {
+        if (ps->pipe)
+            pclose(ps->queryFout);
+        else
+            fclose(ps->queryFout);
+    }
+    if (!fname)
+        ps->queryFout = stdout;
+    else
+    {
+        if (*fname == '|')
+        {
+                    signal(SIGPIPE, SIG_IGN);
+            ps->queryFout = popen(fname+1, "w");
+            ps->pipe = 1;
+        }
+        else
+        {
+            ps->queryFout = fopen(fname, "w");
+            ps->pipe = 0;
+        }
+        if (!ps->queryFout) {
+            perror(fname);
+            ps->queryFout = stdout;
+        }
+    }
     return ps->queryFout;
 }