]> granicus.if.org Git - postgresql/blobdiff - src/bin/pg_dump/pg_backup_db.c
Fix up the PQconnectionUsedPassword mess: create a separate
[postgresql] / src / bin / pg_dump / pg_backup_db.c
index d3962b6db2193396b840168ceef40e87cee1c1d7..d3e7959d8175e5771645c9b6545d0ced6bd55702 100644 (file)
  *     Implements the basic DB functions used by the archiver.
  *
  * IDENTIFICATION
- *       $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.20 2001/06/27 21:21:37 petere Exp $
- *
- * NOTES
- *
- * Modifications - 04-Jan-2001 - pjw@rhyme.com.au
- *
- *       - Check results of PQ routines more carefully.
- *
- * Modifications - 19-Mar-2001 - pjw@rhyme.com.au
- *
- *       - Avoid forcing table name to lower case in FixupBlobXrefs!
+ *       $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.77 2007/12/09 19:01:40 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
 
-#include "pg_dump.h"
-#include "pg_backup.h"
-#include "pg_backup_archiver.h"
 #include "pg_backup_db.h"
+#include "dumputils.h"
+
+#include <unistd.h>
 
-#include <unistd.h>                            /* for getopt() */
 #include <ctype.h>
 
 #ifdef HAVE_TERMIOS_H
 #include <termios.h>
 #endif
 
-#include "libpq-fe.h"
-#include "libpq/libpq-fs.h"
-#ifndef HAVE_STRDUP
-#include "strdup.h"
-#endif
 
-static const char *modulename = "archiver (db)";
+static const char *modulename = gettext_noop("archiver (db)");
 
 static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
-static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, char *newUser);
-static int     _executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc);
-
-
-/*
- * 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(gettext(prompt), stderr);
-
-#ifdef HAVE_TERMIOS_H
-       if (!echo)
-       {
-               tcgetattr(0, &t);
-               t_orig = t;
-               t.c_lflag &= ~ECHO;
-               tcsetattr(0, TCSADRAIN, &t);
-       }
-#endif
-
-       if (fgets(destination, maxlen, stdin) == NULL)
-               destination[0] = '\0';
-
-#ifdef HAVE_TERMIOS_H
-       if (!echo)
-       {
-               tcsetattr(0, TCSADRAIN, &t_orig);
-               fputs("\n", stderr);
-       }
-#endif
-
-       length = strlen(destination);
-       if (length > 0 && destination[length - 1] != '\n')
-       {
-               /* eat rest of the line */
-               char            buf[128];
-               int                     buflen;
+static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser);
+static void notice_processor(void *arg, const char *message);
+static char *_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos);
+static char *_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos);
 
-               do
-               {
-                       if (fgets(buf, sizeof(buf), stdin) == NULL)
-                               break;
-                       buflen = strlen(buf);
-               } while (buflen > 0 && buf[buflen - 1] != '\n');
-       }
-       if (length > 0 && destination[length - 1] == '\n')
-               /* remove trailing newline */
-               destination[length - 1] = '\0';
-
-       return destination;
-}
+static bool _isIdentChar(unsigned char c);
+static bool _isDQChar(unsigned char c, bool atStart);
 
+#define DB_MAX_ERR_STMT 128
 
 static int
