Make pg_restore apply TOC entries statement by statement. Previously, the whole TOC entry was restored with a single PQexec, which caused all statements to fail if one of them failed. This behaviour was different from restoring a plain format dump, where the statements were restored individually. --- src/bin/pg_dump/pg_backup_archiver.c | 131 +++++++++++++++++++++++++++++++++- 1 files changed, 130 insertions(+), 1 deletions(-) diff --git a/src/bin/pg_dump/pg_backup_archiver.c b/src/bin/pg_dump/pg_backup_archiver.c new file mode 100644 index 3aebac8..dd5530f *** a/src/bin/pg_dump/pg_backup_archiver.c --- b/src/bin/pg_dump/pg_backup_archiver.c *************** static ArchiveHandle *_allocAH(const cha *** 55,60 **** --- 55,61 ---- const int compression, ArchiveMode mode, SetupWorkerPtr setupWorkerPtr); static void _getObjectDescription(PQExpBuffer buf, TocEntry *te, ArchiveHandle *AH); + static char *_nextStatement(char **p); static void _printTocEntry(ArchiveHandle *AH, TocEntry *te, RestoreOptions *ropt, bool isData, bool acl_pass); static char *replace_line_endings(const char *str); static void _doSetFixedOutputState(ArchiveHandle *AH); *************** _printTocEntry(ArchiveHandle *AH, TocEnt *** 3177,3183 **** else { if (strlen(te->defn) > 0) ! ahprintf(AH, "%s\n\n", te->defn); } /* --- 3178,3205 ---- else { if (strlen(te->defn) > 0) ! { ! /* ! * Break up the definition into individual statements and issue ! * them one by one. If we didn't do that, plain format dumps ! * would behave differently, because there the dump is restored ! * statement by statement. This can cause different behaviour ! * if one of the statements fails. ! */ ! char *p = te->defn; ! ! while (*p != '\0') ! { ! char *stmt = _nextStatement(&p); ! ! /* append newlines only to the last statement */ ! if (*p == '\0') ! ahprintf(AH, "%s\n\n", stmt); ! else ! ahprintf(AH, "%s", stmt); ! free(stmt); ! } ! } } /* *************** _printTocEntry(ArchiveHandle *AH, TocEnt *** 3252,3257 **** --- 3274,3386 ---- } /* + * Parse *command and extract the next SQL statement. + * The next statement is returned in a newly allocated string, + * and *command is changed to point to the beginning of the next statement. + */ + static char * + _nextStatement(char **command) + { + char *p, *q, *ret, h, *message; + int status = 0; /* 0 normal, 1 single quotes, 2 double quotes, 3 problem */ + + for (p = *command; *p != '\0' && status != 3 && (*p != ';' || status != 0); ++p) + { + switch (*p) + { + case '\'': + if (status == 0) + status = 1; + else if (status == 1) + status = 0; + break; + case '"': + if (status == 0) + status = 2; + else if (status == 2) + status = 0; + break; + case '$': + if (status != 0) + continue; + + /* + * We don't need a full-fledged parser for dollar quotes + * here because we can assume that the dump file was written + * by pg_dump. + * We assume that all names containing a dollar sign will be double + * quoted and that no parameters like $1 will be in SQL statements. + * Then every occurrence of a dollar sign outside quotes would be + * part of a dollar quote. + */ + for (q = p + 1; *q != '\0' && *q != '$'; ++q) + ; + if (*q == '$') + { + char *limit; + + /* copy delimiter including dollars */ + h = *(q + 1); + *(q + 1) = '\0'; + limit = pg_strdup(p); + *(q + 1) = h; + + /* find next occurrence of delimiter */ + q = strstr(q + 1, limit); + + /* skip quoted string if found */ + if (q) + p = q + (strlen(limit) - 1); + else + { + message = "unfinished dollar quote"; + status = 3; + } + + free(limit); + } + else + { + message = "dollar sign is not part of a dollar quote"; + status = 3; + } + break; + default: + continue; + } + } + + /* skip to the beginning of the next non-whitespace */ + while (*p == ';' || *p == '\n' || *p == '\r' || *p == ' ') + ++p; + + if (*p == '\0' && status != 0) + { + message = "unfinished quote"; + status = 3; + } + + if (status == 3) + { + write_msg(modulename, "WARNING: %s", message); + + /* don't split the command string if there was a parsing problem */ + ret = pg_strdup(*command); + *command += strlen(*command); + } + else + { + h = *p; + *p = '\0'; + ret = pg_strdup(*command); + *p = h; + *command = p; + } + + return ret; + } + + /* * Sanitize a string to be included in an SQL comment, by replacing any * newlines with spaces. */ -- 1.7.1