Skip to content

Commit

Permalink
Move materialized views' is-populated status into their pg_class entr…
Browse files Browse the repository at this point in the history
…ies.

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.
  • Loading branch information
tglsfdc committed May 6, 2013
1 parent 5da5798 commit 1d6c72a
Show file tree
Hide file tree
Showing 22 changed files with 141 additions and 179 deletions.
14 changes: 11 additions & 3 deletions doc/src/sgml/catalogs.sgml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -1863,6 +1863,14 @@
<entry>True if table has (or once had) any inheritance children</entry> <entry>True if table has (or once had) any inheritance children</entry>
</row> </row>


<row>
<entry><structfield>relispopulated</structfield></entry>
<entry><type>bool</type></entry>
<entry></entry>
<entry>True if relation is populated (this is true for all
relations other than some materialized views)</entry>
</row>

<row> <row>
<entry><structfield>relfrozenxid</structfield></entry> <entry><structfield>relfrozenxid</structfield></entry>
<entry><type>xid</type></entry> <entry><type>xid</type></entry>
Expand Down Expand Up @@ -7776,14 +7784,14 @@
<row> <row>
<entry><structfield>hasindexes</structfield></entry> <entry><structfield>hasindexes</structfield></entry>
<entry><type>boolean</type></entry> <entry><type>boolean</type></entry>
<entry><literal><link linkend="catalog-pg-class"><structname>pg_class</structname></link>.relhasindex</literal></entry> <entry></entry>
<entry>True if materialized view has (or recently had) any indexes</entry> <entry>True if materialized view has (or recently had) any indexes</entry>
</row> </row>
<row> <row>
<entry><structfield>isscannable</structfield></entry> <entry><structfield>ispopulated</structfield></entry>
<entry><type>boolean</type></entry> <entry><type>boolean</type></entry>
<entry></entry> <entry></entry>
<entry>True if materialized view can currently be scanned</entry> <entry>True if materialized view is currently populated</entry>
</row> </row>
<row> <row>
<entry><structfield>definition</structfield></entry> <entry><structfield>definition</structfield></entry>
Expand Down
9 changes: 0 additions & 9 deletions doc/src/sgml/func.sgml
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14238,10 +14238,6 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
<primary>pg_tablespace_location</primary> <primary>pg_tablespace_location</primary>
</indexterm> </indexterm>


<indexterm>
<primary>pg_relation_is_scannable</primary>
</indexterm>

<indexterm> <indexterm>
<primary>pg_typeof</primary> <primary>pg_typeof</primary>
</indexterm> </indexterm>
Expand Down Expand Up @@ -14410,11 +14406,6 @@ SELECT pg_type_is_visible('myschema.widget'::regtype);
<entry><type>text</type></entry> <entry><type>text</type></entry>
<entry>get the path in the file system that this tablespace is located in</entry> <entry>get the path in the file system that this tablespace is located in</entry>
</row> </row>
<row>
<entry><literal><function>pg_relation_is_scannable(<parameter>relation_oid</parameter>)</function></literal></entry>
<entry><type>boolean</type></entry>
<entry>is the relation scannable; a materialized view which has not been loaded will not be scannable</entry>
</row>
<row> <row>
<entry><literal><function>pg_typeof(<parameter>any</parameter>)</function></literal></entry> <entry><literal><function>pg_typeof(<parameter>any</parameter>)</function></literal></entry>
<entry><type>regtype</type></entry> <entry><type>regtype</type></entry>
Expand Down
21 changes: 1 addition & 20 deletions src/backend/catalog/heap.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -780,6 +780,7 @@ InsertPgClassTuple(Relation pg_class_desc,
values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules); values[Anum_pg_class_relhasrules - 1] = BoolGetDatum(rd_rel->relhasrules);
values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers); values[Anum_pg_class_relhastriggers - 1] = BoolGetDatum(rd_rel->relhastriggers);
values[Anum_pg_class_relhassubclass - 1] = BoolGetDatum(rd_rel->relhassubclass); 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_relfrozenxid - 1] = TransactionIdGetDatum(rd_rel->relfrozenxid);
values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid); values[Anum_pg_class_relminmxid - 1] = MultiXactIdGetDatum(rd_rel->relminmxid);
if (relacl != (Datum) 0) if (relacl != (Datum) 0)
Expand Down Expand Up @@ -1345,26 +1346,6 @@ heap_create_init_fork(Relation rel)
smgrimmedsync(rel->rd_smgr, INIT_FORKNUM); smgrimmedsync(rel->rd_smgr, INIT_FORKNUM);
} }


/*
* 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 * RelationRemoveInheritance
* *
Expand Down
2 changes: 1 addition & 1 deletion src/backend/catalog/system_views.sql
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ CREATE VIEW pg_matviews AS
pg_get_userbyid(C.relowner) AS matviewowner, pg_get_userbyid(C.relowner) AS matviewowner,
T.spcname AS tablespace, T.spcname AS tablespace,
C.relhasindex AS hasindexes, C.relhasindex AS hasindexes,
pg_relation_is_scannable(C.oid) AS isscannable, C.relispopulated AS ispopulated,
pg_get_viewdef(C.oid) AS definition pg_get_viewdef(C.oid) AS definition
FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace) FROM pg_class C LEFT JOIN pg_namespace N ON (N.oid = C.relnamespace)
LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace) LEFT JOIN pg_tablespace T ON (T.oid = C.reltablespace)
Expand Down
7 changes: 1 addition & 6 deletions src/backend/commands/cluster.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
#include "catalog/objectaccess.h" #include "catalog/objectaccess.h"
#include "catalog/toasting.h" #include "catalog/toasting.h"
#include "commands/cluster.h" #include "commands/cluster.h"
#include "commands/matview.h"
#include "commands/tablecmds.h" #include "commands/tablecmds.h"
#include "commands/vacuum.h" #include "commands/vacuum.h"
#include "miscadmin.h" #include "miscadmin.h"
Expand Down Expand Up @@ -388,7 +387,7 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose,
* database. * database.
*/ */
if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW && if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW &&
!OldHeap->rd_ispopulated) !RelationIsPopulated(OldHeap))
{ {
relation_close(OldHeap, AccessExclusiveLock); relation_close(OldHeap, AccessExclusiveLock);
return; return;
Expand Down Expand Up @@ -922,10 +921,6 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
get_namespace_name(RelationGetNamespace(OldHeap)), get_namespace_name(RelationGetNamespace(OldHeap)),
RelationGetRelationName(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; * Scan through the OldHeap, either in OldIndex order or sequentially;
* copy each tuple into the NewHeap, or transiently to the tuplesort * copy each tuple into the NewHeap, or transiently to the tuplesort
Expand Down
11 changes: 7 additions & 4 deletions src/backend/commands/createas.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -359,10 +359,6 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
*/ */
intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); 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. * Check INSERT permission on the constructed table.
* *
Expand All @@ -381,6 +377,13 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)


ExecCheckRTPerms(list_make1(rte), true); 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 * Fill private fields of myState for use by later routines
*/ */
Expand Down
68 changes: 40 additions & 28 deletions src/backend/commands/matview.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,23 +14,23 @@
*/ */
#include "postgres.h" #include "postgres.h"


#include "access/heapam_xlog.h" #include "access/htup_details.h"
#include "access/multixact.h" #include "access/multixact.h"
#include "access/relscan.h"
#include "access/xact.h" #include "access/xact.h"
#include "catalog/catalog.h" #include "catalog/catalog.h"
#include "catalog/heap.h" #include "catalog/indexing.h"
#include "catalog/namespace.h" #include "catalog/namespace.h"
#include "commands/cluster.h" #include "commands/cluster.h"
#include "commands/matview.h" #include "commands/matview.h"
#include "commands/tablecmds.h" #include "commands/tablecmds.h"
#include "executor/executor.h" #include "executor/executor.h"
#include "miscadmin.h" #include "miscadmin.h"
#include "rewrite/rewriteHandler.h" #include "rewrite/rewriteHandler.h"
#include "storage/lmgr.h"
#include "storage/smgr.h" #include "storage/smgr.h"
#include "tcop/tcopprot.h" #include "tcop/tcopprot.h"
#include "utils/rel.h"
#include "utils/snapmgr.h" #include "utils/snapmgr.h"
#include "utils/syscache.h"




typedef struct typedef struct
Expand All @@ -52,38 +52,45 @@ static void refresh_matview_datafill(DestReceiver *dest, Query *query,
const char *queryString); const char *queryString);


