summaryrefslogtreecommitdiff
path: root/src/backend
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2013-05-06 13:26:51 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2013-05-06 13:27:22 -0400
commit1d6c72a55b23554cfb946527dc77f9d80044ae2c (patch)
tree8f7964a0114022585c1a0d90f37d2fd466bb6154 /src/backend
parent5da5798004e90b14332918e7db702271442d465d (diff)
downloadpostgresql-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.c21
-rw-r--r--src/backend/catalog/system_views.sql2
-rw-r--r--src/backend/commands/cluster.c7
-rw-r--r--src/backend/commands/createas.c11
-rw-r--r--src/backend/commands/matview.c68
-rw-r--r--src/backend/commands/vacuumlazy.c6
-rw-r--r--src/backend/utils/adt/dbsize.c27
-rw-r--r--src/backend/utils/cache/relcache.c34
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;
}
/*