-_parse_version(ArchiveHandle *AH, const charversionString)
+_parse_version(ArchiveHandle *AH, const char *versionString)
 {
-       int                     cnt;
-       int                     vmaj, vmin, vrev;
-
-       cnt = sscanf(versionString, "%d.%d.%d", &vmaj, &vmin, &vrev);
-
-       if (cnt < 2)
-       {
-               die_horribly(AH, modulename, "unable to parse version string \"%s\"\n", versionString);
-       }
+       int                     v;
 
-       if (cnt == 2)
-               vrev = 0;
+       v = parse_version(versionString);
+       if (v < 0)
+               die_horribly(AH, modulename, "could not parse version string \"%s\"\n", versionString);
 
-       return (100 * vmaj + vmin) * 100 + vrev;
+       return v;
 }
 
 static void
 _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
 {
-       PGresult   *res;
        int                     myversion;
        const char *remoteversion_str;
        int                     remoteversion;
-       PGconn     *conn = AH->connection;
 
        myversion = _parse_version(AH, PG_VERSION);
 
-       res = PQexec(conn, "SELECT version();");
-       if (!res ||
-               PQresultStatus(res) != PGRES_TUPLES_OK ||
-               PQntuples(res) != 1)
-
-               die_horribly(AH, modulename, "could not get version from server: %s", PQerrorMessage(conn));
-
-       remoteversion_str = PQgetvalue(res, 0, 0);
-       remoteversion = _parse_version(AH, remoteversion_str + 11);
+       remoteversion_str = PQparameterStatus(AH->connection, "server_version");
+       if (!remoteversion_str)
+               die_horribly(AH, modulename, "could not get server_version from libpq\n");
 
-       PQclear(res);
+       remoteversion = _parse_version(AH, remoteversion_str);
 
+       AH->public.remoteVersionStr = strdup(remoteversion_str);
        AH->public.remoteVersion = remoteversion;
 
-       if (myversion != remoteversion 
-               && (remoteversion < AH->public.minRemoteVersion || remoteversion > AH->public.maxRemoteVersion) )
+       if (myversion != remoteversion
+               && (remoteversion < AH->public.minRemoteVersion ||
+                       remoteversion > AH->public.maxRemoteVersion))
        {
-               write_msg(NULL, "server version: %s, %s version: %s\n",
+               write_msg(NULL, "server version: %s; %s version: %s\n",
                                  remoteversion_str, progname, PG_VERSION);
                if (ignoreVersion)
                        write_msg(NULL, "proceeding despite version mismatch\n");
@@ -175,78 +79,40 @@ _check_database_version(ArchiveHandle *AH, bool ignoreVersion)
 }
 
 /*
- * Check if a given user is a superuser.
+ * Reconnect to the server.  If dbname is not NULL, use that database,
+ * else the one associated with the archive handle.  If username is
+ * not NULL, use that user name, else the one from the handle. If
+ * both the database and the user match the existing connection already,
+ * nothing will be done.
+ *
+ * Returns 1 in any case.
  */
 int
-UserIsSuperuser(ArchiveHandle *AH, char *user)
+ReconnectToServer(ArchiveHandle *AH, const char *dbname, const char *username)
 {
-       PQExpBuffer qry = createPQExpBuffer();
-       PGresult   *res;
-       int                     i_usesuper;
-       int                     ntups;
-       int                     isSuper;
-
-       /* Get the superuser setting */
-       appendPQExpBuffer(qry, "select usesuper from pg_user where usename = '%s'", user);
-       res = PQexec(AH->connection, qry->data);
-
-       if (!res)
-               die_horribly(AH, modulename, "null result checking superuser status of %s\n", user);
-
-       if (PQresultStatus(res) != PGRES_TUPLES_OK)
-               die_horribly(AH, modulename, "could not check superuser status of %s: %s",
-                                        user, PQerrorMessage(AH->connection));
-
-       ntups = PQntuples(res);
+       PGconn     *newConn;
+       const char *newdbname;
+       const char *newusername;
 
-       if (ntups == 0)
-               isSuper = 0;
+       if (!dbname)
+               newdbname = PQdb(AH->connection);
        else
-       {
-               i_usesuper = PQfnumber(res, "usesuper");
-               isSuper = (strcmp(PQgetvalue(res, 0, i_usesuper), "t") == 0);
-       }
-       PQclear(res);
-
-       return isSuper;
-}
-
-int
-ConnectedUserIsSuperuser(ArchiveHandle *AH)
-{
-       return UserIsSuperuser(AH, PQuser(AH->connection));
-}
+               newdbname = dbname;
 
-char *
-ConnectedUser(ArchiveHandle *AH)
-{
-       return PQuser(AH->connection);
-}
-
-/*
- * Reconnect the DB associated with the archive handle
- */
-int
-ReconnectDatabase(ArchiveHandle *AH, const char *newdbname, char *newUser)
-{
-       PGconn     *newConn;
-       char       *dbname;
-
-       if (!newdbname || (strcmp(newdbname, "-") == 0))
-               dbname = PQdb(AH->connection);
+       if (!username)
+               newusername = PQuser(AH->connection);
        else
-               dbname = (char *) newdbname;
+               newusername = username;
 
        /* Let's see if the request is already satisfied */
-       if (strcmp(PQuser(AH->connection), newUser) == 0 && strcmp(newdbname, PQdb(AH->connection)) == 0)
+       if (strcmp(newdbname, PQdb(AH->connection)) == 0 &&
+               strcmp(newusername, PQuser(AH->connection)) == 0)
                return 1;
 
-       newConn = _connectDB(AH, dbname, newUser);
+       newConn = _connectDB(AH, newdbname, newusername);
 
        PQfinish(AH->connection);
        AH->connection = newConn;
-       free(AH->username);
-       AH->username = strdup(newUser);
 
        return 1;
 }
@@ -255,17 +121,15 @@ ReconnectDatabase(ArchiveHandle *AH, const char *newdbname, char *newUser)
  * Connect to the db again.
  */
 static PGconn *
-_connectDB(ArchiveHandle *AH, const char *reqdb, char *requser)
+_connectDB(ArchiveHandle *AH, const char *reqdb, const char *requser)
 {
-       int                     need_pass;
        PGconn     *newConn;
-       char       *password = NULL;
-       int                     badPwd = 0;
-       int                     noPwd = 0;
        char       *newdb;
        char       *newuser;
+       char       *password = NULL;
+       bool            new_pass;
 
-       if (!reqdb || (strcmp(reqdb, "-") == 0))
+       if (!reqdb)
                newdb = PQdb(AH->connection);
        else
                newdb = (char *) reqdb;
@@ -275,7 +139,7 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, char *requser)
        else
                newuser = (char *) requser;
 
-       ahlog(AH, 1, "Connecting to %s as %s\n", newdb, newuser);
+       ahlog(AH, 1, "connecting to database \"%s\" as user \"%s\"\n", newdb, newuser);
 
        if (AH->requirePassword)
        {
@@ -286,7 +150,7 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, char *requser)
 
        do
        {
-               need_pass = false;
+               new_pass = false;
                newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
                                                           NULL, NULL, newdb,
                                                           newuser, password);
@@ -295,35 +159,32 @@ _connectDB(ArchiveHandle *AH, const char *reqdb, char *requser)
 
                if (PQstatus(newConn) == CONNECTION_BAD)
                {
-                       noPwd = (strcmp(PQerrorMessage(newConn),
-                                                       "fe_sendauth: no password supplied\n") == 0);
-                       badPwd = (strncmp(PQerrorMessage(newConn),
-                                                         "Password authentication failed for user", 39) == 0);
-
-                       if (noPwd || badPwd)
-                       {
+                       if (!PQconnectionNeedsPassword(newConn))
+                               die_horribly(AH, modulename, "could not reconnect to database: %s",
+                                                        PQerrorMessage(newConn));
+                       PQfinish(newConn);
 
-                               if (badPwd)
-                                       fprintf(stderr, "Password incorrect\n");
+                       if (password)
+                               fprintf(stderr, "Password incorrect\n");
 
-                               fprintf(stderr, "Connecting to %s as %s\n",
-                                               PQdb(AH->connection), newuser);
+                       fprintf(stderr, "Connecting to %s as %s\n",
+                                       newdb, newuser);
 
-                               need_pass = true;
-                               if (password)
-                                       free(password);
-                               password = simple_prompt("Password: ", 100, false);
-                       }
-                       else
-                               die_horribly(AH, modulename, "could not reconnect to database: %s",
-                                                        PQerrorMessage(newConn));
+                       if (password)
+                               free(password);
+                       password = simple_prompt("Password: ", 100, false);
+                       new_pass = true;
                }
-
-       } while (need_pass);
+       } while (new_pass);
 
        if (password)
                free(password);
 
