Commit 52e6e33a authored by Kevin Grittner's avatar Kevin Grittner

Create a distinction between a populated matview and a scannable one.

The intent was that being populated would, long term, be just one
of the conditions which could affect whether a matview was
scannable; being populated should be necessary but not always
sufficient to scan the relation.  Since only CREATE and REFRESH
currently determine the scannability, names and comments
accidentally conflated these concepts, leading to confusion.

Also add missing locking for the SQL function which allows a
test for scannability, and fix a modularity violatiion.

Per complaints from Tom Lane, although its not clear that these
will satisfy his concerns.  Hopefully this will at least better
frame the discussion.
parent 0bf42a5f
...@@ -381,13 +381,14 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, ...@@ -381,13 +381,14 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose,
check_index_is_clusterable(OldHeap, indexOid, recheck, AccessExclusiveLock); check_index_is_clusterable(OldHeap, indexOid, recheck, AccessExclusiveLock);
/* /*
* Quietly ignore the request if the a materialized view is not scannable. * Quietly ignore the request if this is a materialized view which has not
* No harm is done because there is nothing no data to deal with, and we * been populated from its query. No harm is done because there is no data
* don't want to throw an error if this is part of a multi-relation * to deal with, and we don't want to throw an error if this is part of a
* request -- for example, CLUSTER was run on the entire database. * multi-relation request -- for example, CLUSTER was run on the entire
* database.
*/ */
if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW && if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW &&
!OldHeap->rd_isscannable) !OldHeap->rd_ispopulated)
{ {
relation_close(OldHeap, AccessExclusiveLock); relation_close(OldHeap, AccessExclusiveLock);
return; return;
...@@ -923,7 +924,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, ...@@ -923,7 +924,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex,
if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW) if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW)
/* Make sure the heap looks good even if no rows are written. */ /* Make sure the heap looks good even if no rows are written. */
SetRelationIsScannable(NewHeap); SetMatViewToPopulated(NewHeap);
/* /*
* Scan through the OldHeap, either in OldIndex order or sequentially; * Scan through the OldHeap, either in OldIndex order or sequentially;
......
...@@ -417,7 +417,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) ...@@ -417,7 +417,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo)
if (into->relkind == RELKIND_MATVIEW && !into->skipData) if (into->relkind == RELKIND_MATVIEW && !into->skipData)
/* Make sure the heap looks good even if no rows are written. */ /* Make sure the heap looks good even if no rows are written. */
SetRelationIsScannable(intoRelationDesc); SetMatViewToPopulated(intoRelationDesc);
/* /*
* Check INSERT permission on the constructed table. * Check INSERT permission on the constructed table.
......
...@@ -52,22 +52,21 @@ static void refresh_matview_datafill(DestReceiver *dest, Query *query, ...@@ -52,22 +52,21 @@ static void refresh_matview_datafill(DestReceiver *dest, Query *query,
const char *queryString); const char *queryString);
/* /*
* SetRelationIsScannable * SetMatViewToPopulated
* Make the relation appear scannable. * Indicate that the materialized view has been populated by its query.
* *
* NOTE: This is only implemented for materialized views. The heap starts out * NOTE: The heap starts out in a state that doesn't look scannable, and can
* in a state that doesn't look scannable, and can only transition from there * only transition from there to scannable at the time a new heap is created.
* to scannable, unless 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
SetRelationIsScannable(Relation relation) SetMatViewToPopulated(Relation relation)
{ {
Page page; Page page;
Assert(relation->rd_rel->relkind == RELKIND_MATVIEW); Assert(relation->rd_rel->relkind == RELKIND_MATVIEW);
Assert(relation->rd_isscannable == false); Assert(relation->rd_ispopulated == false);
page = (Page) palloc(BLCKSZ); page = (Page) palloc(BLCKSZ);
PageInit(page, BLCKSZ, 0); PageInit(page, BLCKSZ, 0);
...@@ -323,7 +322,7 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) ...@@ -323,7 +322,7 @@ 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();
SetRelationIsScannable(transientrel); 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);
......
...@@ -499,7 +499,8 @@ ExecutorRewind(QueryDesc *queryDesc) ...@@ -499,7 +499,8 @@ ExecutorRewind(QueryDesc *queryDesc)
* Check that relations which are to be accessed are in a scannable * Check that relations which are to be accessed are in a scannable
* state. * state.
* *
* If not, throw error. For a materialized view, suggest refresh. * Currently the only relations which are not are materialized views which
* have not been populated by their queries.
*/ */
static void static void
ExecCheckRelationsScannable(List *rangeTable) ExecCheckRelationsScannable(List *rangeTable)
...@@ -513,32 +514,29 @@ ExecCheckRelationsScannable(List *rangeTable) ...@@ -513,32 +514,29 @@ ExecCheckRelationsScannable(List *rangeTable)
if (rte->rtekind != RTE_RELATION) if (rte->rtekind != RTE_RELATION)
continue; continue;
if (!RelationIdIsScannable(rte->relid)) if (rte->relkind != RELKIND_MATVIEW)
{ continue;
if (rte->relkind == RELKIND_MATVIEW)
{ /* It is OK to target an unpopulated materialized for results. */
/* It is OK to replace the contents of an invalid matview. */
if (rte->isResultRel) if (rte->isResultRel)
continue; continue;
if (!RelationIdIsScannable(rte->relid))
ereport(ERROR, ereport(ERROR,
(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
errmsg("materialized view \"%s\" has not been populated", errmsg("materialized view \"%s\" has not been populated",
get_rel_name(rte->relid)), get_rel_name(rte->relid)),
errhint("Use the REFRESH MATERIALIZED VIEW command."))); errhint("Use the REFRESH MATERIALIZED VIEW command.")));
} }
else
/* This should never happen, so elog will do. */
elog(ERROR, "relation \"%s\" is not flagged as scannable",
get_rel_name(rte->relid));
}
}
} }
/* /*
* Tells whether a relation is scannable. * Tells whether a relation is scannable based on its OID.
*
* Currently only non-populated materialized views are not. This is likely to
* change to include other conditions.
* *
* Currently only non-populated materialzed views are not. * This should only be called while a lock is held on the relation.
*/ */
static bool static bool
RelationIdIsScannable(Oid relid) RelationIdIsScannable(Oid relid)
...@@ -546,9 +544,9 @@ RelationIdIsScannable(Oid relid) ...@@ -546,9 +544,9 @@ RelationIdIsScannable(Oid relid)
Relation relation; Relation relation;
bool result; bool result;
relation = RelationIdGetRelation(relid); relation = heap_open(relid, NoLock);
result = relation->rd_isscannable; result = RelationIsScannable(relation);
RelationClose(relation); heap_close(relation, NoLock);
return result; return result;
} }
...@@ -945,7 +943,14 @@ InitPlan(QueryDesc *queryDesc, int eflags) ...@@ -945,7 +943,14 @@ InitPlan(QueryDesc *queryDesc, int eflags)
/* /*
* Unless we are creating a view or are creating a materialized view WITH * Unless we are creating a view or are creating a materialized view WITH
* NO DATA, ensure that all referenced relations are scannable. * NO DATA, ensure that all referenced relations are scannable. The
* omitted cases will be checked as SELECT statements in a different
* phase, so checking again here would be wasteful and it would generate
* errors on a materialized view referenced as a target.
*
* NB: This is being done after all relations are locked, files have been
* opened, etc., to avoid duplicating that effort or creating deadlock
* possibilities.
*/ */
if ((eflags & EXEC_FLAG_WITH_NO_DATA) == 0) if ((eflags & EXEC_FLAG_WITH_NO_DATA) == 0)
ExecCheckRelationsScannable(rangeTable); ExecCheckRelationsScannable(rangeTable);
......
...@@ -1615,7 +1615,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown) ...@@ -1615,7 +1615,8 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown)
* expansion doesn't give us a lot to work with, so we are trusting * expansion doesn't give us a lot to work with, so we are trusting
* earlier validations to throw error if needed. * earlier validations to throw error if needed.
*/ */
if (rel->rd_rel->relkind == RELKIND_MATVIEW && rel->rd_isscannable) if (rel->rd_rel->relkind == RELKIND_MATVIEW &&
RelationIsScannable(rel))
{ {
heap_close(rel, NoLock); heap_close(rel, NoLock);
continue; continue;
......
...@@ -840,7 +840,8 @@ pg_relation_filepath(PG_FUNCTION_ARGS) ...@@ -840,7 +840,8 @@ pg_relation_filepath(PG_FUNCTION_ARGS)
* Indicate whether a relation is scannable. * Indicate whether a relation is scannable.
* *
* Currently, this is always true except for a materialized view which has not * Currently, this is always true except for a materialized view which has not
* been populated. * been populated. It is expected that other conditions for allowing a
* materialized view to be scanned will be added in later releases.
*/ */
Datum Datum
pg_relation_is_scannable(PG_FUNCTION_ARGS) pg_relation_is_scannable(PG_FUNCTION_ARGS)
...@@ -850,9 +851,13 @@ pg_relation_is_scannable(PG_FUNCTION_ARGS) ...@@ -850,9 +851,13 @@ pg_relation_is_scannable(PG_FUNCTION_ARGS)
bool result; bool result;
relid = PG_GETARG_OID(0); relid = PG_GETARG_OID(0);
relation = RelationIdGetRelation(relid); relation = try_relation_open(relid, AccessShareLock);
result = relation->rd_isscannable;
RelationClose(relation);
if (relation == NULL)
PG_RETURN_BOOL(false);
result = RelationIsScannable(relation);
relation_close(relation, AccessShareLock);
PG_RETURN_BOOL(result); PG_RETURN_BOOL(result);
} }
...@@ -958,9 +958,9 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) ...@@ -958,9 +958,9 @@ RelationBuildDesc(Oid targetRelId, bool insertIt)
if (relation->rd_rel->relkind == RELKIND_MATVIEW && if (relation->rd_rel->relkind == RELKIND_MATVIEW &&
heap_is_matview_init_state(relation)) heap_is_matview_init_state(relation))
relation->rd_isscannable = false; relation->rd_ispopulated = false;
else else
relation->rd_isscannable = true; relation->rd_ispopulated = true;
/* /*
* now we can free the memory allocated for pg_class_tuple * now we can free the memory allocated for pg_class_tuple
...@@ -1531,7 +1531,7 @@ formrdesc(const char *relationName, Oid relationReltype, ...@@ -1531,7 +1531,7 @@ formrdesc(const char *relationName, Oid relationReltype,
* initialize physical addressing information for the relation * initialize physical addressing information for the relation
*/ */
RelationInitPhysicalAddr(relation); RelationInitPhysicalAddr(relation);
relation->rd_isscannable = true; relation->rd_ispopulated = true;
/* /*
* initialize the rel-has-index flag, using hardwired knowledge * initialize the rel-has-index flag, using hardwired knowledge
...@@ -1756,7 +1756,7 @@ RelationReloadIndexInfo(Relation relation) ...@@ -1756,7 +1756,7 @@ RelationReloadIndexInfo(Relation relation)
heap_freetuple(pg_class_tuple); heap_freetuple(pg_class_tuple);
/* We must recalculate physical address in case it changed */ /* We must recalculate physical address in case it changed */
RelationInitPhysicalAddr(relation); RelationInitPhysicalAddr(relation);
relation->rd_isscannable = true; relation->rd_ispopulated = true;
/* /*
* For a non-system index, there are fields of the pg_index row that are * For a non-system index, there are fields of the pg_index row that are
...@@ -1907,9 +1907,9 @@ RelationClearRelation(Relation relation, bool rebuild) ...@@ -1907,9 +1907,9 @@ RelationClearRelation(Relation relation, bool rebuild)
RelationInitPhysicalAddr(relation); RelationInitPhysicalAddr(relation);
if (relation->rd_rel->relkind == RELKIND_MATVIEW && if (relation->rd_rel->relkind == RELKIND_MATVIEW &&
heap_is_matview_init_state(relation)) heap_is_matview_init_state(relation))
relation->rd_isscannable = false; relation->rd_ispopulated = false;
else else
relation->rd_isscannable = true; relation->rd_ispopulated = true;
if (relation->rd_rel->relkind == RELKIND_INDEX) if (relation->rd_rel->relkind == RELKIND_INDEX)
{ {
...@@ -2700,9 +2700,9 @@ RelationBuildLocalRelation(const char *relname, ...@@ -2700,9 +2700,9 @@ RelationBuildLocalRelation(const char *relname,
/* materialized view not initially scannable */ /* materialized view not initially scannable */
if (relkind == RELKIND_MATVIEW) if (relkind == RELKIND_MATVIEW)
rel->rd_isscannable = false; rel->rd_ispopulated = false;
else else
rel->rd_isscannable = true; rel->rd_ispopulated = true;
/* /*
* Okay to insert into the relcache hash tables. * Okay to insert into the relcache hash tables.
...@@ -4450,9 +4450,9 @@ load_relcache_init_file(bool shared) ...@@ -4450,9 +4450,9 @@ load_relcache_init_file(bool shared)
RelationInitPhysicalAddr(rel); RelationInitPhysicalAddr(rel);
if (rel->rd_rel->relkind == RELKIND_MATVIEW && if (rel->rd_rel->relkind == RELKIND_MATVIEW &&
heap_is_matview_init_state(rel)) heap_is_matview_init_state(rel))
rel->rd_isscannable = false; rel->rd_ispopulated = false;
else else
rel->rd_isscannable = true; rel->rd_ispopulated = true;
} }
/* /*
......
...@@ -4264,7 +4264,8 @@ getTables(Archive *fout, int *numTables) ...@@ -4264,7 +4264,8 @@ getTables(Archive *fout, int *numTables)
"c.relhasindex, c.relhasrules, c.relhasoids, " "c.relhasindex, c.relhasrules, c.relhasoids, "
"c.relfrozenxid, tc.oid AS toid, " "c.relfrozenxid, tc.oid AS toid, "
"tc.relfrozenxid AS tfrozenxid, " "tc.relfrozenxid AS tfrozenxid, "
"c.relpersistence, pg_relation_is_scannable(c.oid) as isscannable, " "c.relpersistence, "
"CASE WHEN c.relkind = '%c' THEN pg_relation_is_scannable(c.oid) ELSE 't'::bool END as isscannable, "
"c.relpages, " "c.relpages, "
"CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, "
"d.refobjid AS owning_tab, " "d.refobjid AS owning_tab, "
...@@ -4282,6 +4283,7 @@ getTables(Archive *fout, int *numTables) ...@@ -4282,6 +4283,7 @@ getTables(Archive *fout, int *numTables)
"WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') " "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') "
"ORDER BY c.oid", "ORDER BY c.oid",
username_subquery, username_subquery,
RELKIND_MATVIEW,
RELKIND_SEQUENCE, RELKIND_SEQUENCE,
RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_RELATION, RELKIND_SEQUENCE,
RELKIND_VIEW, RELKIND_COMPOSITE_TYPE, RELKIND_VIEW, RELKIND_COMPOSITE_TYPE,
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
#include "utils/relcache.h" #include "utils/relcache.h"
extern void SetRelationIsScannable(Relation relation); extern void SetMatViewToPopulated(Relation relation);
extern void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, extern void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
ParamListInfo params, char *completionTag); ParamListInfo params, char *completionTag);
......
...@@ -83,7 +83,7 @@ typedef struct RelationData ...@@ -83,7 +83,7 @@ typedef struct RelationData
BackendId rd_backend; /* owning backend id, if temporary relation */ BackendId rd_backend; /* owning backend id, if temporary relation */
bool rd_islocaltemp; /* rel is a temp rel of this session */ bool rd_islocaltemp; /* rel is a temp rel of this session */
bool rd_isnailed; /* rel is nailed in cache */ bool rd_isnailed; /* rel is nailed in cache */
bool rd_isscannable; /* rel can be scanned */ bool rd_ispopulated; /* matview has query results */
bool rd_isvalid; /* relcache entry is valid */ bool rd_isvalid; /* relcache entry is valid */
char rd_indexvalid; /* state of rd_indexlist: 0 = not valid, 1 = char rd_indexvalid; /* state of rd_indexlist: 0 = not valid, 1 =
* valid, 2 = temporarily forced */ * valid, 2 = temporarily forced */
...@@ -407,6 +407,16 @@ typedef struct StdRdOptions ...@@ -407,6 +407,16 @@ typedef struct StdRdOptions
((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \ ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \
!(relation)->rd_islocaltemp) !(relation)->rd_islocaltemp)
/*
* RelationIsScannable
* Currently can only be false for a materialized view which has not been
* populated by its query. This is likely to get more complicated later,
* so use a macro which looks like a function.
*/
#define RelationIsScannable(relation) ((relation)->rd_ispopulated)
/* routines in utils/cache/relcache.c */ /* routines in utils/cache/relcache.c */
extern void RelationIncrementReferenceCount(Relation rel); extern void RelationIncrementReferenceCount(Relation rel);
extern void RelationDecrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment