diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index b0d9784b3f..695b4afa86 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -63,6 +63,7 @@ static TocEntry *getTocEntryByDumpId(ArchiveHandle *AH, DumpId id); static void _moveAfter(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); @@ -379,14 +380,7 @@ RestoreArchive(Archive *AHX, RestoreOptions *ropt) te->tag); /* - * 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) { @@ -396,7 +390,15 @@ RestoreArchive(Archive *AHX, RestoreOptions *ropt) (*AH->PrintTocDataPtr) (AH, te, ropt); - AH->writingCopyData = false; + /* + * Terminate COPY if needed. + */ + if (AH->writingCopyData) + { + if (RestoringToDB(AH)) + EndDBCopyMode(AH, te); + AH->writingCopyData = false; + } _enableTriggersIfNecessary(AH, te, ropt); } @@ -1038,17 +1040,13 @@ ahprintf(ArchiveHandle *AH, const char *fmt,...) { 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)) { @@ -1128,7 +1126,7 @@ dump_lo_buf(ArchiveHandle *AH) /* - * 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). @@ -1180,7 +1178,7 @@ ahwrite(const void *ptr, size_t size, size_t nmemb, ArchiveHandle *AH) * 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); @@ -1730,9 +1728,6 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, 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; diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h index e1e63c06b6..5b61048bef 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -128,28 +128,6 @@ typedef struct _outputContext int gzOut; } OutputContext; -typedef enum -{ - SQL_SCAN = 0, /* normal */ - SQL_IN_SQL_COMMENT, /* -- comment */ - SQL_IN_EXT_COMMENT, /* slash-star comment */ - SQL_IN_SINGLE_QUOTE, /* '...' literal */ - SQL_IN_E_QUOTE, /* E'...' literal */ - SQL_IN_DOUBLE_QUOTE, /* "..." identifier */ - SQL_IN_DOLLAR_TAG, /* possible dollar-quote starting tag */ - SQL_IN_DOLLAR_QUOTE /* body of dollar quote */ -} sqlparseState; - -typedef struct -{ - sqlparseState state; /* see above */ - char lastChar; /* preceding char, or '\0' initially */ - bool backSlash; /* next char is backslash quoted? */ - int braceDepth; /* parenthesis nesting depth */ - PQExpBuffer tagBuf; /* dollar quote tag (NULL if not created) */ - int minTagEndPos; /* first possible end position of $-quote */ -} sqlparseInfo; - typedef enum { STAGE_NONE = 0, @@ -185,9 +163,6 @@ typedef struct _archiveHandle * Added V1.7 */ ArchiveFormat format; /* Archive format */ - sqlparseInfo sqlparse; - PQExpBuffer sqlBuf; - time_t createDate; /* Date archive created */ /* @@ -235,8 +210,6 @@ typedef struct _archiveHandle * required */ bool writingCopyData; /* True when we are sending COPY data */ bool pgCopyIn; /* Currently in libpq 'COPY IN' mode. */ - PQExpBuffer pgCopyBuf; /* Left-over data from incomplete lines in - * COPY IN */ int loFd; /* BLOB fd */ int writingBlob; /* Flag */ diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c index 600ae3d0da..81d5e70134 100644 --- a/src/bin/pg_dump/pg_backup_db.c +++ b/src/bin/pg_dump/pg_backup_db.c @@ -14,26 +14,19 @@ #include "dumputils.h" #include - #include - #ifdef HAVE_TERMIOS_H #include #endif +#define DB_MAX_ERR_STMT 128 + 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, 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) @@ -268,8 +261,10 @@ notice_processor(void *arg, const char *message) } -/* 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) { @@ -286,6 +281,7 @@ 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: @@ -310,78 +306,56 @@ ExecuteSqlCommand(ArchiveHandle *AH, const char *qry, const char *desc) 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) - { - /* fprintf(stderr, "cr was escaped\n"); */ - pos = loc + 1; - if (pos >= (eos - qry)) - { - appendBinaryPQExpBuffer(AH->pgCopyBuf, qry, (eos - qry)); - return eos; - } - } + if (buf[bufLen] == '\0') + ExecuteSqlCommand(AH, buf, "could not execute query"); else - break; + { + 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); + } } - /* We found an unquoted newline */ - qry[loc] = '\0'; - appendPQExpBuffer(AH->pgCopyBuf, "%s\n", qry); - isEnd = (strcmp(AH->pgCopyBuf->data, "\\.\n") == 0); + return 1; +} - /* - * 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); - - if (isEnd && AH->pgCopyIn) +/* + * Terminate a COPY operation during direct-to-DB restore + */ +void +EndDBCopyMode(ArchiveHandle *AH, TocEntry *te) +{ + if (AH->pgCopyIn) { PGresult *res; @@ -392,240 +366,12 @@ _sendCopyLine(ArchiveHandle *AH, char *qry, char *eos) /* 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 @@ -639,32 +385,3 @@ CommitTransaction(ArchiveHandle *AH) { ExecuteSqlCommand(AH, "COMMIT", "could not commit database transaction"); } - -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; -} diff --git a/src/bin/pg_dump/pg_backup_db.h b/src/bin/pg_dump/pg_backup_db.h index 8d09eebe89..3d8522f0a4 100644 --- a/src/bin/pg_dump/pg_backup_db.h +++ b/src/bin/pg_dump/pg_backup_db.h @@ -10,7 +10,9 @@ #include "pg_backup_archiver.h" -extern int ExecuteSqlCommandBuf(ArchiveHandle *AH, void *qry, size_t bufLen); +extern int ExecuteSqlCommandBuf(ArchiveHandle *AH, const char *buf, size_t bufLen); + +extern void EndDBCopyMode(ArchiveHandle *AH, struct _tocEntry * te); extern void StartTransaction(ArchiveHandle *AH); extern void CommitTransaction(ArchiveHandle *AH);