diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index ef9c5f1adc7242cf504f40878c4eee87019a3e06..ed62246cc52db0f00dd66b4139c21ef9f7564acf 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -381,13 +381,14 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose, check_index_is_clusterable(OldHeap, indexOid, recheck, AccessExclusiveLock); /* - * Quietly ignore the request if the a materialized view is not scannable. - * No harm is done because there is nothing no data to deal with, and we - * don't want to throw an error if this is part of a multi-relation - * request -- for example, CLUSTER was run on the entire database. + * Quietly ignore the request if this is a materialized view which has not + * been populated from its query. No harm is done because there is no data + * to deal with, and we don't want to throw an error if this is part of a + * multi-relation request -- for example, CLUSTER was run on the entire + * database. */ if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW && - !OldHeap->rd_isscannable) + !OldHeap->rd_ispopulated) { relation_close(OldHeap, AccessExclusiveLock); return; @@ -923,7 +924,7 @@ copy_heap_data(Oid OIDNewHeap, Oid OIDOldHeap, Oid OIDOldIndex, if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW) /* 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; diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 06bbae5cc59c87bb33e20c35ab9e74a0d5391db6..079fafa06fb3fe268d90b0cd74bfe319c005b517 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -417,7 +417,7 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) if (into->relkind == RELKIND_MATVIEW && !into->skipData) /* Make sure the heap looks good even if no rows are written. */ - SetRelationIsScannable(intoRelationDesc); + SetMatViewToPopulated(intoRelationDesc); /* * Check INSERT permission on the constructed table. diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c index 1d2b34782894b3249b46654239145e89ff2769a2..ac7719e40da2f62605cdefbcf77f8261f41d800f 100644 --- a/src/backend/commands/matview.c +++ b/src/backend/commands/matview.c @@ -52,22 +52,21 @@ static void refresh_matview_datafill(DestReceiver *dest, Query *query, const char *queryString); /* - * SetRelationIsScannable - * Make the relation appear scannable. + * SetMatViewToPopulated + * Indicate that the materialized view has been populated by its query. * - * NOTE: This is only implemented for materialized views. The heap starts out - * in a state that doesn't look scannable, and can only transition from there - * to scannable, unless a new heap is created. + * 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. */ void -SetRelationIsScannable(Relation relation) +SetMatViewToPopulated(Relation relation) { Page page; Assert(relation->rd_rel->relkind == RELKIND_MATVIEW); - Assert(relation->rd_isscannable == false); + Assert(relation->rd_ispopulated == false); page = (Page) palloc(BLCKSZ); PageInit(page, BLCKSZ, 0); @@ -323,7 +322,7 @@ transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) myState->hi_options |= HEAP_INSERT_SKIP_WAL; myState->bistate = GetBulkInsertState(); - SetRelationIsScannable(transientrel); + SetMatViewToPopulated(transientrel); /* Not using WAL requires smgr_targblock be initially invalid */ Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber); diff --git a/src/backend/executor/execMain.c b/src/backend/executor/execMain.c index 1f2a23bcdd6d15c95bd2d0e812e6a6d0064aaa32..2a72e3c9e6ca695acdf1344dffe0813393e57469 100644 --- a/src/backend/executor/execMain.c +++ b/src/backend/executor/execMain.c @@ -499,7 +499,8 @@ ExecutorRewind(QueryDesc *queryDesc) * Check that relations which are to be accessed are in a scannable * 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 ExecCheckRelationsScannable(List *rangeTable) @@ -513,32 +514,29 @@ ExecCheckRelationsScannable(List *rangeTable) if (rte->rtekind != RTE_RELATION) continue; - if (!RelationIdIsScannable(rte->relid)) - { - if (rte->relkind == RELKIND_MATVIEW) - { - /* It is OK to replace the contents of an invalid matview. */ - if (rte->isResultRel) - continue; + if (rte->relkind != RELKIND_MATVIEW) + continue; - ereport(ERROR, - (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("materialized view \"%s\" has not been populated", - get_rel_name(rte->relid)), - 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)); - } + /* It is OK to target an unpopulated materialized for results. */ + if (rte->isResultRel) + continue; + + if (!RelationIdIsScannable(rte->relid)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("materialized view \"%s\" has not been populated", + get_rel_name(rte->relid)), + errhint("Use the REFRESH MATERIALIZED VIEW command."))); } } /* - * 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 RelationIdIsScannable(Oid relid) @@ -546,9 +544,9 @@ RelationIdIsScannable(Oid relid) Relation relation; bool result; - relation = RelationIdGetRelation(relid); - result = relation->rd_isscannable; - RelationClose(relation); + relation = heap_open(relid, NoLock); + result = RelationIsScannable(relation); + heap_close(relation, NoLock); return result; } @@ -945,7 +943,14 @@ InitPlan(QueryDesc *queryDesc, int eflags) /* * 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) ExecCheckRelationsScannable(rangeTable); diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index 8abf5e438c10139e19f83830a16073f9dd2b812f..638fd1a85b5232ed3ece85187fe556c2d1bf8f8e 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -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 * 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); continue; diff --git a/src/backend/utils/adt/dbsize.c b/src/backend/utils/adt/dbsize.c index d589d26070df1b7f9196cfda19e832184a6a78d4..d32d9014c7ef5f16ee45e0cd8df2ab538e383fcf 100644 --- a/src/backend/utils/adt/dbsize.c +++ b/src/backend/utils/adt/dbsize.c @@ -840,7 +840,8 @@ pg_relation_filepath(PG_FUNCTION_ARGS) * Indicate whether a relation is scannable. * * 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 pg_relation_is_scannable(PG_FUNCTION_ARGS) @@ -850,9 +851,13 @@ pg_relation_is_scannable(PG_FUNCTION_ARGS) bool result; relid = PG_GETARG_OID(0); - relation = RelationIdGetRelation(relid); - result = relation->rd_isscannable; - RelationClose(relation); + 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 5b1d1e5b10a53d7647efbb24b40b91ca0d5195c3..670fa8c1667e53024b520f2cc463c3a24d488994 100644 --- a/src/backend/utils/cache/relcache.c +++ b/src/backend/utils/cache/relcache.c @@ -958,9 +958,9 @@ RelationBuildDesc(Oid targetRelId, bool insertIt) if (relation->rd_rel->relkind == RELKIND_MATVIEW && heap_is_matview_init_state(relation)) - relation->rd_isscannable = false; + relation->rd_ispopulated = false; else - relation->rd_isscannable = true; + relation->rd_ispopulated = true; /* * now we can free the memory allocated for pg_class_tuple @@ -1531,7 +1531,7 @@ formrdesc(const char *relationName, Oid relationReltype, * initialize physical addressing information for the relation */ RelationInitPhysicalAddr(relation); - relation->rd_isscannable = true; + relation->rd_ispopulated = true; /* * initialize the rel-has-index flag, using hardwired knowledge @@ -1756,7 +1756,7 @@ RelationReloadIndexInfo(Relation relation) heap_freetuple(pg_class_tuple); /* We must recalculate physical address in case it changed */ 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 @@ -1907,9 +1907,9 @@ RelationClearRelation(Relation relation, bool rebuild) RelationInitPhysicalAddr(relation); if (relation->rd_rel->relkind == RELKIND_MATVIEW && heap_is_matview_init_state(relation)) - relation->rd_isscannable = false; + relation->rd_ispopulated = false; else - relation->rd_isscannable = true; + relation->rd_ispopulated = true; if (relation->rd_rel->relkind == RELKIND_INDEX) { @@ -2700,9 +2700,9 @@ RelationBuildLocalRelation(const char *relname, /* materialized view not initially scannable */ if (relkind == RELKIND_MATVIEW) - rel->rd_isscannable = false; + rel->rd_ispopulated = false; else - rel->rd_isscannable = true; + rel->rd_ispopulated = true; /* * Okay to insert into the relcache hash tables. @@ -4450,9 +4450,9 @@ load_relcache_init_file(bool shared) RelationInitPhysicalAddr(rel); if (rel->rd_rel->relkind == RELKIND_MATVIEW && heap_is_matview_init_state(rel)) - rel->rd_isscannable = false; + rel->rd_ispopulated = false; else - rel->rd_isscannable = true; + rel->rd_ispopulated = true; } /* diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c index 97751afc2da4acbf3581be4bffae5cd7f7f05423..366eca1ffad1a4260d52474ce02c07d51ee5112e 100644 --- a/src/bin/pg_dump/pg_dump.c +++ b/src/bin/pg_dump/pg_dump.c @@ -4264,7 +4264,8 @@ getTables(Archive *fout, int *numTables) "c.relhasindex, c.relhasrules, c.relhasoids, " "c.relfrozenxid, tc.oid AS toid, " "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, " "CASE WHEN c.reloftype <> 0 THEN c.reloftype::pg_catalog.regtype ELSE NULL END AS reloftype, " "d.refobjid AS owning_tab, " @@ -4282,6 +4283,7 @@ getTables(Archive *fout, int *numTables) "WHERE c.relkind in ('%c', '%c', '%c', '%c', '%c', '%c') " "ORDER BY c.oid", username_subquery, + RELKIND_MATVIEW, RELKIND_SEQUENCE, RELKIND_RELATION, RELKIND_SEQUENCE, RELKIND_VIEW, RELKIND_COMPOSITE_TYPE, diff --git a/src/include/commands/matview.h b/src/include/commands/matview.h index b5d52503c0d2d178da22fef1f7fe95cd50f8e3fc..09bc384086fa1054b615e4f7c01dcb0eb125ab68 100644 --- a/src/include/commands/matview.h +++ b/src/include/commands/matview.h @@ -20,7 +20,7 @@ #include "utils/relcache.h" -extern void SetRelationIsScannable(Relation relation); +extern void SetMatViewToPopulated(Relation relation); extern void ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, ParamListInfo params, char *completionTag); diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index a4daf772e57c8e1ad02a0ecb7bfa8c09b265f53b..1fd3f67b1e4861dff57c94ef4d7a0707cecb8612 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -83,7 +83,7 @@ typedef struct RelationData BackendId rd_backend; /* owning backend id, if temporary relation */ bool rd_islocaltemp; /* rel is a temp rel of this session */ 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 */ char rd_indexvalid; /* state of rd_indexlist: 0 = not valid, 1 = * valid, 2 = temporarily forced */ @@ -407,6 +407,16 @@ typedef struct StdRdOptions ((relation)->rd_rel->relpersistence == RELPERSISTENCE_TEMP && \ !(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 */ extern void RelationIncrementReferenceCount(Relation rel); extern void RelationDecrementReferenceCount(Relation rel);