+       /* check for version mismatch */
+       _check_database_version(AH, true);
+
+       PQsetNoticeProcessor(newConn, notice_processor, NULL);
+
        return newConn;
 }
 
@@ -344,30 +205,10 @@ ConnectDatabase(Archive *AHX,
 {
        ArchiveHandle *AH = (ArchiveHandle *) AHX;
        char       *password = NULL;
-       bool            need_pass = false;
+       bool            new_pass;
 
        if (AH->connection)
-               die_horribly(AH, modulename, "already connected to database\n");
-
-       if (!dbname && !(dbname = getenv("PGDATABASE")))
-               die_horribly(AH, modulename, "no database name specified\n");
-
-       AH->dbname = strdup(dbname);
-
-       if (pghost != NULL)
-               AH->pghost = strdup(pghost);
-       else
-               AH->pghost = NULL;
-
-       if (pgport != NULL)
-               AH->pgport = strdup(pgport);
-       else
-               AH->pgport = NULL;
-
-       if (username != NULL)
-               AH->username = strdup(username);
-       else
-               AH->username = NULL;
+               die_horribly(AH, modulename, "already connected to a database\n");
 
        if (reqPwd)
        {
@@ -380,29 +221,28 @@ ConnectDatabase(Archive *AHX,
                AH->requirePassword = false;
 
        /*
-        * Start the connection.  Loop until we have a password if
-        * requested by backend.
+        * Start the connection.  Loop until we have a password if requested by
+        * backend.
         */
        do
        {
-               need_pass = false;
-               AH->connection = PQsetdbLogin(AH->pghost, AH->pgport, NULL, NULL,
-                                                                         AH->dbname, AH->username, password);
+               new_pass = false;
+               AH->connection = PQsetdbLogin(pghost, pgport, NULL, NULL,
+                                                                         dbname, username, password);
 
                if (!AH->connection)
                        die_horribly(AH, modulename, "failed to connect to database\n");
 
                if (PQstatus(AH->connection) == CONNECTION_BAD &&
-                       strcmp(PQerrorMessage(AH->connection), "fe_sendauth: no password supplied\n") == 0 &&
+                       PQconnectionNeedsPassword(AH->connection) &&
+                       password == NULL &&
                        !feof(stdin))
                {
                        PQfinish(AH->connection);
-                       need_pass = true;
-                       free(password);
-                       password = NULL;
                        password = simple_prompt("Password: ", 100, false);
+                       new_pass = true;
                }
-       } while (need_pass);
+       } while (new_pass);
 
        if (password)
                free(password);
@@ -410,59 +250,58 @@ ConnectDatabase(Archive *AHX,
        /* check to see that the backend connection was successfully made */
        if (PQstatus(AH->connection) == CONNECTION_BAD)
                die_horribly(AH, modulename, "connection to database \"%s\" failed: %s",
-                                        AH->dbname, PQerrorMessage(AH->connection));
+                                        PQdb(AH->connection), PQerrorMessage(AH->connection));
 
        /* check for version mismatch */
        _check_database_version(AH, ignoreVersion);
 
-       /*
-        * AH->currUser = PQuser(AH->connection);
-        *
-        * Removed because it prevented an initial \connect when dumping to SQL
-        * in pg_dump.
-        */
+       PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
 
        return AH->connection;
 }
 
-/* Public interface */
-/* Convenience function to send a query. Monitors result to handle COPY statements */
-int
-ExecuteSqlCommand(ArchiveHandle *AH, PQExpBuffer qry, char *desc, bool use_blob)
+
+static void
+notice_processor(void *arg, const char *message)
 {
-       if (use_blob)
-               return _executeSqlCommand(AH, AH->blobConnection, qry, desc);
-       else
-               return _executeSqlCommand(AH, AH->connection, qry, desc);
+       write_msg(NULL, "%s", message);
 }
 
-/*
- * Handle command execution. This is used to execute a command on more than one connection,
- * but the 'pgCopyIn' setting assumes the COPY commands are ONLY executed on the primary
- * setting...an error will be raised otherwise.
- */
+
+/* Public interface */
+/* Convenience function to send a query. Monitors result to handle COPY statements */
 static int
-_executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc)
+ExecuteSqlCommand(ArchiveHandle *AH, PQExpBuffer qry, char *desc)
 {
+       PGconn     *conn = AH->connection;
        PGresult   *res;
+       char            errStmt[DB_MAX_ERR_STMT];
 
        /* fprintf(stderr, "Executing: '%s'\n\n", qry->data); */
        res = PQexec(conn, qry->data);
        if (!res)
-               die_horribly(AH, modulename, "%s: no result from backend\n", desc);
+               die_horribly(AH, modulename, "%s: no result from server\n", desc);
 
        if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
        {
                if (PQresultStatus(res) == PGRES_COPY_IN)
                {
-                       if (conn != AH->connection)
-                               die_horribly(AH, modulename, "COPY command executed in non-primary connection\n");
-
-                       AH->pgCopyIn = 1;
+                       AH->pgCopyIn = true;
                }
                else
-                       die_horribly(AH, modulename, "%s: %s",
-                                                desc, PQerrorMessage(AH->connection));
+               {
+                       strncpy(errStmt, qry->data, DB_MAX_ERR_STMT);
+                       if (errStmt[DB_MAX_ERR_STMT - 1] != '\0')
+                       {
+                               errStmt[DB_MAX_ERR_STMT - 4] = '.';
+                               errStmt[DB_MAX_ERR_STMT - 3] = '.';
+                               errStmt[DB_MAX_ERR_STMT - 2] = '.';
+                               errStmt[DB_MAX_ERR_STMT - 1] = '\0';
+                       }
+                       warn_or_die_horribly(AH, modulename, "%s: %s    Command was: %s\n",
+                                                                desc, PQerrorMessage(AH->connection),
+                                                                errStmt);
+               }
        }
 
        PQclear(res);
@@ -470,356 +309,373 @@ _executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc)
        return strlen(qry->data);
 }
 
