diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c index d9edebb0f4..234e50fb73 100644 --- a/src/bin/pg_dump/pg_backup_archiver.c +++ b/src/bin/pg_dump/pg_backup_archiver.c @@ -620,20 +620,20 @@ restore_toc_entry(ArchiveHandle *AH, TocEntry *te, if (te->copyStmt && strlen(te->copyStmt) > 0) { ahprintf(AH, "%s", te->copyStmt); - AH->writingCopyData = true; + AH->outputKind = OUTPUT_COPYDATA; } + else + AH->outputKind = OUTPUT_OTHERDATA; (*AH->PrintTocDataPtr) (AH, te, ropt); /* * Terminate COPY if needed. */ - if (AH->writingCopyData) - { - if (RestoringToDB(AH)) - EndDBCopyMode(AH, te); - AH->writingCopyData = false; - } + if (AH->outputKind == OUTPUT_COPYDATA && + RestoringToDB(AH)) + EndDBCopyMode(AH, te); + AH->outputKind = OUTPUT_SQLCMDS; /* close out the transaction started above */ if (is_parallel && te->created) @@ -1975,6 +1975,8 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, AH->mode = mode; AH->compression = compression; + memset(&(AH->sqlparse), 0, sizeof(AH->sqlparse)); + /* Open stdout with no compression for AH output handle */ AH->gzOut = 0; AH->OF = stdout; @@ -4194,7 +4196,8 @@ CloneArchive(ArchiveHandle *AH) clone = (ArchiveHandle *) pg_malloc(sizeof(ArchiveHandle)); memcpy(clone, AH, sizeof(ArchiveHandle)); - /* Handle format-independent fields ... none at the moment */ + /* Handle format-independent fields */ + memset(&(clone->sqlparse), 0, sizeof(clone->sqlparse)); /* The clone will have its own connection, so disregard connection state */ clone->connection = NULL; @@ -4227,7 +4230,9 @@ DeCloneArchive(ArchiveHandle *AH) /* Clear format-specific state */ (AH->DeClonePtr) (AH); - /* Clear state allocated by CloneArchive ... none at the moment */ + /* Clear state allocated by CloneArchive */ + if (AH->sqlparse.curCmd) + destroyPQExpBuffer(AH->sqlparse.curCmd); /* Clear any connection-local state */ if (AH->currUser) diff --git a/src/bin/pg_dump/pg_backup_archiver.h b/src/bin/pg_dump/pg_backup_archiver.h index 7a4fd36073..6dd5158ab4 100644 --- a/src/bin/pg_dump/pg_backup_archiver.h +++ b/src/bin/pg_dump/pg_backup_archiver.h @@ -132,6 +132,20 @@ typedef void (*DeClonePtr) (struct _archiveHandle * AH); typedef size_t (*CustomOutPtr) (struct _archiveHandle * AH, const void *buf, size_t len); +typedef enum +{ + SQL_SCAN = 0, /* normal */ + SQL_IN_SINGLE_QUOTE, /* '...' literal */ + SQL_IN_DOUBLE_QUOTE /* "..." identifier */ +} sqlparseState; + +typedef struct +{ + sqlparseState state; /* see above */ + bool backSlash; /* next char is backslash quoted? */ + PQExpBuffer curCmd; /* incomplete line (NULL if not created) */ +} sqlparseInfo; + typedef enum { STAGE_NONE = 0, @@ -140,6 +154,13 @@ typedef enum STAGE_FINALIZING } ArchiverStage; +typedef enum +{ + OUTPUT_SQLCMDS = 0, /* emitting general SQL commands */ + OUTPUT_COPYDATA, /* writing COPY data */ + OUTPUT_OTHERDATA /* writing data as INSERT commands */ +} ArchiverOutput; + typedef enum { REQ_SCHEMA = 1, @@ -167,6 +188,8 @@ typedef struct _archiveHandle * Added V1.7 */ ArchiveFormat format; /* Archive format */ + sqlparseInfo sqlparse; /* state for parsing INSERT data */ + time_t createDate; /* Date archive created */ /* @@ -217,7 +240,7 @@ typedef struct _archiveHandle PGconn *connection; int connectToDB; /* Flag to indicate if direct DB connection is * required */ - bool writingCopyData; /* True when we are sending COPY data */ + ArchiverOutput outputKind; /* Flag for what we're currently writing */ bool pgCopyIn; /* Currently in libpq 'COPY IN' mode. */ int loFd; /* BLOB fd */ diff --git a/src/bin/pg_dump/pg_backup_db.c b/src/bin/pg_dump/pg_backup_db.c index bd1b8efac8..62c8b3356c 100644 --- a/src/bin/pg_dump/pg_backup_db.c +++ b/src/bin/pg_dump/pg_backup_db.c @@ -364,15 +364,93 @@ ExecuteSqlCommand(ArchiveHandle *AH, const char *qry, const char *desc) } +/* + * Process non-COPY table data (that is, INSERT commands). + * + * The commands have been run together as one long string for compressibility, + * and we are receiving them in bufferloads with arbitrary boundaries, so we + * have to locate command boundaries and save partial commands across calls. + * 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. + * + * We have to lex the data to the extent of identifying literals and quoted + * identifiers, so that we can recognize statement-terminating semicolons. + * We assume that INSERT data will not contain SQL comments, E'' literals, + * or dollar-quoted strings, so this is much simpler than a full SQL lexer. + */ +static void +ExecuteInsertCommands(ArchiveHandle *AH, const char *buf, size_t bufLen) +{ + const char *qry = buf; + const char *eos = buf + bufLen; + + /* initialize command buffer if first time through */ + if (AH->sqlparse.curCmd == NULL) + AH->sqlparse.curCmd = createPQExpBuffer(); + + for (; qry < eos; qry++) + { + char ch = *qry; + + /* For neatness, we skip any newlines between commands */ + if (!(ch == '\n' && AH->sqlparse.curCmd->len == 0)) + appendPQExpBufferChar(AH->sqlparse.curCmd, ch); + + switch (AH->sqlparse.state) + { + case SQL_SCAN: /* Default state == 0, set in _allocAH */ + if (ch == ';') + { + /* + * We've found the end of a statement. Send it and reset + * the buffer. + */ + ExecuteSqlCommand(AH, AH->sqlparse.curCmd->data, + "could not execute query"); + resetPQExpBuffer(AH->sqlparse.curCmd); + } + else if (ch == '\'') + { + AH->sqlparse.state = SQL_IN_SINGLE_QUOTE; + AH->sqlparse.backSlash = false; + } + else if (ch == '"') + { + AH->sqlparse.state = SQL_IN_DOUBLE_QUOTE; + } + break; + + case SQL_IN_SINGLE_QUOTE: + /* We needn't handle '' specially */ + if (ch == '\'' && !AH->sqlparse.backSlash) + AH->sqlparse.state = SQL_SCAN; + else if (ch == '\\' && !AH->public.std_strings) + AH->sqlparse.backSlash = !AH->sqlparse.backSlash; + else + AH->sqlparse.backSlash = false; + break; + + case SQL_IN_DOUBLE_QUOTE: + /* We needn't handle "" specially */ + if (ch == '"') + AH->sqlparse.state = SQL_SCAN; + break; + } + } +} + + /* * Implement ahwrite() for direct-to-DB restore */ int ExecuteSqlCommandBuf(ArchiveHandle *AH, const char *buf, size_t bufLen) { - if (AH->writingCopyData) + if (AH->outputKind == OUTPUT_COPYDATA) { /* + * COPY data. + * * 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. @@ -382,9 +460,19 @@ ExecuteSqlCommandBuf(ArchiveHandle *AH, const char *buf, size_t bufLen) die_horribly(AH, modulename, "error returned by PQputCopyData: %s", PQerrorMessage(AH->connection)); } + else if (AH->outputKind == OUTPUT_OTHERDATA) + { + /* + * Table data expressed as INSERT commands. + */ + ExecuteInsertCommands(AH, buf, bufLen); + } else { /* + * General SQL commands; we assume that commands will not be split + * across calls. + * * 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. */ diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 8db4071684..d1598ea4e9 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -1399,6 +1399,14 @@ dumpTableData_copy(Archive *fout, void *dcontext) return 1; } +/* + * Dump table data using INSERT commands. + * + * Caution: when we restore from an archive file direct to database, the + * INSERT commands emitted by this function have to be parsed by + * pg_backup_db.c's ExecuteInsertCommands(), which will not handle comments, + * E'' strings, or dollar-quoted strings. So don't emit anything like that. + */ static int dumpTableData_insert(Archive *fout, void *dcontext) {