/* /*
* SetMatViewToPopulated * SetMatViewPopulatedState
* Indicate that the materialized view has been populated by its query. * Mark a materialized view as populated, or not.
*
* 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.
* *
* NOTE: caller must be holding an appropriate lock on the relation. * NOTE: caller must be holding an appropriate lock on the relation.
*/ */
void 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_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); simple_heap_update(pgrel, &tuple->t_self, tuple);
smgrextend(relation->rd_smgr, MAIN_FORKNUM, 0, (char *) page, true);


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();
} }


/* /*
Expand All @@ -97,14 +104,14 @@ SetMatViewToPopulated(Relation relation)
* If WITH NO DATA was specified, this is effectively like a TRUNCATE; * 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 * otherwise it is like a TRUNCATE followed by an INSERT using the SELECT
* statement associated with the materialized view. The statement node's * 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 * 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 * the new heap, it's better to create the indexes afterwards than to fill them
* incrementally while we load. * incrementally while we load.
* *
* The scannable state is changed based on whether the contents reflect the * The matview's "populated" state is changed based on whether the contents
* result set of the materialized view's query. * reflect the result set of the materialized view's query.
*/ */
void void
ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
Expand Down Expand Up @@ -184,6 +191,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
*/ */
CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW"); 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; tableSpace = matviewRel->rd_rel->reltablespace;


heap_close(matviewRel, NoLock); heap_close(matviewRel, NoLock);
Expand All @@ -192,6 +205,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
OIDNewHeap = make_new_heap(matviewOid, tableSpace); OIDNewHeap = make_new_heap(matviewOid, tableSpace);
dest = CreateTransientRelDestReceiver(OIDNewHeap); dest = CreateTransientRelDestReceiver(OIDNewHeap);


/* Generate the data, if wanted. */
if (!stmt->skipData) if (!stmt->skipData)
refresh_matview_datafill(dest, dataQuery, queryString); refresh_matview_datafill(dest, dataQuery, queryString);


Expand Down Expand Up @@ -300,8 +314,6 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
myState->hi_options |= HEAP_INSERT_SKIP_WAL; myState->hi_options |= HEAP_INSERT_SKIP_WAL;
myState->bistate = GetBulkInsertState(); myState->bistate = GetBulkInsertState();


SetMatViewToPopulated(transientrel);

/* Not using WAL requires smgr_targblock be initially invalid */ /* Not using WAL requires smgr_targblock be initially invalid */
Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber); Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber);
} }
Expand Down
6 changes: 0 additions & 6 deletions src/backend/commands/vacuumlazy.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -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 * 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. * 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; possibly_freeable = vacrelstats->rel_pages - vacrelstats->nonempty_pages;
if (possibly_freeable > 0 && if (possibly_freeable > 0 &&
(possibly_freeable >= REL_TRUNCATE_MINIMUM || (possibly_freeable >= REL_TRUNCATE_MINIMUM ||
Expand Down
27 changes: 0 additions & 27 deletions src/backend/utils/adt/dbsize.c
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -834,30 +834,3 @@ pg_relation_filepath(PG_FUNCTION_ARGS)


PG_RETURN_TEXT_P(cstring_to_text(path)); 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);
}
Loading

0 comments on commit 1d6c72a

Please sign in to comment.