-/* Convenience function to send one or more queries. Monitors result to handle COPY statements */
-int
-ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, int bufLen)
+/*
+ * Used by ExecuteSqlCommandBuf to send one buffered line when running a COPY command.
+ */
+static char *
+_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos)
 {
-       int                     loc;
-       int                     pos = 0;
-       int                     sPos = 0;
-       char       *qry = (char *) qryv;
+       size_t          loc;                    /* Location of next newline */
+       int                     pos = 0;                /* Current position */
+       int                     sPos = 0;               /* Last pos of a slash char */
        int                     isEnd = 0;
-       char       *eos = qry + bufLen;
-
-       /*
-        * fprintf(stderr, "\n\n*****\n
-        * Buffer:\n\n%s\n*******************\n\n", qry);
-        */
 
-       /* If we're in COPY IN mode, then just break it into lines and send... */
-       if (AH->pgCopyIn)
+       /* loop to find unquoted newline ending the line of COPY data */
+       for (;;)
        {
-               for (;;)
+               loc = strcspn(&qry[pos], "\n") + pos;
+
+               /* If no match, then wait */
+               if (loc >= (eos - qry)) /* None found */
                {
+                       appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
+                       return eos;
+               }
+
+               /*
+                * fprintf(stderr, "Found cr at %d, prev char was %c, next was %c\n",
+                * loc, qry[loc-1], qry[loc+1]);
+                */
+
+               /* Count the number of preceding slashes */
+               sPos = loc;
+               while (sPos > 0 && qry[sPos - 1] == '\\')
+                       sPos--;
 
-                       /* Find a lf */
-                       loc = strcspn(&qry[pos], "\n") + pos;
-                       pos = 0;
+               sPos = loc - sPos;
 
-                       /* If no match, then wait */
-                       if (loc >= (eos - qry))         /* None found */
+               /*
+                * If an odd number of preceding slashes, then \n was escaped so set
+                * the next search pos, and loop (if any left).
+                */
+               if ((sPos & 1) == 1)
+               {
+                       /* fprintf(stderr, "cr was escaped\n"); */
+                       pos = loc + 1;
+                       if (pos >= (eos - qry))
                        {
                                appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
-                               break;
-                       };
+                               return eos;
+                       }
+               }
+               else
+                       break;
+       }
 
-                       /*
-                        * fprintf(stderr, "Found cr at %d, prev char was %c, next was
-                        * %c\n", loc, qry[loc-1], qry[loc+1]);
-                        */
+       /* We found an unquoted newline */
+       qry[loc] = '\0';
+       appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
+       isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
 
-                       /* Count the number of preceding slashes */
-                       sPos = loc;
-                       while (sPos > 0 && qry[sPos - 1] == '\\')
-                               sPos--;
+       /*
+        * Note that we drop the data on the floor if libpq has failed to enter
+        * COPY mode; this allows us to behave reasonably when trying to continue
+        * after an error in a COPY command.
+        */
+       if (AH->pgCopyIn &&
+               PQputCopyData(AH->connection, AH->pgCopyBuf->data,
+                                         AH->pgCopyBuf->len) <= 0)
+               die_horribly(AH, modulename, "error returned by PQputCopyData: %s",
+                                        PQerrorMessage(AH->connection));
 
-                       sPos = loc - sPos;
+       resetPQExpBuffer(AH->pgCopyBuf);
 
