When creating materialized views, use REFRESH to load data.
Previously, CREATE MATERIALIZED VIEW ... WITH DATA populated the MV the same way as CREATE TABLE ... AS. Instead, 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. Reported-by: Noah Misch Backpatch-through: 17 Discussion: https://postgr.es/m/20240630222344.db.nmisch@google.com
This commit is contained in:
parent
0a8ca122e5
commit
4b74ebf726
@ -225,10 +225,8 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
|
|||||||
Query *query = castNode(Query, stmt->query);
|
Query *query = castNode(Query, stmt->query);
|
||||||
IntoClause *into = stmt->into;
|
IntoClause *into = stmt->into;
|
||||||
bool is_matview = (into->viewQuery != NULL);
|
bool is_matview = (into->viewQuery != NULL);
|
||||||
|
bool do_refresh = false;
|
||||||
DestReceiver *dest;
|
DestReceiver *dest;
|
||||||
Oid save_userid = InvalidOid;
|
|
||||||
int save_sec_context = 0;
|
|
||||||
int save_nestlevel = 0;
|
|
||||||
ObjectAddress address;
|
ObjectAddress address;
|
||||||
List *rewritten;
|
List *rewritten;
|
||||||
PlannedStmt *plan;
|
PlannedStmt *plan;
|
||||||
@ -263,18 +261,13 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
|
|||||||
Assert(query->commandType == CMD_SELECT);
|
Assert(query->commandType == CMD_SELECT);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* For materialized views, lock down security-restricted operations and
|
* For materialized views, always skip data during table creation, and use
|
||||||
* arrange to make GUC variable changes local to this command. This is
|
* REFRESH instead (see below).
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
if (is_matview)
|
if (is_matview)
|
||||||
{
|
{
|
||||||
GetUserIdAndSecContext(&save_userid, &save_sec_context);
|
do_refresh = !into->skipData;
|
||||||
SetUserIdAndSecContext(save_userid,
|
into->skipData = true;
|
||||||
save_sec_context | SECURITY_RESTRICTED_OPERATION);
|
|
||||||
save_nestlevel = NewGUCNestLevel();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (into->skipData)
|
if (into->skipData)
|
||||||
@ -346,13 +339,18 @@ ExecCreateTableAs(ParseState *pstate, CreateTableAsStmt *stmt,
|
|||||||
PopActiveSnapshot();
|
PopActiveSnapshot();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is_matview)
|
/*
|
||||||
|
* 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)
|
||||||
{
|
{
|
||||||
/* Roll back any GUC changes */
|
RefreshMatViewByOid(address.objectId, false, false,
|
||||||
AtEOXact_GUC(false, save_nestlevel);
|
pstate->p_sourcetext, NULL, qc);
|
||||||
|
|
||||||
/* Restore userid and security context */
|
if (qc)
|
||||||
SetUserIdAndSecContext(save_userid, save_sec_context);
|
qc->commandTag = CMDTAG_SELECT;
|
||||||
}
|
}
|
||||||
|
|
||||||
return address;
|
return address;
|
||||||
|
@ -112,15 +112,44 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
|
|||||||
/*
|
/*
|
||||||
* ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
|
* ExecRefreshMatView -- execute a REFRESH MATERIALIZED VIEW command
|
||||||
*
|
*
|
||||||
|
* 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 shows whether the clause was used.
|
||||||
|
*/
|
||||||
|
ObjectAddress
|
||||||
|
ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
|
||||||
|
ParamListInfo params, QueryCompletion *qc)
|
||||||
|
{
|
||||||
|
Oid matviewOid;
|
||||||
|
LOCKMODE lockmode;
|
||||||
|
|
||||||
|
/* Determine strength of lock needed. */
|
||||||
|
lockmode = stmt->concurrent ? ExclusiveLock : AccessExclusiveLock;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get a lock until end of transaction.
|
||||||
|
*/
|
||||||
|
matviewOid = RangeVarGetRelidExtended(stmt->relation,
|
||||||
|
lockmode, 0,
|
||||||
|
RangeVarCallbackMaintainsTable,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
return RefreshMatViewByOid(matviewOid, stmt->skipData, stmt->concurrent,
|
||||||
|
queryString, params, qc);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* RefreshMatViewByOid -- refresh materialized view by OID
|
||||||
|
*
|
||||||
* This refreshes the materialized view by creating a new table and swapping
|
* This refreshes the materialized view by creating a new table and swapping
|
||||||
* the relfilenumbers of the new table and the old materialized view, so the OID
|
* the relfilenumbers 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
|
* of the original materialized view is preserved. Thus we do not lose GRANT
|
||||||
* nor references to this materialized view.
|
* nor references to this materialized view.
|
||||||
*
|
*
|
||||||
* If WITH NO DATA was specified, this is effectively like a TRUNCATE;
|
* If skipData is true, this is effectively like a TRUNCATE; otherwise it is
|
||||||
* otherwise it is like a TRUNCATE followed by an INSERT using the SELECT
|
* like a TRUNCATE followed by an INSERT using the SELECT statement associated
|
||||||
* statement associated with the materialized view. The statement node's
|
* with the materialized view.
|
||||||
* skipData field shows whether the clause was used.
|
|
||||||
*
|
*
|
||||||
* Indexes are rebuilt too, via REINDEX. Since we are effectively bulk-loading
|
* 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
|
* the new heap, it's better to create the indexes afterwards than to fill them
|
||||||
@ -130,10 +159,10 @@ SetMatViewPopulatedState(Relation relation, bool newstate)
|
|||||||
* reflect the result set of the materialized view's query.
|
* reflect the result set of the materialized view's query.
|
||||||
*/
|
*/
|
||||||
ObjectAddress
|
ObjectAddress
|
||||||
ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
|
RefreshMatViewByOid(Oid matviewOid, bool skipData, bool concurrent,
|
||||||
ParamListInfo params, QueryCompletion *qc)
|
const char *queryString, ParamListInfo params,
|
||||||
|
QueryCompletion *qc)
|
||||||
{
|
{
|
||||||
Oid matviewOid;
|
|
||||||
Relation matviewRel;
|
Relation matviewRel;
|
||||||
RewriteRule *rule;
|
RewriteRule *rule;
|
||||||
List *actions;
|
List *actions;
|
||||||
@ -143,25 +172,12 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
|
|||||||
Oid OIDNewHeap;
|
Oid OIDNewHeap;
|
||||||
DestReceiver *dest;
|
DestReceiver *dest;
|
||||||
uint64 processed = 0;
|
uint64 processed = 0;
|
||||||
bool concurrent;
|
|
||||||
LOCKMODE lockmode;
|
|
||||||
char relpersistence;
|
char relpersistence;
|
||||||
Oid save_userid;
|
Oid save_userid;
|
||||||
int save_sec_context;
|
int save_sec_context;
|
||||||
int save_nestlevel;
|
int save_nestlevel;
|
||||||
ObjectAddress address;
|
ObjectAddress address;
|
||||||
|
|
||||||
/* Determine strength of lock needed. */
|
|
||||||
concurrent = stmt->concurrent;
|
|
||||||
lockmode = concurrent ? ExclusiveLock : AccessExclusiveLock;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Get a lock until end of transaction.
|
|
||||||
*/
|
|
||||||
matviewOid = RangeVarGetRelidExtended(stmt->relation,
|
|
||||||
lockmode, 0,
|
|
||||||
RangeVarCallbackMaintainsTable,
|
|
||||||
NULL);
|
|
||||||
matviewRel = table_open(matviewOid, NoLock);
|
matviewRel = table_open(matviewOid, NoLock);
|
||||||
relowner = matviewRel->rd_rel->relowner;
|
relowner = matviewRel->rd_rel->relowner;
|
||||||
|
|
||||||
@ -190,7 +206,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
|
|||||||
errmsg("CONCURRENTLY cannot be used when the materialized view is not populated")));
|
errmsg("CONCURRENTLY cannot be used when the materialized view is not populated")));
|
||||||
|
|
||||||
/* Check that conflicting options have not been specified. */
|
/* Check that conflicting options have not been specified. */
|
||||||
if (concurrent && stmt->skipData)
|
if (concurrent && skipData)
|
||||||
ereport(ERROR,
|
ereport(ERROR,
|
||||||
(errcode(ERRCODE_SYNTAX_ERROR),
|
(errcode(ERRCODE_SYNTAX_ERROR),
|
||||||
errmsg("%s and %s options cannot be used together",
|
errmsg("%s and %s options cannot be used together",
|
||||||
@ -275,7 +291,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
|
|||||||
* Tentatively mark the matview as populated or not (this will roll back
|
* Tentatively mark the matview as populated or not (this will roll back
|
||||||
* if we fail later).
|
* if we fail later).
|
||||||
*/
|
*/
|
||||||
SetMatViewPopulatedState(matviewRel, !stmt->skipData);
|
SetMatViewPopulatedState(matviewRel, !skipData);
|
||||||
|
|
||||||
/* Concurrent refresh builds new data in temp tablespace, and does diff. */
|
/* Concurrent refresh builds new data in temp tablespace, and does diff. */
|
||||||
if (concurrent)
|
if (concurrent)
|
||||||
@ -301,7 +317,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
|
|||||||
dest = CreateTransientRelDestReceiver(OIDNewHeap);
|
dest = CreateTransientRelDestReceiver(OIDNewHeap);
|
||||||
|
|
||||||
/* Generate the data, if wanted. */
|
/* Generate the data, if wanted. */
|
||||||
if (!stmt->skipData)
|
if (!skipData)
|
||||||
processed = refresh_matview_datafill(dest, dataQuery, queryString);
|
processed = refresh_matview_datafill(dest, dataQuery, queryString);
|
||||||
|
|
||||||
/* Make the matview match the newly generated data. */
|
/* Make the matview match the newly generated data. */
|
||||||
@ -333,7 +349,7 @@ ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
|
|||||||
* inserts and deletes it issues get counted by lower-level code.)
|
* inserts and deletes it issues get counted by lower-level code.)
|
||||||
*/
|
*/
|
||||||
pgstat_count_truncate(matviewRel);
|
pgstat_count_truncate(matviewRel);
|
||||||
if (!stmt->skipData)
|
if (!skipData)
|
||||||
pgstat_count_heap_insert(matviewRel, processed);
|
pgstat_count_heap_insert(matviewRel, processed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,9 @@ extern void SetMatViewPopulatedState(Relation relation, bool newstate);
|
|||||||
|
|
||||||
extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
|
extern ObjectAddress ExecRefreshMatView(RefreshMatViewStmt *stmt, const char *queryString,
|
||||||
ParamListInfo params, QueryCompletion *qc);
|
ParamListInfo params, QueryCompletion *qc);
|
||||||
|
extern ObjectAddress RefreshMatViewByOid(Oid matviewOid, bool skipData, bool concurrent,
|
||||||
|
const char *queryString, ParamListInfo params,
|
||||||
|
QueryCompletion *qc);
|
||||||
|
|
||||||
extern DestReceiver *CreateTransientRelDestReceiver(Oid transientoid);
|
extern DestReceiver *CreateTransientRelDestReceiver(Oid transientoid);
|
||||||
|
|
||||||
|
@ -129,8 +129,8 @@ $$;
|
|||||||
CREATE TABLE test_maint(i INT);
|
CREATE TABLE test_maint(i INT);
|
||||||
INSERT INTO test_maint VALUES (1), (2);
|
INSERT INTO test_maint VALUES (1), (2);
|
||||||
CREATE MATERIALIZED VIEW test_maint_mv AS SELECT fn(i) FROM test_maint;
|
CREATE MATERIALIZED VIEW test_maint_mv AS SELECT fn(i) FROM test_maint;
|
||||||
NOTICE: current search_path: test_maint_search_path
|
NOTICE: current search_path: pg_catalog, pg_temp
|
||||||
NOTICE: current search_path: test_maint_search_path
|
NOTICE: current search_path: pg_catalog, pg_temp
|
||||||
-- the following commands should see search_path as pg_catalog, pg_temp
|
-- the following commands should see search_path as pg_catalog, pg_temp
|
||||||
CREATE INDEX test_maint_idx ON test_maint_search_path.test_maint (fn(i));
|
CREATE INDEX test_maint_idx ON test_maint_search_path.test_maint (fn(i));
|
||||||
NOTICE: current search_path: pg_catalog, pg_temp
|
NOTICE: current search_path: pg_catalog, pg_temp
|
||||||
|
Loading…
x
Reference in New Issue
Block a user