diff options
Diffstat (limited to 'src/backend/commands')
-rw-r--r-- | src/backend/commands/Makefile | 2 | ||||
-rw-r--r-- | src/backend/commands/alter.c | 2 | ||||
-rw-r--r-- | src/backend/commands/analyze.c | 9 | ||||
-rw-r--r-- | src/backend/commands/cluster.c | 18 | ||||
-rw-r--r-- | src/backend/commands/comment.c | 14 | ||||
-rw-r--r-- | src/backend/commands/copy.c | 11 | ||||
-rw-r--r-- | src/backend/commands/createas.c | 137 | ||||
-rw-r--r-- | src/backend/commands/event_trigger.c | 2 | ||||
-rw-r--r-- | src/backend/commands/explain.c | 35 | ||||
-rw-r--r-- | src/backend/commands/indexcmds.c | 6 | ||||
-rw-r--r-- | src/backend/commands/matview.c | 374 | ||||
-rw-r--r-- | src/backend/commands/prepare.c | 2 | ||||
-rw-r--r-- | src/backend/commands/seclabel.c | 5 | ||||
-rw-r--r-- | src/backend/commands/tablecmds.c | 158 | ||||
-rw-r--r-- | src/backend/commands/typecmds.c | 3 | ||||
-rw-r--r-- | src/backend/commands/vacuum.c | 21 | ||||
-rw-r--r-- | src/backend/commands/view.c | 68 |
17 files changed, 708 insertions, 159 deletions
diff --git a/src/backend/commands/Makefile b/src/backend/commands/Makefile index 3c322a34413..22f116b78df 100644 --- a/src/backend/commands/Makefile +++ b/src/backend/commands/Makefile @@ -16,7 +16,7 @@ OBJS = aggregatecmds.o alter.o analyze.o async.o cluster.o comment.o \ collationcmds.o constraint.o conversioncmds.o copy.o createas.o \ dbcommands.o define.o discard.o dropcmds.o \ event_trigger.o explain.o extension.o foreigncmds.o functioncmds.o \ - indexcmds.o lockcmds.o operatorcmds.o opclasscmds.o \ + indexcmds.o lockcmds.o matview.o operatorcmds.o opclasscmds.o \ portalcmds.o prepare.o proclang.o \ schemacmds.o seclabel.o sequence.o tablecmds.o tablespace.o trigger.o \ tsearchcmds.o typecmds.o user.o vacuum.o vacuumlazy.o \ diff --git a/src/backend/commands/alter.c b/src/backend/commands/alter.c index 269d19cea6c..416a068fc75 100644 --- a/src/backend/commands/alter.c +++ b/src/backend/commands/alter.c @@ -317,6 +317,7 @@ ExecRenameStmt(RenameStmt *stmt) case OBJECT_TABLE: case OBJECT_SEQUENCE: case OBJECT_VIEW: + case OBJECT_MATVIEW: case OBJECT_INDEX: case OBJECT_FOREIGN_TABLE: return RenameRelation(stmt); @@ -393,6 +394,7 @@ ExecAlterObjectSchemaStmt(AlterObjectSchemaStmt *stmt) case OBJECT_SEQUENCE: case OBJECT_TABLE: case OBJECT_VIEW: + case OBJECT_MATVIEW: return AlterTableNamespace(stmt); case OBJECT_DOMAIN: diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c index d7b17a5aba6..ad9c911542d 100644 --- a/src/backend/commands/analyze.c +++ b/src/backend/commands/analyze.c @@ -206,11 +206,12 @@ analyze_rel(Oid relid, VacuumStmt *vacstmt, BufferAccessStrategy bstrategy) } /* - * Check that it's a plain table or foreign table; we used to do this in - * get_rel_oids() but seems safer to check after we've locked the - * relation. + * Check that it's a plain table, materialized view, or foreign table; we + * used to do this in get_rel_oids() but seems safer to check after we've + * locked the relation. */ - if (onerel->rd_rel->relkind == RELKIND_RELATION) + if (onerel->rd_rel->relkind == RELKIND_RELATION || + onerel->rd_rel->relkind == RELKIND_MATVIEW) { /* Regular table, so we'll use the regular row acquisition function */ acquirefunc = acquire_sample_rows; diff --git a/src/backend/commands/cluster.c b/src/backend/commands/cluster.c index c0cb2f66545..8ab8c175195 100644 --- a/src/backend/commands/cluster.c +++ b/src/backend/commands/cluster.c @@ -29,6 +29,7 @@ #include "catalog/namespace.h" #include "catalog/toasting.h" #include "commands/cluster.h" +#include "commands/matview.h" #include "commands/tablecmds.h" #include "commands/vacuum.h" #include "miscadmin.h" @@ -379,6 +380,19 @@ 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. + */ + if (OldHeap->rd_rel->relkind == RELKIND_MATVIEW && + !OldHeap->rd_isscannable) + { + relation_close(OldHeap, AccessExclusiveLock); + return; + } + + /* * All predicate locks on the tuples or pages are about to be made * invalid, because we move tuples around. Promote them to relation * locks. Predicate locks on indexes will be promoted when they are @@ -901,6 +915,10 @@ 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. */ + SetRelationIsScannable(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/comment.c b/src/backend/commands/comment.c index 3ec12e7cb9d..60db27c2057 100644 --- a/src/backend/commands/comment.c +++ b/src/backend/commands/comment.c @@ -83,15 +83,17 @@ CommentObject(CommentStmt *stmt) case OBJECT_COLUMN: /* - * Allow comments only on columns of tables, views, composite - * types, and foreign tables (which are the only relkinds for - * which pg_dump will dump per-column comments). In particular we - * wish to disallow comments on index columns, because the naming - * of an index's columns may change across PG versions, so dumping - * per-column comments could create reload failures. + * Allow comments only on columns of tables, views, materialized + * views, composite types, and foreign tables (which are the only + * relkinds for which pg_dump will dump per-column comments). In + * particular we wish to disallow comments on index columns, + * because the naming of an index's columns may change across PG + * versions, so dumping per-column comments could create reload + * failures. */ if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_VIEW && + relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, diff --git a/src/backend/commands/copy.c b/src/backend/commands/copy.c index c651ea30280..4825bca363f 100644 --- a/src/backend/commands/copy.c +++ b/src/backend/commands/copy.c @@ -1496,6 +1496,12 @@ BeginCopyTo(Relation rel, errmsg("cannot copy from view \"%s\"", RelationGetRelationName(rel)), errhint("Try the COPY (SELECT ...) TO variant."))); + else if (rel->rd_rel->relkind == RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot copy from materialized view \"%s\"", + RelationGetRelationName(rel)), + errhint("Try the COPY (SELECT ...) TO variant."))); else if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -2016,6 +2022,11 @@ CopyFrom(CopyState cstate) (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("cannot copy to view \"%s\"", RelationGetRelationName(cstate->rel)))); + else if (cstate->rel->rd_rel->relkind == RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("cannot copy to materialized view \"%s\"", + RelationGetRelationName(cstate->rel)))); else if (cstate->rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), diff --git a/src/backend/commands/createas.c b/src/backend/commands/createas.c index 66a49db330e..a3ff1d56c81 100644 --- a/src/backend/commands/createas.c +++ b/src/backend/commands/createas.c @@ -2,6 +2,8 @@ * * createas.c * Execution of CREATE TABLE ... AS, a/k/a SELECT INTO + * Since CREATE MATERIALIZED VIEW shares syntax and most behaviors, + * implement that here, too. * * We implement this by diverting the query's normal output to a * specialized DestReceiver type. @@ -27,8 +29,11 @@ #include "access/xact.h" #include "catalog/toasting.h" #include "commands/createas.h" +#include "commands/matview.h" #include "commands/prepare.h" #include "commands/tablecmds.h" +#include "commands/view.h" +#include "parser/analyze.h" #include "parser/parse_clause.h" #include "rewrite/rewriteHandler.h" #include "storage/smgr.h" @@ -43,6 +48,7 @@ typedef struct { DestReceiver pub; /* publicly-known function pointers */ IntoClause *into; /* target relation specification */ + Query *viewParse; /* the query which defines/populates data */ /* These fields are filled by intorel_startup: */ Relation rel; /* relation to write to */ CommandId output_cid; /* cmin to insert in output tuples */ @@ -57,6 +63,62 @@ static void intorel_destroy(DestReceiver *self); /* + * Common setup needed by both normal execution and EXPLAIN ANALYZE. + */ +Query * +SetupForCreateTableAs(Query *query, IntoClause *into, const char *queryString, + ParamListInfo params, DestReceiver *dest) +{ + List *rewritten; + Query *viewParse = NULL; + + Assert(query->commandType == CMD_SELECT); + + if (into->relkind == RELKIND_MATVIEW) + viewParse = (Query *) parse_analyze((Node *) copyObject(query), + queryString, NULL, 0)->utilityStmt; + + /* + * Parse analysis was done already, but we still have to run the rule + * rewriter. We do not do AcquireRewriteLocks: we assume the query either + * came straight from the parser, or suitable locks were acquired by + * plancache.c. + * + * Because the rewriter and planner tend to scribble on the input, we make + * a preliminary copy of the source querytree. This prevents problems in + * the case that CTAS is in a portal or plpgsql function and is executed + * repeatedly. (See also the same hack in EXPLAIN and PREPARE.) + */ + rewritten = QueryRewrite((Query *) copyObject(query)); + + /* SELECT should never rewrite to more or less than one SELECT query */ + if (list_length(rewritten) != 1) + elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT"); + query = (Query *) linitial(rewritten); + + Assert(query->commandType == CMD_SELECT); + + /* Save the query after rewrite but before planning. */ + ((DR_intorel *) dest)->viewParse = viewParse; + ((DR_intorel *) dest)->into = into; + + if (into->relkind == RELKIND_MATVIEW) + { + /* + * A materialized view would either need to save parameters for use in + * maintaining or loading the data or prohibit them entirely. The + * latter seems safer and more sane. + */ + if (params != NULL && params->numParams > 0) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views may not be defined using bound parameters"))); + } + + return query; +} + +/* * ExecCreateTableAs -- execute a CREATE TABLE AS command */ void @@ -66,7 +128,6 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, Query *query = (Query *) stmt->query; IntoClause *into = stmt->into; DestReceiver *dest; - List *rewritten; PlannedStmt *plan; QueryDesc *queryDesc; ScanDirection dir; @@ -90,26 +151,8 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, return; } - Assert(query->commandType == CMD_SELECT); - - /* - * Parse analysis was done already, but we still have to run the rule - * rewriter. We do not do AcquireRewriteLocks: we assume the query either - * came straight from the parser, or suitable locks were acquired by - * plancache.c. - * - * Because the rewriter and planner tend to scribble on the input, we make - * a preliminary copy of the source querytree. This prevents problems in - * the case that CTAS is in a portal or plpgsql function and is executed - * repeatedly. (See also the same hack in EXPLAIN and PREPARE.) - */ - rewritten = QueryRewrite((Query *) copyObject(stmt->query)); - /* SELECT should never rewrite to more or less than one SELECT query */ - if (list_length(rewritten) != 1) - elog(ERROR, "unexpected rewrite result for CREATE TABLE AS SELECT"); - query = (Query *) linitial(rewritten); - Assert(query->commandType == CMD_SELECT); + query = SetupForCreateTableAs(query, into, queryString, params, dest); /* plan the query */ plan = pg_plan_query(query, 0, params); @@ -169,15 +212,21 @@ ExecCreateTableAs(CreateTableAsStmt *stmt, const char *queryString, int GetIntoRelEFlags(IntoClause *intoClause) { + int flags; /* * We need to tell the executor whether it has to produce OIDs or not, * because it doesn't have enough information to do so itself (since we * can't build the target relation until after ExecutorStart). */ if (interpretOidsOption(intoClause->options)) - return EXEC_FLAG_WITH_OIDS; + flags = EXEC_FLAG_WITH_OIDS; else - return EXEC_FLAG_WITHOUT_OIDS; + flags = EXEC_FLAG_WITHOUT_OIDS; + + if (intoClause->skipData) + flags |= EXEC_FLAG_WITH_NO_DATA; + + return flags; } /* @@ -299,12 +348,38 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) if (lc != NULL) ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("CREATE TABLE AS specifies too many column names"))); + errmsg("too many column names are specified"))); + + /* + * Enforce validations needed for materialized views only. + */ + if (into->relkind == RELKIND_MATVIEW) + { + /* + * Prohibit a data-modifying CTE in the query used to create a + * materialized view. It's not sufficiently clear what the user would + * want to happen if the MV is refreshed or incrementally maintained. + */ + if (myState->viewParse->hasModifyingCTE) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views must not use data-modifying statements in WITH"))); + + /* + * Check whether any temporary database objects are used in the + * creation query. It would be hard to refresh data or incrementally + * maintain it if a source disappeared. + */ + if (isQueryUsingTempRelation(myState->viewParse)) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("materialized views must not use temporary tables or views"))); + } /* * Actually create the target table */ - intoRelationId = DefineRelation(create, RELKIND_RELATION, InvalidOid); + intoRelationId = DefineRelation(create, into->relkind, InvalidOid); /* * If necessary, create a TOAST table for the target table. Note that @@ -324,11 +399,22 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) AlterTableCreateToastTable(intoRelationId, toast_options); + /* Create the "view" part of a materialized view. */ + if (into->relkind == RELKIND_MATVIEW) + { + StoreViewQuery(intoRelationId, myState->viewParse, false); + CommandCounterIncrement(); + } + /* * Finally we can open the target table */ intoRelationDesc = heap_open(intoRelationId, AccessExclusiveLock); + if (into->relkind == RELKIND_MATVIEW && !into->skipData) + /* Make sure the heap looks good even if no rows are written. */ + SetRelationIsScannable(intoRelationDesc); + /* * Check INSERT permission on the constructed table. * @@ -338,7 +424,8 @@ intorel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) rte = makeNode(RangeTblEntry); rte->rtekind = RTE_RELATION; rte->relid = intoRelationId; - rte->relkind = RELKIND_RELATION; + rte->relkind = into->relkind; + rte->isResultRel = true; rte->requiredPerms = ACL_INSERT; for (attnum = 1; attnum <= intoRelationDesc->rd_att->natts; attnum++) diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c index 18b37537c0f..596178fbda7 100644 --- a/src/backend/commands/event_trigger.c +++ b/src/backend/commands/event_trigger.c @@ -67,6 +67,7 @@ static event_trigger_support_data event_trigger_support[] = { { "FUNCTION", true }, { "INDEX", true }, { "LANGUAGE", true }, + { "MATERIALIZED VIEW", true }, { "OPERATOR", true }, { "OPERATOR CLASS", true }, { "OPERATOR FAMILY", true }, @@ -217,6 +218,7 @@ check_ddl_tag(const char *tag) */ if (pg_strcasecmp(tag, "CREATE TABLE AS") == 0 || pg_strcasecmp(tag, "SELECT INTO") == 0 || + pg_strcasecmp(tag, "REFRESH MATERIALIZED VIEW") == 0 || pg_strcasecmp(tag, "ALTER DEFAULT PRIVILEGES") == 0 || pg_strcasecmp(tag, "ALTER LARGE OBJECT") == 0) return EVENT_TRIGGER_COMMAND_TAG_OK; diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c index fbad0d027ae..989b52da9d4 100644 --- a/src/backend/commands/explain.c +++ b/src/backend/commands/explain.c @@ -47,7 +47,7 @@ explain_get_index_name_hook_type explain_get_index_name_hook = NULL; #define X_NOWHITESPACE 4 static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es, - const char *queryString, ParamListInfo params); + const char *queryString, DestReceiver *dest, ParamListInfo params); static void report_triggers(ResultRelInfo *rInfo, bool show_relname, ExplainState *es); static double elapsed_time(instr_time *starttime); @@ -218,7 +218,7 @@ ExplainQuery(ExplainStmt *stmt, const char *queryString, foreach(l, rewritten) { ExplainOneQuery((Query *) lfirst(l), NULL, &es, - queryString, params); + queryString, None_Receiver, params); /* Separate plans with an appropriate separator */ if (lnext(l) != NULL) @@ -299,7 +299,8 @@ ExplainResultDesc(ExplainStmt *stmt) */ static void ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es, - const char *queryString, ParamListInfo params) + const char *queryString, DestReceiver *dest, + ParamListInfo params) { /* planner will not cope with utility statements */ if (query->commandType == CMD_UTILITY) @@ -319,7 +320,7 @@ ExplainOneQuery(Query *query, IntoClause *into, ExplainState *es, plan = pg_plan_query(query, 0, params); /* run it (if needed) and produce output */ - ExplainOnePlan(plan, into, es, queryString, params); + ExplainOnePlan(plan, into, es, queryString, dest, params); } } @@ -343,19 +344,23 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, if (IsA(utilityStmt, CreateTableAsStmt)) { + DestReceiver *dest; + /* * We have to rewrite the contained SELECT and then pass it back to * ExplainOneQuery. It's probably not really necessary to copy the * contained parsetree another time, but let's be safe. */ CreateTableAsStmt *ctas = (CreateTableAsStmt *) utilityStmt; - List *rewritten; + Query *query = (Query *) ctas->query; + + dest = CreateIntoRelDestReceiver(into); Assert(IsA(ctas->query, Query)); - rewritten = QueryRewrite((Query *) copyObject(ctas->query)); - Assert(list_length(rewritten) == 1); - ExplainOneQuery((Query *) linitial(rewritten), ctas->into, es, - queryString, params); + + query = SetupForCreateTableAs(query, ctas->into, queryString, params, dest); + + ExplainOneQuery(query, ctas->into, es, queryString, dest, params); } else if (IsA(utilityStmt, ExecuteStmt)) ExplainExecuteQuery((ExecuteStmt *) utilityStmt, into, es, @@ -396,9 +401,8 @@ ExplainOneUtility(Node *utilityStmt, IntoClause *into, ExplainState *es, */ void ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, - const char *queryString, ParamListInfo params) + const char *queryString, DestReceiver *dest, ParamListInfo params) { - DestReceiver *dest; QueryDesc *queryDesc; instr_time starttime; double totaltime = 0; @@ -422,15 +426,6 @@ ExplainOnePlan(PlannedStmt *plannedstmt, IntoClause *into, ExplainState *es, PushCopiedSnapshot(GetActiveSnapshot()); UpdateActiveSnapshotCommandId(); - /* - * Normally we discard the query's output, but if explaining CREATE TABLE - * AS, we'd better use the appropriate tuple receiver. - */ - if (into) - dest = CreateIntoRelDestReceiver(into); - else - dest = None_Receiver; - /* Create a QueryDesc for the query */ queryDesc = CreateQueryDesc(plannedstmt, queryString, GetActiveSnapshot(), InvalidSnapshot, diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c index c3385a113af..f855befd4d4 100644 --- a/src/backend/commands/indexcmds.c +++ b/src/backend/commands/indexcmds.c @@ -355,7 +355,8 @@ DefineIndex(IndexStmt *stmt, relationId = RelationGetRelid(rel); namespaceId = RelationGetNamespace(rel); - if (rel->rd_rel->relkind != RELKIND_RELATION) + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_MATVIEW) { if (rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE) @@ -1835,7 +1836,8 @@ ReindexDatabase(const char *databaseName, bool do_system, bool do_user) { Form_pg_class classtuple = (Form_pg_class) GETSTRUCT(tuple); - if (classtuple->relkind != RELKIND_RELATION) + if (classtuple->relkind != RELKIND_RELATION && + classtuple->relkind != RELKIND_MATVIEW) continue; /* Skip temp tables of other backends; we can't reindex them at all */ diff --git a/src/backend/commands/matview.c b/src/backend/commands/matview.c new file mode 100644 index 00000000000..e040bedb7e5 --- /dev/null +++ b/src/backend/commands/matview.c @@ -0,0 +1,374 @@ +/*------------------------------------------------------------------------- + * + * matview.c + * materialized view support + * + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/backend/commands/matview.c + * + *------------------------------------------------------------------------- + */ +#include "postgres.h" + +#include "access/multixact.h" +#include "access/relscan.h" +#include "access/xact.h" +#include "catalog/catalog.h" +#include "catalog/heap.h" +#include "catalog/namespace.h" +#include "commands/cluster.h" +#include "commands/matview.h" +#include "commands/tablecmds.h" +#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/snapmgr.h" + + +typedef struct +{ + DestReceiver pub; /* publicly-known function pointers */ + Oid transientoid; /* OID of new heap into which to store */ + /* These fields are filled by transientrel_startup: */ + Relation transientrel; /* relation to write to */ + CommandId output_cid; /* cmin to insert in output tuples */ + int hi_options; /* heap_insert performance options */ + BulkInsertState bistate; /* bulk insert state */ +} DR_transientrel; + +static void transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo); +static void transientrel_receive(TupleTableSlot *slot, DestReceiver *self); +static void transientrel_shutdown(DestReceiver *self); +static void transientrel_destroy(DestReceiver *self); +static void refresh_matview_datafill(DestReceiver *dest, Query *query, + const char *queryString); + +/* + * SetRelationIsScannable + * Make the relation appear scannable. + * + * 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: caller must be holding an appropriate lock on the relation. + */ +void +SetRelationIsScannable(Relation relation) +{ + Page page; + + Assert(relation->rd_rel->relkind == RELKIND_MATVIEW); + Assert(relation->rd_isscannable == false); + + RelationOpenSmgr(relation); + page = (Page) palloc(BLCKSZ); + PageInit(page, BLCKSZ, 0); + smgrextend(relation->rd_smgr, MAIN_FORKNUM, 0, (char *) page, true); + pfree(page); + + smgrimmedsync(relation->rd_smgr, MAIN_FORKNUM); + + RelationCacheInvalidateEntry(relation->rd_id); +} + +/* + * ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command + * + * This refreshes the materialized view by creating a new table and swapping + * the relfilenodes of the new table and the old materialized view, so the OID + * of the original materialized view is preserved. Thus we do not lose GRANT + * nor references to this materialized view. + * + * 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. + * + * 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. + */ +void +ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString, + ParamListInfo params, char *completionTag) +{ + Oid matviewOid; + Relation matviewRel; + RewriteRule *rule; + List *actions; + Query *dataQuery; + Oid tableSpace; + Oid OIDNewHeap; + DestReceiver *dest; + + /* + * Get a lock until end of transaction. + */ + matviewOid = RangeVarGetRelidExtended(stmt->relation, + AccessExclusiveLock, false, false, + RangeVarCallbackOwnsTable, NULL); + matviewRel = heap_open(matviewOid, NoLock); + + /* Make sure it is a materialized view. */ + if (matviewRel->rd_rel->relkind != RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("\"%s\" is not a materialized view", + RelationGetRelationName(matviewRel)))); + + /* + * We're not using materialized views in the system catalogs. + */ + Assert(!IsSystemRelation(matviewRel)); + + Assert(!matviewRel->rd_rel->relhasoids); + + /* + * Check that everything is correct for a refresh. Problems at this point + * are internal errors, so elog is sufficient. + */ + if (matviewRel->rd_rel->relhasrules == false || + matviewRel->rd_rules->numLocks < 1) + elog(ERROR, + "materialized view \"%s\" is missing rewrite information", + RelationGetRelationName(matviewRel)); + + if (matviewRel->rd_rules->numLocks > 1) + elog(ERROR, + "materialized view \"%s\" has too many rules", + RelationGetRelationName(matviewRel)); + + rule = matviewRel->rd_rules->rules[0]; + if (rule->event != CMD_SELECT || !(rule->isInstead)) + elog(ERROR, + "the rule for materialized view \"%s\" is not a SELECT INSTEAD OF rule", + RelationGetRelationName(matviewRel)); + + actions = rule->actions; + if (list_length(actions) != 1) + elog(ERROR, + "the rule for materialized view \"%s\" is not a single action", + RelationGetRelationName(matviewRel)); + + /* + * The stored query was rewritten at the time of the MV definition, but + * has not been scribbled on by the planner. + */ + dataQuery = (Query *) linitial(actions); + Assert(IsA(dataQuery, Query)); + + /* + * Check for active uses of the relation in the current transaction, such + * as open scans. + * + * NB: We count on this to protect us against problems with refreshing the + * data using HEAP_INSERT_FROZEN. + */ + CheckTableNotInUse(matviewRel, "REFRESH MATERIALIZED VIEW"); + + tableSpace = matviewRel->rd_rel->reltablespace; + + heap_close(matviewRel, NoLock); + + /* Create the transient table that will receive the regenerated data. */ + OIDNewHeap = make_new_heap(matviewOid, tableSpace); + dest = CreateTransientRelDestReceiver(OIDNewHeap); + + if (!stmt->skipData) + refresh_matview_datafill(dest, dataQuery, queryString); + + /* + * Swap the physical files of the target and transient tables, then + * rebuild the target's indexes and throw away the transient table. + */ + finish_heap_swap(matviewOid, OIDNewHeap, false, false, true, RecentXmin, + ReadNextMultiXactId()); + + RelationCacheInvalidateEntry(matviewOid); +} + +/* + * refresh_matview_datafill + */ +static void +refresh_matview_datafill(DestReceiver *dest, Query *query, + const char *queryString) +{ + List *rewritten; + PlannedStmt *plan; + QueryDesc *queryDesc; + List *rtable; + RangeTblEntry *initial_rte; + RangeTblEntry *second_rte; + + rewritten = QueryRewrite((Query *) copyObject(query)); + + /* SELECT should never rewrite to more or less than one SELECT query */ + if (list_length(rewritten) != 1) + elog(ERROR, "unexpected rewrite result for REFRESH MATERIALIZED VIEW"); + query = (Query *) linitial(rewritten); + + /* Check for user-requested abort. */ + CHECK_FOR_INTERRUPTS(); + + /* + * Kludge here to allow refresh of a materialized view which is invalid + * (that is, it was created or refreshed WITH NO DATA. We flag the first + * two RangeTblEntry list elements, which were added to the front of the + * rewritten Query to keep the rules system happy, with the isResultRel + * flag to indicate that it is OK if they are flagged as invalid. See + * UpdateRangeTableOfViewParse() for details. + * + * NOTE: The rewrite has switched the frist two RTEs, but they are still + * in the first two positions. If that behavior changes, the asserts here + * will fail. + */ + rtable = query->rtable; + initial_rte = ((RangeTblEntry *) linitial(rtable)); + Assert(strcmp(initial_rte->alias->aliasname, "new")); + initial_rte->isResultRel = true; + second_rte = ((RangeTblEntry *) lsecond(rtable)); + Assert(strcmp(second_rte->alias->aliasname, "old")); + second_rte->isResultRel = true; + + /* Plan the query which will generate data for the refresh. */ + plan = pg_plan_query(query, 0, NULL); + + /* + * Use a snapshot with an updated command ID to ensure this query sees + * results of any previously executed queries. (This could only matter if + * the planner executed an allegedly-stable function that changed the + * database contents, but let's do it anyway to be safe.) + */ + PushCopiedSnapshot(GetActiveSnapshot()); + UpdateActiveSnapshotCommandId(); + + /* Create a QueryDesc, redirecting output to our tuple receiver */ + queryDesc = CreateQueryDesc(plan, queryString, + GetActiveSnapshot(), InvalidSnapshot, + dest, NULL, 0); + + /* call ExecutorStart to prepare the plan for execution */ + ExecutorStart(queryDesc, EXEC_FLAG_WITHOUT_OIDS); + + /* run the plan */ + ExecutorRun(queryDesc, ForwardScanDirection, 0L); + + /* and clean up */ + ExecutorFinish(queryDesc); + ExecutorEnd(queryDesc); + + FreeQueryDesc(queryDesc); + + PopActiveSnapshot(); +} + +DestReceiver * +CreateTransientRelDestReceiver(Oid transientoid) +{ + DR_transientrel *self = (DR_transientrel *) palloc0(sizeof(DR_transientrel)); + + self->pub.receiveSlot = transientrel_receive; + self->pub.rStartup = transientrel_startup; + self->pub.rShutdown = transientrel_shutdown; + self->pub.rDestroy = transientrel_destroy; + self->pub.mydest = DestTransientRel; + self->transientoid = transientoid; + + return (DestReceiver *) self; +} + +/* + * transientrel_startup --- executor startup + */ +static void +transientrel_startup(DestReceiver *self, int operation, TupleDesc typeinfo) +{ + DR_transientrel *myState = (DR_transientrel *) self; + Relation transientrel; + + transientrel = heap_open(myState->transientoid, NoLock); + + /* + * Fill private fields of myState for use by later routines + */ + myState->transientrel = transientrel; + myState->output_cid = GetCurrentCommandId(true); + + /* + * We can skip WAL-logging the insertions, unless PITR or streaming + * replication is in use. We can skip the FSM in any case. + */ + myState->hi_options = HEAP_INSERT_SKIP_FSM | HEAP_INSERT_FROZEN; + if (!XLogIsNeeded()) + myState->hi_options |= HEAP_INSERT_SKIP_WAL; + myState->bistate = GetBulkInsertState(); + + SetRelationIsScannable(transientrel); + + /* Not using WAL requires smgr_targblock be initially invalid */ + Assert(RelationGetTargetBlock(transientrel) == InvalidBlockNumber); +} + +/* + * transientrel_receive --- receive one tuple + */ +static void +transientrel_receive(TupleTableSlot *slot, DestReceiver *self) +{ + DR_transientrel *myState = (DR_transientrel *) self; + HeapTuple tuple; + + /* + * get the heap tuple out of the tuple table slot, making sure we have a + * writable copy + */ + tuple = ExecMaterializeSlot(slot); + + heap_insert(myState->transientrel, + tuple, + myState->output_cid, + myState->hi_options, + myState->bistate); + + /* We know this is a newly created relation, so there are no indexes */ +} + +/* + * transientrel_shutdown --- executor end + */ +static void +transientrel_shutdown(DestReceiver *self) +{ + DR_transientrel *myState = (DR_transientrel *) self; + + FreeBulkInsertState(myState->bistate); + + /* If we skipped using WAL, must heap_sync before commit */ + if (myState->hi_options & HEAP_INSERT_SKIP_WAL) + heap_sync(myState->transientrel); + + /* close transientrel, but keep lock until commit */ + heap_close(myState->transientrel, NoLock); + myState->transientrel = NULL; +} + +/* + * transientrel_destroy --- release DestReceiver object + */ +static void +transientrel_destroy(DestReceiver *self) +{ + pfree(self); +} diff --git a/src/backend/commands/prepare.c b/src/backend/commands/prepare.c index 62208eb9950..c79bc020c2a 100644 --- a/src/backend/commands/prepare.c +++ b/src/backend/commands/prepare.c @@ -665,7 +665,7 @@ ExplainExecuteQuery(ExecuteStmt *execstmt, IntoClause *into, ExplainState *es, PlannedStmt *pstmt = (PlannedStmt *) lfirst(p); if (IsA(pstmt, PlannedStmt)) - ExplainOnePlan(pstmt, into, es, query_string, paramLI); + ExplainOnePlan(pstmt, into, es, query_string, None_Receiver, paramLI); else ExplainOneUtility((Node *) pstmt, into, es, query_string, paramLI); diff --git a/src/backend/commands/seclabel.c b/src/backend/commands/seclabel.c index c83cda1b10a..3b27ac26c8e 100644 --- a/src/backend/commands/seclabel.c +++ b/src/backend/commands/seclabel.c @@ -101,11 +101,12 @@ ExecSecLabelStmt(SecLabelStmt *stmt) /* * Allow security labels only on columns of tables, views, - * composite types, and foreign tables (which are the only - * relkinds for which pg_dump will dump labels). + * materialized views, composite types, and foreign tables (which + * are the only relkinds for which pg_dump will dump labels). */ if (relation->rd_rel->relkind != RELKIND_RELATION && relation->rd_rel->relkind != RELKIND_VIEW && + relation->rd_rel->relkind != RELKIND_MATVIEW && relation->rd_rel->relkind != RELKIND_COMPOSITE_TYPE && relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index eeddd9a80b9..2a55e025779 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -217,6 +217,12 @@ static const struct dropmsgstrings dropmsgstringarray[] = { gettext_noop("view \"%s\" does not exist, skipping"), gettext_noop("\"%s\" is not a view"), gettext_noop("Use DROP VIEW to remove a view.")}, + {RELKIND_MATVIEW, + ERRCODE_UNDEFINED_TABLE, + gettext_noop("materialized view \"%s\" does not exist"), + gettext_noop("materialized view \"%s\" does not exist, skipping"), + gettext_noop("\"%s\" is not a materialized view"), + gettext_noop("Use DROP MATERIALIZED VIEW to remove a materialized view.")}, {RELKIND_INDEX, ERRCODE_UNDEFINED_OBJECT, gettext_noop("index \"%s\" does not exist"), @@ -248,9 +254,10 @@ struct DropRelationCallbackState /* Alter table target-type flags for ATSimplePermissions */ #define ATT_TABLE 0x0001 #define ATT_VIEW 0x0002 -#define ATT_INDEX 0x0004 -#define ATT_COMPOSITE_TYPE 0x0008 -#define ATT_FOREIGN_TABLE 0x0010 +#define ATT_MATVIEW 0x0004 +#define ATT_INDEX 0x0008 +#define ATT_COMPOSITE_TYPE 0x0010 +#define ATT_FOREIGN_TABLE 0x0020 static void truncate_check_rel(Relation rel); static List *MergeAttributes(List *schema, List *supers, char relpersistence, @@ -399,6 +406,8 @@ static void RangeVarCallbackForDropRelation(const RangeVar *rel, Oid relOid, static void RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, void *arg); +static bool isQueryUsingTempRelation_walker(Node *node, void *context); + /* ---------------------------------------------------------------- * DefineRelation @@ -735,7 +744,7 @@ DropErrorMsgWrongType(const char *relname, char wrongkind, char rightkind) /* * RemoveRelations * Implements DROP TABLE, DROP INDEX, DROP SEQUENCE, DROP VIEW, - * DROP FOREIGN TABLE + * DROP MATERIALIZED VIEW, DROP FOREIGN TABLE */ void RemoveRelations(DropStmt *drop) @@ -787,6 +796,10 @@ RemoveRelations(DropStmt *drop) relkind = RELKIND_VIEW; break; + case OBJECT_MATVIEW: + relkind = RELKIND_MATVIEW; + break; + case OBJECT_FOREIGN_TABLE: relkind = RELKIND_FOREIGN_TABLE; break; @@ -2067,12 +2080,13 @@ renameatt_check(Oid myrelid, Form_pg_class classform, bool recursing) */ if (relkind != RELKIND_RELATION && relkind != RELKIND_VIEW && + relkind != RELKIND_MATVIEW && relkind != RELKIND_COMPOSITE_TYPE && relkind != RELKIND_INDEX && relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, view, composite type, index, or foreign table", + errmsg("\"%s\" is not a table, view, materialized view, composite type, index, or foreign table", NameStr(classform->relname)))); /* @@ -2989,12 +3003,12 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_SetOptions: /* ALTER COLUMN SET ( options ) */ case AT_ResetOptions: /* ALTER COLUMN RESET ( options ) */ - ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_FOREIGN_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE); /* This command never recurses */ pass = AT_PASS_MISC; break; case AT_SetStorage: /* ALTER COLUMN SET STORAGE */ - ATSimplePermissions(rel, ATT_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode); /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -3007,7 +3021,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_DROP; break; case AT_AddIndex: /* ADD INDEX */ - ATSimplePermissions(rel, ATT_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); /* This command never recurses */ /* No command-specific prep needed */ pass = AT_PASS_ADD_INDEX; @@ -3054,7 +3068,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, break; case AT_ClusterOn: /* CLUSTER ON */ case AT_DropCluster: /* SET WITHOUT CLUSTER */ - ATSimplePermissions(rel, ATT_TABLE); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW); /* These commands never recurse */ /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -3081,7 +3095,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, pass = AT_PASS_DROP; break; case AT_SetTableSpace: /* SET TABLESPACE */ - ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX); + ATSimplePermissions(rel, ATT_TABLE | ATT_MATVIEW | ATT_INDEX); /* This command never recurses */ ATPrepSetTableSpace(tab, rel, cmd->name, lockmode); pass = AT_PASS_MISC; /* doesn't actually matter */ @@ -3089,7 +3103,7 @@ ATPrepCmd(List **wqueue, Relation rel, AlterTableCmd *cmd, case AT_SetRelOptions: /* SET (...) */ case AT_ResetRelOptions: /* RESET (...) */ case AT_ReplaceRelOptions: /* reset them all, then set just these */ - ATSimplePermissions(rel, ATT_TABLE | ATT_INDEX | ATT_VIEW); + ATSimplePermissions(rel, ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX); /* This command never recurses */ /* No command-specific prep needed */ pass = AT_PASS_MISC; @@ -3202,7 +3216,8 @@ ATRewriteCatalogs(List **wqueue, LOCKMODE lockmode) { AlteredTableInfo *tab = (AlteredTableInfo *) lfirst(ltab); - if (tab->relkind == RELKIND_RELATION) + if (tab->relkind == RELKIND_RELATION || + tab->relkind == RELKIND_MATVIEW) AlterTableCreateToastTable(tab->relid, (Datum) 0); } } @@ -3937,6 +3952,9 @@ ATSimplePermissions(Relation rel, int allowed_targets) case RELKIND_VIEW: actual_target = ATT_VIEW; break; + case RELKIND_MATVIEW: + actual_target = ATT_MATVIEW; + break; case RELKIND_INDEX: actual_target = ATT_INDEX; break; @@ -3983,18 +4001,27 @@ ATWrongRelkindError(Relation rel, int allowed_targets) case ATT_TABLE: msg = _("\"%s\" is not a table"); break; - case ATT_TABLE | ATT_INDEX: - msg = _("\"%s\" is not a table or index"); - break; case ATT_TABLE | ATT_VIEW: msg = _("\"%s\" is not a table or view"); break; + case ATT_TABLE | ATT_VIEW | ATT_MATVIEW | ATT_INDEX: + msg = _("\"%s\" is not a table, view, materialized view, or index"); + break; + case ATT_TABLE | ATT_MATVIEW: + msg = _("\"%s\" is not a table or materialized view"); + break; + case ATT_TABLE | ATT_MATVIEW | ATT_INDEX: + msg = _("\"%s\" is not a table, materialized view, or index"); + break; case ATT_TABLE | ATT_FOREIGN_TABLE: msg = _("\"%s\" is not a table or foreign table"); break; case ATT_TABLE | ATT_COMPOSITE_TYPE | ATT_FOREIGN_TABLE: msg = _("\"%s\" is not a table, composite type, or foreign table"); break; + case ATT_TABLE | ATT_MATVIEW | ATT_INDEX | ATT_FOREIGN_TABLE: + msg = _("\"%s\" is not a table, materialized view, composite type, or foreign table"); + break; case ATT_VIEW: msg = _("\"%s\" is not a view"); break; @@ -4147,7 +4174,8 @@ find_composite_type_dependencies(Oid typeOid, Relation origRelation, rel = relation_open(pg_depend->objid, AccessShareLock); att = rel->rd_att->attrs[pg_depend->objsubid - 1]; - if (rel->rd_rel->relkind == RELKIND_RELATION) + if (rel->rd_rel->relkind == RELKIND_RELATION || + rel->rd_rel->relkind == RELKIND_MATVIEW) { if (origTypeName) ereport(ERROR, @@ -4975,11 +5003,12 @@ ATPrepSetStatistics(Relation rel, const char *colName, Node *newValue, LOCKMODE * allowSystemTableMods to be turned on. */ if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_MATVIEW && rel->rd_rel->relkind != RELKIND_INDEX && rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, index, or foreign table", + errmsg("\"%s\" is not a table, materialized view, index, or foreign table", RelationGetRelationName(rel)))); /* Permissions checks */ @@ -8087,6 +8116,7 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock { case RELKIND_RELATION: case RELKIND_VIEW: + case RELKIND_MATVIEW: case RELKIND_FOREIGN_TABLE: /* ok to change owner */ break; @@ -8243,11 +8273,12 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock tuple_class->relkind == RELKIND_COMPOSITE_TYPE); /* - * If we are operating on a table, also change the ownership of any - * indexes and sequences that belong to the table, as well as the - * table's toast table (if it has one) + * If we are operating on a table or materialized view, also change + * the ownership of any indexes and sequences that belong to the + * relation, as well as its toast table (if it has one). */ if (tuple_class->relkind == RELKIND_RELATION || + tuple_class->relkind == RELKIND_MATVIEW || tuple_class->relkind == RELKIND_TOASTVALUE) { List *index_oid_list; @@ -8263,7 +8294,8 @@ ATExecChangeOwner(Oid relationOid, Oid newOwnerId, bool recursing, LOCKMODE lock list_free(index_oid_list); } - if (tuple_class->relkind == RELKIND_RELATION) + if (tuple_class->relkind == RELKIND_RELATION || + tuple_class->relkind == RELKIND_MATVIEW) { /* If it has a toast table, recurse to change its ownership */ if (tuple_class->reltoastrelid != InvalidOid) @@ -8533,6 +8565,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, case RELKIND_RELATION: case RELKIND_TOASTVALUE: case RELKIND_VIEW: + case RELKIND_MATVIEW: (void) heap_reloptions(rel->rd_rel->relkind, newOptions, true); break; case RELKIND_INDEX: @@ -8541,7 +8574,7 @@ ATExecSetRelOptions(Relation rel, List *defList, AlterTableType operation, default: ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table, index, or TOAST table", + errmsg("\"%s\" is not a table, view, materialized view, index, or TOAST table", RelationGetRelationName(rel)))); break; } @@ -9824,8 +9857,9 @@ AlterTableNamespace(AlterObjectSchemaStmt *stmt) } /* - * The guts of relocating a table to another namespace: besides moving - * the table itself, its dependent objects are relocated to the new schema. + * The guts of relocating a table or materialized view to another namespace: + * besides moving the relation itself, its dependent objects are relocated to + * the new schema. */ void AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid, @@ -9846,7 +9880,8 @@ AlterTableNamespaceInternal(Relation rel, Oid oldNspOid, Oid nspOid, nspOid, false, false, objsMoved); /* Fix other dependent stuff */ - if (rel->rd_rel->relkind == RELKIND_RELATION) + if (rel->rd_rel->relkind == RELKIND_RELATION || + rel->rd_rel->relkind == RELKIND_MATVIEW) { AlterIndexNamespaces(classRel, rel, oldNspOid, nspOid, objsMoved); AlterSeqNamespaces(classRel, rel, oldNspOid, nspOid, @@ -10257,10 +10292,11 @@ AtEOSubXact_on_commit_actions(bool isCommit, SubTransactionId mySubid, /* * This is intended as a callback for RangeVarGetRelidExtended(). It allows - * the table to be locked only if (1) it's a plain table or TOAST table and - * (2) the current user is the owner (or the superuser). This meets the - * permission-checking needs of both CLUSTER and REINDEX TABLE; we expose it - * here so that it can be used by both. + * the relation to be locked only if (1) it's a plain table, materialized + * view, or TOAST table and (2) the current user is the owner (or the + * superuser). This meets the permission-checking needs of CLUSTER, REINDEX + * TABLE, and REFRESH MATERIALIZED VIEW; we expose it here so that it can be + * used by all. */ void RangeVarCallbackOwnsTable(const RangeVar *relation, @@ -10280,10 +10316,11 @@ RangeVarCallbackOwnsTable(const RangeVar *relation, relkind = get_rel_relkind(relId); if (!relkind) return; - if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE) + if (relkind != RELKIND_RELATION && relkind != RELKIND_TOASTVALUE && + relkind != RELKIND_MATVIEW) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("\"%s\" is not a table", relation->relname))); + errmsg("\"%s\" is not a table or materialized view", relation->relname))); /* Check permissions */ if (!pg_class_ownercheck(relId, GetUserId())) @@ -10365,6 +10402,11 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a view", rv->relname))); + if (reltype == OBJECT_MATVIEW && relkind != RELKIND_MATVIEW) + ereport(ERROR, + (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("\"%s\" is not a materialized view", rv->relname))); + if (reltype == OBJECT_FOREIGN_TABLE && relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -10401,9 +10443,9 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, * Don't allow ALTER TABLE .. SET SCHEMA on relations that can't be moved * to a different schema, such as indexes and TOAST tables. */ - if (IsA(stmt, AlterObjectSchemaStmt) &&relkind != RELKIND_RELATION - && relkind != RELKIND_VIEW && relkind != RELKIND_SEQUENCE - && relkind != RELKIND_FOREIGN_TABLE) + if (IsA(stmt, AlterObjectSchemaStmt) && relkind != RELKIND_RELATION + && relkind != RELKIND_VIEW && relkind != RELKIND_MATVIEW + && relkind != RELKIND_SEQUENCE && relkind != RELKIND_FOREIGN_TABLE) ereport(ERROR, (errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("\"%s\" is not a table, view, sequence, or foreign table", @@ -10411,3 +10453,51 @@ RangeVarCallbackForAlterRelation(const RangeVar *rv, Oid relid, Oid oldrelid, ReleaseSysCache(tuple); } + +/* + * Returns true iff any relation underlying this query is a temporary database + * object (table, view, or materialized view). + * + */ +bool +isQueryUsingTempRelation(Query *query) +{ + return isQueryUsingTempRelation_walker((Node *) query, NULL); +} + +static bool +isQueryUsingTempRelation_walker(Node *node, void *context) +{ + if (node == NULL) + return false; + + if (IsA(node, Query)) + { + Query *query = (Query *) node; + ListCell *rtable; + + foreach(rtable, query->rtable) + { + RangeTblEntry *rte = lfirst(rtable); + + if (rte->rtekind == RTE_RELATION) + { + Relation rel = heap_open(rte->relid, AccessShareLock); + char relpersistence = rel->rd_rel->relpersistence; + + heap_close(rel, AccessShareLock); + if (relpersistence == RELPERSISTENCE_TEMP) + return true; + } + } + + return query_tree_walker(query, + isQueryUsingTempRelation_walker, + context, + QTW_IGNORE_JOINALIASES); + } + + return expression_tree_walker(node, + isQueryUsingTempRelation_walker, + context); +} diff --git a/src/backend/commands/typecmds.c b/src/backend/commands/typecmds.c index 0e55263d4ef..1ba6d5e6e99 100644 --- a/src/backend/commands/typecmds.c +++ b/src/backend/commands/typecmds.c @@ -2803,7 +2803,8 @@ get_rels_with_domain(Oid domainOid, LOCKMODE lockmode) format_type_be(domainOid)); /* Otherwise we can ignore views, composite types, etc */ - if (rel->rd_rel->relkind != RELKIND_RELATION) + if (rel->rd_rel->relkind != RELKIND_RELATION && + rel->rd_rel->relkind != RELKIND_MATVIEW) { relation_close(rel, lockmode); continue; diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index 4800b437640..c984488e034 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -341,23 +341,26 @@ get_rel_oids(Oid relid, const RangeVar *vacrel) } else { - /* Process all plain relations listed in pg_class */ + /* + * Process all plain relations and materialized views listed in + * pg_class + */ Relation pgclass; HeapScanDesc scan; HeapTuple tuple; - ScanKeyData key; - - ScanKeyInit(&key, - Anum_pg_class_relkind, - BTEqualStrategyNumber, F_CHAREQ, - CharGetDatum(RELKIND_RELATION)); pgclass = heap_open(RelationRelationId, AccessShareLock); - scan = heap_beginscan(pgclass, SnapshotNow, 1, &key); + scan = heap_beginscan(pgclass, SnapshotNow, 0, NULL); while ((tuple = heap_getnext(scan, ForwardScanDirection)) != NULL) { + Form_pg_class classForm = (Form_pg_class) GETSTRUCT(tuple); + + if (classForm->relkind != RELKIND_RELATION && + classForm->relkind != RELKIND_MATVIEW) + continue; + /* Make a relation list entry for this guy */ oldcontext = MemoryContextSwitchTo(vac_context); oid_list = lappend_oid(oid_list, HeapTupleGetOid(tuple)); @@ -743,6 +746,7 @@ vac_update_datfrozenxid(void) * InvalidTransactionId in relfrozenxid anyway.) */ if (classForm->relkind != RELKIND_RELATION && + classForm->relkind != RELKIND_MATVIEW && classForm->relkind != RELKIND_TOASTVALUE) continue; @@ -1045,6 +1049,7 @@ vacuum_rel(Oid relid, VacuumStmt *vacstmt, bool do_toast, bool for_wraparound) * relation. */ if (onerel->rd_rel->relkind != RELKIND_RELATION && + onerel->rd_rel->relkind != RELKIND_MATVIEW && onerel->rd_rel->relkind != RELKIND_TOASTVALUE) { ereport(WARNING, diff --git a/src/backend/commands/view.c b/src/backend/commands/view.c index 4d10f80ec45..aba6944bdfa 100644 --- a/src/backend/commands/view.c +++ b/src/backend/commands/view.c @@ -36,57 +36,6 @@ static void checkViewTupleDesc(TupleDesc newdesc, TupleDesc olddesc); -static bool isViewOnTempTable_walker(Node *node, void *context); - -/*--------------------------------------------------------------------- - * isViewOnTempTable - * - * Returns true iff any of the relations underlying this view are - * temporary tables. - *--------------------------------------------------------------------- - */ -static bool -isViewOnTempTable(Query *viewParse) -{ - return isViewOnTempTable_walker((Node *) viewParse, NULL); -} - -static bool -isViewOnTempTable_walker(Node *node, void *context) -{ - if (node == NULL) - return false; - - if (IsA(node, Query)) - { - Query *query = (Query *) node; - ListCell *rtable; - - foreach(rtable, query->rtable) - { - RangeTblEntry *rte = lfirst(rtable); - - if (rte->rtekind == RTE_RELATION) - { - Relation rel = heap_open(rte->relid, AccessShareLock); - char relpersistence = rel->rd_rel->relpersistence; - - heap_close(rel, AccessShareLock); - if (relpersistence == RELPERSISTENCE_TEMP) - return true; - } - } - - return query_tree_walker(query, - isViewOnTempTable_walker, - context, - QTW_IGNORE_JOINALIASES); - } - - return expression_tree_walker(node, - isViewOnTempTable_walker, - context); -} /*--------------------------------------------------------------------- * DefineVirtualRelation @@ -506,7 +455,7 @@ DefineView(ViewStmt *stmt, const char *queryString) */ view = copyObject(stmt->view); /* don't corrupt original command */ if (view->relpersistence == RELPERSISTENCE_PERMANENT - && isViewOnTempTable(viewParse)) + && isQueryUsingTempRelation(viewParse)) { view->relpersistence = RELPERSISTENCE_TEMP; ereport(NOTICE, @@ -530,6 +479,17 @@ DefineView(ViewStmt *stmt, const char *queryString) */ CommandCounterIncrement(); + StoreViewQuery(viewOid, viewParse, stmt->replace); + + return viewOid; +} + +/* + * Use the rules system to store the query for the view. + */ +void +StoreViewQuery(Oid viewOid, Query *viewParse, bool replace) +{ /* * The range table of 'viewParse' does not contain entries for the "OLD" * and "NEW" relations. So... add them! @@ -539,7 +499,5 @@ DefineView(ViewStmt *stmt, const char *queryString) /* * Now create the rules associated with the view. */ - DefineViewRules(viewOid, viewParse, stmt->replace); - - return viewOid; + DefineViewRules(viewOid, viewParse, replace); } |