-                       /*
-                        * If an odd number of preceding slashes, then \n was escaped
-                        * so set the next search pos, and restart (if any left).
-                        */
-                       if ((sPos & 1) == 1)
-                       {
-                               /* fprintf(stderr, "cr was escaped\n"); */
-                               pos = loc + 1;
-                               if (pos >= (eos - qry))
-                               {
-                                       appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
-                                       break;
-                               }
-                       }
-                       else
-                       {
-                               /* We got a good cr */
-                               qry[loc] = '\0';
-                               appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
-                               qry += loc + 1;
-                               isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
-
-                               /*---------
-                                * fprintf(stderr, "Sending '%s' via
-                                *              COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd);
-                                *---------
-                                */
+       if (isEnd && AH->pgCopyIn)
+       {
+               PGresult   *res;
 
-                               if (PQputline(AH->connection, AH->pgCopyBuf->data) != 0)
-                                       die_horribly(AH, modulename, "error returned by PQputline\n");
+               if (PQputCopyEnd(AH->connection, NULL) <= 0)
+                       die_horribly(AH, modulename, "error returned by PQputCopyEnd: %s",
+                                                PQerrorMessage(AH->connection));
 
-                               resetPQExpBuffer(AH->pgCopyBuf);
+               /* Check command status and return to normal libpq state */
+               res = PQgetResult(AH->connection);
+               if (PQresultStatus(res) != PGRES_COMMAND_OK)
+                       warn_or_die_horribly(AH, modulename, "COPY failed: %s",
+                                                                PQerrorMessage(AH->connection));
+               PQclear(res);
 
-                               /*
-                                * fprintf(stderr, "Buffer is '%s'\n",
-                                * AH->pgCopyBuf->data);
-                                */
+               AH->pgCopyIn = false;
+       }
 
-                               if (isEnd)
-                               {
-                                       if (PQendcopy(AH->connection) != 0)
-                                               die_horribly(AH, modulename, "error returned by PQendcopy\n");
+       return qry + loc + 1;
+}
 
-                                       AH->pgCopyIn = 0;
-                                       break;
+/*
+ * Used by ExecuteSqlCommandBuf to send one buffered line of SQL
+ * (not data for the copy command).
+ */
+static char *
+_sendSQLLine(ArchiveHandle *AH, char *qry, char *eos)
+{
+       /*
+        * The following is a mini state machine to assess the end of an SQL
+        * statement. It really only needs to parse good SQL, or at least that's
+        * the theory... End-of-statement is assumed to be an unquoted,
+        * un-commented semi-colon that's not within any parentheses.
+        *
+        * Note: the input can be split into bufferloads at arbitrary boundaries.
+        * Therefore all state must be kept in AH->sqlparse, not in local
+        * variables of this routine.  We assume that AH->sqlparse was filled with
+        * zeroes when created.
+        */
+       for (; qry < eos; qry++)
+       {
+               switch (AH->sqlparse.state)
+               {
+                       case SQL_SCAN:          /* Default state == 0, set in _allocAH */
+                               if (*qry == ';' && AH->sqlparse.braceDepth == 0)
+                               {
+                                       /*
+                                        * We've found the end of a statement. Send it and reset
+                                        * the buffer.
+                                        */
+                                       appendPQExpBufferChar(AH->sqlBuf, ';');         /* inessential */
+                                       ExecuteSqlCommand(AH, AH->sqlBuf,
+                                                                         "could not execute query");
+                                       resetPQExpBuffer(AH->sqlBuf);
+                                       AH->sqlparse.lastChar = '\0';
+
+                                       /*
+                                        * Remove any following newlines - so that embedded COPY
+                                        * commands don't get a starting newline.
+                                        */
+                                       qry++;
+                                       while (qry < eos && *qry == '\n')
+                                               qry++;
+
+                                       /* We've finished one line, so exit */
+                                       return qry;
+                               }
+                               else if (*qry == '\'')
+                               {
+                                       if (AH->sqlparse.lastChar == 'E')
+                                               AH->sqlparse.state = SQL_IN_E_QUOTE;
+                                       else
+                                               AH->sqlparse.state = SQL_IN_SINGLE_QUOTE;
+                                       AH->sqlparse.backSlash = false;
+                               }
+                               else if (*qry == '"')
+                               {
+                                       AH->sqlparse.state = SQL_IN_DOUBLE_QUOTE;
                                }
 
-                       }
-
-                       /* Make sure we're not past the original buffer end */
-                       if (qry >= eos)
+                               /*
+                                * Look for dollar-quotes. We make the assumption that
+                                * $-quotes will not have an ident character just before them
+                                * in pg_dump output.  XXX is this good enough?
+                                */
+                               else if (*qry == '$' && !_isIdentChar(AH->sqlparse.lastChar))
+                               {
+                                       AH->sqlparse.state = SQL_IN_DOLLAR_TAG;
+                                       /* initialize separate buffer with possible tag */
+                                       if (AH->sqlparse.tagBuf == NULL)
+                                               AH->sqlparse.tagBuf = createPQExpBuffer();
+                                       else
+                                               resetPQExpBuffer(AH->sqlparse.tagBuf);
+                                       appendPQExpBufferChar(AH->sqlparse.tagBuf, *qry);
+                               }
+                               else if (*qry == '-' && AH->sqlparse.lastChar == '-')
+                                       AH->sqlparse.state = SQL_IN_SQL_COMMENT;
+                               else if (*qry == '*' && AH->sqlparse.lastChar == '/')
+                                       AH->sqlparse.state = SQL_IN_EXT_COMMENT;
+                               else if (*qry == '(')
+                                       AH->sqlparse.braceDepth++;
+                               else if (*qry == ')')
+                                       AH->sqlparse.braceDepth--;
                                break;
 
-               }
-       }
+                       case SQL_IN_SQL_COMMENT:
+                               if (*qry == '\n')
+                                       AH->sqlparse.state = SQL_SCAN;
+                               break;
 
-       /* We may have finished Copy In, and have a non-empty buffer */
-       if (!AH->pgCopyIn)
-       {
+                       case SQL_IN_EXT_COMMENT:
 
-               /*
-                * The following is a mini state machine to assess then of of an
-                * SQL statement. It really only needs to parse good SQL, or at
-                * least that's the theory... End-of-statement is assumed to be an
-                * unquoted, un commented semi-colon.
-                */
+                               /*
+                                * This isn't fully correct, because we don't account for
+                                * nested slash-stars, but pg_dump never emits such.
+                                */
+                               if (AH->sqlparse.lastChar == '*' && *qry == '/')
+                                       AH->sqlparse.state = SQL_SCAN;
+                               break;
 
-               /*
-                * fprintf(stderr, "Buffer at start is: '%s'\n\n",
-                * AH->sqlBuf->data);
-                */
+                       case SQL_IN_SINGLE_QUOTE:
+                               /* We needn't handle '' specially */
+                               if (*qry == '\'' && !AH->sqlparse.backSlash)
+                                       AH->sqlparse.state = SQL_SCAN;
+                               else if (*qry == '\\')
+                                       AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
+                               else
+                                       AH->sqlparse.backSlash = false;
+                               break;
 
-               for (pos = 0; pos < (eos - qry); pos++)
-               {
-                       appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
-                       /* fprintf(stderr, " %c",qry[pos]); */
+                       case SQL_IN_E_QUOTE:
 
-                       switch (AH->sqlparse.state)
-                       {
+                               /*
+                                * Eventually we will need to handle '' specially, because
+                                * after E'...''... we should still be in E_QUOTE state.
+                                *
+                                * XXX problem: how do we tell whether the dump was made by a
+                                * version that thinks backslashes aren't special in non-E
+                                * literals??
+                                */
+                               if (*qry == '\'' && !AH->sqlparse.backSlash)
+                                       AH->sqlparse.state = SQL_SCAN;
+                               else if (*qry == '\\')
+                                       AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
+                               else
+                                       AH->sqlparse.backSlash = false;
+                               break;
 
-                               case SQL_SCAN:  /* Default state == 0, set in _allocAH */
+                       case SQL_IN_DOUBLE_QUOTE:
+                               /* We needn't handle "" specially */
+                               if (*qry == '"')
+                                       AH->sqlparse.state = SQL_SCAN;
+                               break;
 
-                                       if (qry[pos] == ';' && AH->sqlparse.braceDepth == 0)
-                                       {
-                                               /* Send It & reset the buffer */
+                       case SQL_IN_DOLLAR_TAG:
+                               if (*qry == '$')
+                               {
+                                       /* Do not add the closing $ to tagBuf */
+                                       AH->sqlparse.state = SQL_IN_DOLLAR_QUOTE;
+                                       AH->sqlparse.minTagEndPos = AH->sqlBuf->len + AH->sqlparse.tagBuf->len + 1;
+                               }
+                               else if (_isDQChar(*qry, (AH->sqlparse.tagBuf->len == 1)))
+                               {
+                                       /* Valid, so add to tag */
+                                       appendPQExpBufferChar(AH->sqlparse.tagBuf, *qry);
+                               }
+                               else
+                               {
+                                       /*
+                                        * Ooops, we're not really in a dollar-tag.  Valid tag
+                                        * chars do not include the various chars we look for in
+                                        * this state machine, so it's safe to just jump from this
+                                        * state back to SCAN.  We have to back up the qry pointer
+                                        * so that the current character gets rescanned in SCAN
+                                        * state; and then "continue" so that the bottom-of-loop
+                                        * actions aren't done yet.
+                                        */
+                                       AH->sqlparse.state = SQL_SCAN;
+                                       qry--;
+                                       continue;
+                               }
+                               break;
 
-                                               /*
-                                                * fprintf(stderr, "    sending: '%s'\n\n",
-                                                * AH->sqlBuf->data);
-                                                */
-                                               ExecuteSqlCommand(AH, AH->sqlBuf, "could not execute query", false);
-                                               resetPQExpBuffer(AH->sqlBuf);
-                                               AH->sqlparse.lastChar = '\0';
-                                       }
-                                       else
-                                       {
-                                               if (qry[pos] == '"' || qry[pos] == '\'')
-                                               {
-                                                       /* fprintf(stderr,"[startquote]\n"); */
-                                                       AH->sqlparse.state = SQL_IN_QUOTE;
-                                                       AH->sqlparse.quoteChar = qry[pos];
-                                                       AH->sqlparse.backSlash = 0;
-                                               }
-                                               else if (qry[pos] == '-' && AH->sqlparse.lastChar == '-')
-                                                       AH->sqlparse.state = SQL_IN_SQL_COMMENT;
-                                               else if (qry[pos] == '*' && AH->sqlparse.lastChar == '/')
-                                                       AH->sqlparse.state = SQL_IN_EXT_COMMENT;
-                                               else if (qry[pos] == '(')
-                                                       AH->sqlparse.braceDepth++;
-                                               else if (qry[pos] == ')')
-                                                       AH->sqlparse.braceDepth--;
-
-                                               AH->sqlparse.lastChar = qry[pos];
-                                       }
-
-                                       break;
-
-                               case SQL_IN_SQL_COMMENT:
-
-                                       if (qry[pos] == '\n')
-                                               AH->sqlparse.state = SQL_SCAN;
-                                       break;
-
-                               case SQL_IN_EXT_COMMENT:
-
-                                       if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
-                                               AH->sqlparse.state = SQL_SCAN;
-                                       break;
-
-                               case SQL_IN_QUOTE:
-
-                                       if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
-                                       {
-                                               /* fprintf(stderr,"[endquote]\n"); */
-                                               AH->sqlparse.state = SQL_SCAN;
-                                       }
-                                       else
-                                       {
-
-                                               if (qry[pos] == '\\')
-                                               {
-                                                       if (AH->sqlparse.lastChar == '\\')
-                                                               AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
-                                                       else
-                                                               AH->sqlparse.backSlash = 1;
-                                               }
-                                               else
-                                                       AH->sqlparse.backSlash = 0;
-                                       }
-                                       break;
+                       case SQL_IN_DOLLAR_QUOTE:
 
-                       }
-                       AH->sqlparse.lastChar = qry[pos];
-                       /* fprintf(stderr, "\n"); */
+                               /*
+                                * If we are at a $, see whether what precedes it matches
+                                * tagBuf.      (Remember that the trailing $ of the tag was not
+                                * added to tagBuf.)  However, don't compare until we have
+                                * enough data to be a possible match --- this is needed to
+                                * avoid false match on '$a$a$...'
+                                */
+                               if (*qry == '$' &&
+                                       AH->sqlBuf->len >= AH->sqlparse.minTagEndPos &&
+                                       strcmp(AH->sqlparse.tagBuf->data,
+                                                  AH->sqlBuf->data + AH->sqlBuf->len - AH->sqlparse.tagBuf->len) == 0)
+                                       AH->sqlparse.state = SQL_SCAN;
+                               break;
                }
 
+               appendPQExpBufferChar(AH->sqlBuf, *qry);
+               AH->sqlparse.lastChar = *qry;
        }
 
-       return 1;
+       /*
+        * If we get here, we've processed entire bufferload with no complete SQL
+        * stmt
+        */
+       return eos;
 }
 
