/* Close the output */
errno = 0; if (!EndCompressFileHandle(AH->OF))
pg_fatal("could not close output file: %m");
}
/* Public */ void
SetArchiveOptions(Archive *AH, DumpOptions *dopt, RestoreOptions *ropt)
{ /* Caller can omit dump options, in which case we synthesize them */ if (dopt == NULL && ropt != NULL)
dopt = dumpOptionsFromRestoreOptions(ropt);
/* Save options for later access */
AH->dopt = dopt;
AH->ropt = ropt;
}
/* Decide which TOC entries will be dumped/restored, and mark them */
curSection = SECTION_PRE_DATA; for (te = AH->toc->next; te != AH->toc; te = te->next)
{ /* *Whenwritinganarchive,wealsotakethisopportunitytocheck *thatwehavegeneratedtheentriesinasaneorderthatrespects *thesectiondivisions.Whenreading,don'tcomplain,sincebuggy *oldversionsofpg_dumpmightgenerateout-of-orderarchives.
*/ if (AH->mode != archModeRead)
{ switch (te->section)
{ case SECTION_NONE: /* ok to be anywhere */ break; case SECTION_PRE_DATA: if (curSection != SECTION_PRE_DATA)
pg_log_warning("archive items not in correct section order"); break; case SECTION_DATA: if (curSection == SECTION_POST_DATA)
pg_log_warning("archive items not in correct section order"); break; case SECTION_POST_DATA: /* ok no matter which section we were in */ break; default:
pg_fatal("unexpected section code %d",
(int) te->section); break;
}
}
if (te->section != SECTION_NONE)
curSection = te->section;
/* *Ifwe'regoingtodoparallelrestore,therearesomerestrictions.
*/
parallel_mode = (AH->public.numWorkers > 1 && ropt->useDB); if (parallel_mode)
{ /* We haven't got round to making this work for all archive formats */ if (AH->ClonePtr == NULL || AH->ReopenPtr == NULL)
pg_fatal("parallel restore is not supported with this archive file format");
/* Doesn't work if the archive represents dependencies as OIDs */ if (AH->version < K_VERS_1_8)
pg_fatal("parallel restore is not supported with archives made by pre-8.0 pg_dump");
/* *Makesurewewon'tneed(de)compressionwehaven'tgot
*/ if (AH->PrintTocDataPtr != NULL)
{ for (te = AH->toc->next; te != AH->toc; te = te->next)
{ if (te->hadDumper && (te->reqs & REQ_DATA) != 0)
{ char *errmsg = supports_compression(AH->compression_spec);
if (errmsg)
pg_fatal("cannot restore from compressed archive (%s)",
errmsg); else break;
}
}
}
/* *Prepareindexarrays,sowecanassumewehavethemthroughoutrestore. *It'spossiblewealreadydidthis,though.
*/ if (AH->tocsByDumpId == NULL)
buildTocEntryArrays(AH);
/* *Ifwe'reusingaDBconnection,thenconnectit.
*/ if (ropt->useDB)
{
pg_log_info("connecting to database for restore"); if (AH->version < K_VERS_1_3)
pg_fatal("direct database connections are not supported in pre-1.3 archives");
if (AH->archiveRemoteVersion)
ahprintf(AH, "-- Dumped from database version %s\n",
AH->archiveRemoteVersion); if (AH->archiveDumpVersion)
ahprintf(AH, "-- Dumped by pg_dump version %s\n",
AH->archiveDumpVersion);
ahprintf(AH, "\n");
if (AH->public.verbose)
dumpTimestamp(AH, "Started on", AH->createDate);
if (ropt->single_txn)
{ if (AH->connection)
StartTransaction(AHX); else
ahprintf(AH, "BEGIN;\n\n");
}
if (mark)
{
*mark = '\0';
appendPQExpBuffer(ftStmt, "%s%s IF EXISTS%s",
dropStmt, buffer,
mark + strlen(buffer));
} else
{ /* complain and emit unmodified command */
pg_log_warning("could not find where to insert IF EXISTS in statement \"%s\"",
dropStmtOrig);
appendPQExpBufferStr(ftStmt, dropStmt);
}
}
if (parallel_mode)
{ /* *Inparallelmode,turncontrolovertotheparallel-restorelogic.
*/
ParallelState *pstate;
TocEntry pending_list;
/* The archive format module may need some setup for this */ if (AH->PrepParallelRestorePtr)
AH->PrepParallelRestorePtr(AH);
pending_list_header_init(&pending_list);
/* This runs PRE_DATA items and then disconnects from the database */
restore_toc_entries_prefork(AH, &pending_list);
Assert(AH->connection == NULL);
/* ParallelBackupStart() will actually fork the processes */
pstate = ParallelBackupStart(AH);
restore_toc_entries_parallel(AH, pstate, &pending_list);
ParallelBackupEnd(AH, pstate);
/* reconnect the leader and see if we missed something */
restore_toc_entries_postfork(AH, &pending_list);
Assert(AH->connection != NULL);
} else
{ /* *Inserialmode,processeverythinginthreephases:normalitems, *thenACLs,thenpost-ACLitems.Wemightbeabletoskiponeor *bothextraphasesinsomecases,egdata-onlyrestores.
*/ bool haveACL = false; bool havePostACL = false;
for (te = AH->toc->next; te != AH->toc; te = te->next)
{ if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) == 0) continue; /* ignore if not to be dumped at all */
switch (_tocEntryRestorePass(te))
{ case RESTORE_PASS_MAIN:
(void) restore_toc_entry(AH, te, false); break; case RESTORE_PASS_ACL:
haveACL = true; break; case RESTORE_PASS_POST_ACL:
havePostACL = true; break;
}
}
if (haveACL)
{ for (te = AH->toc->next; te != AH->toc; te = te->next)
{ if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) != 0 &&
_tocEntryRestorePass(te) == RESTORE_PASS_ACL)
(void) restore_toc_entry(AH, te, false);
}
}
if (havePostACL)
{ for (te = AH->toc->next; te != AH->toc; te = te->next)
{ if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) != 0 &&
_tocEntryRestorePass(te) == RESTORE_PASS_POST_ACL)
(void) restore_toc_entry(AH, te, false);
}
}
}
/* *Closeoutanypersistenttransactionwemayhave.Whilethesetwo *casesarestartedindifferentplaces,wecanendbothcaseshere.
*/ if (ropt->single_txn || ropt->txn_size > 0)
{ if (AH->connection)
CommitTransaction(AHX); else
ahprintf(AH, "COMMIT;\n\n");
}
if (AH->public.verbose)
dumpTimestamp(AH, "Completed on", time(NULL));
/* Show namespace in log message if available */ if (te->namespace)
pg_log_info("creating %s \"%s.%s\"",
te->desc, te->namespace, te->tag); else
pg_log_info("creating %s \"%s\"",
te->desc, te->tag);
if (!(ropt->single_txn || ropt->txn_size > 0))
{ if (AH->connection)
CommitTransaction(&AH->public); else
ahprintf(AH, "COMMIT;\n\n");
}
pg_log_info(ngettext("restored %d large object", "restored %d large objects",
AH->loCount),
AH->loCount);
}
/* *CalledbyaformathandlertoinitiaterestorationofaLO
*/ void
StartRestoreLO(ArchiveHandle *AH, Oid oid, bool drop)
{ bool old_lo_style = (AH->version < K_VERS_1_12);
Oid loOid;
AH->loCount++;
/* Initialize the LO Buffer */ if (AH->lo_buf == NULL)
{ /* First time through (in this process) so allocate the buffer */
AH->lo_buf_size = LOBBUFSIZE;
AH->lo_buf = pg_malloc(LOBBUFSIZE);
}
AH->lo_buf_used = 0;
pg_log_info("restoring large object with OID %u", oid);
/* With an old archive we must do drop and create logic here */ if (old_lo_style && drop)
DropLOIfExists(AH, oid);
if (AH->connection)
{ if (old_lo_style)
{
loOid = lo_create(AH->connection, oid); if (loOid == 0 || loOid != oid)
pg_fatal("could not create large object %u: %s",
oid, PQerrorMessage(AH->connection));
}
AH->loFd = lo_open(AH->connection, oid, INV_WRITE); if (AH->loFd == -1)
pg_fatal("could not open large object %u: %s",
oid, PQerrorMessage(AH->connection));
} else
{ if (old_lo_style)
ahprintf(AH, "SELECT pg_catalog.lo_open(pg_catalog.lo_create('%u'), %d);\n",
oid, INV_WRITE); else
ahprintf(AH, "SELECT pg_catalog.lo_open('%u', %d);\n",
oid, INV_WRITE);
}
AH->writingLO = true;
}
void
EndRestoreLO(ArchiveHandle *AH, Oid oid)
{ if (AH->lo_buf_used > 0)
{ /* Write remaining bytes from the LO buffer */
dump_lo_buf(AH);
}
/* Truncate line at comment, if any */
cmnt = strchr(linebuf.data, ';'); if (cmnt != NULL)
{
cmnt[0] = '\0';
linebuf.len = cmnt - linebuf.data;
}
/* Ignore if all blank */ if (strspn(linebuf.data, " \t\r\n") == linebuf.len) continue;
/* Get an ID, check it's valid and not already seen */
id = strtol(linebuf.data, &endptr, 10); if (endptr == linebuf.data || id <= 0 || id > AH->maxDumpId ||
ropt->idWanted[id - 1])
{
pg_log_warning("line ignored: %s", linebuf.data); continue;
}
/* Find TOC entry */
te = getTocEntryByDumpId(AH, id); if (!te)
pg_fatal("could not find entry for ID %d",
id);
/* Mark it wanted */
ropt->idWanted[id - 1] = true;
if (!CFH->open_func(filename, fn, mode, CFH))
{ if (filename)
pg_fatal("could not open output file \"%s\": %m", filename); else
pg_fatal("could not open output file: %m");
}
/* *DumpthecurrentcontentsoftheLOdatabufferwhilewritingaLO
*/ staticvoid
dump_lo_buf(ArchiveHandle *AH)
{ if (AH->connection)
{ int res;
res = lo_write(AH->connection, AH->loFd, AH->lo_buf, AH->lo_buf_used);
pg_log_debug(ngettext("wrote %zu byte of large object data (result = %d)", "wrote %zu bytes of large object data (result = %d)",
AH->lo_buf_used),
AH->lo_buf_used, res); /* We assume there are no short writes, only errors */ if (res != AH->lo_buf_used)
warn_or_exit_horribly(AH, "could not write to large object: %s",
PQerrorMessage(AH->connection));
} else
{
PQExpBuffer buf = createPQExpBuffer();
for (te = AH->toc->next; te != AH->toc; te = te->next)
{ /* this check is purely paranoia, maxDumpId should be correct */ if (te->dumpId <= 0 || te->dumpId > maxDumpId)
pg_fatal("bad dumpId");
/* tocsByDumpId indexes all TOCs by their dump ID */
AH->tocsByDumpId[te->dumpId] = te;
size_t
WriteOffset(ArchiveHandle *AH, pgoff_t o, int wasSet)
{ int off;
/* Save the flag */
AH->WriteBytePtr(AH, wasSet);
/* Write out pgoff_t smallest byte first, prevents endian mismatch */ for (off = 0; off < sizeof(pgoff_t); off++)
{
AH->WriteBytePtr(AH, o & 0xFF);
o >>= 8;
} returnsizeof(pgoff_t) + 1;
}
int
ReadOffset(ArchiveHandle *AH, pgoff_t * o)
{ int i; int off; int offsetFlg;
/* Initialize to zero */
*o = 0;
/* Check for old version */ if (AH->version < K_VERS_1_7)
{ /* Prior versions wrote offsets using WriteInt */
i = ReadInt(AH); /* -1 means not set */ if (i < 0) return K_OFFSET_POS_NOT_SET; elseif (i == 0) return K_OFFSET_NO_DATA;
/* Cast to pgoff_t because it was written as an int. */
*o = (pgoff_t) i; return K_OFFSET_POS_SET;
}
/*
* Check if the specified archive is a directory. If so, check if
* there's a "toc.dat" (or "toc.dat.{gz,lz4,zst}") file in it.
*/
if (stat(AH->fSpec, &st) == 0 && S_ISDIR(st.st_mode))
{
AH->format = archDirectory;
if (_fileExistsInDirectory(AH->fSpec, "toc.dat"))
return AH->format;
#ifdef HAVE_LIBZ
if (_fileExistsInDirectory(AH->fSpec, "toc.dat.gz"))
return AH->format;
#endif
#ifdef USE_LZ4
if (_fileExistsInDirectory(AH->fSpec, "toc.dat.lz4"))
return AH->format;
#endif
#ifdef USE_ZSTD
if (_fileExistsInDirectory(AH->fSpec, "toc.dat.zst"))
return AH->format;
#endif
pg_fatal("directory \"%s\" does not appear to be a valid archive (\"toc.dat\" does not exist)",
AH->fSpec);
fh = NULL; /* keep compiler quiet */
}
else
{
fh = fopen(AH->fSpec, PG_BINARY_R);
if (!fh)
pg_fatal("could not open input file \"%s\": %m", AH->fSpec);
}
}
else
{
fh = stdin;
if (!fh)
pg_fatal("could not open input file: %m");
}
if ((cnt = fread(sig, 1, 5, fh)) != 5)
{
if (ferror(fh))
pg_fatal("could not read input file: %m");
else
pg_fatal("input file is too short (read %lu, expected 5)",
(unsigned long) cnt);
}
/* Save it, just in case we need it later */
memcpy(&AH->lookahead[0], sig, 5);
AH->lookaheadLen = 5;
if (strncmp(sig, "PGDMP", 5) == 0)
{
/* It's custom format, stop here */
AH->format = archCustom;
AH->readHeader = 1;
}
else
{
/*
* *Maybe* we have a tar archive format file or a text dump ... So,
* read first 512 byte header...
*/
cnt = fread(&AH->lookahead[AH->lookaheadLen], 1, 512 - AH->lookaheadLen, fh);
/* read failure is checked below */
AH->lookaheadLen += cnt;
if (AH->lookaheadLen >= strlen(TEXT_DUMPALL_HEADER) &&
(strncmp(AH->lookahead, TEXT_DUMP_HEADER, strlen(TEXT_DUMP_HEADER)) == 0 ||
strncmp(AH->lookahead, TEXT_DUMPALL_HEADER, strlen(TEXT_DUMPALL_HEADER)) == 0))
{
/*
* looks like it's probably a text format dump. so suggest they
* try psql
*/
pg_fatal("input file appears to be a text format dump. Please use psql.");
}
if (AH->lookaheadLen != 512)
{
if (feof(fh))
pg_fatal("input file does not appear to be a valid archive (too short?)");
else
READ_ERROR_EXIT(fh);
}
if (!isValidTarHeader(AH->lookahead))
pg_fatal("input file does not appear to be a valid tar archive");
AH->format = archTar;
}
/* Close the file if we opened it */
if (wantClose)
{
if (fclose(fh) != 0)
pg_fatal("could not close input file: %m");
/* Forget lookahead, since we'll re-read header after re-opening */
AH->readHeader = 0;
AH->lookaheadLen = 0;
}
/* Open stdout with no compression for AH output handle */
out_compress_spec.algorithm = PG_COMPRESSION_NONE;
CFH = InitCompressFileHandle(out_compress_spec);
if (!CFH->open_func(NULL, fileno(stdout), PG_BINARY_A, CFH))
pg_fatal("could not open stdout for appending: %m");
AH->OF = CFH;
/*
* On Windows, we need to use binary mode to read/write non-text files,
* which include all archive formats as well as compressed plain text.
* Force stdin/stdout into binary mode if that is what we are using.
*/
#ifdef WIN32
if ((fmt != archNull || compression_spec.algorithm != PG_COMPRESSION_NONE) &&
(AH->fSpec == NULL || strcmp(AH->fSpec, "") == 0))
{
if (mode == archModeWrite)
_setmode(fileno(stdout), O_BINARY);
else
_setmode(fileno(stdin), O_BINARY);
}
#endif
switch (AH->format)
{
case archCustom:
InitArchiveFmt_Custom(AH);
break;
case archNull:
InitArchiveFmt_Null(AH);
break;
case archDirectory:
InitArchiveFmt_Directory(AH);
break;
case archTar:
InitArchiveFmt_Tar(AH);
break;
default:
pg_fatal("unrecognized file format \"%d\"", AH->format);
}
return AH;
}
/*
* Write out all data (tables & LOs)
*/
void
WriteDataChunks(ArchiveHandle *AH, ParallelState *pstate)
{
TocEntry *te;
if (pstate && pstate->numWorkers > 1)
{
/*
* In parallel mode, this code runs in the leader process. We
* construct an array of candidate TEs, then sort it into decreasing
* size order, then dispatch each TE to a data-transfer worker. By
* dumping larger tables first, we avoid getting into a situation
* where we're down to one job and it'sbig, losing parallelism.
*/
TocEntry **tes;
int ntes;
tes = (TocEntry **) pg_malloc(AH->tocCount * sizeof(TocEntry *));
ntes = 0;
for (te = AH->toc->next; te != AH->toc; te = te->next)
{
/* Consider only TEs with dataDumper functions ... */
if (!te->dataDumper)
continue;
/* ... and ignore ones not enabled for dump */
if ((te->reqs & REQ_DATA) == 0)
continue;
tes[ntes++] = te;
}
if (ntes > 1)
qsort(tes, ntes, sizeof(TocEntry *), TocEntrySizeCompareQsort);
for (int i = 0; i < ntes; i++)
DispatchJobForTocEntry(AH, pstate, tes[i], ACT_DUMP,
mark_dump_job_done, NULL);
pg_free(tes);
/* Now wait for workers to finish. */
WaitForWorkers(AH, pstate, WFW_ALL_IDLE);
}
else
{
/* Non-parallel mode: just dump all candidate TEs sequentially. */
for (te = AH->toc->next; te != AH->toc; te = te->next)
{
/* Must have same filter conditions as above */
if (!te->dataDumper)
continue;
if ((te->reqs & REQ_DATA) == 0)
continue;
WriteDataChunksForTocEntry(AH, te);
}
}
}
/*
* Callback function that's invoked in the leader process after a step has
* been parallel dumped.
*
* We don't need to do anything except check for worker failure.
*/
static void
mark_dump_job_done(ArchiveHandle *AH,
TocEntry *te,
int status,
void *callback_data)
{
pg_log_info("finished item %d %s %s",
te->dumpId, te->desc, te->tag);
if (status != 0)
pg_fatal("worker process failed: exit code %d",
status);
}
/* OID is recorded as a string for historical reasons */
sprintf(workbuf, "%u", te->catalogId.tableoid);
WriteStr(AH, workbuf);
sprintf(workbuf, "%u", te->catalogId.oid);
WriteStr(AH, workbuf);
if (te->defnLen)
{
/*
* defnLen should only be set for custom format's second call to
* WriteToc(), which rewrites the TOC in place to update data
* offsets. Instead of calling the defnDumper a second time
* (which could involve re-executing queries), just skip writing
* the entry. While regenerating the definition should
* theoretically produce the same result as before, it's expensive
* and feels risky.
*
* The custom format only calls WriteToc() a second time if
* fseeko() is usable (see _CloseArchive() in pg_backup_custom.c),
* so we can safely use it without checking. For other formats,
* we fail because one of our assumptions must no longer hold
* true.
*
* XXX This is a layering violation, but the alternative is an
* awkward and complicated callback infrastructure for this
* special case. This might be worth revisiting in the future.
*/
if (AH->format != archCustom)
pg_fatal("unexpected TOC entry in WriteToc(): %d %s %s",
te->dumpId, te->desc, te->tag);
if (fseeko(AH->FH, te->defnLen, SEEK_CUR) != 0)
pg_fatal("error during file seek: %m");
}
else if (te->defnDumper)
{
char *defn = te->defnDumper((Archive *) AH, te->defnDumperArg, te);
/* Dump list of dependencies */
for (i = 0; i < te->nDeps; i++)
{
sprintf(workbuf, "%d", te->dependencies[i]);
WriteStr(AH, workbuf);
}
WriteStr(AH, NULL); /* Terminate List */
if (AH->WriteExtraTocPtr)
AH->WriteExtraTocPtr(AH, te);
}
}
void
ReadToc(ArchiveHandle *AH)
{
int i;
char *tmp;
DumpId *deps;
int depIdx;
;
TocEntry *te;
bool is_supported;
AH->tocCount = ReadInt(AH);
AH->maxDumpId = 0;
for (i = 0; i < AH->tocCount; i++)
{
te = (TocEntry *) pg_malloc0(sizeof(TocEntry));
te->dumpId = ReadInt(AH);
if (te->dumpId > AH->maxDumpId)
AH->maxDumpId = te->dumpId;
/* Sanity check */
if (te->dumpId <= 0)
pg_fatal("entry ID %d out of range -- perhaps a corrupt TOC",
te->dumpId);
if (AH->ReadExtraTocPtr)
AH->ReadExtraTocPtr(AH, te);
pg_log_debug("read TOC entry %d (ID %d) for %s %s", i, te->dumpId, te->desc, te->tag);
/* link completed entry into TOC circular list */
te->prev = AH->toc->prev;
AH->toc->prev->next = te;
AH->toc->prev = te;
te->next = AH->toc;
/* special processing immediately upon read for some items */
if (strcmp(te->desc, "ENCODING") == 0)
processEncodingEntry(AH, te);
else if (strcmp(te->desc, "STDSTRINGS") == 0)
processStdStringsEntry(AH, te);
else if (strcmp(te->desc, "SEARCHPATH") == 0)
processSearchPathEntry(AH, te);
}
}
static void
processEncodingEntry(ArchiveHandle *AH, TocEntry *te)
{
/* te->defn should have the form SET client_encoding = 'foo'; */
char *defn = pg_strdup(te->defn);
char *ptr1;
char *ptr2 = NULL;
int encoding;
static void
processStdStringsEntry(ArchiveHandle *AH, TocEntry *te)
{
/* te->defn should have the form SET standard_conforming_strings = 'x'; */
char *ptr1;
static void
processSearchPathEntry(ArchiveHandle *AH, TocEntry *te)
{
/*
* te->defn should contain acommand to set search_path. We just copy it
* verbatim for use later.
*/
AH->public.searchpath = pg_strdup(te->defn);
}
if (ropt->schemaNames.head != NULL)
{
missing_name = simple_string_list_not_touched(&ropt->schemaNames);
if (missing_name != NULL)
pg_fatal("schema \"%s\" not found", missing_name);
}
if (ropt->tableNames.head != NULL)
{
missing_name = simple_string_list_not_touched(&ropt->tableNames);
if (missing_name != NULL)
pg_fatal("table \"%s\" not found", missing_name);
}
if (ropt->indexNames.head != NULL)
{
missing_name = simple_string_list_not_touched(&ropt->indexNames);
if (missing_name != NULL)
pg_fatal("index \"%s\" not found", missing_name);
}
if (ropt->functionNames.head != NULL)
{
missing_name = simple_string_list_not_touched(&ropt->functionNames);
if (missing_name != NULL)
pg_fatal("function \"%s\" not found", missing_name);
}
if (ropt->triggerNames.head != NULL)
{
missing_name = simple_string_list_not_touched(&ropt->triggerNames);
if (missing_name != NULL)
pg_fatal("trigger \"%s\" not found", missing_name);
}
}
/*
* Determine whether we want to restore this TOC entry.
*
* Returns 0 if entry should be skipped, or some combination of the
* REQ_SCHEMA, REQ_DATA, and REQ_STATS bits if we want to restore schema, data
* and/or statistics portions of this TOC entry, or REQ_SPECIAL if it's a
* special entry.
*/
static int
_tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
{
int res = REQ_SCHEMA | REQ_DATA;
RestoreOptions *ropt = AH->public.ropt;
/* These items are treated specially */
if (strcmp(te->desc, "ENCODING") == 0 ||
strcmp(te->desc, "STDSTRINGS") == 0 ||
strcmp(te->desc, "SEARCHPATH") == 0)
return REQ_SPECIAL;
if (strcmp(te->desc, "STATISTICS DATA") == 0)
{
if (!ropt->dumpStatistics)
return 0;
res = REQ_STATS;
}
/*
* DATABASE and DATABASE PROPERTIES also have a special rule: they are
* restored in createDB mode, and not restored otherwise, independently of
* all else.
*/
if (strcmp(te->desc, "DATABASE") == 0 ||
strcmp(te->desc, "DATABASE PROPERTIES") == 0)
{
if (ropt->createDB)
return REQ_SCHEMA;
else
return 0;
}
/*
* Process exclusions that affect certain classes of TOC entries.
*/
/* If it's an ACL, maybe ignore it */
if (ropt->aclsSkip && _tocEntryIsACL(te))
return 0;
/* If it's a comment, maybe ignore it */
if (ropt->no_comments && strcmp(te->desc, "COMMENT") == 0)
return 0;
/* If it's a policy, maybe ignore it */
if (ropt->no_policies &&
(strcmp(te->desc, "POLICY") == 0 ||
strcmp(te->desc, "ROW SECURITY") == 0))
return 0;
/*
* If it's a comment on a policy, a publication, or a subscription, maybe
* ignore it.
*/
if (strcmp(te->desc, "COMMENT") == 0)
{
if (ropt->no_policies &&
strncmp(te->tag, "POLICY", strlen("POLICY")) == 0)
return 0;
if (ropt->no_publications &&
strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0)
return 0;
/*
* If it's a publication or a table part of a publication, maybe ignore
* it.
*/
if (ropt->no_publications &&
(strcmp(te->desc, "PUBLICATION") == 0 ||
strcmp(te->desc, "PUBLICATION TABLE") == 0 ||
strcmp(te->desc, "PUBLICATION TABLES IN SCHEMA") == 0))
return 0;
/* If it's a security label, maybe ignore it */
if (ropt->no_security_labels && strcmp(te->desc, "SECURITY LABEL") == 0)
return 0;
/*
* If it's a security label on a publication or a subscription, maybe
* ignore it.
*/
if (strcmp(te->desc, "SECURITY LABEL") == 0)
{
if (ropt->no_publications &&
strncmp(te->tag, "PUBLICATION", strlen("PUBLICATION")) == 0)
return 0;
/* If it's a subscription, maybe ignore it */
if (ropt->no_subscriptions && strcmp(te->desc, "SUBSCRIPTION") == 0)
return 0;
/* Ignore it if section is not to be dumped/restored */
switch (curSection)
{
case SECTION_PRE_DATA:
if (!(ropt->dumpSections & DUMP_PRE_DATA))
return 0;
break;
case SECTION_DATA:
if (!(ropt->dumpSections & DUMP_DATA))
return 0;
break;
case SECTION_POST_DATA:
if (!(ropt->dumpSections & DUMP_POST_DATA))
return 0;
break;
default:
/* shouldn't get here, really, but ignore it */
return 0;
}
/* Ignore it if rejected by idWanted[] (cf. SortTocFromFile) */
if (ropt->idWanted && !ropt->idWanted[te->dumpId - 1])
return 0;
/*
* Check options for selective dump/restore.
*/
if (strcmp(te->desc, "ACL") == 0 ||
strcmp(te->desc, "COMMENT") == 0 ||
strcmp(te->desc, "STATISTICS DATA") == 0 ||
strcmp(te->desc, "SECURITY LABEL") == 0)
{
/* Database properties react to createDB, not selectivity options. */
if (strncmp(te->tag, "DATABASE ", 9) == 0)
{
if (!ropt->createDB)
return 0;
}
else if (ropt->schemaNames.head != NULL ||
ropt->schemaExcludeNames.head != NULL ||
ropt->selTypes)
{
/*
* In a selective dump/restore, we want to restore these dependent
* TOC entry types only if their parent object is being restored.
* Without selectivity options, we let through everything in the
* archive. Note there may be such entries with no parent, eg
* non-default ACLs for built-in objects. Also, we make
* per-column ACLs additionally depend on the table's ACL if any
* to ensure correct restore order, so those dependencies should
* be ignored in this check.
*
* This code depends on the parent having been marked already,
* which should be the case; if it isn't, perhaps due to
* SortTocFromFile rearrangement, skipping the dependent entry
* seems prudent anyway.
*
* Ideally we'd handle, eg, table CHECK constraints this way too.
* But it's hard to tell which of their dependencies is the one to
* consult.
*/
bool dumpthis = false;
for (int i = 0; i < te->nDeps; i++)
{
TocEntry *pte = getTocEntryByDumpId(AH, te->dependencies[i]);
if (!pte)
continue; /* probably shouldn't happen */
if (strcmp(pte->desc, "ACL") == 0)
continue; /* ignore dependency on another ACL */
if (pte->reqs == 0)
continue; /* this object isn't marked, so ignore it */
/* Found a parent to be dumped, so we want to dump this too */
dumpthis = true;
break;
}
if (!dumpthis)
return 0;
}
}
else
{
/* Apply selective-restore rules for standalone TOC entries. */
if (ropt->schemaNames.head != NULL)
{
/* If no namespace is specified, it means all. */
if (!te->namespace)
return 0;
if (!simple_string_list_member(&ropt->schemaNames, te->namespace))
return 0;
}
/*
* Determine whether the TOC entry contains schema and/or data components,
* and mask off inapplicable REQ bits. If it had a dataDumper, assume
* it's both schema and data. Otherwise it's probably schema-only, but
* there are exceptions.
*/
if (!te->hadDumper)
{
/*
* Special Case: If 'SEQUENCE SET' or anything to do with LOs, then it
* is considered a data entry. We don't need to check for BLOBS or
* old-style BLOB COMMENTS entries, because they will have hadDumper =
* true ... but we do need to check new-style BLOB ACLs, comments,
* etc.
*/
if (strcmp(te->desc, "SEQUENCE SET") == 0 ||
strcmp(te->desc, "BLOB") == 0 ||
strcmp(te->desc, "BLOB METADATA") == 0 ||
(strcmp(te->desc, "ACL") == 0 &&
strncmp(te->tag, "LARGE OBJECT", 12) == 0) ||
(strcmp(te->desc, "COMMENT") == 0 &&
strncmp(te->tag, "LARGE OBJECT", 12) == 0) ||
(strcmp(te->desc, "SECURITY LABEL") == 0 &&
strncmp(te->tag, "LARGE OBJECT", 12) == 0))
res = res & REQ_DATA;
else
res = res & ~REQ_DATA;
}
/*
* If there's no definition command, there's no schema component. Treat
* "load via partition root" comments as not schema.
*/
if (!te->defn || !te->defn[0] ||
strncmp(te->defn, "-- load via partition root ", 27) == 0)
res = res & ~REQ_SCHEMA;
/*
* Special case: <Init> type with <Max OID> tag; this is obsolete and we
* always ignore it.
*/
if ((strcmp(te->desc, "<Init>") == 0) && (strcmp(te->tag, "Max OID") == 0))
return 0;
/* Mask it if we don't want data */
if (!ropt->dumpData)
{
/*
* The sequence_data option overrides dumpData for SEQUENCE SET.
*
* In binary-upgrade mode, even with dumpData unset, we do not mask
* out large objects. (Only large object definitions, comments and
* other metadata should be generated in binary-upgrade mode, not the
* actual data, but that need not concern us here.)
*/
if (!(ropt->sequence_data && strcmp(te->desc, "SEQUENCE SET") == 0) &&
!(ropt->binary_upgrade &&
(strcmp(te->desc, "BLOB") == 0 ||
strcmp(te->desc, "BLOB METADATA") == 0 ||
(strcmp(te->desc, "ACL") == 0 &&
strncmp(te->tag, "LARGE OBJECT", 12) == 0) ||
(strcmp(te->desc, "COMMENT") == 0 &&
strncmp(te->tag, "LARGE OBJECT", 12) == 0) ||
(strcmp(te->desc, "SECURITY LABEL") == 0 &&
strncmp(te->tag, "LARGE OBJECT", 12) == 0))))
res = res & (REQ_SCHEMA | REQ_STATS);
}
/* Mask it if we don't want schema */
if (!ropt->dumpSchema)
res = res & (REQ_DATA | REQ_STATS);
return res;
}
/*
* Identify which pass we should restore this TOC entry in.
*
* See notes with the RestorePass typedef in pg_backup_archiver.h.
*/
static RestorePass
_tocEntryRestorePass(TocEntry *te)
{
/* "ACL LANGUAGE" was a crock emitted only in PG 7.4 */
if (strcmp(te->desc, "ACL") == 0 ||
strcmp(te->desc, "ACL LANGUAGE") == 0 ||
strcmp(te->desc, "DEFAULT ACL") == 0)
return RESTORE_PASS_ACL;
if (strcmp(te->desc, "EVENT TRIGGER") == 0 ||
strcmp(te->desc, "MATERIALIZED VIEW DATA") == 0)
return RESTORE_PASS_POST_ACL;
/*
* Comments and security labels need to be emitted in the same pass as
* their parent objects. ACLs haven't got comments and security labels,
* and neither do matview data objects, but event triggers do.
* (Fortunately, event triggers haven't got ACLs, or we'd need yet another
* weird special case.)
*/
if ((strcmp(te->desc, "COMMENT") == 0 ||
strcmp(te->desc, "SECURITY LABEL") == 0) &&
strncmp(te->tag, "EVENT TRIGGER ", 14) == 0)
return RESTORE_PASS_POST_ACL;
/*
* If statistics data is dependent on materialized view data, it must be
* deferred to RESTORE_PASS_POST_ACL. Those entries are already marked as
* SECTION_POST_DATA, and some other stats entries (e.g., index stats)
* will also be marked as SECTION_POST_DATA. Additionally, our lookahead
* code in fetchAttributeStats() assumes that we dump all statistics data
* entries in TOC order. To ensure this assumption holds, we move all
* statistics data entries in SECTION_POST_DATA to RESTORE_PASS_POST_ACL.
*/
if (strcmp(te->desc, "STATISTICS DATA") == 0 &&
te->section == SECTION_POST_DATA)
return RESTORE_PASS_POST_ACL;
/* All else can be handled in the main pass. */
return RESTORE_PASS_MAIN;
}
/*
* Identify TOC entries that are ACLs.
*
* Note: it seems worth duplicating some code here to avoid a hard-wired
* assumption that these are exactly the same entries that we restore during
* the RESTORE_PASS_ACL phase.
*/
static bool
_tocEntryIsACL(TocEntry *te)
{
/* "ACL LANGUAGE" was a crock emitted only in PG 7.4 */
if (strcmp(te->desc, "ACL") == 0 ||
strcmp(te->desc, "ACL LANGUAGE") == 0 ||
strcmp(te->desc, "DEFAULT ACL") == 0)
return true;
return false;
}
/*
* Issue SET commands for parameters that we want to have set the same way
* at all times during execution of a restore script.
*/
static void
_doSetFixedOutputState(ArchiveHandle *AH)
{
RestoreOptions *ropt = AH->public.ropt;
/* Adjust row-security state */
if (ropt && ropt->enable_row_security)
ahprintf(AH, "SET row_security = on;\n");
else
ahprintf(AH, "SET row_security = off;\n");
/*
* In --transaction-size mode, we should always be in a transaction when
* we begin to restore objects.
*/
if (ropt && ropt->txn_size > 0)
{
if (AH->connection)
StartTransaction(&AH->public);
else
ahprintf(AH, "\nBEGIN;\n");
AH->txnCount = 0;
}
ahprintf(AH, "\n");
}
/*
* Issue a SET SESSION AUTHORIZATION command. Caller is responsible
* for updating state if appropriate. If user is NULL or an empty string,
* the specification DEFAULT will be used.
*/
static void
_doSetSessionAuth(ArchiveHandle *AH, const char *user)
{
PQExpBuffer cmd = createPQExpBuffer();
/*
* SQL requires a string literal here. Might as well be correct.
*/
if (user && *user)
appendStringLiteralAHX(cmd, user, AH);
else
appendPQExpBufferStr(cmd, "DEFAULT");
appendPQExpBufferChar(cmd, ';');
if (RestoringToDB(AH))
{
PGresult *res;
res = PQexec(AH->connection, cmd->data);
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
/* NOT warn_or_exit_horribly... use -O instead to skip this. */
pg_fatal("could not set session user to \"%s\": %s",
user, PQerrorMessage(AH->connection));
/*
* Issue the commands to connect to the specified database.
*
* If we're currently restoring right into a database, this will
* actually establish a connection. Otherwise it puts a \connect into
* the scriptoutput.
*/
static void
_reconnectToDB(ArchiveHandle *AH, const char *dbname)
{
if (RestoringToDB(AH))
ReconnectToServer(AH, dbname);
else
{
PQExpBufferData connectbuf;
RestoreOptions *ropt = AH->public.ropt;
/*
* We must temporarily exit restricted mode for \connect, etc.
* Anything added between this line and the following \restrict must
* be careful to avoid any possible meta-command injection vectors.
*/
ahprintf(AH, "\\unrestrict %s\n", ropt->restrict_key);
/*
* NOTE: currUser keeps track of what the imaginary session user in our
* script is. It's now effectively reset to the original userID.
*/
free(AH->currUser);
AH->currUser = NULL;
/* don't assume we still know the output schema, tablespace, etc either */
free(AH->currSchema);
AH->currSchema = NULL;
/* re-establish fixed state */
_doSetFixedOutputState(AH);
}
/*
* Become the specified user, and update state to avoid redundant commands
*
* NULL or empty argument is taken to mean restoring the session default
*/
static void
_becomeUser(ArchiveHandle *AH, const char *user)
{
if (!user)
user = ""; /* avoid null pointers */
if (AH->currUser && strcmp(AH->currUser, user) == 0)
return; /* no need to do anything */
_doSetSessionAuth(AH, user);
/*
* NOTE: currUser keeps track of what the imaginary session user in our
* script is
*/
free(AH->currUser);
AH->currUser = pg_strdup(user);
}
/*
* Become the owner of the given TOC entry object. If
* changes in ownership are not allowed, this doesn't do anything.
*/
static void
_becomeOwner(ArchiveHandle *AH, TocEntry *te)
{
RestoreOptions *ropt = AH->public.ropt;
if (ropt && (ropt->noOwner || !ropt->use_setsessauth))
return;
_becomeUser(AH, te->owner);
}
/*
* Issue the commands to select the specified schema as the current schema
* in the target database.
*/
static void
_selectOutputSchema(ArchiveHandle *AH, const char *schemaName)
{
PQExpBuffer qry;
/*
* If there was a SEARCHPATH TOC entry, we're supposed to just stay with
* that search_path rather than switching to entry-specific paths.
* Otherwise, it's an old archive that will not restore correctly unless
* we set the search_path as it's expecting.
*/
if (AH->public.searchpath)
return;
if (!schemaName || *schemaName == '\0' ||
(AH->currSchema && strcmp(AH->currSchema, schemaName) == 0))
return; /* no need to do anything */
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
warn_or_exit_horribly(AH, "could not set \"search_path\" to \"%s\": %s",
schemaName, PQerrorMessage(AH->connection));
/*
* Issue the commands to select the specified tablespace as the current one
* in the target database.
*/
static void
_selectTablespace(ArchiveHandle *AH, const char *tablespace)
{
RestoreOptions *ropt = AH->public.ropt;
PQExpBuffer qry;
const char *want,
*have;
/* do nothing in --no-tablespaces mode */
if (ropt->noTablespace)
return;
have = AH->currTablespace;
want = tablespace;
/* no need to do anything for non-tablespace object */
if (!want)
return;
if (have && strcmp(want, have) == 0)
return; /* no need to do anything */
qry = createPQExpBuffer();
if (strcmp(want, "") == 0)
{
/* We want the tablespace to be the database's default */
appendPQExpBufferStr(qry, "SET default_tablespace = ''");
}
else
{
/* We want an explicit tablespace */
appendPQExpBuffer(qry, "SET default_tablespace = %s", fmtId(want));
}
if (RestoringToDB(AH))
{
PGresult *res;
res = PQexec(AH->connection, qry->data);
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
warn_or_exit_horribly(AH, "could not set \"default_tablespace\" to %s: %s",
fmtId(want), PQerrorMessage(AH->connection));
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
warn_or_exit_horribly(AH, "could not set \"default_table_access_method\": %s",
PQerrorMessage(AH->connection));
/*
* Set the proper default table access method for atable without storage.
* Currently, this is required only for partitioned tables with atable AM.
*/
static void
_printTableAccessMethodNoStorage(ArchiveHandle *AH, TocEntry *te)
{
RestoreOptions *ropt = AH->public.ropt;
const char *tableam = te->tableam;
PQExpBuffer cmd;
/* do nothing in --no-table-access-method mode */
if (ropt->noTableAm)
return;
if (!res || PQresultStatus(res) != PGRES_COMMAND_OK)
warn_or_exit_horribly(AH, "could not alter table access method: %s",
PQerrorMessage(AH->connection));
PQclear(res);
}
else
ahprintf(AH, "%s\n\n", cmd->data);
destroyPQExpBuffer(cmd);
}
/*
* Extract an object description for a TOC entry, and append it to buf.
*
* This is used for ALTER ... OWNER TO.
*
* If the object type has no owner, do nothing.
*/
static void
_getObjectDescription(PQExpBuffer buf, const TocEntry *te)
{
const char *type = te->desc;
/*
* These object types require additional decoration. Fortunately, the
* information needed is exactly what's in the DROP command.
*/
else if (strcmp(type, "AGGREGATE") == 0 ||
strcmp(type, "FUNCTION") == 0 ||
strcmp(type, "OPERATOR") == 0 ||
strcmp(type, "OPERATOR CLASS") == 0 ||
strcmp(type, "OPERATOR FAMILY") == 0 ||
strcmp(type, "PROCEDURE") == 0)
{
/* Chop "DROP " off the front and make a modifiable copy */
char *first = pg_strdup(te->dropStmt + 5);
char *last;
/* point to last character in string */
last = first + strlen(first) - 1;
/* Strip off any ';' or '\n' at the end */
while (last >= first && (*last == '\n' || *last == ';'))
last--;
*(last + 1) = '\0';
appendPQExpBufferStr(buf, first);
free(first);
return;
}
/* these object types don't have separate owners */
else if (strcmp(type, "CAST") == 0 ||
strcmp(type, "CHECK CONSTRAINT") == 0 ||
strcmp(type, "CONSTRAINT") == 0 ||
strcmp(type, "DATABASE PROPERTIES") == 0 ||
strcmp(type, "DEFAULT") == 0 ||
strcmp(type, "FK CONSTRAINT") == 0 ||
strcmp(type, "INDEX") == 0 ||
strcmp(type, "RULE") == 0 ||
strcmp(type, "TRIGGER") == 0 ||
strcmp(type, "ROW SECURITY") == 0 ||
strcmp(type, "POLICY") == 0 ||
strcmp(type, "USER MAPPING") == 0)
{
/* do nothing */
}
else
pg_fatal("don't know how to set owner for object type \"%s\"", type);
}
/*
* Emit the SQL commands to create the object represented by a TOC entry
*
* This now also includes issuing an ALTER OWNER command to restore the
* object's ownership, if wanted. But note that the object's permissions
* will remain at default, until the matching ACL TOC entry is restored.
*/
static void
_printTocEntry(ArchiveHandle *AH, TocEntry *te, const char *pfx)
{
RestoreOptions *ropt = AH->public.ropt;
/*
* Select owner, schema, tablespace and default AM as necessary. The
* default access method for partitioned tables is handled after
* generating the object definition, as it requires an ALTER command
* rather than SET.
*/
_becomeOwner(AH, te);
_selectOutputSchema(AH, te->namespace);
_selectTablespace(AH, te->tablespace);
if (te->relkind != RELKIND_PARTITIONED_TABLE)
_selectTableAccessMethod(AH, te->tableam);
/* Emit header comment for item */
if (!AH->noTocComments)
{
char *sanitized_name;
char *sanitized_schema;
char *sanitized_owner;
ahprintf(AH, "--\n");
if (AH->public.verbose)
{
ahprintf(AH, "-- TOC entry %d (class %u OID %u)\n",
te->dumpId, te->catalogId.tableoid, te->catalogId.oid);
if (te->nDeps > 0)
{
int i;
ahprintf(AH, "-- Dependencies:");
for (i = 0; i < te->nDeps; i++)
ahprintf(AH, " %d", te->dependencies[i]);
ahprintf(AH, "\n");
}
}
if (AH->PrintExtraTocPtr != NULL)
AH->PrintExtraTocPtr(AH, te);
ahprintf(AH, "--\n\n");
}
/*
* Actually print the definition. Normally we can just print the defn
* string if any, but we have four special cases:
*
* 1. A crude hack for suppressing AUTHORIZATION clause that old pg_dump
* versions put into CREATE SCHEMA. Don't mutate the variant for schema
* "public" that is a comment. We have to do this when --no-owner mode is
* selected. This is ugly, but I see no other good way ...
*
* 2. BLOB METADATA entries need special processing since their defn
* strings are just lists of OIDs, not complete SQL commands.
*
* 3. ACL LARGE OBJECTS entries need special processing because they
* contain only one copy of the ACL GRANT/REVOKE commands, which we must
* apply to each large object listed in the associated BLOB METADATA.
*
* 4. Entries with a defnDumper need to call it to generate the
* definition. This is primarily intended to provide a way to save memory
* for objects that would otherwise need a lot of it (e.g., statistics
* data).
*/
if (ropt->noOwner &&
strcmp(te->desc, "SCHEMA") == 0 && strncmp(te->defn, "--", 2) != 0)
{
ahprintf(AH, "CREATE SCHEMA %s;\n\n\n", fmtId(te->tag));
}
else if (strcmp(te->desc, "BLOB METADATA") == 0)
{
IssueCommandPerBlob(AH, te, "SELECT pg_catalog.lo_create('", "')");
}
else if (strcmp(te->desc, "ACL") == 0 &&
strncmp(te->tag, "LARGE OBJECTS", 13) == 0)
{
IssueACLPerBlob(AH, te);
}
else if (te->defnLen && AH->format != archTar)
{
/*
* If defnLen is set, the defnDumper has already been called for this
* TOC entry. We don't normally expect a defnDumper to be called for
* a TOC entry a second time in _printTocEntry(), but there's an
* exception. The tar format first calls WriteToc(), which scans the
* entire TOC, and then it later calls RestoreArchive() to generate
* restore.sql, which scans the TOC again. There doesn't appear to be
* a good way to prevent a second defnDumper call in this case without
* storing the definition in memory, which defeats the purpose. This
* second defnDumper invocation should generate the same output as the
* first, but even if it doesn't, the worst-case scenario is that
* restore.sql might have different statistics data than the archive.
*
* In all other cases, encountering a TOC entry a second time in
* _printTocEntry() is unexpected, so we fail because one of our
* assumptions must no longer hold true.
*
* XXX This is a layering violation, but the alternative is an awkward
* and complicated callback infrastructure for this special case. This
* might be worth revisiting in the future.
*/
pg_fatal("unexpected TOC entry in _printTocEntry(): %d %s %s",
te->dumpId, te->desc, te->tag);
}
else if (te->defnDumper)
{
char *defn = te->defnDumper((Archive *) AH, te->defnDumperArg, te);
/*
* If the defn string contains multiple SQL commands, txn_size mode
* should count it as N actions not one. But rather than build a full
* SQL parser, approximate this by counting semicolons. One case
* where that tends to be badly fooled is function definitions, so
* ignore them. (restore_toc_entry will count one action anyway.)
*/
if (ropt->txn_size > 0 &&
strcmp(te->desc, "FUNCTION") != 0 &&
strcmp(te->desc, "PROCEDURE") != 0)
{
const char *p = te->defn;
int nsemis = 0;
/*
* If we aren't using SET SESSION AUTH to determine ownership, we must
* instead issue an ALTER OWNER command. Schema "public" is special; when
* a dump emits a comment in lieu of creating it, we use ALTER OWNER even
* when using SET SESSION for all other objects. We assume that anything
* without a DROP command is not a separately ownable object.
*/
if (!ropt->noOwner &&
(!ropt->use_setsessauth ||
(strcmp(te->desc, "SCHEMA") == 0 &&
strncmp(te->defn, "--", 2) == 0)) &&
te->owner && strlen(te->owner) > 0 &&
te->dropStmt && strlen(te->dropStmt) > 0)
{
if (strcmp(te->desc, "BLOB METADATA") == 0)
{
/* BLOB METADATA needs special code to handle multiple LOs */
char *cmdEnd = psprintf(" OWNER TO %s", fmtId(te->owner));
IssueCommandPerBlob(AH, te, "ALTER LARGE OBJECT ", cmdEnd);
pg_free(cmdEnd);
}
else
{
/* For all other cases, we can use _getObjectDescription */
PQExpBufferData temp;
/*
* If _getObjectDescription() didn't fill the buffer, then there
* is no owner.
*/
if (temp.data[0])
ahprintf(AH, "ALTER %s OWNER TO %s;\n\n",
temp.data, fmtId(te->owner));
termPQExpBuffer(&temp);
}
}
/*
* Selecta partitioned table's default AM, once the table definition has
* been generated.
*/
if (te->relkind == RELKIND_PARTITIONED_TABLE)
_printTableAccessMethodNoStorage(AH, te);
/*
* If it's an ACL entry, it might contain SET SESSION AUTHORIZATION
* commands, so we can no longer assume we know the current auth setting.
*/
if (_tocEntryIsACL(te))
{
free(AH->currUser);
AH->currUser = NULL;
}
}
/*
* Write the file header for a custom-format archive
*/
void
WriteHead(ArchiveHandle *AH)
{
struct tm crtm;
/*
* If we haven't already read the header, do so.
*
* NB: this code must agree with _discoverArchiveFormat(). Maybe find a
* way to unify the cases?
*/
if (!AH->readHeader)
{
char tmpMag[7];
AH->ReadBufPtr(AH, tmpMag, 5);
if (strncmp(tmpMag, "PGDMP", 5) != 0)
pg_fatal("did not find magic string in file header");
}
if (AH->format != fmt)
pg_fatal("expected format (%d) differs from format found in file (%d)",
AH->format, fmt);
if (AH->version >= K_VERS_1_15)
AH->compression_spec.algorithm = AH->ReadBytePtr(AH);
else if (AH->version >= K_VERS_1_2)
{
/* Guess the compression method based on the level */
if (AH->version < K_VERS_1_4)
AH->compression_spec.level = AH->ReadBytePtr(AH);
else
AH->compression_spec.level = ReadInt(AH);
errmsg = supports_compression(AH->compression_spec);
if (errmsg)
{
pg_log_warning("archive is compressed, but this installation does not support compression (%s) -- no data will be available",
errmsg);
pg_free(errmsg);
}
/*
* Newer versions of glibc have mktime() report failure if tm_isdst is
* inconsistent with the prevailing timezone, e.g. tm_isdst = 1 when
* TZ=UTC. This is problematic when restoring an archive under a
* different timezone setting. If we get a failure, try again with
* tm_isdst set to -1 ("don't know").
*
* XXX with or without this hack, we reconstruct createDate
* incorrectly when the prevailing timezone is different from
* pg_dump's. Next time we bump the archive version, we should flush
* this representation and store a plain seconds-since-the-Epoch
* timestamp instead.
*/
AH->createDate = mktime(&crtm);
if (AH->createDate == (time_t) -1)
{
crtm.tm_isdst = -1;
AH->createDate = mktime(&crtm);
if (AH->createDate == (time_t) -1)
pg_log_warning("invalid creation date in header");
}
}
if (AH->version >= K_VERS_1_4)
{
AH->archdbname = ReadStr(AH);
}
/*
* checkSeek
* check to see if ftell/fseek can be performed.
*/
bool
checkSeek(FILE *fp)
{
pgoff_t tpos;
/* Check that ftello works on this file */
tpos = ftello(fp);
if (tpos < 0)
return false;
/*
* Check that fseeko(SEEK_SET) works, too. NB: we used to try to test
* this with fseeko(fp, 0, SEEK_CUR). But some platforms treat that as a
* successful no-op even on files that are otherwise unseekable.
*/
if (fseeko(fp, tpos, SEEK_SET) != 0)
return false;
/*
* Main engine for parallel restore.
*
* Parallel restore is done in three phases. In this first phase,
* we'll process all SECTION_PRE_DATA TOC entries that are allowed to be
* processed in the RESTORE_PASS_MAIN pass. (In practice, that's all
* PRE_DATA items other than ACLs.) Entries we can't process now are
* added to the pending_list for later phases to deal with.
*/
static void
restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list)
{
bool skipped_some;
TocEntry *next_work_item;
/* Adjust dependency information */
fix_dependencies(AH);
/*
* Do all the early stuff in a single connection in the parent. There's no
* great point in running it in parallel, in fact it will actually run
* faster in a single connection because we avoid all the connection and
* setup overhead. Also, pre-9.2 pg_dump versions were not very good
* about showing all the dependencies of SECTION_PRE_DATA items, so we do
* not risk trying to process them out-of-order.
*
* Stuff that we can't do immediately gets added to the pending_list.
* Note: we don't yet filter out entries that aren't going to be restored.
* They might participate in dependency chains connecting entries that
* should be restored, so we treat them as live until we actually process
* them.
*
* Note: as of 9.2, it should be guaranteed that all PRE_DATA items appear
* before DATA items, and all DATA items before POST_DATA items. That is
* not certain to be true in older archives, though, and in any case use
* of a list file would destroy that ordering (cf. SortTocFromFile). So
* this loop cannot assume that it holds.
*/
AH->restorePass = RESTORE_PASS_MAIN;
skipped_some = false;
for (next_work_item = AH->toc->next; next_work_item != AH->toc; next_work_item = next_work_item->next)
{
bool do_now = true;
if (next_work_item->section != SECTION_PRE_DATA)
{
/* DATA and POST_DATA items are just ignored for now */
if (next_work_item->section == SECTION_DATA ||
next_work_item->section == SECTION_POST_DATA)
{
do_now = false;
skipped_some = true;
}
else
{
/*
* SECTION_NONE items, such as comments, can be processed now
* if we are still in the PRE_DATA part of the archive. Once
* we've skipped any items, we have to consider whether the
* comment's dependencies are satisfied, so skip it for now.
*/
if (skipped_some)
do_now = false;
}
}
/*
* Also skip items that need to be forced into later passes. We need
* not set skipped_some in this case, since by assumption no main-pass
* items could depend on these.
*/
if (_tocEntryRestorePass(next_work_item) != RESTORE_PASS_MAIN)
do_now = false;
if (do_now)
{
/* OK, restore the item and update its dependencies */
pg_log_info("processing item %d %s %s",
next_work_item->dumpId,
next_work_item->desc, next_work_item->tag);
/* Reduce dependencies, but don't move anything to ready_heap */
reduce_dependencies(AH, next_work_item, NULL);
}
else
{
/* Nope, so add it to pending_list */
pending_list_append(pending_list, next_work_item);
}
}
/*
* In --transaction-size mode, we must commit the open transaction before
* dropping the database connection. This also ensures that child workers
* can see the objects we've created so far.
*/
if (AH->public.ropt->txn_size > 0)
CommitTransaction(&AH->public);
/*
* Now close parent connection in prep for parallel steps. We do this
* mainly to ensure that we don't exceed the specified number of parallel
* connections.
*/
DisconnectDatabase(&AH->public);
/* blow away any transient state from the old connection */
free(AH->currUser);
AH->currUser = NULL;
free(AH->currSchema);
AH->currSchema = NULL;
free(AH->currTablespace);
AH->currTablespace = NULL;
free(AH->currTableAm);
AH->currTableAm = NULL;
}
/*
* Main engine for parallel restore.
*
* Parallel restore is done in three phases. In this second phase,
* we process entries by dispatching them to parallel worker children
* (processes on Unix, threads on Windows), each of which connects
* separately to the database. Inter-entry dependencies are respected,
* and so is the RestorePass multi-pass structure. When we can no longer
* make any entries ready to process, we exit. Normally, there will be
* nothing left to do; but if there is, the third phase will mop up.
*/
static void
restore_toc_entries_parallel(ArchiveHandle *AH, ParallelState *pstate,
TocEntry *pending_list)
{
binaryheap *ready_heap;
TocEntry *next_work_item;
/* Set up ready_heap with enough room for all known TocEntrys */
ready_heap = binaryheap_allocate(AH->tocCount,
TocEntrySizeCompareBinaryheap,
NULL);
/*
* The pending_list contains all items that we need to restore. Move all
* items that are available to process immediately into the ready_heap.
* After this setup, the pending list is everything that needs to be done
* but is blocked by one or more dependencies, while the ready heap
* contains items that have no remaining dependencies and are OK to
* process in the current restore pass.
*/
AH->restorePass = RESTORE_PASS_MAIN;
move_to_ready_heap(pending_list, ready_heap, AH->restorePass);
/*
* main parent loop
*
* Keep going until there is no worker still running AND there is no work
* left to be done. Note invariant: at top of loop, there should always
* be at least one worker available to dispatch a job to.
*/
pg_log_info("entering main parallel loop");
for (;;)
{
/* Look for an item ready to be dispatched to a worker */
next_work_item = pop_next_work_item(ready_heap, pstate);
if (next_work_item != NULL)
{
/* If not to be restored, don't waste time launching a worker */
if ((next_work_item->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) == 0)
{
pg_log_info("skipping item %d %s %s",
next_work_item->dumpId,
next_work_item->desc, next_work_item->tag);
/* Update its dependencies as though we'd completed it */
reduce_dependencies(AH, next_work_item, ready_heap);
/* Loop around to see if anything else can be dispatched */
continue;
}
/* Dispatch to some worker */
DispatchJobForTocEntry(AH, pstate, next_work_item, ACT_RESTORE,
mark_restore_job_done, ready_heap);
}
else if (IsEveryWorkerIdle(pstate))
{
/*
* Nothing is ready and no worker is running, so we're done with
* the current pass or maybe with the whole process.
*/
if (AH->restorePass == RESTORE_PASS_LAST)
break; /* No more parallel processing is possible */
/* Advance to next restore pass */
AH->restorePass++;
/* That probably allows some stuff to be made ready */
move_to_ready_heap(pending_list, ready_heap, AH->restorePass);
/ scriptjava.lang.StringIndexOutOfBoundsException: Range [11, 10) out of bounds for length 35
}
else
{
/*
* We have nothing ready, but at least one child is working, so
* wait for some subjob to finish.
*/
}
/*
* Before dispatching another job, check to see if anything has
* finished. We should check every time through the loop so as to
* reduce dependencies as soon as possible. If we were unable to
* dispatch any job this time through, wait until some worker finishes
* (and, hopefully, unblocks some pending item). If we did dispatch
* something, continue as soon as there's at least one idle worker.
* Note that in either case, there's guaranteed to be at least one
* idle worker when we return to the top of the loop. This ensures we
* won't block inside DispatchJobForTocEntry, which would be
* undesirable: we'd rather postpone dispatching until we see what's
* been unblocked by finished jobs.
*/
WaitForWorkers(AH, pstate,
next_work_item ? WFW_ONE_IDLE : WFW_GOT_STATUS);
}
/* There should now be nothing in ready_heap. */
Assert(binaryheap_empty(ready_heap));
binaryheap_free(ready_heap);
pg_log_info("finished main parallel loop");
}
/*
* Main engine for parallel restore.
*
* Parallel restore is done in three phases. In this third phase,
* we mop up any remaining TOC entries by processing them serially.
* This phase normally should have nothing to do, but if we've somehow
* gotten stuck due to circular dependencies or some such, this provides
* at least some chance of completing the restore successfully.
*/
static void
restore_toc_entries_postfork(ArchiveHandle *AH, TocEntry *pending_list)
{
RestoreOptions *ropt = AH->public.ropt;
TocEntry *te;
/*
* Now reconnect the single parent connection.
*/
ConnectDatabaseAhx((Archive *) AH, &ropt->cparams, true);
/* re-establish fixed state */
_doSetFixedOutputState(AH);
/*
* Make sure there is no work left due to, say, circular dependencies, or
* some other pathological condition. If so, do it in the single parent
* connection. We don't sweat about RestorePass ordering; it's likely we
* already violated that.
*/
for (te = pending_list->pending_next; te != pending_list; te = te->pending_next)
{
pg_log_info("processing missed item %d %s %s",
te->dumpId, te->desc, te->tag);
(void) restore_toc_entry(AH, te, false);
}
}
/*
* Check if te1 has an exclusive lock requirement for an item that te2 also
* requires, whether or not te2's requirement is for an exclusive lock.
*/
static bool
has_lock_conflicts(TocEntry *te1, TocEntry *te2)
{
int j,
k;
for (j = 0; j < te1->nLockDeps; j++)
{
for (k = 0; k < te2->nDeps; k++)
{
if (te1->lockDeps[j] == te2->dependencies[k])
return true;
}
}
return false;
}
/*
* Initialize the header of the pending-items list.
*
* This is a circular list with a dummy TocEntry as header, just like the
* main TOC list; but we use separate list links so that an entry can be in
* the main TOC list as well as in the pending list.
*/
static void
pending_list_header_init(TocEntry *l)
{
l->pending_prev = l->pending_next = l;
}
/* Append te to the end of the pending-list headed by l */
static void
pending_list_append(TocEntry *l, TocEntry *te)
{
te->pending_prev = l->pending_prev;
l->pending_prev->pending_next = te;
l->pending_prev = te;
te->pending_next = l;
}
/* Remove te from the pending-list */
static void
pending_list_remove(TocEntry *te)
{
te->pending_prev->pending_next = te->pending_next;
te->pending_next->pending_prev = te->pending_prev;
te->pending_prev = NULL;
te->pending_next = NULL;
}
/* Sort by decreasing dataLength */
if (te1->dataLength > te2->dataLength)
return -1;
if (te1->dataLength < te2->dataLength)
return 1;
/* For equal dataLengths, sort by dumpId, just to be stable */
if (te1->dumpId < te2->dumpId)
return -1;
if (te1->dumpId > te2->dumpId)
return 1;
return 0;
}
/* binaryheap comparator for sorting TocEntries by dataLength */
static int
TocEntrySizeCompareBinaryheap(void *p1, void *p2, void *arg)
{
/* return opposite of qsort comparator for max-heap */
return -TocEntrySizeCompareQsort(&p1, &p2);
}
/*
* Move all immediately-ready items from pending_list to ready_heap.
*
* Items are considered ready if they have no remaining dependencies and
* they belong in the current restore pass. (See also reduce_dependencies,
* which applies the same logic one-at-a-time.)
*/
static void
move_to_ready_heap(TocEntry *pending_list,
binaryheap *ready_heap,
RestorePass pass)
{
TocEntry *te;
TocEntry *next_te;
for (te = pending_list->pending_next; te != pending_list; te = next_te)
{
/* must save list link before possibly removing te from list */
next_te = te->pending_next;
if (te->depCount == 0 &&
_tocEntryRestorePass(te) == pass)
{
/* Remove it from pending_list ... */
pending_list_remove(te);
/* ... and add to ready_heap */
binaryheap_add(ready_heap, te);
}
}
}
/*
* Find the next work item (if any) that is capable of being run now,
* and remove it from the ready_heap.
*
* Returns the item, or NULL if nothing is runnable.
*
* To qualify, the item must have no remaining dependencies
* and no requirements for locks that are incompatible with
* items currently running. Items in the ready_heap are known to have
* no remaining dependencies, but we have to check for lock conflicts.
*/
static TocEntry *
pop_next_work_item(binaryheap *ready_heap,
ParallelState *pstate)
{
/*
* Search the ready_heap until we find a suitable item. Note that we do a
* sequential scan through the heap nodes, so even though we will first
* try to choose the highest-priority item, we might end up picking
* something with a much lower priority. However, we expect that we will
* typically be able to pick one of the first few items, which should
* usually have a relatively high priority.
*/
for (int i = 0; i < binaryheap_size(ready_heap); i++)
{
TocEntry *te = (TocEntry *) binaryheap_get_node(ready_heap, i);
bool conflicts = false;
/*
* Check to see if the item would need exclusive lock on something
* that a currently running item also needs lock on, or vice versa. If
* so, we don't want to schedule them together.
*/
for (int k = 0; k < pstate->numWorkers; k++)
{
TocEntry *running_te = pstate->te[k];
if (running_te == NULL)
continue;
if (has_lock_conflicts(te, running_te) ||
has_lock_conflicts(running_te, te))
{
conflicts = true;
break;
}
}
if (conflicts)
continue;
/* passed all tests, so this item can run */
binaryheap_remove_node(ready_heap, i);
return te;
}
pg_log_debug("no item ready");
return NULL;
}
/*
* Restore a single TOC item in parallel with others
*
* this is run in the worker, i.e. in a thread (Windows) or a separate process
* (everything else). A worker process executes several such work items during
* a parallel backup or restore. Once we terminate here and report back that
* our work is finished, the leader process will assign us a new work item.
*/
int
parallel_restore(ArchiveHandle *AH, TocEntry *te)
{
int status;
Assert(AH->connection != NULL);
/* Count only errors associated with this TOC entry */
AH->public.n_errors = 0;
/* Restore the TOC item */
status = restore_toc_entry(AH, te, true);
return status;
}
/*
* Callback function that's invoked in the leader process after a step has
* been parallel restored.
*
* Update status and reduce the dependency count of any dependent items.
*/
static void
mark_restore_job_done(ArchiveHandle *AH,
TocEntry *te,
int status,
void *callback_data)
{
binaryheap *ready_heap = (binaryheap *) callback_data;
if (status == WORKER_CREATE_DONE)
mark_create_done(AH, te);
else if (status == WORKER_INHIBIT_DATA)
{
inhibit_data_for_failed_table(AH, te);
AH->public.n_errors++;
}
else if (status == WORKER_IGNORED_ERRORS)
AH->public.n_errors++;
else if (status != 0)
pg_fatal("worker process failed: exit code %d",
status);
reduce_dependencies(AH, te, ready_heap);
}
/*
* Process the dependency information into aform useful for parallel restore.
*
* This function takes care of fixing up some missing or badly designed
* dependencies, and then prepares subsidiary data structures that will be
* used in the main parallel-restore logic, including:
* 1. We build the revDeps[] arrays of incoming dependency dumpIds.
* 2. We set up depCount fields that are the number of as-yet-unprocessed
* dependencies for each TOC entry.
*
* We also identify locking dependencies so that we can avoid trying to
* schedule conflicting items at the same time.
*/
static void
fix_dependencies(ArchiveHandle *AH)
{
TocEntry *te;
int i;
/*
* Initialize the depCount/revDeps/nRevDeps fields, and make sure the TOC
* items are marked as not being in any parallel-processing list.
*/
for (te = AH->toc->next; te != AH->toc; te = te->next)
{
te->depCount = te->nDeps;
te->revDeps = NULL;
te->nRevDeps = 0;
te->pending_prev = NULL;
te->pending_next = NULL;
}
/*
* POST_DATA items that are shown as depending on atable need to be
* re-pointed to depend on that table's data, instead. This ensures they
* won't get scheduled until the data has been loaded.
*/
repoint_table_dependencies(AH);
/*
* Pre-8.4 versions of pg_dump neglected to set up a dependency from BLOB
* COMMENTS to BLOBS. Cope. (We assume there's only one BLOBS and only
* one BLOB COMMENTS in such files.)
*/
if (AH->version < K_VERS_1_11)
{
for (te = AH->toc->next; te != AH->toc; te = te->next)
{
if (strcmp(te->desc, "BLOB COMMENTS") == 0 && te->nDeps == 0)
{
TocEntry *te2;
/*
* At this point we start to build the revDeps reverse-dependency arrays,
* so all changes of dependencies must be complete.
*/
/*
* Count the incoming dependencies for each item. Also, it is possible
* that the dependencies list items that are not in the archive at all
* (that should not happen in 9.2 and later, but is highly likely in older
* archives). Subtract such items from the depCounts.
*/
for (te = AH->toc->next; te != AH->toc; te = te->next)
{
for (i = 0; i < te->nDeps; i++)
{
DumpId depid = te->dependencies[i];
/*
* Allocate space for revDeps[] arrays, and reset nRevDeps so we can use
* it as a counter below.
*/
for (te = AH->toc->next; te != AH->toc; te = te->next)
{
if (te->nRevDeps > 0)
te->revDeps = (DumpId *) pg_malloc(te->nRevDeps * sizeof(DumpId));
te->nRevDeps = 0;
}
/*
* Build the revDeps[] arrays of incoming-dependency dumpIds. This had
* better agree with the loops above.
*/
for (te = AH->toc->next; te != AH->toc; te = te->next)
{
for (i = 0; i < te->nDeps; i++)
{
DumpId depid = te->dependencies[i];
/*
* Lastly, work out the locking dependencies.
*/
for (te = AH->toc->next; te != AH->toc; te = te->next)
{
te->lockDeps = NULL;
te->nLockDeps = 0;
identify_locking_dependencies(AH, te);
}
}
/*
* Change dependencies on table items to depend on table data items instead,
* but only in POST_DATA items.
*
* Also, for any item having such dependency(s), set its dataLength to the
* largest dataLength of the table data items it depends on. This ensures
* that parallel restore will prioritize larger jobs (index builds, FK
* constraint checks, etc) over smaller ones, avoiding situations where we
* end a restore with only one active job working on a large table.
*/
static void
repoint_table_dependencies(ArchiveHandle *AH)
{
TocEntry *te;
int i;
DumpId olddep;
for (te = AH->toc->next; te != AH->toc; te = te->next)
{
if (te->section != SECTION_POST_DATA)
continue;
for (i = 0; i < te->nDeps; i++)
{
olddep = te->dependencies[i];
if (olddep <= AH->maxDumpId &&
AH->tableDataId[olddep] != 0)
{
DumpId tabledataid = AH->tableDataId[olddep];
TocEntry *tabledatate = AH->tocsByDumpId[tabledataid];
/*
* Identify which objects we'll need exclusive lock on in order to restore
* the given TOC entry (*other* than the one identified by the TOC entry
* itself). Record their dump IDs in the entry's lockDeps[] array.
*/
static void
identify_locking_dependencies(ArchiveHandle *AH, TocEntry *te)
{
DumpId *lockids;
int nlockids;
int i;
/*
* We only care about this for POST_DATA items. PRE_DATA items are not
* run in parallel, and DATA items are all independent by assumption.
*/
if (te->section != SECTION_POST_DATA)
return;
/* Quick exit if no dependencies at all */
if (te->nDeps == 0)
return;
/*
* Most POST_DATA items are ALTER TABLEs or some moral equivalent of that,
* and hence require exclusive lock. However, we know that CREATE INDEX
* does not. (Maybe someday index-creating CONSTRAINTs will fall in that
* category too ... but today is not that day.)
*/
if (strcmp(te->desc, "INDEX") == 0)
return;
/*
* We assume the entry requires exclusive lock on each TABLE or TABLE DATA
* item listed among its dependencies. Originally all of these would have
* been TABLE items, but repoint_table_dependencies would have repointed
* them to the TABLE DATA items if those are present (which they might not
* be, eg in a schema-only dump). Note that all of the entries we are
* processing here are POST_DATA; otherwise there might be a significant
* difference between a dependency on atable and a dependency on its
* data, so that closer analysis would be needed here.
*/
lockids = (DumpId *) pg_malloc(te->nDeps * sizeof(DumpId));
nlockids = 0;
for (i = 0; i < te->nDeps; i++)
{
DumpId depid = te->dependencies[i];
/*
* Remove the specified TOC entry from the depCounts of items that depend on
* it, thereby possibly making them ready-to-run. Any pending item that
* becomes ready should be moved to the ready_heap, if that's provided.
*/
static void
reduce_dependencies(ArchiveHandle *AH, TocEntry *te,
binaryheap *ready_heap)
{
int i;
pg_log_debug("reducing dependencies for %d", te->dumpId);
for (i = 0; i < te->nRevDeps; i++)
{
TocEntry *otherte = AH->tocsByDumpId[te->revDeps[i]];
/*
* It's ready if it has no remaining dependencies, and it belongs in
* the current restore pass, and it is currently a member of the
* pending list (that check is needed to prevent double restore in
* some cases where a list-file forces out-of-order restoring).
* However, if ready_heap == NULL then caller doesn't want any list
* memberships changed.
*/
if (otherte->depCount == 0 &&
_tocEntryRestorePass(otherte) == AH->restorePass &&
otherte->pending_prev != NULL &&
ready_heap != NULL)
{
/* Remove it from pending list ... */
pending_list_remove(otherte);
/* ... and add to ready_heap */
binaryheap_add(ready_heap, otherte);
}
}
}
/*
* Set the created flag on the DATA member corresponding to the given
* TABLE member
*/
static void
mark_create_done(ArchiveHandle *AH, TocEntry *te)
{
if (AH->tableDataId[te->dumpId] != 0)
{
TocEntry *ted = AH->tocsByDumpId[AH->tableDataId[te->dumpId]];
ted->created = true;
}
}
/*
* Mark the DATA member corresponding to the given TABLE member
* as not wanted
*/
static void
inhibit_data_for_failed_table(ArchiveHandle *AH, TocEntry *te)
{
pg_log_info("table \"%s\" could not be created, will not restore its data",
te->tag);
if (AH->tableDataId[te->dumpId] != 0)
{
TocEntry *ted = AH->tocsByDumpId[AH->tableDataId[te->dumpId]];
ted->reqs = 0;
}
}
/*
* Clone and de-clone routines used in parallel restoration.
*
* Enough of the structure is cloned to ensure that there is no
* conflict between different threads each with their own clone.
*/
ArchiveHandle *
CloneArchive(ArchiveHandle *AH)
{
ArchiveHandle *clone;
/* Likewise flat-copy the RestoreOptions, so we can alter them locally */
clone->public.ropt = (RestoreOptions *) pg_malloc(sizeof(RestoreOptions));
memcpy(clone->public.ropt, AH->public.ropt, sizeof(RestoreOptions));
/* The clone will have its own connection, so disregard connection state */
clone->connection = NULL;
clone->connCancel = NULL;
clone->currUser = NULL;
clone->currSchema = NULL;
clone->currTableAm = NULL;
clone->currTablespace = NULL;
/* savedPassword must be local in case we change it while connecting */
if (clone->savedPassword)
clone->savedPassword = pg_strdup(clone->savedPassword);
/* clone has its own error count, too */
clone->public.n_errors = 0;
/* clones should not share lo_buf */
clone->lo_buf = NULL;
/*
* Clone connections disregard --transaction-size; they must commit after
* each command so that the results are immediately visible to other
* workers.
*/
clone->public.ropt->txn_size = 0;
/*
* Connect our new clone object to the database, using the same connection
* parameters used for the original connection.
*/
ConnectDatabaseAhx((Archive *) clone, &clone->public.ropt->cparams, true);
/* re-establish fixed state */
if (AH->mode == archModeRead)
_doSetFixedOutputState(clone);
/* in write case, setupDumpWorker will fix up connection state */
/* Let the format-specific code have a chance too */
clone->ClonePtr(clone);
/*
* Release clone-local storage.
*
* Note: we assume any clone-local connection was already closed.
*/
void
DeCloneArchive(ArchiveHandle *AH)
{
/* Should not have an open database connection */
Assert(AH->connection == NULL);
/* Clear format-specific state */
AH->DeClonePtr(AH);
/* Clear state allocated by CloneArchive */
if (AH->sqlparse.curCmd)
destroyPQExpBuffer(AH->sqlparse.curCmd);
/* Clear any connection-local state */
free(AH->currUser);
free(AH->currSchema);
free(AH->currTablespace);
free(AH->currTableAm);
free(AH->savedPassword);
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.