* Implements the basic DB functions used by the archiver.
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.33 2002/05/29 01:38:56 tgl Exp $
+ * $PostgreSQL: pgsql/src/bin/pg_dump/pg_backup_db.c,v 1.84 2009/06/11 14:49:07 momjian Exp $
*
*-------------------------------------------------------------------------
*/
-#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 = gettext_noop("archiver (db)");
-static void _check_database_version(ArchiveHandle *AH, bool ignoreVersion);
+static void _check_database_version(ArchiveHandle *AH);
static PGconn *_connectDB(ArchiveHandle *AH, const char *newdbname, const char *newUser);
-static int _executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc);
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);
+static bool _isIdentChar(unsigned char c);
+static bool _isDQChar(unsigned char c, bool atStart);
-/*
- * simple_prompt --- borrowed from psql
- *
- * Generalized function especially intended for reading in usernames and
- * password interactively. Reads from /dev/tty or stdin/stderr.
- *
- * 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).
- */
-static bool prompt_state = false;
-
-char *
-simple_prompt(const char *prompt, int maxlen, bool echo)
-{
- int length;
- char *destination;
- FILE *termin,
- *termout;
-
-#ifdef HAVE_TERMIOS_H
- struct termios t_orig,
- t;
-#endif
-
- destination = (char *) malloc(maxlen + 2);
- if (!destination)
- return NULL;
-
- prompt_state = true; /* disable SIGINT */
-
- /*
- * Do not try to collapse these into one "w+" mode file. Doesn't work
- * on some platforms (eg, HPUX 10.20).
- */
- termin = fopen("/dev/tty", "r");
- termout = fopen("/dev/tty", "w");
- if (!termin || !termout)
- {
- if (termin)
- fclose(termin);
- if (termout)
- fclose(termout);
- termin = stdin;
- termout = stderr;
- }
-
-#ifdef HAVE_TERMIOS_H
- if (!echo)
- {
- tcgetattr(fileno(termin), &t);
- t_orig = t;
- t.c_lflag &= ~ECHO;
- tcsetattr(fileno(termin), TCSAFLUSH, &t);
- }
-#endif
-
- if (prompt)
- {
- fputs(gettext(prompt), termout);
- fflush(termout);
- }
-
- if (fgets(destination, maxlen, termin) == NULL)
- destination[0] = '\0';
-
- length = strlen(destination);
- if (length > 0 && destination[length - 1] != '\n')
- {
- /* eat rest of the line */
- char buf[128];
- int buflen;
-
- do
- {
- if (fgets(buf, sizeof(buf), termin) == 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';
-
-#ifdef HAVE_TERMIOS_H
- if (!echo)
- {
- tcsetattr(fileno(termin), TCSAFLUSH, &t_orig);
- fputs("\n", termout);
- fflush(termout);
- }
-#endif
-
- if (termin != stdin)
- {
- fclose(termin);
- fclose(termout);
- }
-
- prompt_state = false; /* SIGINT okay again */
-
- return destination;
-}
-
+#define DB_MAX_ERR_STMT 128
static int
_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)
+_check_database_version(ArchiveHandle *AH)
{
- 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 = PQparameterStatus(AH->connection, "server_version");
+ if (!remoteversion_str)
+ die_horribly(AH, modulename, "could not get server_version from libpq\n");
- remoteversion_str = PQgetvalue(res, 0, 0);
- remoteversion = _parse_version(AH, remoteversion_str + 11);
-
- 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))
+ && (remoteversion < AH->public.minRemoteVersion ||
+ remoteversion > AH->public.maxRemoteVersion))
{
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");
- else
- die_horribly(AH, NULL, "aborting because of version mismatch (Use the -i option to proceed anyway.)\n");
+ die_horribly(AH, NULL, "aborting because of server version mismatch\n");
}
}
* 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 and match the existing connection
- * already, nothing will be done.
+ * both the database and the user match the existing connection already,
+ * nothing will be done.
*
* Returns 1 in any case.
*/
newusername = username;
/* Let's see if the request is already satisfied */
- if (strcmp(newusername, PQuser(AH->connection)) == 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, newdbname, newusername);
PQfinish(AH->connection);
AH->connection = newConn;
- free(AH->username);
- AH->username = strdup(newusername);
- /* XXX Why don't we update AH->dbname? */
-
- /* don't assume we still know the output schema */
- if (AH->currSchema)
- free(AH->currSchema);
- AH->currSchema = strdup("");
-
return 1;
}
/*
* Connect to the db again.
+ *
+ * Note: it's not really all that sensible to use a single-entry password
+ * cache if the username keeps changing. In current usage, however, the
+ * username never does change, so one savedPassword is sufficient. We do
+ * update the cache on the off chance that the password has changed since the
+ * start of the run.
*/
static PGconn *
_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;
+ const char *newdb;
+ const char *newuser;
+ char *password = AH->savedPassword;
+ bool new_pass;
if (!reqdb)
newdb = PQdb(AH->connection);
else
- newdb = (char *) reqdb;
+ newdb = reqdb;
- if (!requser || (strlen(requser) == 0))
+ if (!requser || strlen(requser) == 0)
newuser = PQuser(AH->connection);
else
- newuser = (char *) requser;
+ newuser = requser;
- ahlog(AH, 1, "connecting to database %s as user %s\n", newdb, newuser);
+ ahlog(AH, 1, "connecting to database \"%s\" as user \"%s\"\n",
+ newdb, newuser);
- if (AH->requirePassword)
+ if (AH->promptPassword == TRI_YES && password == NULL)
{
password = simple_prompt("Password: ", 100, false);
if (password == NULL)
do
{
- need_pass = false;
+ new_pass = false;
newConn = PQsetdbLogin(PQhost(AH->connection), PQport(AH->connection),
NULL, NULL, newdb,
newuser, password);
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 (!PQconnectionNeedsPassword(newConn))
+ die_horribly(AH, modulename, "could not reconnect to database: %s",
+ PQerrorMessage(newConn));
+ PQfinish(newConn);
- if (noPwd || badPwd)
- {
+ if (password)
+ fprintf(stderr, "Password incorrect\n");
- if (badPwd)
- fprintf(stderr, "Password incorrect\n");
+ fprintf(stderr, "Connecting to %s as %s\n",
+ newdb, newuser);
- fprintf(stderr, "Connecting to %s as %s\n",
- PQdb(AH->connection), newuser);
+ if (password)
+ free(password);
- need_pass = true;
- if (password)
- free(password);
+ if (AH->promptPassword != TRI_NO)
password = simple_prompt("Password: ", 100, false);
- }
else
- die_horribly(AH, modulename, "could not reconnect to database: %s",
- PQerrorMessage(newConn));
+ die_horribly(AH, modulename, "connection needs password\n");
+
+ if (password == NULL)
+ die_horribly(AH, modulename, "out of memory\n");
+ new_pass = true;
}
+ } while (new_pass);
- } while (need_pass);
+ AH->savedPassword = password;
- if (password)
- free(password);
+ /* check for version mismatch */
+ _check_database_version(AH);
PQsetNoticeProcessor(newConn, notice_processor, NULL);
* Make a database connection with the given parameters. The
* connection handle is returned, the parameters are stored in AHX.
* An interactive password prompt is automatically issued if required.
+ *
+ * Note: it's not really all that sensible to use a single-entry password
+ * cache if the username keeps changing. In current usage, however, the
+ * username never does change, so one savedPassword is sufficient.
*/
PGconn *
ConnectDatabase(Archive *AHX,
const char *pghost,
const char *pgport,
const char *username,
- const int reqPwd,
- const int ignoreVersion)
+ enum trivalue prompt_password)
{
ArchiveHandle *AH = (ArchiveHandle *) AHX;
- char *password = NULL;
- bool need_pass = false;
+ char *password = AH->savedPassword;
+ bool new_pass;
if (AH->connection)
die_horribly(AH, modulename, "already connected to a 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;
-
- if (reqPwd)
+ if (prompt_password == TRI_YES && password == NULL)
{
password = simple_prompt("Password: ", 100, false);
if (password == NULL)
die_horribly(AH, modulename, "out of memory\n");
- AH->requirePassword = true;
}
- else
- AH->requirePassword = false;
+ AH->promptPassword = prompt_password;
/*
- * 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 &&
- !feof(stdin))
+ PQconnectionNeedsPassword(AH->connection) &&
+ password == NULL &&
+ prompt_password != TRI_NO)
{
PQfinish(AH->connection);
- need_pass = true;
- free(password);
- password = NULL;
password = simple_prompt("Password: ", 100, false);
+ if (password == NULL)
+ die_horribly(AH, modulename, "out of memory\n");
+ new_pass = true;
}
- } while (need_pass);
+ } while (new_pass);
- if (password)
- free(password);
+ AH->savedPassword = password;
/* 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);
+ _check_database_version(AH);
PQsetNoticeProcessor(AH->connection, notice_processor, NULL);
/* 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)
-{
- if (use_blob)
- return _executeSqlCommand(AH, AH->blobConnection, qry, desc);
- else
- return _executeSqlCommand(AH, AH->connection, qry, desc);
-}
-
-/*
- * 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.
- */
-static int
-_executeSqlCommand(ArchiveHandle *AH, PGconn *conn, PQExpBuffer qry, char *desc)
+static void
+ExecuteSqlCommand(ArchiveHandle *AH, const char *qry, const 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 server\n", desc);
+#ifdef NOT_USED
+ fprintf(stderr, "Executing: '%s'\n\n", qry);
+#endif
+ res = PQexec(conn, qry);
- if (PQresultStatus(res) != PGRES_COMMAND_OK && PQresultStatus(res) != PGRES_TUPLES_OK)
+ switch (PQresultStatus(res))
{
- 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;
- }
- else
- die_horribly(AH, modulename, "%s: %s",
- desc, PQerrorMessage(AH->connection));
+ case PGRES_COMMAND_OK:
+ case PGRES_TUPLES_OK:
+ /* A-OK */
+ break;
+ case PGRES_COPY_IN:
+ /* Assume this is an expected result */
+ AH->pgCopyIn = true;
+ break;
+ default:
+ /* trouble */
+ strncpy(errStmt, qry, 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(conn), errStmt);
+ break;
}
PQclear(res);
-
- return strlen(qry->data);
}
/*
static char *
_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos)
{
- int loc; /* Location of next newline */
+ size_t loc; /* Location of next newline */
int pos = 0; /* Current position */
int sPos = 0; /* Last pos of a slash char */
int isEnd = 0;
}
/*
- * fprintf(stderr, "Found cr at %d, prev char was %c, next was
- * %c\n", loc, qry[loc-1], qry[loc+1]);
+ * 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 - sPos;
/*
- * If an odd number of preceding slashes, then \n was escaped so
- * set the next search pos, and loop (if any left).
+ * 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)
{
appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
- /*---------
- * fprintf(stderr, "Sending '%s' via
- * COPY (at end = %d)\n\n", AH->pgCopyBuf->data, isEnd);
- *---------
+ /*
+ * 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 (PQputline(AH->connection, AH->pgCopyBuf->data) != 0)
- die_horribly(AH, modulename, "error returned by PQputline\n");
+ 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));
resetPQExpBuffer(AH->pgCopyBuf);
- /*
- * fprintf(stderr, "Buffer is '%s'\n", AH->pgCopyBuf->data);
- */
-
- if (isEnd)
+ if (isEnd && AH->pgCopyIn)
{
- if (PQendcopy(AH->connection) != 0)
- die_horribly(AH, modulename, "error returned by PQendcopy\n");
+ PGresult *res;
+
+ if (PQputCopyEnd(AH->connection, NULL) <= 0)
+ die_horribly(AH, modulename, "error returned by PQputCopyEnd: %s",
+ PQerrorMessage(AH->connection));
- AH->pgCopyIn = 0;
+ /* 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);
+
+ AH->pgCopyIn = false;
}
return qry + loc + 1;
}
/*
- * Used by ExecuteSqlCommandBuf to send one buffered line of SQL (not data for the copy command).
+ * 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)
{
- int pos = 0; /* Current position */
-
/*
* 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.
- */
-
- /*
- * fprintf(stderr, "Buffer at start is: '%s'\n\n", AH->sqlBuf->data);
+ * 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 (pos = 0; pos < (eos - qry); pos++)
+ for (; qry < eos; qry++)
{
- appendPQExpBufferChar(AH->sqlBuf, qry[pos]);
- /* fprintf(stderr, " %c",qry[pos]); */
-
switch (AH->sqlparse.state)
{
-
case SQL_SCAN: /* Default state == 0, set in _allocAH */
-
- if (qry[pos] == ';' && AH->sqlparse.braceDepth == 0)
+ if (*qry == ';' && AH->sqlparse.braceDepth == 0)
{
- /* Send It & reset the buffer */
-
/*
- * fprintf(stderr, " sending: '%s'\n\n",
- * AH->sqlBuf->data);
+ * We've found the end of a statement. Send it and reset
+ * the buffer.
*/
- ExecuteSqlCommand(AH, AH->sqlBuf, "could not execute query", false);
+ appendPQExpBufferChar(AH->sqlBuf, ';'); /* inessential */
+ ExecuteSqlCommand(AH, AH->sqlBuf->data,
+ "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.
+ * Remove any following newlines - so that embedded COPY
+ * commands don't get a starting newline.
*/
- pos++;
- for (; pos < (eos - qry) && qry[pos] == '\n'; pos++);
+ qry++;
+ while (qry < eos && *qry == '\n')
+ qry++;
- /* We've got our line, so exit */
- return qry + pos;
+ /* We've finished one line, so exit */
+ return qry;
}
- else
+ else if (*qry == '\'')
{
- 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];
+ 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;
}
+ /*
+ * 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[pos] == '\n')
+ if (*qry == '\n')
AH->sqlparse.state = SQL_SCAN;
break;
case SQL_IN_EXT_COMMENT:
- if (AH->sqlparse.lastChar == '*' && qry[pos] == '/')
+ /*
+ * 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;
- case SQL_IN_QUOTE:
+ 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;
- if (!AH->sqlparse.backSlash && AH->sqlparse.quoteChar == qry[pos])
- {
- /* fprintf(stderr,"[endquote]\n"); */
+ case SQL_IN_E_QUOTE:
+
+ /*
+ * 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_IN_DOUBLE_QUOTE:
+ /* We needn't handle "" specially */
+ if (*qry == '"')
AH->sqlparse.state = SQL_SCAN;
+ break;
+
+ 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
{
-
- if (qry[pos] == '\\')
- {
- if (AH->sqlparse.lastChar == '\\')
- AH->sqlparse.backSlash = !AH->sqlparse.backSlash;
- else
- AH->sqlparse.backSlash = 1;
- }
- else
- AH->sqlparse.backSlash = 0;
+ /*
+ * 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;
+ case SQL_IN_DOLLAR_QUOTE:
+
+ /*
+ * 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;
}
- AH->sqlparse.lastChar = qry[pos];
- /* fprintf(stderr, "\n"); */
+
+ appendPQExpBufferChar(AH->sqlBuf, *qry);
+ AH->sqlparse.lastChar = *qry;
}
/*
- * If we get here, we've processed entire string with no complete SQL
+ * If we get here, we've processed entire bufferload with no complete SQL
* stmt
*/
return eos;
-
}
/* Convenience function to send one or more queries. Monitors result to handle COPY statements */
int
-ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, int bufLen)
+ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qryv, size_t bufLen)
{
char *qry = (char *) qryv;
char *eos = qry + bufLen;
/*
- * fprintf(stderr, "\n\n*****\n
- * Buffer:\n\n%s\n*******************\n\n", qry);
+ * fprintf(stderr, "\n\n*****\n Buffer:\n\n%s\n*******************\n\n",
+ * qry);
*/
/* Could switch between command and COPY IN mode at each line */
while (qry < eos)
{
- if (AH->pgCopyIn)
+ /*
+ * 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.
+ */
+ if (AH->pgCopyIn || AH->writingCopyData)
qry = _sendCopyLine(AH, qry, eos);
else
qry = _sendSQLLine(AH, qry, eos);
}
void
-FixupBlobRefs(ArchiveHandle *AH, TocEntry *te)
-{
- PQExpBuffer tblName;
- PQExpBuffer tblQry;
- PGresult *res,
- *uRes;
- int i,
- n;
- char *attr;
-
- if (strcmp(te->name, BLOB_XREF_TABLE) == 0)
- return;
-
- tblName = createPQExpBuffer();
- tblQry = createPQExpBuffer();
-
- if (te->namespace && strlen(te->namespace) > 0)
- appendPQExpBuffer(tblName, "%s.",
- fmtId(te->namespace, false));
- appendPQExpBuffer(tblName, "%s",
- fmtId(te->name, false));
-
- appendPQExpBuffer(tblQry,
- "SELECT a.attname FROM "
- "pg_catalog.pg_attribute a, pg_catalog.pg_type t "
- "WHERE a.attnum > 0 AND a.attrelid = '%s'::pg_catalog.regclass "
- "AND a.atttypid = t.oid AND t.typname in ('oid', 'lo')",
- tblName->data);
-
- res = PQexec(AH->blobConnection, tblQry->data);
- if (!res)
- die_horribly(AH, modulename, "could not find oid columns of table \"%s\": %s",
- te->name, PQerrorMessage(AH->connection));
-
- if ((n = PQntuples(res)) == 0)
- {
- /* nothing to do */
- ahlog(AH, 1, "no OID type columns in table %s\n", te->name);
- }
-
- for (i = 0; i < n; i++)
- {
- attr = PQgetvalue(res, i, 0);
-
- ahlog(AH, 1, "fixing large object cross-references for %s.%s\n",
- te->name, attr);
-
- resetPQExpBuffer(tblQry);
-
- /* Can't use fmtId twice in one call... */
- appendPQExpBuffer(tblQry,
- "UPDATE %s SET %s = %s.newOid",
- tblName->data, fmtId(attr, false),
- BLOB_XREF_TABLE);
- appendPQExpBuffer(tblQry,
- " FROM %s WHERE %s.oldOid = %s.%s",
- BLOB_XREF_TABLE,
- BLOB_XREF_TABLE,
- tblName->data, fmtId(attr, false));
-
- ahlog(AH, 10, "SQL: %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, te->name, PQerrorMessage(AH->blobConnection));
-
- if (PQresultStatus(uRes) != PGRES_COMMAND_OK)
- die_horribly(AH, modulename,
- "error while updating column \"%s\" of table \"%s\": %s",
- attr, te->name, PQerrorMessage(AH->blobConnection));
-
- PQclear(uRes);
- }
-
- PQclear(res);
- destroyPQExpBuffer(tblName);
- destroyPQExpBuffer(tblQry);
-}
-
-/**********
- * Convenient SQL calls
- **********/
-void
-CreateBlobXrefTable(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 large object cross-references\n");
-
- appendPQExpBuffer(qry, "Create Temporary Table %s(oldOid pg_catalog.oid, newOid pg_catalog.oid);", BLOB_XREF_TABLE);
-
- ExecuteSqlCommand(AH, qry, "could not create large object 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 large object cross-reference table", true);
-
- destroyPQExpBuffer(qry);
-}
-
-void
-InsertBlobXref(ArchiveHandle *AH, Oid old, Oid new)
-{
- PQExpBuffer qry = createPQExpBuffer();
-
- appendPQExpBuffer(qry,
- "Insert Into %s(oldOid, newOid) Values ('%u', '%u');",
- BLOB_XREF_TABLE, old, new);
-
- ExecuteSqlCommand(AH, qry, "could not create large object cross-reference entry", true);
-
- destroyPQExpBuffer(qry);
-}
-
-void
StartTransaction(ArchiveHandle *AH)
{
- PQExpBuffer qry = createPQExpBuffer();
-
- appendPQExpBuffer(qry, "Begin;");
-
- ExecuteSqlCommand(AH, qry, "could not start database transaction", false);
- AH->txActive = true;
-
- destroyPQExpBuffer(qry);
+ ExecuteSqlCommand(AH, "BEGIN", "could not start database transaction");
}
void
-StartTransactionXref(ArchiveHandle *AH)
+CommitTransaction(ArchiveHandle *AH)
{
- PQExpBuffer qry = createPQExpBuffer();
-
- appendPQExpBuffer(qry, "Begin;");
-
- ExecuteSqlCommand(AH, qry,
- "could not start transaction for large object cross-references", true);
- AH->blobTxActive = true;
-
- destroyPQExpBuffer(qry);
+ ExecuteSqlCommand(AH, "COMMIT", "could not commit database transaction");
}
-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;
-
- destroyPQExpBuffer(qry);
+ 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 large object cross-references", true);
- AH->blobTxActive = false;
-
- destroyPQExpBuffer(qry);
+ 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;
}