-void
-FixupBlobRefs(ArchiveHandle *AH, char *tablename)
-{
-       PQExpBuffer tblQry = createPQExpBuffer();
-       PGresult   *res,
-                          *uRes;
-       int                     i,
-                               n;
-       char       *attr;
-
-       if (strcmp(tablename, BLOB_XREF_TABLE) == 0)
-               return;
-
-       appendPQExpBuffer(tblQry, "SELECT a.attname FROM pg_class c, pg_attribute a, pg_type t "
-        " WHERE a.attnum > 0 AND a.attrelid = c.oid AND a.atttypid = t.oid "
-                         " AND t.typname = 'oid' AND c.relname = '%s';", tablename);
 
-       res = PQexec(AH->blobConnection, tblQry->data);
-       if (!res)
-               die_horribly(AH, modulename, "could not find oid columns of table \"%s\": %s",
-                                        tablename, PQerrorMessage(AH->connection));
+/* Convenience function to send one or more queries. Monitors result to handle COPY statements */
+int
+ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, size_t bufLen)
+{
+       char       *qry = (char *) qryv;
+       char       *eos = qry + bufLen;
 
-       if ((n = PQntuples(res)) == 0)
-       {
-               /* We're done */
-               ahlog(AH, 1, "No OID attributes in table %s\n", tablename);
-               PQclear(res);
-               return;
-       }
+       /*
+        * fprintf(stderr, "\n\n*****\n Buffer:\n\n%s\n*******************\n\n",
+        * qry);
+        */
 
-       for (i = 0; i < n; i++)
+       /* Could switch between command and COPY IN mode at each line */
+       while (qry < eos)
        {
-               attr = PQgetvalue(res, i, 0);
-
-               ahlog(AH, 1, " - %s.%s\n", tablename, attr);
-
-               resetPQExpBuffer(tblQry);
-
                /*
-                * We should use coalesce here (rather than 'exists'), but it
-                * seems to be broken in 7.0.2 (weird optimizer strategy)
+                * If libpq is in CopyIn mode *or* if the archive structure shows we
+                * are sending COPY data, treat the data as COPY data.  The pgCopyIn
+                * check is only needed for backwards compatibility with ancient
+                * archive files that might just issue a COPY command without marking
+                * it properly.  Note that in an archive entry that has a copyStmt,
+                * all data up to the end of the entry will go to _sendCopyLine, and
+                * therefore will be dropped if libpq has failed to enter COPY mode.
+                * Also, if a "\." data terminator is found, anything remaining in the
+                * archive entry will be dropped.
                 */
-               appendPQExpBuffer(tblQry, "UPDATE \"%s\" SET \"%s\" = ", tablename, attr);
-               appendPQExpBuffer(tblQry, " (SELECT x.newOid FROM \"%s\" x WHERE x.oldOid = \"%s\".\"%s\")",
-                                                 BLOB_XREF_TABLE, tablename, attr);
-               appendPQExpBuffer(tblQry, " where exists"
-                                 "(select * from %s x where x.oldOid = \"%s\".\"%s\");",
-                                                 BLOB_XREF_TABLE, tablename, attr);
-
-               ahlog(AH, 10, " - sql:\n%s\n", tblQry->data);
-
-               uRes = PQexec(AH->blobConnection, tblQry->data);
-               if (!uRes)
-                       die_horribly(AH, modulename,
-                                                "could not update column \"%s\" of table \"%s\": %s",
-                                                attr, tablename, PQerrorMessage(AH->blobConnection));
-
-               if (PQresultStatus(uRes) != PGRES_COMMAND_OK)
-                       die_horribly(AH, modulename,
-                                                "error while updating column \"%s\" of table \"%s\": %s",
-                                                attr, tablename, PQerrorMessage(AH->blobConnection));
-
-               PQclear(uRes);
+               if (AH->pgCopyIn || AH->writingCopyData)
+                       qry = _sendCopyLine(AH, qry, eos);
+               else
+                       qry = _sendSQLLine(AH, qry, eos);
        }
 
-       PQclear(res);
-
+       return 1;
 }
 
