aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2021-05-21 15:12:08 -0400
committerTom Lane <tgl@sss.pgh.pa.us>2021-05-21 15:12:37 -0400
commit61feb8670824c8dfb66094f2a87df2395be0665c (patch)
tree51a50db998d50b3967b69a7388e478216f30a4a5
parentdfe51ffbe78a16368073c2838c04e603d3d76915 (diff)
downloadpostgresql-61feb8670824c8dfb66094f2a87df2395be0665c.tar.gz
postgresql-61feb8670824c8dfb66094f2a87df2395be0665c.zip
Disallow whole-row variables in GENERATED expressions.
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
-rw-r--r--src/backend/catalog/heap.c15
-rw-r--r--src/test/regress/expected/generated.out7
-rw-r--r--src/test/regress/sql/generated.sql3
3 files changed, 23 insertions, 2 deletions
diff --git a/src/backend/catalog/heap.c b/src/backend/catalog/heap.c
index 46a383294e9..7ac0edd6693 100644
--- a/src/backend/catalog/heap.c
+++ b/src/backend/catalog/heap.c
@@ -2977,15 +2977,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;
}
diff --git a/src/test/regress/expected/generated.out b/src/test/regress/expected/generated.out
index 13414ed3b8a..9eec3f3716d 100644
--- a/src/test/regress/expected/generated.out
+++ b/src/test/regress/expected/generated.out
@@ -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
diff --git a/src/test/regress/sql/generated.sql b/src/test/regress/sql/generated.sql
index ece4f66db3f..9bc4f766066 100644
--- a/src/test/regress/sql/generated.sql
+++ b/src/test/regress/sql/generated.sql
@@ -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);