Add support for PostgreSQL 17
Compilation errors and warning are fixed. The design of create_immv is also chaned as similar to PG17, that is, firstly a relation is created without data then it is populated by using the refresh logic. This commit contains the following changes: - Change functions to use a safe search_path during maintenance operations when used with PostgreSQL 17 This prevents maintenance operations (automatic maintenance of IMMVs and refresh_immv) from performing unsafe access. Functions used by IMMVs that need to reference non-default schemas must specify a search path during function creation. - refresh_immv can be executed by users with the MAINTAIN privilege when used with PostgreSQL 17 Issue #90
This commit is contained in:
parent
7dff2f5402
commit
d702c8099d
4 changed files with 258 additions and 206 deletions
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
The `pg_ivm` module provides Incremental View Maintenance (IVM) feature for PostgreSQL.
|
||||
|
||||
The extension is compatible with PostgreSQL 13, 14, 15, and 16.
|
||||
The extension is compatible with PostgreSQL 13, 14, 15, 16, and 17.
|
||||
|
||||
## Description
|
||||
|
||||
|
|
@ -54,6 +54,8 @@ postgres=# SELECT * FROM m; -- automatically updated
|
|||
(4 rows)
|
||||
```
|
||||
|
||||
Note that if you use PostgreSQL 17 or later, during automatic maintenance of an IMMV, the `search_path` is temporarily changed to `pg_catalog, pg_temp`.
|
||||
|
||||
## Installation
|
||||
To install `pg_ivm`, execute this in the module's directory:
|
||||
|
||||
|
|
@ -97,10 +99,12 @@ Use `refresh_immv` function to refresh IMMV.
|
|||
refresh_immv(immv_name text, with_data bool) RETURNS bigint
|
||||
```
|
||||
|
||||
`refresh_immv` completely replaces the contents of an IMMV as `REFRESH MATERIALIZED VIEW` command does for a materialized view. To execute this function you must be the owner of the IMMV. The old contents are discarded.
|
||||
`refresh_immv` completely replaces the contents of an IMMV as `REFRESH MATERIALIZED VIEW` command does for a materialized view. To execute this function you must be the owner of the IMMV (with PostgreSQL 16 or earlier) or have the `MAINTAIN` privilege on the IMMV (with PostgreSQL 17 or later). The old contents are discarded.
|
||||
|
||||
The with_data flag is corresponding to `WITH [NO] DATA` option of REFRESH MATERIALIZED VIEW` command. If with_data is true, the backing query is executed to provide the new data, and if the IMMV is unpopulated, triggers for maintaining the view are created. Also, a unique index is created for IMMV if it is possible and the view doesn't have that yet. If with_data is false, no new data is generated and the IMMV become unpopulated, and the triggers are dropped from the IMMV. Note that unpopulated IMMV is still scannable although the result is empty. This behaviour may be changed in future to raise an error when an unpopulated IMMV is scanned.
|
||||
|
||||
Note that if you use PostgreSQL 17 or later, while `refresh_immv` is running, the `search_path` is temporarily changed to `pg_catalog, pg_temp`.
|
||||
|
||||
#### get_immv_def
|
||||
|
||||
`get_immv_def` reconstructs the underlying SELECT command for an IMMV. (This is a decompiled reconstruction, not the original text of the command.)
|
||||
|
|
|
|||
371
createas.c
371
createas.c
|
|
@ -11,9 +11,9 @@
|
|||
*/
|
||||
#include "postgres.h"
|
||||
|
||||
#include "access/xact.h"
|
||||
#include "access/genam.h"
|
||||
#include "access/heapam.h"
|
||||
#include "access/reloptions.h"
|
||||
#include "access/xact.h"
|
||||
#include "catalog/dependency.h"
|
||||
#include "catalog/index.h"
|
||||
#include "catalog/indexing.h"
|
||||
|
|
@ -21,16 +21,15 @@
|
|||
#include "catalog/pg_constraint.h"
|
||||
#include "catalog/pg_inherits.h"
|
||||
#include "catalog/pg_trigger_d.h"
|
||||
#include "catalog/toasting.h"
|
||||
#include "commands/createas.h"
|
||||
#include "commands/defrem.h"
|
||||
#include "commands/tablecmds.h"
|
||||
#include "commands/tablespace.h"
|
||||
#include "commands/trigger.h"
|
||||
#include "executor/execdesc.h"
|
||||
#include "executor/executor.h"
|
||||
#include "miscadmin.h"
|
||||
#include "nodes/makefuncs.h"
|
||||
#include "nodes/nodeFuncs.h"
|
||||
#include "nodes/pathnodes.h"
|
||||
#include "optimizer/optimizer.h"
|
||||
#include "optimizer/prep.h"
|
||||
#include "parser/parser.h"
|
||||
|
|
@ -40,12 +39,11 @@
|
|||
#include "parser/parse_type.h"
|
||||
#include "rewrite/rewriteHandler.h"
|
||||
#include "rewrite/rewriteManip.h"
|
||||
#include "tcop/tcopprot.h"
|
||||
#include "utils/builtins.h"
|
||||
#include "utils/fmgroids.h"
|
||||
#include "utils/lsyscache.h"
|
||||
#include "utils/regproc.h"
|
||||
#include "utils/snapmgr.h"
|
||||
#include "utils/rel.h"
|
||||
#include "utils/syscache.h"
|
||||
|
||||
#include "pg_ivm.h"
|
||||
|
|
@ -62,6 +60,9 @@ typedef struct
|
|||
BulkInsertState bistate; /* bulk insert state */
|
||||
} DR_intorel;
|
||||
|
||||
/* utility functions for IMMV definition creation */
|
||||
static ObjectAddress create_immv_internal(List *attrList, IntoClause *into);
|
||||
static ObjectAddress create_immv_nodata(List *tlist, IntoClause *into);
|
||||
|
||||
typedef struct
|
||||
{
|
||||
|
|
@ -80,12 +81,152 @@ static bool check_ivm_restriction_walker(Node *node, check_ivm_restriction_conte
|
|||
static Bitmapset *get_primary_key_attnos_from_query(Query *query, List **constraintList);
|
||||
static bool check_aggregate_supports_ivm(Oid aggfnoid);
|
||||
|
||||
static void StoreImmvQuery(Oid viewOid, bool ispopulated, Query *viewQuery);
|
||||
static void StoreImmvQuery(Oid viewOid, Query *viewQuery);
|
||||
|
||||
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM < 140000)
|
||||
static bool CreateTableAsRelExists(CreateTableAsStmt *ctas);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* create_immv_internal
|
||||
*
|
||||
* Internal utility used for the creation of the definition of an IMMV.
|
||||
* Caller needs to provide a list of attributes (ColumnDef nodes).
|
||||
*
|
||||
* This imitates PostgreSQL's create_ctas_internal().
|
||||
*/
|
||||
static ObjectAddress
|
||||
create_immv_internal(List *attrList, IntoClause *into)
|
||||
{
|
||||
CreateStmt *create = makeNode(CreateStmt);
|
||||
char relkind;
|
||||
Datum toast_options;
|
||||
static char *validnsps[] = HEAP_RELOPT_NAMESPACES;
|
||||
ObjectAddress intoRelationAddr;
|
||||
|
||||
/* This code supports both CREATE TABLE AS and CREATE MATERIALIZED VIEW */
|
||||
/* relkind of IMMV must be RELKIND_RELATION */
|
||||
relkind = RELKIND_RELATION;
|
||||
|
||||
/*
|
||||
* Create the target relation by faking up a CREATE TABLE parsetree and
|
||||
* passing it to DefineRelation.
|
||||
*/
|
||||
create->relation = into->rel;
|
||||
create->tableElts = attrList;
|
||||
create->inhRelations = NIL;
|
||||
create->ofTypename = NULL;
|
||||
create->constraints = NIL;
|
||||
create->options = into->options;
|
||||
create->oncommit = into->onCommit;
|
||||
create->tablespacename = into->tableSpaceName;
|
||||
create->if_not_exists = false;
|
||||
create->accessMethod = into->accessMethod;
|
||||
|
||||
/*
|
||||
* Create the relation. (This will error out if there's an existing view,
|
||||
* so we don't need more code to complain if "replace" is false.)
|
||||
*/
|
||||
intoRelationAddr = DefineRelation(create, relkind, InvalidOid, NULL, NULL);
|
||||
|
||||
/*
|
||||
* If necessary, create a TOAST table for the target table. Note that
|
||||
* NewRelationCreateToastTable ends with CommandCounterIncrement(), so
|
||||
* that the TOAST table will be visible for insertion.
|
||||
*/
|
||||
CommandCounterIncrement();
|
||||
|
||||
/* parse and validate reloptions for the toast table */
|
||||
toast_options = transformRelOptions((Datum) 0,
|
||||
create->options,
|
||||
"toast",
|
||||
validnsps,
|
||||
true, false);
|
||||
|
||||
(void) heap_reloptions(RELKIND_TOASTVALUE, toast_options, true);
|
||||
|
||||
NewRelationCreateToastTable(intoRelationAddr.objectId, toast_options);
|
||||
|
||||
/* Create the "view" part of an IMMV. */
|
||||
StoreImmvQuery(intoRelationAddr.objectId, (Query *) into->viewQuery);
|
||||
CommandCounterIncrement();
|
||||
|
||||
return intoRelationAddr;
|
||||
}
|
||||
|
||||
/*
|
||||
* create_immv_nodata
|
||||
*
|
||||
* Create an IMMV when WITH NO DATA is used, starting from
|
||||
* the targetlist of the view definition.
|
||||
*
|
||||
* This imitates PostgreSQL's create_ctas_nodata().
|
||||
*/
|
||||
static ObjectAddress
|
||||
create_immv_nodata(List *tlist, IntoClause *into)
|
||||
{
|
||||
List *attrList;
|
||||
ListCell *t,
|
||||
*lc;
|
||||
|
||||
/*
|
||||
* Build list of ColumnDefs from non-junk elements of the tlist. If a
|
||||
* column name list was specified in CREATE TABLE AS, override the column
|
||||
* names in the query. (Too few column names are OK, too many are not.)
|
||||
*/
|
||||
attrList = NIL;
|
||||
lc = list_head(into->colNames);
|
||||
foreach(t, tlist)
|
||||
{
|
||||
TargetEntry *tle = (TargetEntry *) lfirst(t);
|
||||
|
||||
if (!tle->resjunk)
|
||||
{
|
||||
ColumnDef *col;
|
||||
char *colname;
|
||||
|
||||
if (lc)
|
||||
{
|
||||
colname = strVal(lfirst(lc));
|
||||
lc = lnext(into->colNames, lc);
|
||||
}
|
||||
else
|
||||
colname = tle->resname;
|
||||
|
||||
col = makeColumnDef(colname,
|
||||
exprType((Node *) tle->expr),
|
||||
exprTypmod((Node *) tle->expr),
|
||||
exprCollation((Node *) tle->expr));
|
||||
|
||||
/*
|
||||
* It's possible that the column is of a collatable type but the
|
||||
* collation could not be resolved, so double-check. (We must
|
||||
* check this here because DefineRelation would adopt the type's
|
||||
* default collation rather than complaining.)
|
||||
*/
|
||||
if (!OidIsValid(col->collOid) &&
|
||||
type_is_collatable(col->typeName->typeOid))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_INDETERMINATE_COLLATION),
|
||||
errmsg("no collation was derived for column \"%s\" with collatable type %s",
|
||||
col->colname,
|
||||
format_type_be(col->typeName->typeOid)),
|
||||
errhint("Use the COLLATE clause to set the collation explicitly.")));
|
||||
|
||||
attrList = lappend(attrList, col);
|
||||
}
|
||||
}
|
||||
|
||||
if (lc != NULL)
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||
errmsg("too many column names were specified")));
|
||||
|
||||
/* Create the relation definition using the ColumnDef list */
|
||||
return create_immv_internal(attrList, into);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* ExecCreateImmv -- execute a create_immv() function
|
||||
*
|
||||
|
|
@ -98,183 +239,67 @@ ExecCreateImmv(ParseState *pstate, CreateTableAsStmt *stmt,
|
|||
{
|
||||
Query *query = castNode(Query, stmt->query);
|
||||
IntoClause *into = stmt->into;
|
||||
bool is_matview = (into->viewQuery != NULL);
|
||||
DestReceiver *dest;
|
||||
Oid save_userid = InvalidOid;
|
||||
int save_sec_context = 0;
|
||||
int save_nestlevel = 0;
|
||||
bool do_refresh = false;
|
||||
ObjectAddress address;
|
||||
List *rewritten;
|
||||
PlannedStmt *plan;
|
||||
QueryDesc *queryDesc;
|
||||
Query *viewQuery = (Query *) into->viewQuery;
|
||||
|
||||
/*
|
||||
* We use this always true flag to imitate ExecCreaetTableAs(9
|
||||
* aiming to make it easier to follow up the original code.
|
||||
*/
|
||||
const bool is_ivm = true;
|
||||
|
||||
/* must be a CREATE MATERIALIZED VIEW statement */
|
||||
Assert(is_matview);
|
||||
|
||||
/*
|
||||
* Set into->viewQuery must to NULL because we want to make a
|
||||
* table instead of a materialized view. Before that, save the
|
||||
* view query.
|
||||
*/
|
||||
viewQuery = (Query *) into->viewQuery;
|
||||
into->viewQuery = NULL;
|
||||
|
||||
/* Check if the relation exists or not */
|
||||
if (CreateTableAsRelExists(stmt))
|
||||
return InvalidObjectAddress;
|
||||
|
||||
/*
|
||||
* Create the tuple receiver object and insert info it will need
|
||||
*/
|
||||
dest = CreateIntoRelDestReceiver(into);
|
||||
|
||||
/*
|
||||
* The contained Query must be a SELECT.
|
||||
*/
|
||||
Assert(query->commandType == CMD_SELECT);
|
||||
|
||||
/*
|
||||
* For materialized views, lock down security-restricted operations and
|
||||
* arrange to make GUC variable changes local to this command. This is
|
||||
* not necessary for security, but this keeps the behavior similar to
|
||||
* REFRESH MATERIALIZED VIEW. Otherwise, one could create a materialized
|
||||
* view not possible to refresh.
|
||||
* For materialized views, always skip data during table creation, and use
|
||||
* REFRESH instead (see below).
|
||||
*/
|
||||
if (is_matview)
|
||||
do_refresh = !into->skipData;
|
||||
|
||||
/* check if the query is supported in IMMV definition */
|
||||
if (contain_mutable_functions((Node *) query))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("mutable function is not supported on incrementally maintainable materialized view"),
|
||||
errhint("functions must be marked IMMUTABLE")));
|
||||
|
||||
check_ivm_restriction((Node *) query);
|
||||
|
||||
/* For IMMV, we need to rewrite matview query */
|
||||
query = rewriteQueryForIMMV(query, into->colNames);
|
||||
|
||||
/*
|
||||
* If WITH NO DATA was specified, do not go through the rewriter,
|
||||
* planner and executor. Just define the relation using a code path
|
||||
* similar to CREATE VIEW. This avoids dump/restore problems stemming
|
||||
* from running the planner before all dependencies are set up.
|
||||
*/
|
||||
address = create_immv_nodata(query->targetList, into);
|
||||
|
||||
/*
|
||||
* For materialized views, reuse the REFRESH logic, which locks down
|
||||
* security-restricted operations and restricts the search_path. This
|
||||
* reduces the chance that a subsequent refresh will fail.
|
||||
*/
|
||||
if (do_refresh)
|
||||
{
|
||||
GetUserIdAndSecContext(&save_userid, &save_sec_context);
|
||||
SetUserIdAndSecContext(save_userid,
|
||||
save_sec_context | SECURITY_RESTRICTED_OPERATION);
|
||||
save_nestlevel = NewGUCNestLevel();
|
||||
}
|
||||
Relation matviewRel;
|
||||
|
||||
if (is_matview && is_ivm)
|
||||
{
|
||||
/* check if the query is supported in IMMV definition */
|
||||
if (contain_mutable_functions((Node *) query))
|
||||
ereport(ERROR,
|
||||
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
||||
errmsg("mutable function is not supported on incrementally maintainable materialized view"),
|
||||
errhint("functions must be marked IMMUTABLE")));
|
||||
RefreshImmvByOid(address.objectId, false, pstate->p_sourcetext, qc);
|
||||
|
||||
check_ivm_restriction((Node *) query);
|
||||
|
||||
/* For IMMV, we need to rewrite matview query */
|
||||
query = rewriteQueryForIMMV(viewQuery, into->colNames);
|
||||
|
||||
}
|
||||
|
||||
if (into->skipData)
|
||||
{
|
||||
/*
|
||||
* If WITH NO DATA was specified, do not go through the rewriter,
|
||||
* planner and executor. Just define the relation using a code path
|
||||
* similar to CREATE VIEW. This avoids dump/restore problems stemming
|
||||
* from running the planner before all dependencies are set up.
|
||||
*/
|
||||
|
||||
/* XXX: Currently, WITH NO DATA is not supported in the extension version */
|
||||
//address = create_ctas_nodata(query->targetList, into);
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
rewritten = QueryRewrite(query);
|
||||
|
||||
/* SELECT should never rewrite to more or less than one SELECT query */
|
||||
if (list_length(rewritten) != 1)
|
||||
elog(ERROR, "unexpected rewrite result for %s",
|
||||
is_matview ? "CREATE MATERIALIZED VIEW" :
|
||||
"CREATE TABLE AS SELECT");
|
||||
query = linitial_node(Query, rewritten);
|
||||
Assert(query->commandType == CMD_SELECT);
|
||||
|
||||
/* plan the query */
|
||||
plan = pg_plan_query(query, pstate->p_sourcetext,
|
||||
CURSOR_OPT_PARALLEL_OK, params);
|
||||
|
||||
/*
|
||||
* 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
|
||||
* parallel to the EXPLAIN code path.)
|
||||
*/
|
||||
PushCopiedSnapshot(GetActiveSnapshot());
|
||||
UpdateActiveSnapshotCommandId();
|
||||
|
||||
/* Create a QueryDesc, redirecting output to our tuple receiver */
|
||||
queryDesc = CreateQueryDesc(plan, pstate->p_sourcetext,
|
||||
GetActiveSnapshot(), InvalidSnapshot,
|
||||
dest, params, queryEnv, 0);
|
||||
|
||||
/* call ExecutorStart to prepare the plan for execution */
|
||||
ExecutorStart(queryDesc, GetIntoRelEFlags(into));
|
||||
|
||||
/* run the plan to completion */
|
||||
ExecutorRun(queryDesc, ForwardScanDirection, 0L, true);
|
||||
|
||||
/* save the rowcount if we're given a qc to fill */
|
||||
if (qc)
|
||||
SetQueryCompletion(qc, CMDTAG_SELECT, queryDesc->estate->es_processed);
|
||||
qc->commandTag = CMDTAG_SELECT;
|
||||
|
||||
/* get object address that intorel_startup saved for us */
|
||||
address = ((DR_intorel *) dest)->reladdr;
|
||||
matviewRel = table_open(address.objectId, NoLock);
|
||||
|
||||
/* and clean up */
|
||||
ExecutorFinish(queryDesc);
|
||||
ExecutorEnd(queryDesc);
|
||||
/* Create an index on incremental maintainable materialized view, if possible */
|
||||
CreateIndexOnIMMV(query, matviewRel);
|
||||
|
||||
FreeQueryDesc(queryDesc);
|
||||
/* Create triggers to prevent IMMV from beeing changed */
|
||||
CreateChangePreventTrigger(address.objectId);
|
||||
|
||||
PopActiveSnapshot();
|
||||
}
|
||||
|
||||
/* Create the "view" part of an IMMV. */
|
||||
StoreImmvQuery(address.objectId, !into->skipData, viewQuery);
|
||||
|
||||
if (is_matview)
|
||||
{
|
||||
/* Roll back any GUC changes */
|
||||
AtEOXact_GUC(false, save_nestlevel);
|
||||
|
||||
/* Restore userid and security context */
|
||||
SetUserIdAndSecContext(save_userid, save_sec_context);
|
||||
|
||||
if (is_ivm)
|
||||
{
|
||||
Oid matviewOid = address.objectId;
|
||||
Relation matviewRel = table_open(matviewOid, NoLock);
|
||||
|
||||
if (!into->skipData)
|
||||
{
|
||||
/* Create an index on incremental maintainable materialized view, if possible */
|
||||
CreateIndexOnIMMV(viewQuery, matviewRel);
|
||||
|
||||
/*
|
||||
* Create triggers on incremental maintainable materialized view
|
||||
* This argument should use 'query'. This needs to use a rewritten query,
|
||||
* because a sublink in jointree is not supported by this function.
|
||||
*/
|
||||
CreateIvmTriggersOnBaseTables(query, matviewOid);
|
||||
|
||||
/* Create triggers to prevent IMMV from beeing changed */
|
||||
CreateChangePreventTrigger(matviewOid);
|
||||
}
|
||||
table_close(matviewRel, NoLock);
|
||||
}
|
||||
table_close(matviewRel, NoLock);
|
||||
}
|
||||
|
||||
return address;
|
||||
|
|
@ -1375,11 +1400,7 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
|
|||
index->excludeOpNames = NIL;
|
||||
index->idxcomment = NULL;
|
||||
index->indexOid = InvalidOid;
|
||||
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 170000)
|
||||
index->iswithoutoverlaps = false;
|
||||
index->oldNumber = InvalidRelFileNumber;
|
||||
index->oldFirstRelfilelocatorSubid = InvalidSubTransactionId;
|
||||
#elif defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000)
|
||||
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000)
|
||||
index->oldNumber = InvalidRelFileNumber;
|
||||
index->oldFirstRelfilelocatorSubid = InvalidSubTransactionId;
|
||||
#else
|
||||
|
|
@ -1486,18 +1507,10 @@ CreateIndexOnIMMV(Query *query, Relation matviewRel)
|
|||
|
||||
indexRel = index_open(indexoid, AccessShareLock);
|
||||
|
||||
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 170000)
|
||||
if (CheckIndexCompatible(indexRel->rd_id,
|
||||
index->accessMethod,
|
||||
index->indexParams,
|
||||
index->excludeOpNames,
|
||||
index->iswithoutoverlaps))
|
||||
#else
|
||||
if (CheckIndexCompatible(indexRel->rd_id,
|
||||
index->accessMethod,
|
||||
index->indexParams,
|
||||
index->excludeOpNames))
|
||||
#endif
|
||||
hasCompatibleIndex = true;
|
||||
|
||||
index_close(indexRel, AccessShareLock);
|
||||
|
|
@ -1677,7 +1690,7 @@ get_primary_key_attnos_from_query(Query *query, List **constraintList)
|
|||
* Store the query for the IMMV to pg_ivwm_immv
|
||||
*/
|
||||
static void
|
||||
StoreImmvQuery(Oid viewOid, bool ispopulated, Query *viewQuery)
|
||||
StoreImmvQuery(Oid viewOid, Query *viewQuery)
|
||||
{
|
||||
char *querytree = nodeToString((Node *) viewQuery);
|
||||
Datum values[Natts_pg_ivm_immv];
|
||||
|
|
@ -1691,7 +1704,7 @@ StoreImmvQuery(Oid viewOid, bool ispopulated, Query *viewQuery)
|
|||
memset(isNulls, false, sizeof(isNulls));
|
||||
|
||||
values[Anum_pg_ivm_immv_immvrelid -1 ] = ObjectIdGetDatum(viewOid);
|
||||
values[Anum_pg_ivm_immv_ispopulated -1 ] = BoolGetDatum(ispopulated);
|
||||
values[Anum_pg_ivm_immv_ispopulated -1 ] = BoolGetDatum(false);
|
||||
values[Anum_pg_ivm_immv_viewdef -1 ] = CStringGetTextDatum(querytree);
|
||||
|
||||
pgIvmImmv = table_open(PgIvmImmvRelationId(), RowExclusiveLock);
|
||||
|
|
|
|||
83
matview.c
83
matview.c
|
|
@ -234,6 +234,40 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData,
|
|||
const char *queryString, QueryCompletion *qc)
|
||||
{
|
||||
Oid matviewOid;
|
||||
LOCKMODE lockmode;
|
||||
|
||||
/* Determine strength of lock needed. */
|
||||
//concurrent = stmt->concurrent;
|
||||
//lockmode = concurrent ? ExclusiveLock : AccessExclusiveLock;
|
||||
lockmode = AccessExclusiveLock;
|
||||
|
||||
/*
|
||||
* Get a lock until end of transaction.
|
||||
*/
|
||||
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM < 170000)
|
||||
matviewOid = RangeVarGetRelidExtended(relation,
|
||||
lockmode, 0,
|
||||
RangeVarCallbackOwnsTable,
|
||||
NULL);
|
||||
#else
|
||||
matviewOid = RangeVarGetRelidExtended(relation,
|
||||
lockmode, 0,
|
||||
RangeVarCallbackMaintainsTable,
|
||||
NULL);
|
||||
#endif
|
||||
|
||||
return RefreshImmvByOid(matviewOid, skipData, queryString, qc);
|
||||
}
|
||||
|
||||
/*
|
||||
* RefreshMatViewByOid -- refresh IMMV view by OID
|
||||
*
|
||||
* This imitates PostgreSQL's RefreshMatViewByOid().
|
||||
*/
|
||||
ObjectAddress
|
||||
RefreshImmvByOid(Oid matviewOid, bool skipData,
|
||||
const char *queryString, QueryCompletion *qc)
|
||||
{
|
||||
Relation matviewRel;
|
||||
Query *dataQuery = NULL; /* initialized to keep compiler happy */
|
||||
Query *viewQuery;
|
||||
|
|
@ -242,8 +276,6 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData,
|
|||
Oid OIDNewHeap;
|
||||
DestReceiver *dest;
|
||||
uint64 processed = 0;
|
||||
//bool concurrent;
|
||||
LOCKMODE lockmode;
|
||||
char relpersistence;
|
||||
Oid save_userid;
|
||||
int save_sec_context;
|
||||
|
|
@ -259,18 +291,7 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData,
|
|||
bool isnull;
|
||||
Datum datum;
|
||||
|
||||
/* Determine strength of lock needed. */
|
||||
//concurrent = stmt->concurrent;
|
||||
//lockmode = concurrent ? ExclusiveLock : AccessExclusiveLock;
|
||||
lockmode = AccessExclusiveLock;
|
||||
|
||||
/*
|
||||
* Get a lock until end of transaction.
|
||||
*/
|
||||
matviewOid = RangeVarGetRelidExtended(relation,
|
||||
lockmode, 0,
|
||||
RangeVarCallbackOwnsTable, NULL);
|
||||
matviewRel = table_open(matviewOid, lockmode);
|
||||
matviewRel = table_open(matviewOid, NoLock);
|
||||
relowner = matviewRel->rd_rel->relowner;
|
||||
|
||||
/*
|
||||
|
|
@ -282,8 +303,12 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData,
|
|||
SetUserIdAndSecContext(relowner,
|
||||
save_sec_context | SECURITY_RESTRICTED_OPERATION);
|
||||
save_nestlevel = NewGUCNestLevel();
|
||||
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 170000)
|
||||
RestrictSearchPath();
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Make sure it is an IMMV:
|
||||
* Get the entry in pg_ivm_immv. If it doesn't exist, the relation
|
||||
* is not IMMV.
|
||||
*/
|
||||
|
|
@ -306,6 +331,15 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData,
|
|||
Assert(!isnull);
|
||||
oldPopulated = DatumGetBool(datum);
|
||||
|
||||
/*
|
||||
* 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 TABLE_INSERT_FROZEN.
|
||||
*/
|
||||
CheckTableNotInUse(matviewRel, "refresh an IMMV");
|
||||
|
||||
/* Tentatively mark the IMMV as populated or not (this will roll back
|
||||
* if we fail later).
|
||||
*/
|
||||
|
|
@ -343,15 +377,6 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData,
|
|||
if (!skipData)
|
||||
dataQuery = rewriteQueryForIMMV(viewQuery,NIL);
|
||||
|
||||
/*
|
||||
* 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 TABLE_INSERT_FROZEN.
|
||||
*/
|
||||
CheckTableNotInUse(matviewRel, "refresh an IMMV");
|
||||
|
||||
tableSpace = matviewRel->rd_rel->reltablespace;
|
||||
relpersistence = matviewRel->rd_rel->relpersistence;
|
||||
|
||||
|
|
@ -452,8 +477,13 @@ ExecRefreshImmv(const RangeVar *relation, bool skipData,
|
|||
if (!skipData)
|
||||
pgstat_count_heap_insert(matviewRel, processed);
|
||||
|
||||
/*
|
||||
* Create triggers on incremental maintainable materialized view
|
||||
* This argument should use 'dataQuery'. This needs to use a rewritten query,
|
||||
* because a sublink in jointree is not supported by this function.
|
||||
*/
|
||||
if (!skipData && !oldPopulated)
|
||||
CreateIvmTriggersOnBaseTables(viewQuery, matviewOid);
|
||||
CreateIvmTriggersOnBaseTables(dataQuery, matviewOid);
|
||||
|
||||
table_close(matviewRel, NoLock);
|
||||
|
||||
|
|
@ -534,7 +564,7 @@ refresh_immv_datafill(DestReceiver *dest, Query *query,
|
|||
ExecutorStart(queryDesc, 0);
|
||||
|
||||
/* run the plan */
|
||||
ExecutorRun(queryDesc, ForwardScanDirection, 0L, true);
|
||||
ExecutorRun(queryDesc, ForwardScanDirection, 0, true);
|
||||
|
||||
processed = queryDesc->estate->es_processed;
|
||||
|
||||
|
|
@ -889,6 +919,9 @@ IVM_immediate_maintenance(PG_FUNCTION_ARGS)
|
|||
SetUserIdAndSecContext(relowner,
|
||||
save_sec_context | SECURITY_RESTRICTED_OPERATION);
|
||||
save_nestlevel = NewGUCNestLevel();
|
||||
#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 170000)
|
||||
RestrictSearchPath();
|
||||
#endif
|
||||
|
||||
/* get view query*/
|
||||
query = get_immv_query(matviewRel);
|
||||
|
|
|
|||
2
pg_ivm.h
2
pg_ivm.h
|
|
@ -48,6 +48,8 @@ extern void makeIvmAggColumn(ParseState *pstate, Aggref *aggref, char *resname,
|
|||
extern Query *get_immv_query(Relation matviewRel);
|
||||
extern ObjectAddress ExecRefreshImmv(const RangeVar *relation, bool skipData,
|
||||
const char *queryString, QueryCompletion *qc);
|
||||
extern ObjectAddress RefreshImmvByOid(Oid matviewOid, bool skipData,
|
||||
const char *queryString, QueryCompletion *qc);
|
||||
extern bool ImmvIncrementalMaintenanceIsEnabled(void);
|
||||
extern Query *get_immv_query(Relation matviewRel);
|
||||
extern Datum IVM_immediate_before(PG_FUNCTION_ARGS);
|
||||
|
|
|
|||
Loading…
Reference in a new issue