From 96953052a12c9cc87700bdca5fa030626a80e707 Mon Sep 17 00:00:00 2001 From: Tom Lane Date: Sat, 20 Jul 2024 13:40:15 -0400 Subject: [PATCH] Correctly check updatability of columns targeted by INSERT...DEFAULT. If a view has some updatable and some non-updatable columns, we failed to verify updatability of any columns for which an INSERT or UPDATE on the view explicitly specifies a DEFAULT item (unless the view has a declared default for that column, which is rare anyway, and one would almost certainly not write one for a non-updatable column). This would lead to an unexpected "attribute number N not found in view targetlist" error rather than the intended error. Per bug #18546 from Alexander Lakhin. This bug is old, so back-patch to all supported branches. Discussion: https://postgr.es/m/18546-84a292e759a9361d@postgresql.org --- src/backend/rewrite/rewriteHandler.c | 29 ++++++++++++------- src/test/regress/expected/updatable_views.out | 3 ++ src/test/regress/sql/updatable_views.sql | 1 + 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/backend/rewrite/rewriteHandler.c b/src/backend/rewrite/rewriteHandler.c index e678f34df7d..a7722f96036 100644 --- a/src/backend/rewrite/rewriteHandler.c +++ b/src/backend/rewrite/rewriteHandler.c @@ -2987,7 +2987,7 @@ relation_is_updatable(Oid reloid, * * This is used with simply-updatable views to map column-permissions sets for * the view columns onto the matching columns in the underlying base relation. - * The targetlist is expected to be a list of plain Vars of the underlying + * Relevant entries in the targetlist must be plain Vars of the underlying * relation (as per the checks above in view_query_is_auto_updatable). */ static Bitmapset * @@ -3084,6 +3084,9 @@ rewriteTargetView(Query *parsetree, Relation view) */ viewquery = copyObject(get_view_query(view)); + /* Locate RTE describing the view in the outer query */ + view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable); + /* The view must be updatable, else fail */ auto_update_detail = view_query_is_auto_updatable(viewquery, @@ -3126,17 +3129,26 @@ rewriteTargetView(Query *parsetree, Relation view) } /* - * For INSERT/UPDATE the modified columns must all be updatable. Note that - * we get the modified columns from the query's targetlist, not from the - * result RTE's insertedCols and/or updatedCols set, since - * rewriteTargetListIU may have added additional targetlist entries for - * view defaults, and these must also be updatable. + * For INSERT/UPDATE the modified columns must all be updatable. */ if (parsetree->commandType != CMD_DELETE) { - Bitmapset *modified_cols = NULL; + Bitmapset *modified_cols; char *non_updatable_col; + /* + * Compute the set of modified columns as those listed in the result + * RTE's insertedCols and/or updatedCols sets plus those that are + * targets of the query's targetlist(s). We must consider the query's + * targetlist because rewriteTargetListIU may have added additional + * targetlist entries for view defaults, and these must also be + * updatable. But rewriteTargetListIU can also remove entries if they + * are DEFAULT markers and the column's default is NULL, so + * considering only the targetlist would also be wrong. + */ + modified_cols = bms_union(view_rte->insertedCols, + view_rte->updatedCols); + foreach(lc, parsetree->targetList) { TargetEntry *tle = (TargetEntry *) lfirst(lc); @@ -3194,9 +3206,6 @@ rewriteTargetView(Query *parsetree, Relation view) } } - /* Locate RTE describing the view in the outer query */ - view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable); - /* * If we get here, view_query_is_auto_updatable() has verified that the * view contains a single base relation. diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out index c27e2e803dd..78791442490 100644 --- a/src/test/regress/expected/updatable_views.out +++ b/src/test/regress/expected/updatable_views.out @@ -1598,6 +1598,9 @@ DETAIL: View columns that refer to system columns are not updatable. INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail ERROR: cannot insert into column "s" of view "rw_view1" DETAIL: View columns that are not columns of their base relation are not updatable. +INSERT INTO rw_view1 (s, c, a) VALUES (default, default, 1.1); -- should fail +ERROR: cannot insert into column "s" of view "rw_view1" +DETAIL: View columns that are not columns of their base relation are not updatable. INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK a | s | c -----+-------------------+------------------- diff --git a/src/test/regress/sql/updatable_views.sql b/src/test/regress/sql/updatable_views.sql index eaee0b7e1d7..0a3176e25d1 100644 --- a/src/test/regress/sql/updatable_views.sql +++ b/src/test/regress/sql/updatable_views.sql @@ -845,6 +845,7 @@ CREATE VIEW rw_view1 AS INSERT INTO rw_view1 VALUES (null, null, 1.1, null); -- should fail INSERT INTO rw_view1 (s, c, a) VALUES (null, null, 1.1); -- should fail +INSERT INTO rw_view1 (s, c, a) VALUES (default, default, 1.1); -- should fail INSERT INTO rw_view1 (a) VALUES (1.1) RETURNING a, s, c; -- OK UPDATE rw_view1 SET s = s WHERE a = 1.1; -- should fail UPDATE rw_view1 SET a = 1.05 WHERE a = 1.1 RETURNING s; -- OK