-/**********
- *     Convenient SQL calls
- **********/
 void
-CreateBlobXrefTable(ArchiveHandle *AH)
+StartTransaction(ArchiveHandle *AH)
 {
        PQExpBuffer qry = createPQExpBuffer();
 
-       /* IF we don't have a BLOB connection, then create one */
-       if (!AH->blobConnection)
-               AH->blobConnection = _connectDB(AH, NULL, NULL);
-
-       ahlog(AH, 1, "Creating table for BLOBS xrefs\n");
-
-       appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid oid, newOid oid);", BLOB_XREF_TABLE);
-
-       ExecuteSqlCommand(AH, qry, "could not create BLOB cross reference table", true);
-
-       resetPQExpBuffer(qry);
-
-       appendPQExpBuffer(qry, "Create Unique Index %s_ix on %s(oldOid)", BLOB_XREF_TABLE, BLOB_XREF_TABLE);
-       ExecuteSqlCommand(AH, qry, "could not create index on BLOB cross reference table", true);
-}
-
-void
-InsertBlobXref(ArchiveHandle *AH, int old, int new)
-{
-       PQExpBuffer qry = createPQExpBuffer();
+       appendPQExpBuffer(qry, "BEGIN");
 
-       appendPQExpBuffer(qry, "Insert Into %s(oldOid, newOid) Values (%d, %d);", BLOB_XREF_TABLE, old, new);
+       ExecuteSqlCommand(AH, qry, "could not start database transaction");
 
-       ExecuteSqlCommand(AH, qry, "could not create BLOB cross reference entry", true);
+       destroyPQExpBuffer(qry);
 }
 
 void
