static void _moveBefore(ArchiveHandle *AH, TocEntry *pos, TocEntry *te);
static int _discoverArchiveFormat(ArchiveHandle *AH);
+static int RestoringToDB(ArchiveHandle *AH);
static void dump_lo_buf(ArchiveHandle *AH);
static void _write_msg(const char *modulename, const char *fmt, va_list ap);
static void _die_horribly(ArchiveHandle *AH, const char *modulename, const char *fmt, va_list ap);
}
/*
- * If we have a copy statement, use it. As of V1.3, these
- * are separate to allow easy import from withing a
- * database connection. Pre 1.3 archives can not use DB
- * connections and are sent to output only.
- *
- * For V1.3+, the table data MUST have a copy statement so
- * that we can go into appropriate mode with libpq.
+ * If we have a copy statement, use it.
*/
if (te->copyStmt && strlen(te->copyStmt) > 0)
{
(*AH->PrintTocDataPtr) (AH, te, ropt);
- AH->writingCopyData = false;
+ /*
+ * Terminate COPY if needed.
+ */
+ if (AH->writingCopyData)
+ {
+ if (RestoringToDB(AH))
+ EndDBCopyMode(AH, te);
+ AH->writingCopyData = false;
+ }
/* close out the transaction started above */
if (is_parallel && te->created)
{
char *p = NULL;
va_list ap;
- int bSize = strlen(fmt) + 256; /* Should be enough */
+ int bSize = strlen(fmt) + 256; /* Usually enough */
int cnt = -1;
/*
* This is paranoid: deal with the possibility that vsnprintf is willing
- * to ignore trailing null
- */
-
- /*
- * or returns > 0 even if string does not fit. It may be the case that it
- * returns cnt = bufsize
+ * to ignore trailing null or returns > 0 even if string does not fit.
+ * It may be the case that it returns cnt = bufsize.
*/
while (cnt < 0 || cnt >= (bSize - 1))
{
/*
- * Write buffer to the output file (usually stdout). This is user for
+ * Write buffer to the output file (usually stdout). This is used for
* outputting 'restore' scripts etc. It is even possible for an archive
* format to create a custom output routine to 'fake' a restore if it
* wants to generate a script (see TAR output).
* connected then send it to the DB.
*/
if (RestoringToDB(AH))
- return ExecuteSqlCommandBuf(AH, (void *) ptr, size * nmemb); /* Always 1, currently */
+ return ExecuteSqlCommandBuf(AH, (const char *) ptr, size * nmemb);
else
{
res = fwrite((void *) ptr, size, nmemb, AH->OF);
AH->mode = mode;
AH->compression = compression;
- AH->pgCopyBuf = createPQExpBuffer();
- AH->sqlBuf = createPQExpBuffer();
-
/* Open stdout with no compression for AH output handle */
AH->gzOut = 0;
AH->OF = stdout;
die_horribly(AH, modulename, "out of memory\n");
memcpy(clone, AH, sizeof(ArchiveHandle));
- /* Handle format-independent fields */
- clone->pgCopyBuf = createPQExpBuffer();
- clone->sqlBuf = createPQExpBuffer();
- clone->sqlparse.tagBuf = NULL;
+ /* Handle format-independent fields ... none at the moment */
/* The clone will have its own connection, so disregard connection state */
clone->connection = NULL;
/* Clear format-specific state */
(AH->DeClonePtr) (AH);
- /* Clear state allocated by CloneArchive */
- destroyPQExpBuffer(AH->pgCopyBuf);
- destroyPQExpBuffer(AH->sqlBuf);
- if (AH->sqlparse.tagBuf)
- destroyPQExpBuffer(AH->sqlparse.tagBuf);
+ /* Clear state allocated by CloneArchive ... none at the moment */
/* Clear any connection-local state */
if (AH->currUser)
#include "dumputils.h"
#include <unistd.h>
-
#include <ctype.h>
-
#ifdef HAVE_TERMIOS_H
#include <termios.h>
#endif
+#define DB_MAX_ERR_STMT 128
+
static const char *modulename = gettext_noop("archiver (db)");
static void _check_database_version(ArchiveHandle *AH);
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);
-
-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 char *versionString)
}
-/* Public interface */
-/* Convenience function to send a query. Monitors result to handle COPY statements */
+/*
+ * Convenience function to send a query.
+ * Monitors result to detect COPY statements
+ */
static void
ExecuteSqlCommand(ArchiveHandle *AH, const char *qry, const char *desc)
{
{
case PGRES_COMMAND_OK:
case PGRES_TUPLES_OK:
+ case PGRES_EMPTY_QUERY:
/* A-OK */
break;
case PGRES_COPY_IN:
PQclear(res);
}
+
/*
- * Used by ExecuteSqlCommandBuf to send one buffered line when running a COPY command.
+ * Implement ahwrite() for direct-to-DB restore
*/
-static char *
-_sendCopyLine(ArchiveHandle *AH, char *qry, char *eos)
+int
+ExecuteSqlCommandBuf(ArchiveHandle *AH, const char *buf, size_t bufLen)
{
- size_t loc; /* Location of next newline */
- int pos = 0; /* Current position */
- int sPos = 0; /* Last pos of a slash char */
- int isEnd = 0;
-
- /* loop to find unquoted newline ending the line of COPY data */
- for (;;)
+ if (AH->writingCopyData)
{
- 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]);
+ * 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.
*/
-
- /* Count the number of preceding slashes */
- sPos = loc;
- while (sPos > 0 && qry[sPos - 1] == '\\')
- sPos--;
-
- sPos = loc - sPos;
-
+ if (AH->pgCopyIn &&
+ PQputCopyData(AH->connection, buf, bufLen) <= 0)
+ die_horribly(AH, modulename, "error returned by PQputCopyData: %s",
+ PQerrorMessage(AH->connection));
+ }
+ else
+ {
/*
- * If an odd number of preceding slashes, then \n was escaped so set
- * the next search pos, and loop (if any left).
+ * In most cases the data passed to us will be a null-terminated
+ * string, but if it's not, we have to add a trailing null.
*/
- if ((sPos & 1) == 1)
+ if (buf[bufLen] == '\0')
+ ExecuteSqlCommand(AH, buf, "could not execute query");
+ else
{
- /* fprintf(stderr, "cr was escaped\n"); */
- pos = loc + 1;
- if (pos >= (eos - qry))
- {
- appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry));
- return eos;
- }
+ char *str = (char *) malloc(bufLen + 1);
+
+ if (!str)
+ die_horribly(AH, modulename, "out of memory\n");
+ memcpy(str, buf, bufLen);
+ str[bufLen] = '\0';
+ ExecuteSqlCommand(AH, str, "could not execute query");
+ free(str);
}
- else
- break;
}
- /* We found an unquoted newline */
- qry[loc] = '\0';
- appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry);
- isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0);
-
- /*
- * 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));
-
- resetPQExpBuffer(AH->pgCopyBuf);
+ return 1;
+}
- if (isEnd && AH->pgCopyIn)
+/*
+ * Terminate a COPY operation during direct-to-DB restore
+ */
+void
+EndDBCopyMode(ArchiveHandle *AH, TocEntry *te)
+{
+ if (AH->pgCopyIn)
{
PGresult *res;
/* 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));
+ warn_or_die_horribly(AH, modulename, "COPY failed for table \"%s\": %s",
+ te->tag, 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).
- */
-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->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.
- */
- 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;
- }
-
- /*
- * 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;
-
- case SQL_IN_EXT_COMMENT:
-
- /*
- * 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_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;
-
- 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
- {
- /*
- * 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;
- }
-
- appendPQExpBufferChar(AH->sqlBuf, *qry);
- AH->sqlparse.lastChar = *qry;
- }
-
- /*
- * 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, size_t bufLen)
-{
- char *qry = (char *) qryv;
- char *eos = qry + bufLen;
-
- /*
- * 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 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);
- }
-
- return 1;
}
void
oid, oid);
}
}
-
-static bool
-_isIdentChar(unsigned char c)
-{
- 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;
-}
-
-static bool
-_isDQChar(unsigned char c, bool atStart)
-{
- 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;
-}