diff --git a/src/backend/parser/analyze.c b/src/backend/parser/analyze.c index b46fadc27d..cf1c123b72 100644 --- a/src/backend/parser/analyze.c +++ b/src/backend/parser/analyze.c @@ -963,9 +963,6 @@ transformOnConflictClause(ParseState *pstate, if (onConflictClause->action == ONCONFLICT_UPDATE) { Relation targetrel = pstate->p_target_relation; - Var *var; - TargetEntry *te; - int attno; /* * All INSERT expressions have been parsed, get ready for potentially @@ -974,75 +971,36 @@ transformOnConflictClause(ParseState *pstate, pstate->p_is_insert = false; /* - * Add range table entry for the EXCLUDED pseudo relation; relkind is + * Add range table entry for the EXCLUDED pseudo relation. relkind is * set to composite to signal that we're not dealing with an actual - * relation. + * relation, and no permission checks are required on it. (We'll + * check the actual target relation, instead.) */ exclRte = addRangeTableEntryForRelation(pstate, targetrel, makeAlias("excluded", NIL), false, false); exclRte->relkind = RELKIND_COMPOSITE_TYPE; + exclRte->requiredPerms = 0; + /* other permissions fields in exclRte are already empty */ + exclRelIndex = list_length(pstate->p_rtable); - /* - * Build a targetlist representing the columns of the EXCLUDED pseudo - * relation. Have to be careful to use resnos that correspond to - * attnos of the underlying relation. - */ - for (attno = 0; attno < targetrel->rd_rel->relnatts; attno++) - { - Form_pg_attribute attr = targetrel->rd_att->attrs[attno]; - char *name; - - if (attr->attisdropped) - { - /* - * can't use atttypid here, but it doesn't really matter what - * type the Const claims to be. - */ - var = (Var *) makeNullConst(INT4OID, -1, InvalidOid); - name = ""; - } - else - { - var = makeVar(exclRelIndex, attno + 1, - attr->atttypid, attr->atttypmod, - attr->attcollation, - 0); - name = pstrdup(NameStr(attr->attname)); - } - - te = makeTargetEntry((Expr *) var, - attno + 1, - name, - false); - - /* don't require select access yet */ - exclRelTlist = lappend(exclRelTlist, te); - } - - /* - * Add a whole-row-Var entry to support references to "EXCLUDED.*". - * Like the other entries in exclRelTlist, its resno must match the - * Var's varattno, else the wrong things happen while resolving - * references in setrefs.c. This is against normal conventions for - * targetlists, but it's okay since we don't use this as a real tlist. - */ - var = makeVar(exclRelIndex, InvalidAttrNumber, - targetrel->rd_rel->reltype, - -1, InvalidOid, 0); - te = makeTargetEntry((Expr *) var, InvalidAttrNumber, NULL, true); - exclRelTlist = lappend(exclRelTlist, te); + /* Create EXCLUDED rel's targetlist for use by EXPLAIN */ + exclRelTlist = BuildOnConflictExcludedTargetlist(targetrel, + exclRelIndex); /* * Add EXCLUDED and the target RTE to the namespace, so that they can - * be used in the UPDATE statement. + * be used in the UPDATE subexpressions. */ addRTEtoQuery(pstate, exclRte, false, true, true); addRTEtoQuery(pstate, pstate->p_target_rangetblentry, false, true, true); + /* + * Now transform the UPDATE subexpressions. + */ onConflictSet = transformUpdateTargetList(pstate, onConflictClause->targetList); @@ -1067,6 +1025,74 @@ transformOnConflictClause(ParseState *pstate, } +/* + * BuildOnConflictExcludedTargetlist + * Create target list for the EXCLUDED pseudo-relation of ON CONFLICT, + * representing the columns of targetrel with varno exclRelIndex. + * + * Note: Exported for use in the rewriter. + */ +List * +BuildOnConflictExcludedTargetlist(Relation targetrel, + Index exclRelIndex) +{ + List *result = NIL; + int attno; + Var *var; + TargetEntry *te; + + /* + * Note that resnos of the tlist must correspond to attnos of the + * underlying relation, hence we need entries for dropped columns too. + */ + for (attno = 0; attno < RelationGetNumberOfAttributes(targetrel); attno++) + { + Form_pg_attribute attr = TupleDescAttr(targetrel->rd_att, attno); + char *name; + + if (attr->attisdropped) + { + /* + * can't use atttypid here, but it doesn't really matter what type + * the Const claims to be. + */ + var = (Var *) makeNullConst(INT4OID, -1, InvalidOid); + name = ""; + } + else + { + var = makeVar(exclRelIndex, attno + 1, + attr->atttypid, attr->atttypmod, + attr->attcollation, + 0); + name = pstrdup(NameStr(attr->attname)); + } + + te = makeTargetEntry((Expr *) var, + attno + 1, + name, + false); + + result = lappend(result, te); + } + + /* + * Add a whole-row-Var entry to support references to "EXCLUDED.*". Like + * the other entries in the EXCLUDED tlist, its resno must match the Var's + * varattno, else the wrong things happen while resolving references in + * setrefs.c. This is against normal conventions for targetlists, but + * it's okay since we don't use this as a real tlist. + */ + var = makeVar(exclRelIndex, InvalidAttrNumber, + targetrel->rd_rel->reltype, + -1, InvalidOid, 0); + te = makeTargetEntry((Expr *) var, InvalidAttrNumber, NULL, true); + result = lappend(result, te); + + return result; +} + + /* * count_rowexpr_columns - * get number of columns contained in a ROW() expression; diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index c793bec936..b481ac50b6 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -28,6 +28,7 @@ #include "nodes/nodeFuncs.h" #include "parser/analyze.h" #include "parser/parse_coerce.h" +#include "parser/parse_relation.h" #include "parser/parsetree.h" #include "rewrite/rewriteDefine.h" #include "rewrite/rewriteHandler.h" @@ -1707,6 +1708,17 @@ fireRIRrules(Query *parsetree, List *activeRIRs, bool forUpdatePushedDown) if (rte->relkind == RELKIND_MATVIEW) continue; + /* + * In INSERT ... ON CONFLICT, ignore the EXCLUDED pseudo-relation; + * even if it points to a view, we needn't expand it, and should not + * because we want the RTE to remain of RTE_RELATION type. Otherwise, + * it would get changed to RTE_SUBQUERY type, which is an + * untested/unsupported situation. + */ + if (parsetree->onConflict && + rt_index == parsetree->onConflict->exclRelIndex) + continue; + /* * If the table is not referenced in the query, then we ignore it. * This prevents infinite expansion loop due to new rtable entries @@ -2812,8 +2824,6 @@ rewriteTargetView(Query *parsetree, Relation view) */ base_rte->relkind = base_rel->rd_rel->relkind; - heap_close(base_rel, NoLock); - /* * If the view query contains any sublink subqueries then we need to also * acquire locks on any relations they refer to. We know that there won't @@ -2971,6 +2981,93 @@ rewriteTargetView(Query *parsetree, Relation view) } } + /* + * For INSERT .. ON CONFLICT .. DO UPDATE, we must also update assorted + * stuff in the onConflict data structure. + */ + if (parsetree->onConflict && + parsetree->onConflict->action == ONCONFLICT_UPDATE) + { + Index old_exclRelIndex, + new_exclRelIndex; + RangeTblEntry *new_exclRte; + List *tmp_tlist; + + /* + * Like the INSERT/UPDATE code above, update the resnos in the + * auxiliary UPDATE targetlist to refer to columns of the base + * relation. + */ + foreach(lc, parsetree->onConflict->onConflictSet) + { + TargetEntry *tle = (TargetEntry *) lfirst(lc); + TargetEntry *view_tle; + + if (tle->resjunk) + continue; + + view_tle = get_tle_by_resno(view_targetlist, tle->resno); + if (view_tle != NULL && !view_tle->resjunk && IsA(view_tle->expr, Var)) + tle->resno = ((Var *) view_tle->expr)->varattno; + else + elog(ERROR, "attribute number %d not found in view targetlist", + tle->resno); + } + + /* + * Also, create a new RTE for the EXCLUDED pseudo-relation, using the + * query's new base rel (which may well have a different column list + * from the view, hence we need a new column alias list). This should + * match transformOnConflictClause. In particular, note that the + * relkind is set to composite to signal that we're not dealing with + * an actual relation, and no permissions checks are wanted. + */ + old_exclRelIndex = parsetree->onConflict->exclRelIndex; + + new_exclRte = addRangeTableEntryForRelation(make_parsestate(NULL), + base_rel, + makeAlias("excluded", + NIL), + false, false); + new_exclRte->relkind = RELKIND_COMPOSITE_TYPE; + new_exclRte->requiredPerms = 0; + /* other permissions fields in new_exclRte are already empty */ + + parsetree->rtable = lappend(parsetree->rtable, new_exclRte); + new_exclRelIndex = parsetree->onConflict->exclRelIndex = + list_length(parsetree->rtable); + + /* + * Replace the targetlist for the EXCLUDED pseudo-relation with a new + * one, representing the columns from the new base relation. + */ + parsetree->onConflict->exclRelTlist = + BuildOnConflictExcludedTargetlist(base_rel, new_exclRelIndex); + + /* + * Update all Vars in the ON CONFLICT clause that refer to the old + * EXCLUDED pseudo-relation. We want to use the column mappings + * defined in the view targetlist, but we need the outputs to refer to + * the new EXCLUDED pseudo-relation rather than the new target RTE. + * Also notice that "EXCLUDED.*" will be expanded using the view's + * rowtype, which seems correct. + */ + tmp_tlist = copyObject(view_targetlist); + + ChangeVarNodes((Node *) tmp_tlist, new_rt_index, + new_exclRelIndex, 0); + + parsetree->onConflict = (OnConflictExpr *) + ReplaceVarsFromTargetList((Node *) parsetree->onConflict, + old_exclRelIndex, + 0, + view_rte, + tmp_tlist, + REPLACEVARS_REPORT_ERROR, + 0, + &parsetree->hasSubLinks); + } + /* * For UPDATE/DELETE, pull up any WHERE quals from the view. We know that * any Vars in the quals must reference the one base relation, so we need @@ -3089,6 +3186,8 @@ rewriteTargetView(Query *parsetree, Relation view) } } + heap_close(base_rel, NoLock); + return parsetree; } diff --git a/src/include/parser/analyze.h b/src/include/parser/analyze.h index 5ba322a262..c4f1b70b1d 100644 --- a/src/include/parser/analyze.h +++ b/src/include/parser/analyze.h @@ -42,4 +42,7 @@ extern void applyLockingClause(Query *qry, Index rtindex, LockClauseStrength strength, LockWaitPolicy waitPolicy, bool pushedDown); +extern List *BuildOnConflictExcludedTargetlist(Relation targetrel, + Index exclRelIndex); + #endif /* ANALYZE_H */ diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index 6162d40e59..2f2bab7afa 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -2472,3 +2472,197 @@ ERROR: new row violates check option for view "v1" DETAIL: Failing row contains (-1, invalid). DROP VIEW v1; DROP TABLE t1; +-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's +-- columns are named and ordered differently than the underlying table's. +create table uv_iocu_tab (a text unique, b float); +insert into uv_iocu_tab values ('xyxyxy', 0); +create view uv_iocu_view as + select b, b+1 as c, a, '2.0'::text as two from uv_iocu_tab; +insert into uv_iocu_view (a, b) values ('xyxyxy', 1) + on conflict (a) do update set b = uv_iocu_view.b; +select * from uv_iocu_tab; + a | b +--------+--- + xyxyxy | 0 +(1 row) + +insert into uv_iocu_view (a, b) values ('xyxyxy', 1) + on conflict (a) do update set b = excluded.b; +select * from uv_iocu_tab; + a | b +--------+--- + xyxyxy | 1 +(1 row) + +-- OK to access view columns that are not present in underlying base +-- relation in the ON CONFLICT portion of the query +insert into uv_iocu_view (a, b) values ('xyxyxy', 3) + on conflict (a) do update set b = cast(excluded.two as float); +select * from uv_iocu_tab; + a | b +--------+--- + xyxyxy | 2 +(1 row) + +explain (costs off) +insert into uv_iocu_view (a, b) values ('xyxyxy', 3) + on conflict (a) do update set b = excluded.b where excluded.c > 0; + QUERY PLAN +----------------------------------------------------------------------------------- + Insert on uv_iocu_tab + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: uv_iocu_tab_a_key + Conflict Filter: ((excluded.b + '1'::double precision) > '0'::double precision) + -> Result +(5 rows) + +insert into uv_iocu_view (a, b) values ('xyxyxy', 3) + on conflict (a) do update set b = excluded.b where excluded.c > 0; +select * from uv_iocu_tab; + a | b +--------+--- + xyxyxy | 3 +(1 row) + +drop view uv_iocu_view; +drop table uv_iocu_tab; +-- Test whole-row references to the view +create table uv_iocu_tab (a int unique, b text); +create view uv_iocu_view as + select b as bb, a as aa, uv_iocu_tab::text as cc from uv_iocu_tab; +insert into uv_iocu_view (aa,bb) values (1,'x'); +explain (costs off) +insert into uv_iocu_view (aa,bb) values (1,'y') + on conflict (aa) do update set bb = 'Rejected: '||excluded.* + where excluded.aa > 0 + and excluded.bb != '' + and excluded.cc is not null; + QUERY PLAN +--------------------------------------------------------------------------------------------------------- + Insert on uv_iocu_tab + Conflict Resolution: UPDATE + Conflict Arbiter Indexes: uv_iocu_tab_a_key + Conflict Filter: ((excluded.a > 0) AND (excluded.b <> ''::text) AND ((excluded.*)::text IS NOT NULL)) + -> Result +(5 rows) + +insert into uv_iocu_view (aa,bb) values (1,'y') + on conflict (aa) do update set bb = 'Rejected: '||excluded.* + where excluded.aa > 0 + and excluded.bb != '' + and excluded.cc is not null; +select * from uv_iocu_view; + bb | aa | cc +-------------------------+----+--------------------------------- + Rejected: (y,1,"(1,y)") | 1 | (1,"Rejected: (y,1,""(1,y)"")") +(1 row) + +-- Test omitting a column of the base relation +delete from uv_iocu_view; +insert into uv_iocu_view (aa,bb) values (1,'x'); +insert into uv_iocu_view (aa) values (1) + on conflict (aa) do update set bb = 'Rejected: '||excluded.*; +select * from uv_iocu_view; + bb | aa | cc +-----------------------+----+------------------------------- + Rejected: (,1,"(1,)") | 1 | (1,"Rejected: (,1,""(1,)"")") +(1 row) + +alter table uv_iocu_tab alter column b set default 'table default'; +insert into uv_iocu_view (aa) values (1) + on conflict (aa) do update set bb = 'Rejected: '||excluded.*; +select * from uv_iocu_view; + bb | aa | cc +-------------------------------------------------------+----+--------------------------------------------------------------------- + Rejected: ("table default",1,"(1,""table default"")") | 1 | (1,"Rejected: (""table default"",1,""(1,""""table default"""")"")") +(1 row) + +alter view uv_iocu_view alter column bb set default 'view default'; +insert into uv_iocu_view (aa) values (1) + on conflict (aa) do update set bb = 'Rejected: '||excluded.*; +select * from uv_iocu_view; + bb | aa | cc +-----------------------------------------------------+----+------------------------------------------------------------------- + Rejected: ("view default",1,"(1,""view default"")") | 1 | (1,"Rejected: (""view default"",1,""(1,""""view default"""")"")") +(1 row) + +-- Should fail to update non-updatable columns +insert into uv_iocu_view (aa) values (1) + on conflict (aa) do update set cc = 'XXX'; +ERROR: cannot insert into column "cc" of view "uv_iocu_view" +DETAIL: View columns that are not columns of their base relation are not updatable. +drop view uv_iocu_view; +drop table uv_iocu_tab; +-- ON CONFLICT DO UPDATE permissions checks +create user regress_view_user1; +create user regress_view_user2; +set session authorization regress_view_user1; +create table base_tbl(a int unique, b text, c float); +insert into base_tbl values (1,'xxx',1.0); +create view rw_view1 as select b as bb, c as cc, a as aa from base_tbl; +grant select (aa,bb) on rw_view1 to regress_view_user2; +grant insert on rw_view1 to regress_view_user2; +grant update (bb) on rw_view1 to regress_view_user2; +set session authorization regress_view_user2; +insert into rw_view1 values ('yyy',2.0,1) + on conflict (aa) do update set bb = excluded.cc; -- Not allowed +ERROR: permission denied for relation rw_view1 +insert into rw_view1 values ('yyy',2.0,1) + on conflict (aa) do update set bb = rw_view1.cc; -- Not allowed +ERROR: permission denied for relation rw_view1 +insert into rw_view1 values ('yyy',2.0,1) + on conflict (aa) do update set bb = excluded.bb; -- OK +insert into rw_view1 values ('zzz',2.0,1) + on conflict (aa) do update set bb = rw_view1.bb||'xxx'; -- OK +insert into rw_view1 values ('zzz',2.0,1) + on conflict (aa) do update set cc = 3.0; -- Not allowed +ERROR: permission denied for relation rw_view1 +reset session authorization; +select * from base_tbl; + a | b | c +---+--------+--- + 1 | yyyxxx | 1 +(1 row) + +set session authorization regress_view_user1; +grant select (a,b) on base_tbl to regress_view_user2; +grant insert (a,b) on base_tbl to regress_view_user2; +grant update (a,b) on base_tbl to regress_view_user2; +set session authorization regress_view_user2; +create view rw_view2 as select b as bb, c as cc, a as aa from base_tbl; +insert into rw_view2 (aa,bb) values (1,'xxx') + on conflict (aa) do update set bb = excluded.bb; -- Not allowed +ERROR: permission denied for relation base_tbl +create view rw_view3 as select b as bb, a as aa from base_tbl; +insert into rw_view3 (aa,bb) values (1,'xxx') + on conflict (aa) do update set bb = excluded.bb; -- OK +reset session authorization; +select * from base_tbl; + a | b | c +---+-----+--- + 1 | xxx | 1 +(1 row) + +set session authorization regress_view_user2; +create view rw_view4 as select aa, bb, cc FROM rw_view1; +insert into rw_view4 (aa,bb) values (1,'yyy') + on conflict (aa) do update set bb = excluded.bb; -- Not allowed +ERROR: permission denied for relation rw_view1 +create view rw_view5 as select aa, bb FROM rw_view1; +insert into rw_view5 (aa,bb) values (1,'yyy') + on conflict (aa) do update set bb = excluded.bb; -- OK +reset session authorization; +select * from base_tbl; + a | b | c +---+-----+--- + 1 | yyy | 1 +(1 row) + +drop view rw_view5; +drop view rw_view4; +drop view rw_view3; +drop view rw_view2; +drop view rw_view1; +drop table base_tbl; +drop user regress_view_user1; +drop user regress_view_user2; diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql index 868b22ae56..9093b09632 100644 --- a/src/test/regress/sql/updatable_views.sql +++ b/src/test/regress/sql/updatable_views.sql @@ -1112,3 +1112,138 @@ INSERT INTO v1 VALUES (-1, 'invalid'); -- should fail DROP VIEW v1; DROP TABLE t1; + +-- Check INSERT .. ON CONFLICT DO UPDATE works correctly when the view's +-- columns are named and ordered differently than the underlying table's. +create table uv_iocu_tab (a text unique, b float); +insert into uv_iocu_tab values ('xyxyxy', 0); +create view uv_iocu_view as + select b, b+1 as c, a, '2.0'::text as two from uv_iocu_tab; + +insert into uv_iocu_view (a, b) values ('xyxyxy', 1) + on conflict (a) do update set b = uv_iocu_view.b; +select * from uv_iocu_tab; +insert into uv_iocu_view (a, b) values ('xyxyxy', 1) + on conflict (a) do update set b = excluded.b; +select * from uv_iocu_tab; + +-- OK to access view columns that are not present in underlying base +-- relation in the ON CONFLICT portion of the query +insert into uv_iocu_view (a, b) values ('xyxyxy', 3) + on conflict (a) do update set b = cast(excluded.two as float); +select * from uv_iocu_tab; + +explain (costs off) +insert into uv_iocu_view (a, b) values ('xyxyxy', 3) + on conflict (a) do update set b = excluded.b where excluded.c > 0; + +insert into uv_iocu_view (a, b) values ('xyxyxy', 3) + on conflict (a) do update set b = excluded.b where excluded.c > 0; +select * from uv_iocu_tab; + +drop view uv_iocu_view; +drop table uv_iocu_tab; + +-- Test whole-row references to the view +create table uv_iocu_tab (a int unique, b text); +create view uv_iocu_view as + select b as bb, a as aa, uv_iocu_tab::text as cc from uv_iocu_tab; + +insert into uv_iocu_view (aa,bb) values (1,'x'); +explain (costs off) +insert into uv_iocu_view (aa,bb) values (1,'y') + on conflict (aa) do update set bb = 'Rejected: '||excluded.* + where excluded.aa > 0 + and excluded.bb != '' + and excluded.cc is not null; +insert into uv_iocu_view (aa,bb) values (1,'y') + on conflict (aa) do update set bb = 'Rejected: '||excluded.* + where excluded.aa > 0 + and excluded.bb != '' + and excluded.cc is not null; +select * from uv_iocu_view; + +-- Test omitting a column of the base relation +delete from uv_iocu_view; +insert into uv_iocu_view (aa,bb) values (1,'x'); +insert into uv_iocu_view (aa) values (1) + on conflict (aa) do update set bb = 'Rejected: '||excluded.*; +select * from uv_iocu_view; + +alter table uv_iocu_tab alter column b set default 'table default'; +insert into uv_iocu_view (aa) values (1) + on conflict (aa) do update set bb = 'Rejected: '||excluded.*; +select * from uv_iocu_view; + +alter view uv_iocu_view alter column bb set default 'view default'; +insert into uv_iocu_view (aa) values (1) + on conflict (aa) do update set bb = 'Rejected: '||excluded.*; +select * from uv_iocu_view; + +-- Should fail to update non-updatable columns +insert into uv_iocu_view (aa) values (1) + on conflict (aa) do update set cc = 'XXX'; + +drop view uv_iocu_view; +drop table uv_iocu_tab; + +-- ON CONFLICT DO UPDATE permissions checks +create user regress_view_user1; +create user regress_view_user2; + +set session authorization regress_view_user1; +create table base_tbl(a int unique, b text, c float); +insert into base_tbl values (1,'xxx',1.0); +create view rw_view1 as select b as bb, c as cc, a as aa from base_tbl; + +grant select (aa,bb) on rw_view1 to regress_view_user2; +grant insert on rw_view1 to regress_view_user2; +grant update (bb) on rw_view1 to regress_view_user2; + +set session authorization regress_view_user2; +insert into rw_view1 values ('yyy',2.0,1) + on conflict (aa) do update set bb = excluded.cc; -- Not allowed +insert into rw_view1 values ('yyy',2.0,1) + on conflict (aa) do update set bb = rw_view1.cc; -- Not allowed +insert into rw_view1 values ('yyy',2.0,1) + on conflict (aa) do update set bb = excluded.bb; -- OK +insert into rw_view1 values ('zzz',2.0,1) + on conflict (aa) do update set bb = rw_view1.bb||'xxx'; -- OK +insert into rw_view1 values ('zzz',2.0,1) + on conflict (aa) do update set cc = 3.0; -- Not allowed +reset session authorization; +select * from base_tbl; + +set session authorization regress_view_user1; +grant select (a,b) on base_tbl to regress_view_user2; +grant insert (a,b) on base_tbl to regress_view_user2; +grant update (a,b) on base_tbl to regress_view_user2; + +set session authorization regress_view_user2; +create view rw_view2 as select b as bb, c as cc, a as aa from base_tbl; +insert into rw_view2 (aa,bb) values (1,'xxx') + on conflict (aa) do update set bb = excluded.bb; -- Not allowed +create view rw_view3 as select b as bb, a as aa from base_tbl; +insert into rw_view3 (aa,bb) values (1,'xxx') + on conflict (aa) do update set bb = excluded.bb; -- OK +reset session authorization; +select * from base_tbl; + +set session authorization regress_view_user2; +create view rw_view4 as select aa, bb, cc FROM rw_view1; +insert into rw_view4 (aa,bb) values (1,'yyy') + on conflict (aa) do update set bb = excluded.bb; -- Not allowed +create view rw_view5 as select aa, bb FROM rw_view1; +insert into rw_view5 (aa,bb) values (1,'yyy') + on conflict (aa) do update set bb = excluded.bb; -- OK +reset session authorization; +select * from base_tbl; + +drop view rw_view5; +drop view rw_view4; +drop view rw_view3; +drop view rw_view2; +drop view rw_view1; +drop table base_tbl; +drop user regress_view_user1; +drop user regress_view_user2;