]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Disallow whole-row variables in GENERATED expressions.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 21 May 2021 19:12:08 +0000 (15:12 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 21 May 2021 19:12:19 +0000 (15:12 -0400)
This was previously allowed, but I think that was just an oversight.
It's a clear violation of the rule that a generated column cannot
depend on itself or other generated columns.  Moreover, because the
code was relying on the assumption that no such cross-references
exist, it was pretty easy to crash ALTER TABLE and perhaps other
places.  Even if you managed not to crash, you got quite unstable,
implementation-dependent results.

Per report from Vitaly Ustinov.
Back-patch to v12 where GENERATED came in.

Discussion: https://postgr.es/m/CAM_DEiWR2DPT6U4xb-Ehigozzd3n3G37ZB1+867zbsEVtYoJww@mail.gmail.com

src/backend/catalog/heap.c
src/test/regress/expected/generated.out
src/test/regress/sql/generated.sql

index 229393b01fbd30d1eb46eed00e9fc9607e7b1897..b34b8b94b3a2d1410dc75960260e9e4852403351 100644 (file)
@@ -2976,15 +2976,26 @@ check_nested_generated_walker(Node *node, void *context)
                AttrNumber      attnum;
 
                relid = rt_fetch(var->varno, pstate->p_rtable)->relid;
+               if (!OidIsValid(relid))
+                       return false;           /* XXX shouldn't we raise an error? */
+
                attnum = var->varattno;
 
-               if (OidIsValid(relid) && AttributeNumberIsValid(attnum) && get_attgenerated(relid, attnum))
+               if (attnum > 0 && get_attgenerated(relid, attnum))
                        ereport(ERROR,
-                                       (errcode(ERRCODE_SYNTAX_ERROR),
+                                       (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
                                         errmsg("cannot use generated column \"%s\" in column generation expression",
                                                        get_attname(relid, attnum, false)),
                                         errdetail("A generated column cannot reference another generated column."),
                                         parser_errposition(pstate, var->location)));
+               /* A whole-row Var is necessarily self-referential, so forbid it */
+               if (attnum == 0)
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_INVALID_OBJECT_DEFINITION),
+                                        errmsg("cannot use whole-row variable in column generation expression"),
+                                        errdetail("This would cause the generated column to depend on its own value."),
+                                        parser_errposition(pstate, var->location)));
+               /* System columns were already checked in the parser */
 
                return false;
        }
index 82a9e73ebc139031b311b05f26301b4576eae56b..6a6b77677f71045e0757cf8d82786c02662006e4 100644 (file)
@@ -46,6 +46,13 @@ ERROR:  cannot use generated column "b" in column generation expression
 LINE 1: ...AYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STO...
                                                              ^
 DETAIL:  A generated column cannot reference another generated column.
+-- a whole-row var is a self-reference on steroids, so disallow that too
+CREATE TABLE gtest_err_2c (a int PRIMARY KEY,
+    b int GENERATED ALWAYS AS (num_nulls(gtest_err_2c)) STORED);
+ERROR:  cannot use whole-row variable in column generation expression
+LINE 2:     b int GENERATED ALWAYS AS (num_nulls(gtest_err_2c)) STOR...
+                                                 ^
+DETAIL:  This would cause the generated column to depend on its own value.
 -- invalid reference
 CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED);
 ERROR:  column "c" does not exist
index 208f1d91faa3f02d42d177db8df4eab82445c782..58a7d4104fc4d6081f909270947d738b5bc06bee 100644 (file)
@@ -17,6 +17,9 @@ CREATE TABLE gtest_err_1 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) S
 -- references to other generated columns, including self-references
 CREATE TABLE gtest_err_2a (a int PRIMARY KEY, b int GENERATED ALWAYS AS (b * 2) STORED);
 CREATE TABLE gtest_err_2b (a int PRIMARY KEY, b int GENERATED ALWAYS AS (a * 2) STORED, c int GENERATED ALWAYS AS (b * 3) STORED);
+-- a whole-row var is a self-reference on steroids, so disallow that too
+CREATE TABLE gtest_err_2c (a int PRIMARY KEY,
+    b int GENERATED ALWAYS AS (num_nulls(gtest_err_2c)) STORED);
 
 -- invalid reference
 CREATE TABLE gtest_err_3 (a int PRIMARY KEY, b int GENERATED ALWAYS AS (c * 2) STORED);