mirror of
https://github.com/postgres/postgres.git
synced 2025-08-15 14:02:29 +03:00
Add a materialized view relations.
A materialized view has a rule just like a view and a heap and other physical properties like a table. The rule is only used to populate the table, references in queries refer to the materialized data. This is a minimal implementation, but should still be useful in many cases. Currently data is only populated "on demand" by the CREATE MATERIALIZED VIEW and REFRESH MATERIALIZED VIEW statements. It is expected that future releases will add incremental updates with various timings, and that a more refined concept of defining what is "fresh" data will be developed. At some point it may even be possible to have queries use a materialized in place of references to underlying tables, but that requires the other above-mentioned features to be working first. Much of the documentation work by Robert Haas. Review by Noah Misch, Thom Brown, Robert Haas, Marko Tiikkaja Security review by KaiGai Kohei, with a decision on how best to implement sepgsql still pending.
This commit is contained in:
@@ -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 \
|
||||
|
@@ -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:
|
||||
|
@@ -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;
|
||||
|
@@ -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"
|
||||
@@ -378,6 +379,19 @@ cluster_rel(Oid tableOid, Oid indexOid, bool recheck, bool verbose,
|
||||
if (OidIsValid(indexOid))
|
||||
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
|
||||
@@ -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
|
||||
|
@@ -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,
|
||||
|
@@ -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),
|
||||
|
@@ -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 */
|
||||
@@ -56,6 +62,62 @@ static void intorel_shutdown(DestReceiver *self);
|
||||
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
|
||||
*/
|
||||
@@ -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++)
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -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 */
|
||||
|
374
src/backend/commands/matview.c
Normal file
374
src/backend/commands/matview.c
Normal file
@@ -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);
|
||||
}
|
@@ -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);
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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,
|
||||
|
@@ -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);
|
||||
}
|
||||
|
Reference in New Issue
Block a user