1
0
mirror of https://github.com/postgres/postgres.git synced 2025-11-09 06:21:09 +03:00

Move materialized views' is-populated status into their pg_class entries.

Previously this state was represented by whether the view's disk file had
zero or nonzero size, which is problematic for numerous reasons, since it's
breaking a fundamental assumption about heap storage.  This was done to
allow unlogged matviews to revert to unpopulated status after a crash
despite our lack of any ability to update catalog entries post-crash.
However, this poses enough risk of future problems that it seems better to
not support unlogged matviews until we can find another way.  Accordingly,
revert that choice as well as a number of existing kluges forced by it
in favor of creating a pg_class.relispopulated flag column.
This commit is contained in:
Tom Lane
2013-05-06 13:26:51 -04:00
parent 5da5798004
commit 1d6c72a55b
22 changed files with 141 additions and 179 deletions

View File

@@ -30,7 +30,6 @@
#include "catalog/objectaccess.h"
#include "catalog/toasting.h"
#include "commands/cluster.h"
#include "commands/matview.h"
#include "commands/tablecmds.h"
#include "commands/vacuum.h"
#include "miscadmin.h"
@@ -388,7 +387,7 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose,
* database.
*/
if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW &&
!OldHeap->rd_ispopulated)
!RelationIsPopulated(OldHeap))
{
relation_close(OldHeap, AccessExclusiveLock);
return;
@@ -922,10 +921,6 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
get_namespace_name(RelationGetNamespace(OldHeap)),
RelationGetRelationName(OldHeap))));
if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW)
/* Make sure the heap looks good even if no rows are written. */
SetMatViewToPopulated(NewHeap);
/*
* Scan through the OldHeap, either in OldIndex order or sequentially;
* copy each tuple into the NewHeap, or transiently to the tuplesort

View File

@@ -359,10 +359,6 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
*/
intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock);
if (is_matview && !into->skipData)
/* Make sure the heap looks good even if no rows are written. */
SetMatViewToPopulated(intoRelationDesc);
/*
* Check INSERT permission on the constructed table.
*
@@ -381,6 +377,13 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
ExecCheckRTPerms(list_make1(rte), true);
/*
* Tentatively mark the target as populated, if it's a matview and we're
* going to fill it; otherwise, no change needed.
*/
if (is_matview && !into->skipData)
SetMatViewPopulatedState(intoRelationDesc, true);
/*
* Fill private fields of myState for use by later routines
*/

View File

@@ -14,12 +14,11 @@
*/
#include "postgres.h"
#include "access/heapam_xlog.h"
#include "access/htup_details.h"
#include "access/multixact.h"
#include "access/relscan.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/heap.h"
#include "catalog/indexing.h"
#include "catalog/namespace.h"
#include "commands/cluster.h"
#include "commands/matview.h"
@@ -27,10 +26,11 @@
#include "executor/executor.h"
#include "miscadmin.h"
#include "rewrite/rewriteHandler.h"
#include "storage/lmgr.h"
#include "storage/smgr.h"
#include "tcop/tcopprot.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
typedef struct
@@ -52,38 +52,45 @@ static void refresh_matview_datafill(DestReceiver *dest, Query *query,
const char *queryString);
/*
* SetMatViewToPopulated
* Indicate that the materialized view has been populated by its query.
*
* NOTE: The heap starts out in a state that doesn't look scannable, and can
* only transition from there to scannable at the time a new heap is created.
* SetMatViewPopulatedState
* Mark a materialized view as populated, or not.
*
* NOTE: caller must be holding an appropriate lock on the relation.
*/
void
SetMatViewToPopulated(Relation relation)
SetMatViewPopulatedState(Relation relation, bool newstate)
{
Page page;
Relation pgrel;
HeapTuple tuple;
Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
Assert(relation->rd_ispopulated == false);
page = (Page) palloc(BLCKSZ);
PageInit(page, BLCKSZ, 0);
/*
* Update relation's pg_class entry. Crucial side-effect: other backends
* (and this one too!) are sent SI message to make them rebuild relcache
* entries.
*/
pgrel = heap_open(RelationRelationId, RowExclusiveLock);
tuple = SearchSysCacheCopy1(RELOID,
ObjectIdGetDatum(RelationGetRelid(relation)));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u",
RelationGetRelid(relation));
if (RelationNeedsWAL(relation))
log_newpage(&(relation->rd_node), MAIN_FORKNUM, 0, page);
((Form_pg_class) GETSTRUCT(tuple))->relispopulated = newstate;
RelationOpenSmgr(relation);
simple_heap_update(pgrel, &tuple->t_self, tuple);
PageSetChecksumInplace(page, 0);
smgrextend(relation->rd_smgr, MAIN_FORKNUM, 0, (char *) page, true);
CatalogUpdateIndexes(pgrel, tuple);
pfree(page);
heap_freetuple(tuple);
heap_close(pgrel, RowExclusiveLock);
smgrimmedsync(relation->rd_smgr, MAIN_FORKNUM);
RelationCacheInvalidateEntry(relation->rd_id);
/*
* Advance command counter to make the updated pg_class row locally
* visible.
*/
CommandCounterIncrement();
}
/*
@@ -97,14 +104,14 @@ SetMatViewToPopulated(Relation relation)
* If WITH NO DATA was specified, this is effectively like a TRUNCATE;
* otherwise it is like a TRUNCATE followed by an INSERT using the SELECT
* statement associated with the materialized view. The statement node's
* skipData field is used to indicate that the clause was used.
* skipData field shows whether the clause was used.
*
* Indexes are rebuilt too, via REINDEX. Since we are effectively bulk-loading
* the new heap, it's better to create the indexes afterwards than to fill them
* incrementally while we load.
*
* The scannable state is changed based on whether the contents reflect the
* result set of the materialized view's query.
* The matview's "populated" state is changed based on whether the contents
* reflect the result set of the materialized view's query.
*/
void
ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
@@ -184,6 +191,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
*/
CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW");
/*
* Tentatively mark the matview as populated or not (this will roll back
* if we fail later).
*/
SetMatViewPopulatedState(matviewRel, !stmt->skipData);
tableSpace = matviewRel->rd_rel->reltablespace;
heap_close(matviewRel, NoLock);
@@ -192,6 +205,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
OIDNewHeap = make_new_heap(matviewOid, tableSpace);
dest = CreateTransientRelDestReceiver(OIDNewHeap);
/* Generate the data, if wanted. */
if (!stmt->skipData)
refresh_matview_datafill(dest, dataQuery, queryString);
@@ -300,8 +314,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
myState->hi_options |= HEAP_INSERT_SKIP_WAL;
myState->bistate = GetBulkInsertState();
SetMatViewToPopulated(transientrel);
/* Not using WAL requires smgr_targblock be initially invalid */
Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber);
}

View File

@@ -230,13 +230,7 @@ lazy_vacuum_rel(Relation onerel, VacuumStmt *vacstmt,
*
* Don't even think about it unless we have a shot at releasing a goodly
* number of pages. Otherwise, the time taken isn't worth it.
*
* Leave a populated materialized view with at least one page.
*/
if (onerel->rd_rel->relkind == RELKIND_MATVIEW &&
vacrelstats->nonempty_pages == 0)
vacrelstats->nonempty_pages = 1;
possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages;
if (possibly_freeable > 0 &&
(possibly_freeable >= REL_TRUNCATE_MINIMUM ||