]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Correctly check updatability of columns targeted by INSERT...DEFAULT.
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 20 Jul 2024 17:40:15 +0000 (13:40 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 20 Jul 2024 17:40:15 +0000 (13:40 -0400)
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
src/test/regress/expected/updatable_views.out
src/test/regress/sql/updatable_views.sql

index 8a29fbbc46525e63a46ec14c7baef311a108fc18..e1d805d113edac14656375d3241d6110bc7895ad 100644 (file)
@@ -2986,7 +2986,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 *
@@ -3186,6 +3186,10 @@ rewriteTargetView(Query *parsetree, Relation view)
         */
        viewquery = copyObject(get_view_query(view));
 
+       /* Locate RTE and perminfo describing the view in the outer query */
+       view_rte = rt_fetch(parsetree->resultRelation, parsetree->rtable);
+       view_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, view_rte);
+
        /*
         * Are we doing INSERT/UPDATE, or MERGE containing INSERT/UPDATE?  If so,
         * various additional checks on the view columns need to be applied, and
@@ -3225,17 +3229,26 @@ rewriteTargetView(Query *parsetree, Relation view)
 
        /*
         * For INSERT/UPDATE (or MERGE containing 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.
+        * columns must all be updatable.
         */
        if (insert_or_update)
        {
-               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_perminfo->insertedCols,
+                                                                 view_perminfo->updatedCols);
+
                foreach(lc, parsetree->targetList)
                {
                        TargetEntry *tle = (TargetEntry *) lfirst(lc);
@@ -3337,9 +3350,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.
@@ -3434,10 +3444,7 @@ rewriteTargetView(Query *parsetree, Relation view)
         * Note: the original view's RTEPermissionInfo remains in the query's
         * rteperminfos so that the executor still performs appropriate
         * permissions checks for the query caller's use of the view.
-        */
-       view_perminfo = getRTEPermissionInfo(parsetree->rteperminfos, view_rte);
-
-       /*
+        *
         * Disregard the perminfo in viewquery->rteperminfos that the base_rte
         * would currently be pointing at, because we'd like it to point now to a
         * new one that will be filled below.  Must set perminfoindex to 0 to not
index 44aba0d1dcbcde0ec5cee4df9143749c0531a923..420769a40c9df052a1a51dc9564d686f08b120bf 100644 (file)
@@ -2101,6 +2101,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         
 -----+-------------------+-------------------
index abfa5574a6e51286c67c873050e1123f6c2a6b4a..93b693ae837612efee05be37d7c5bd6e85bcb147 100644 (file)
@@ -1111,6 +1111,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