mirror of
https://github.com/postgres/postgres.git
synced 2025-07-31 22:04:40 +03:00
Matview statistics depend on matview data.
REFRESH MATERIALIZED VIEW replaces the storage, which resets statistics, so statistics must be restored afterward. If both statistics and data are being dumped for a materialized view, add a dependency from the former to the latter. Defer the statistics to SECTION_POST_DATA, and use RESTORE_PASS_POST_ACL. Reported-by: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> Reviewed-by: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com> Discussion: https://postgr.es/m/CAExHW5s47kmubpbbRJzSM-Zfe0Tj2O3GBagB7YAyE8rQ-V24Uw@mail.gmail.com
This commit is contained in:
@ -72,7 +72,7 @@ static void processEncodingEntry(ArchiveHandle *AH, TocEntry *te);
|
|||||||
static void processStdStringsEntry(ArchiveHandle *AH, TocEntry *te);
|
static void processStdStringsEntry(ArchiveHandle *AH, TocEntry *te);
|
||||||
static void processSearchPathEntry(ArchiveHandle *AH, TocEntry *te);
|
static void processSearchPathEntry(ArchiveHandle *AH, TocEntry *te);
|
||||||
static int _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH);
|
static int _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH);
|
||||||
static RestorePass _tocEntryRestorePass(TocEntry *te);
|
static RestorePass _tocEntryRestorePass(ArchiveHandle *AH, TocEntry *te);
|
||||||
static bool _tocEntryIsACL(TocEntry *te);
|
static bool _tocEntryIsACL(TocEntry *te);
|
||||||
static void _disableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te);
|
static void _disableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te);
|
||||||
static void _enableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te);
|
static void _enableTriggersIfNecessary(ArchiveHandle *AH, TocEntry *te);
|
||||||
@ -102,7 +102,8 @@ static void pending_list_append(TocEntry *l, TocEntry *te);
|
|||||||
static void pending_list_remove(TocEntry *te);
|
static void pending_list_remove(TocEntry *te);
|
||||||
static int TocEntrySizeCompareQsort(const void *p1, const void *p2);
|
static int TocEntrySizeCompareQsort(const void *p1, const void *p2);
|
||||||
static int TocEntrySizeCompareBinaryheap(void *p1, void *p2, void *arg);
|
static int TocEntrySizeCompareBinaryheap(void *p1, void *p2, void *arg);
|
||||||
static void move_to_ready_heap(TocEntry *pending_list,
|
static void move_to_ready_heap(ArchiveHandle *AH,
|
||||||
|
TocEntry *pending_list,
|
||||||
binaryheap *ready_heap,
|
binaryheap *ready_heap,
|
||||||
RestorePass pass);
|
RestorePass pass);
|
||||||
static TocEntry *pop_next_work_item(binaryheap *ready_heap,
|
static TocEntry *pop_next_work_item(binaryheap *ready_heap,
|
||||||
@ -748,7 +749,7 @@ RestoreArchive(Archive *AHX)
|
|||||||
if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) == 0)
|
if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) == 0)
|
||||||
continue; /* ignore if not to be dumped at all */
|
continue; /* ignore if not to be dumped at all */
|
||||||
|
|
||||||
switch (_tocEntryRestorePass(te))
|
switch (_tocEntryRestorePass(AH, te))
|
||||||
{
|
{
|
||||||
case RESTORE_PASS_MAIN:
|
case RESTORE_PASS_MAIN:
|
||||||
(void) restore_toc_entry(AH, te, false);
|
(void) restore_toc_entry(AH, te, false);
|
||||||
@ -767,7 +768,7 @@ RestoreArchive(Archive *AHX)
|
|||||||
for (te = AH->toc->next; te != AH->toc; te = te->next)
|
for (te = AH->toc->next; te != AH->toc; te = te->next)
|
||||||
{
|
{
|
||||||
if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) != 0 &&
|
if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) != 0 &&
|
||||||
_tocEntryRestorePass(te) == RESTORE_PASS_ACL)
|
_tocEntryRestorePass(AH, te) == RESTORE_PASS_ACL)
|
||||||
(void) restore_toc_entry(AH, te, false);
|
(void) restore_toc_entry(AH, te, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -777,7 +778,7 @@ RestoreArchive(Archive *AHX)
|
|||||||
for (te = AH->toc->next; te != AH->toc; te = te->next)
|
for (te = AH->toc->next; te != AH->toc; te = te->next)
|
||||||
{
|
{
|
||||||
if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) != 0 &&
|
if ((te->reqs & (REQ_SCHEMA | REQ_DATA | REQ_STATS)) != 0 &&
|
||||||
_tocEntryRestorePass(te) == RESTORE_PASS_POST_ACL)
|
_tocEntryRestorePass(AH, te) == RESTORE_PASS_POST_ACL)
|
||||||
(void) restore_toc_entry(AH, te, false);
|
(void) restore_toc_entry(AH, te, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -3219,7 +3220,7 @@ _tocEntryRequired(TocEntry *te, teSection curSection, ArchiveHandle *AH)
|
|||||||
* See notes with the RestorePass typedef in pg_backup_archiver.h.
|
* See notes with the RestorePass typedef in pg_backup_archiver.h.
|
||||||
*/
|
*/
|
||||||
static RestorePass
|
static RestorePass
|
||||||
_tocEntryRestorePass(TocEntry *te)
|
_tocEntryRestorePass(ArchiveHandle *AH, TocEntry *te)
|
||||||
{
|
{
|
||||||
/* "ACL LANGUAGE" was a crock emitted only in PG 7.4 */
|
/* "ACL LANGUAGE" was a crock emitted only in PG 7.4 */
|
||||||
if (strcmp(te->desc, "ACL") == 0 ||
|
if (strcmp(te->desc, "ACL") == 0 ||
|
||||||
@ -3240,6 +3241,26 @@ _tocEntryRestorePass(TocEntry *te)
|
|||||||
strncmp(te->tag, "EVENT TRIGGER ", 14) == 0)
|
strncmp(te->tag, "EVENT TRIGGER ", 14) == 0)
|
||||||
return RESTORE_PASS_POST_ACL;
|
return RESTORE_PASS_POST_ACL;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If statistics data is dependent on materialized view data, it must be
|
||||||
|
* deferred to RESTORE_PASS_POST_ACL.
|
||||||
|
*/
|
||||||
|
if (strcmp(te->desc, "STATISTICS DATA") == 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < te->nDeps; i++)
|
||||||
|
{
|
||||||
|
DumpId depid = te->dependencies[i];
|
||||||
|
|
||||||
|
if (depid <= AH->maxDumpId && AH->tocsByDumpId[depid] != NULL)
|
||||||
|
{
|
||||||
|
TocEntry *otherte = AH->tocsByDumpId[depid];
|
||||||
|
|
||||||
|
if (strcmp(otherte->desc, "MATERIALIZED VIEW DATA") == 0)
|
||||||
|
return RESTORE_PASS_POST_ACL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* All else can be handled in the main pass. */
|
/* All else can be handled in the main pass. */
|
||||||
return RESTORE_PASS_MAIN;
|
return RESTORE_PASS_MAIN;
|
||||||
}
|
}
|
||||||
@ -4249,7 +4270,7 @@ restore_toc_entries_prefork(ArchiveHandle *AH, TocEntry *pending_list)
|
|||||||
* not set skipped_some in this case, since by assumption no main-pass
|
* not set skipped_some in this case, since by assumption no main-pass
|
||||||
* items could depend on these.
|
* items could depend on these.
|
||||||
*/
|
*/
|
||||||
if (_tocEntryRestorePass(next_work_item) != RESTORE_PASS_MAIN)
|
if (_tocEntryRestorePass(AH, next_work_item) != RESTORE_PASS_MAIN)
|
||||||
do_now = false;
|
do_now = false;
|
||||||
|
|
||||||
if (do_now)
|
if (do_now)
|
||||||
@ -4331,7 +4352,7 @@ restore_toc_entries_parallel(ArchiveHandle *AH, ParallelState *pstate,
|
|||||||
* process in the current restore pass.
|
* process in the current restore pass.
|
||||||
*/
|
*/
|
||||||
AH->restorePass = RESTORE_PASS_MAIN;
|
AH->restorePass = RESTORE_PASS_MAIN;
|
||||||
move_to_ready_heap(pending_list, ready_heap, AH->restorePass);
|
move_to_ready_heap(AH, pending_list, ready_heap, AH->restorePass);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* main parent loop
|
* main parent loop
|
||||||
@ -4380,7 +4401,7 @@ restore_toc_entries_parallel(ArchiveHandle *AH, ParallelState *pstate,
|
|||||||
/* Advance to next restore pass */
|
/* Advance to next restore pass */
|
||||||
AH->restorePass++;
|
AH->restorePass++;
|
||||||
/* That probably allows some stuff to be made ready */
|
/* That probably allows some stuff to be made ready */
|
||||||
move_to_ready_heap(pending_list, ready_heap, AH->restorePass);
|
move_to_ready_heap(AH, pending_list, ready_heap, AH->restorePass);
|
||||||
/* Loop around to see if anything's now ready */
|
/* Loop around to see if anything's now ready */
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -4551,7 +4572,8 @@ TocEntrySizeCompareBinaryheap(void *p1, void *p2, void *arg)
|
|||||||
* which applies the same logic one-at-a-time.)
|
* which applies the same logic one-at-a-time.)
|
||||||
*/
|
*/
|
||||||
static void
|
static void
|
||||||
move_to_ready_heap(TocEntry *pending_list,
|
move_to_ready_heap(ArchiveHandle *AH,
|
||||||
|
TocEntry *pending_list,
|
||||||
binaryheap *ready_heap,
|
binaryheap *ready_heap,
|
||||||
RestorePass pass)
|
RestorePass pass)
|
||||||
{
|
{
|
||||||
@ -4564,7 +4586,7 @@ move_to_ready_heap(TocEntry *pending_list,
|
|||||||
next_te = te->pending_next;
|
next_te = te->pending_next;
|
||||||
|
|
||||||
if (te->depCount == 0 &&
|
if (te->depCount == 0 &&
|
||||||
_tocEntryRestorePass(te) == pass)
|
_tocEntryRestorePass(AH, te) == pass)
|
||||||
{
|
{
|
||||||
/* Remove it from pending_list ... */
|
/* Remove it from pending_list ... */
|
||||||
pending_list_remove(te);
|
pending_list_remove(te);
|
||||||
@ -4958,7 +4980,7 @@ reduce_dependencies(ArchiveHandle *AH, TocEntry *te,
|
|||||||
* memberships changed.
|
* memberships changed.
|
||||||
*/
|
*/
|
||||||
if (otherte->depCount == 0 &&
|
if (otherte->depCount == 0 &&
|
||||||
_tocEntryRestorePass(otherte) == AH->restorePass &&
|
_tocEntryRestorePass(AH, otherte) == AH->restorePass &&
|
||||||
otherte->pending_prev != NULL &&
|
otherte->pending_prev != NULL &&
|
||||||
ready_heap != NULL)
|
ready_heap != NULL)
|
||||||
{
|
{
|
||||||
|
@ -3002,6 +3002,19 @@ makeTableDataInfo(DumpOptions *dopt, TableInfo *tbinfo)
|
|||||||
|
|
||||||
tbinfo->dataObj = tdinfo;
|
tbinfo->dataObj = tdinfo;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Materialized view statistics must be restored after the data, because
|
||||||
|
* REFRESH MATERIALIZED VIEW replaces the storage and resets the stats.
|
||||||
|
*
|
||||||
|
* The dependency is added here because the statistics objects are created
|
||||||
|
* first.
|
||||||
|
*/
|
||||||
|
if (tbinfo->relkind == RELKIND_MATVIEW && tbinfo->stats != NULL)
|
||||||
|
{
|
||||||
|
tbinfo->stats->section = SECTION_POST_DATA;
|
||||||
|
addObjectDependency(&tbinfo->stats->dobj, tdinfo->dobj.dumpId);
|
||||||
|
}
|
||||||
|
|
||||||
/* Make sure that we'll collect per-column info for this table. */
|
/* Make sure that we'll collect per-column info for this table. */
|
||||||
tbinfo->interesting = true;
|
tbinfo->interesting = true;
|
||||||
}
|
}
|
||||||
@ -6893,7 +6906,32 @@ getRelationStatistics(Archive *fout, DumpableObject *rel, int32 relpages,
|
|||||||
info->relkind = relkind;
|
info->relkind = relkind;
|
||||||
info->indAttNames = indAttNames;
|
info->indAttNames = indAttNames;
|
||||||
info->nindAttNames = nindAttNames;
|
info->nindAttNames = nindAttNames;
|
||||||
info->postponed_def = false;
|
|
||||||
|
/*
|
||||||
|
* Ordinarily, stats go in SECTION_DATA for tables and
|
||||||
|
* SECTION_POST_DATA for indexes.
|
||||||
|
*
|
||||||
|
* However, the section may be updated later for materialized view
|
||||||
|
* stats. REFRESH MATERIALIZED VIEW replaces the storage and resets
|
||||||
|
* the stats, so the stats must be restored after the data. Also, the
|
||||||
|
* materialized view definition may be postponed to SECTION_POST_DATA
|
||||||
|
* (see repairMatViewBoundaryMultiLoop()).
|
||||||
|
*/
|
||||||
|
switch (info->relkind)
|
||||||
|
{
|
||||||
|
case RELKIND_RELATION:
|
||||||
|
case RELKIND_PARTITIONED_TABLE:
|
||||||
|
case RELKIND_MATVIEW:
|
||||||
|
info->section = SECTION_DATA;
|
||||||
|
break;
|
||||||
|
case RELKIND_INDEX:
|
||||||
|
case RELKIND_PARTITIONED_INDEX:
|
||||||
|
info->section = SECTION_POST_DATA;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
pg_fatal("cannot dump statistics for relation kind '%c'",
|
||||||
|
info->relkind);
|
||||||
|
}
|
||||||
|
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
@ -7292,9 +7330,17 @@ getTables(Archive *fout, int *numTables)
|
|||||||
|
|
||||||
/* Add statistics */
|
/* Add statistics */
|
||||||
if (tblinfo[i].interesting)
|
if (tblinfo[i].interesting)
|
||||||
getRelationStatistics(fout, &tblinfo[i].dobj, tblinfo[i].relpages,
|
{
|
||||||
PQgetvalue(res, i, i_reltuples),
|
RelStatsInfo *stats;
|
||||||
relallvisible, tblinfo[i].relkind, NULL, 0);
|
|
||||||
|
stats = getRelationStatistics(fout, &tblinfo[i].dobj,
|
||||||
|
tblinfo[i].relpages,
|
||||||
|
PQgetvalue(res, i, i_reltuples),
|
||||||
|
relallvisible,
|
||||||
|
tblinfo[i].relkind, NULL, 0);
|
||||||
|
if (tblinfo[i].relkind == RELKIND_MATVIEW)
|
||||||
|
tblinfo[i].stats = stats;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Read-lock target tables to make sure they aren't DROPPED or altered
|
* Read-lock target tables to make sure they aren't DROPPED or altered
|
||||||
@ -10491,34 +10537,6 @@ appendNamedArgument(PQExpBuffer out, Archive *fout, const char *argname,
|
|||||||
appendPQExpBuffer(out, "::%s", argtype);
|
appendPQExpBuffer(out, "::%s", argtype);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Decide which section to use based on the relkind of the parent object.
|
|
||||||
*
|
|
||||||
* NB: materialized views may be postponed from SECTION_PRE_DATA to
|
|
||||||
* SECTION_POST_DATA to resolve some kinds of dependency problems. If so, the
|
|
||||||
* matview stats will also be postponed to SECTION_POST_DATA. See
|
|
||||||
* repairMatViewBoundaryMultiLoop().
|
|
||||||
*/
|
|
||||||
static teSection
|
|
||||||
statisticsDumpSection(const RelStatsInfo *rsinfo)
|
|
||||||
{
|
|
||||||
switch (rsinfo->relkind)
|
|
||||||
{
|
|
||||||
case RELKIND_RELATION:
|
|
||||||
case RELKIND_PARTITIONED_TABLE:
|
|
||||||
case RELKIND_MATVIEW:
|
|
||||||
return SECTION_DATA;
|
|
||||||
case RELKIND_INDEX:
|
|
||||||
case RELKIND_PARTITIONED_INDEX:
|
|
||||||
return SECTION_POST_DATA;
|
|
||||||
default:
|
|
||||||
pg_fatal("cannot dump statistics for relation kind '%c'",
|
|
||||||
rsinfo->relkind);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; /* keep compiler quiet */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* dumpRelationStats --
|
* dumpRelationStats --
|
||||||
*
|
*
|
||||||
@ -10531,8 +10549,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
|
|||||||
PGresult *res;
|
PGresult *res;
|
||||||
PQExpBuffer query;
|
PQExpBuffer query;
|
||||||
PQExpBuffer out;
|
PQExpBuffer out;
|
||||||
DumpId *deps = NULL;
|
|
||||||
int ndeps = 0;
|
|
||||||
int i_attname;
|
int i_attname;
|
||||||
int i_inherited;
|
int i_inherited;
|
||||||
int i_null_frac;
|
int i_null_frac;
|
||||||
@ -10553,13 +10569,6 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
|
|||||||
if (!fout->dopt->dumpStatistics)
|
if (!fout->dopt->dumpStatistics)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* dependent on the relation definition, if doing schema */
|
|
||||||
if (fout->dopt->dumpSchema)
|
|
||||||
{
|
|
||||||
deps = dobj->dependencies;
|
|
||||||
ndeps = dobj->nDeps;
|
|
||||||
}
|
|
||||||
|
|
||||||
query = createPQExpBuffer();
|
query = createPQExpBuffer();
|
||||||
if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
|
if (!fout->is_prepared[PREPQUERY_GETATTRIBUTESTATS])
|
||||||
{
|
{
|
||||||
@ -10737,11 +10746,10 @@ dumpRelationStats(Archive *fout, const RelStatsInfo *rsinfo)
|
|||||||
ARCHIVE_OPTS(.tag = dobj->name,
|
ARCHIVE_OPTS(.tag = dobj->name,
|
||||||
.namespace = dobj->namespace->dobj.name,
|
.namespace = dobj->namespace->dobj.name,
|
||||||
.description = "STATISTICS DATA",
|
.description = "STATISTICS DATA",
|
||||||
.section = rsinfo->postponed_def ?
|
.section = rsinfo->section,
|
||||||
SECTION_POST_DATA : statisticsDumpSection(rsinfo),
|
|
||||||
.createStmt = out->data,
|
.createStmt = out->data,
|
||||||
.deps = deps,
|
.deps = dobj->dependencies,
|
||||||
.nDeps = ndeps));
|
.nDeps = dobj->nDeps));
|
||||||
|
|
||||||
destroyPQExpBuffer(out);
|
destroyPQExpBuffer(out);
|
||||||
destroyPQExpBuffer(query);
|
destroyPQExpBuffer(query);
|
||||||
@ -19429,7 +19437,7 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
|
|||||||
break;
|
break;
|
||||||
case DO_REL_STATS:
|
case DO_REL_STATS:
|
||||||
/* stats section varies by parent object type, DATA or POST */
|
/* stats section varies by parent object type, DATA or POST */
|
||||||
if (statisticsDumpSection((RelStatsInfo *) dobj) == SECTION_DATA)
|
if (((RelStatsInfo *) dobj)->section == SECTION_DATA)
|
||||||
{
|
{
|
||||||
addObjectDependency(dobj, preDataBound->dumpId);
|
addObjectDependency(dobj, preDataBound->dumpId);
|
||||||
addObjectDependency(postDataBound, dobj->dumpId);
|
addObjectDependency(postDataBound, dobj->dumpId);
|
||||||
|
@ -369,6 +369,7 @@ typedef struct _tableInfo
|
|||||||
bool *notnull_islocal; /* true if NOT NULL has local definition */
|
bool *notnull_islocal; /* true if NOT NULL has local definition */
|
||||||
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
|
struct _attrDefInfo **attrdefs; /* DEFAULT expressions */
|
||||||
struct _constraintInfo *checkexprs; /* CHECK constraints */
|
struct _constraintInfo *checkexprs; /* CHECK constraints */
|
||||||
|
struct _relStatsInfo *stats; /* only set for matviews */
|
||||||
bool needs_override; /* has GENERATED ALWAYS AS IDENTITY */
|
bool needs_override; /* has GENERATED ALWAYS AS IDENTITY */
|
||||||
char *amname; /* relation access method */
|
char *amname; /* relation access method */
|
||||||
|
|
||||||
@ -449,7 +450,7 @@ typedef struct _relStatsInfo
|
|||||||
*/
|
*/
|
||||||
char **indAttNames; /* attnames of the index, in order */
|
char **indAttNames; /* attnames of the index, in order */
|
||||||
int32 nindAttNames; /* number of attnames stored (can be 0) */
|
int32 nindAttNames; /* number of attnames stored (can be 0) */
|
||||||
bool postponed_def; /* stats must be postponed into post-data */
|
teSection section; /* stats may appear in data or post-data */
|
||||||
} RelStatsInfo;
|
} RelStatsInfo;
|
||||||
|
|
||||||
typedef struct _statsExtInfo
|
typedef struct _statsExtInfo
|
||||||
|
@ -820,7 +820,7 @@ repairMatViewBoundaryMultiLoop(DumpableObject *boundaryobj,
|
|||||||
RelStatsInfo *nextinfo = (RelStatsInfo *) nextobj;
|
RelStatsInfo *nextinfo = (RelStatsInfo *) nextobj;
|
||||||
|
|
||||||
if (nextinfo->relkind == RELKIND_MATVIEW)
|
if (nextinfo->relkind == RELKIND_MATVIEW)
|
||||||
nextinfo->postponed_def = true;
|
nextinfo->section = SECTION_POST_DATA;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user