-StartTransaction(ArchiveHandle *AH)
+CommitTransaction(ArchiveHandle *AH)
 {
        PQExpBuffer qry = createPQExpBuffer();
 
-       appendPQExpBuffer(qry, "Begin;");
-
-       ExecuteSqlCommand(AH, qry, "could not start database transaction", false);
-       AH->txActive = true;
-}
-
-void
-StartTransactionXref(ArchiveHandle *AH)
-{
-       PQExpBuffer qry = createPQExpBuffer();
+       appendPQExpBuffer(qry, "COMMIT");
 
-       appendPQExpBuffer(qry, "Begin;");
+       ExecuteSqlCommand(AH, qry, "could not commit database transaction");
 
-       ExecuteSqlCommand(AH, qry,
-                                         "could not start transaction for BLOB cross references", true);
-       AH->blobTxActive = true;
+       destroyPQExpBuffer(qry);
 }
 
-void
-CommitTransaction(ArchiveHandle *AH)
+static bool
+_isIdentChar(unsigned char c)
 {
-       PQExpBuffer qry = createPQExpBuffer();
-
-       appendPQExpBuffer(qry, "Commit;");
-
-       ExecuteSqlCommand(AH, qry, "could not commit database transaction", false);
-       AH->txActive = false;
+       if ((c >= 'a' && c <= 'z')
+               || (c >= 'A' && c <= 'Z')
+               || (c >= '0' && c <= '9')
+               || (c == '_')
+               || (c == '$')
+               || (c >= (unsigned char) '\200')                /* no need to check <= \377 */
+               )
+               return true;
+       else
+               return false;
 }
 
-void
-CommitTransactionXref(ArchiveHandle *AH)
+static bool
+_isDQChar(unsigned char c, bool atStart)
 {
-       PQExpBuffer qry = createPQExpBuffer();
-
-       appendPQExpBuffer(qry, "Commit;");
-
-       ExecuteSqlCommand(AH, qry, "could not commit transaction for BLOB cross references", true);
-       AH->blobTxActive = false;
+       if ((c >= 'a' && c <= 'z')
+               || (c >= 'A' && c <= 'Z')
+               || (c == '_')
+               || (!atStart && c >= '0' && c <= '9')
+               || (c >= (unsigned char) '\200')                /* no need to check <= \377 */
+               )
+               return true;
+       else
+               return false;
 }