diff options
| author | Tom Lane <tgl@sss.pgh.pa.us> | 2013-05-06 13:26:51 -0400 |
|---|---|---|
| committer | Tom Lane <tgl@sss.pgh.pa.us> | 2013-05-06 13:27:22 -0400 |
| commit | 1d6c72a55b23554cfb946527dc77f9d80044ae2c (patch) | |
| tree | 8f7964a0114022585c1a0d90f37d2fd466bb6154 /src/backend | |
| parent | 5da5798004e90b14332918e7db702271442d465d (diff) | |
| download | postgresql-1d6c72a55b23554cfb946527dc77f9d80044ae2c.tar.gz | |
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.
Diffstat (limited to 'src/backend')
| -rw-r--r-- | src/backend/catalog/heap.c | 21 | ||||
| -rw-r--r-- | src/backend/catalog/system_views.sql | 2 | ||||
| -rw-r--r-- | src/backend/commands/cluster.c | 7 | ||||
| -rw-r--r-- | src/backend/commands/createas.c | 11 | ||||
| -rw-r--r-- | src/backend/commands/matview.c | 68 | ||||
| -rw-r--r-- | src/backend/commands/vacuumlazy.c | 6 | ||||
| -rw-r--r-- | src/backend/utils/adt/dbsize.c | 27 | ||||
| -rw-r--r-- | src/backend/utils/cache/relcache.c | 34 |
8 files changed, 59 insertions, 117 deletions
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c index 0b4c659ff0..24a8474cb5 100644 --- a/src/backend/catalog/heap.c +++ b/src/backend/catalog/heap.c @@ -780,6 +780,7 @@ InsertPgClassTuple(Relation pg_class_desc, values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules); values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers); values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass); + values[Anum_pg_class_relispopulated - 1] = BoolGetDatum(rd_rel->relispopulated); values[Anum_pg_class_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid); values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid); if (relacl != (Datum) 0) @@ -1346,26 +1347,6 @@ heap_create_init_fork(Relation rel) } /* - * Check whether a materialized view is in an initial, unloaded state. - * - * The check here must match what is set up in heap_create_init_fork(). - * Currently the init fork is an empty file. A missing heap is also - * considered to be unloaded. - */ -bool -heap_is_matview_init_state(Relation rel) -{ - Assert(rel->rd_rel->relkind == RELKIND_MATVIEW); - - RelationOpenSmgr(rel); - - if (!smgrexists(rel->rd_smgr, MAIN_FORKNUM)) - return true; - - return (smgrnblocks(rel->rd_smgr, MAIN_FORKNUM) < 1); -} - -/* * RelationRemoveInheritance * * Formerly, this routine checked for child relations and aborted the diff --git a/src/backend/catalog/system_views.sql b/src/backend/catalog/system_views.sql index 57adbf607d..a03bfa684b 100644 --- a/src/backend/catalog/system_views.sql +++ b/src/backend/catalog/system_views.sql @@ -101,7 +101,7 @@ CREATE VIEW pg_matviews AS pg_get_userbyid(C.relowner) AS matviewowner, T.spcname AS tablespace, C.relhasindex AS hasindexes, - pg_relation_is_scannable(C.oid) AS isscannable, + C.relispopulated AS ispopulated, pg_get_viewdef(C.oid) AS definition FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace) diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index ed62246cc5..878b6254f5 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -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 diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index de65c4c781..14973f8e7c 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -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. * @@ -382,6 +378,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 */ myState->rel = intoRelationDesc; diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index da373045cc..5491c84c76 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -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); - if (RelationNeedsWAL(relation)) - log_newpage(&(relation->rd_node), MAIN_FORKNUM, 0, page); + /* + * 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)); - RelationOpenSmgr(relation); + ((Form_pg_class) GETSTRUCT(tuple))->relispopulated = newstate; - PageSetChecksumInplace(page, 0); - smgrextend(relation->rd_smgr, MAIN_FORKNUM, 0, (char *) page, true); + simple_heap_update(pgrel, &tuple->t_self, tuple); - pfree(page); + CatalogUpdateIndexes(pgrel, tuple); - smgrimmedsync(relation->rd_smgr, MAIN_FORKNUM); + heap_freetuple(tuple); + heap_close(pgrel, RowExclusiveLock); - 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); } diff --git a/src/backend/commands/vacuumlazy.c b/src/backend/commands/vacuumlazy.c index 02f3cf3c20..9d304153b8 100644 --- a/src/backend/commands/vacuumlazy.c +++ b/src/backend/commands/vacuumlazy.c @@ -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 || diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index d32d9014c7..4c4e1ed820 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -834,30 +834,3 @@ pg_relation_filepath(PG_FUNCTION_ARGS) PG_RETURN_TEXT_P(cstring_to_text(path)); } - - -/* - * Indicate whether a relation is scannable. - * - * Currently, this is always true except for a materialized view which has not - * been populated. It is expected that other conditions for allowing a - * materialized view to be scanned will be added in later releases. - */ -Datum -pg_relation_is_scannable(PG_FUNCTION_ARGS) -{ - Oid relid; - Relation relation; - bool result; - - relid = PG_GETARG_OID(0); - relation = try_relation_open(relid, AccessShareLock); - - if (relation == NULL) - PG_RETURN_BOOL(false); - - result = RelationIsScannable(relation); - - relation_close(relation, AccessShareLock); - PG_RETURN_BOOL(result); -} diff --git a/src/backend/utils/cache/relcache.c b/src/backend/utils/cache/relcache.c index 670fa8c166..7888d38723 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -37,7 +37,6 @@ #include "access/transam.h" #include "access/xact.h" #include "catalog/catalog.h" -#include "catalog/heap.h" #include "catalog/index.h" #include "catalog/indexing.h" #include "catalog/namespace.h" @@ -956,12 +955,6 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) /* make sure relation is marked as having no open file yet */ relation->rd_smgr = NULL; - if (relation->rd_rel->relkind == RELKIND_MATVIEW && - heap_is_matview_init_state(relation)) - relation->rd_ispopulated = false; - else - relation->rd_ispopulated = true; - /* * now we can free the memory allocated for pg_class_tuple */ @@ -1459,6 +1452,9 @@ formrdesc(const char *relationName, Oid relationReltype, /* formrdesc is used only for permanent relations */ relation->rd_rel->relpersistence = RELPERSISTENCE_PERMANENT; + /* ... and they're always populated, too */ + relation->rd_rel->relispopulated = true; + relation->rd_rel->relpages = 0; relation->rd_rel->reltuples = 0; relation->rd_rel->relallvisible = 0; @@ -1531,7 +1527,6 @@ formrdesc(const char *relationName, Oid relationReltype, * initialize physical addressing information for the relation */ RelationInitPhysicalAddr(relation); - relation->rd_ispopulated = true; /* * initialize the rel-has-index flag, using hardwired knowledge @@ -1756,7 +1751,6 @@ RelationReloadIndexInfo(Relation relation) heap_freetuple(pg_class_tuple); /* We must recalculate physical address in case it changed */ RelationInitPhysicalAddr(relation); - relation->rd_ispopulated = true; /* * For a non-system index, there are fields of the pg_index row that are @@ -1905,11 +1899,6 @@ RelationClearRelation(Relation relation, bool rebuild) if (relation->rd_isnailed) { RelationInitPhysicalAddr(relation); - if (relation->rd_rel->relkind == RELKIND_MATVIEW && - heap_is_matview_init_state(relation)) - relation->rd_ispopulated = false; - else - relation->rd_ispopulated = true; if (relation->rd_rel->relkind == RELKIND_INDEX) { @@ -2671,6 +2660,12 @@ RelationBuildLocalRelation(const char *relname, break; } + /* if it's a materialized view, it's not populated initially */ + if (relkind == RELKIND_MATVIEW) + rel->rd_rel->relispopulated = false; + else + rel->rd_rel->relispopulated = true; + /* * Insert relation physical and logical identifiers (OIDs) into the right * places. For a mapped relation, we set relfilenode to zero and rely on @@ -2698,12 +2693,6 @@ RelationBuildLocalRelation(const char *relname, RelationInitPhysicalAddr(rel); - /* materialized view not initially scannable */ - if (relkind == RELKIND_MATVIEW) - rel->rd_ispopulated = false; - else - rel->rd_ispopulated = true; - /* * Okay to insert into the relcache hash tables. */ @@ -4448,11 +4437,6 @@ load_relcache_init_file(bool shared) */ RelationInitLockInfo(rel); RelationInitPhysicalAddr(rel); - if (rel->rd_rel->relkind == RELKIND_MATVIEW && - heap_is_matview_init_state(rel)) - rel->rd_ispopulated = false; - else - rel->rd_ispopulated = true; } /* |
