Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 4ac8aaa

Browse files
committed
Fix handling of generated columns in ALTER TABLE.
ALTER TABLE failed if a column referenced in a GENERATED expression had been added or changed in type earlier in the ALTER command. That's because the GENERATED expression needs to be evaluated against the table's updated tuples, but it was being evaluated against the original tuples. (Fortunately the executor has adequate cross-checks to notice the mismatch, so we just got an obscure error message and not anything more dangerous.) Per report from Andreas Joseph Krogh. Back-patch to v12 where GENERATED was added. Discussion: https://postgr.es/m/VisenaEmail.200.231b0a41523275d0.16ea7f800c7@tc7-visena
1 parent 30a3e77 commit 4ac8aaa

File tree

3 files changed

+132
-29
lines changed

3 files changed

+132
-29
lines changed

src/backend/commands/tablecmds.c

+31-2
Original file line numberDiff line numberDiff line change
@@ -192,13 +192,15 @@ typedef struct NewConstraint
192192
* Phase 3 copy (this could be either a new column with a non-null default, or
193193
* a column that we're changing the type of). Columns without such an entry
194194
* are just copied from the old table during ATRewriteTable. Note that the
195-
* expr is an expression over *old* table values.
195+
* expr is an expression over *old* table values, except when is_generated
196+
* is true; then it is an expression over columns of the *new* tuple.
196197
*/
197198
typedef struct NewColumnValue
198199
{
199200
AttrNumber attnum; /* which column */
200201
Expr *expr; /* expression to compute */
201202
ExprState *exprstate; /* execution state */
203+
bool is_generated; /* is it a GENERATED expression? */
202204
} NewColumnValue;
203205

204206
/*
@@ -4961,14 +4963,19 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
49614963

49624964
/*
49634965
* Process supplied expressions to replace selected columns.
4964-
* Expression inputs come from the old tuple.
4966+
*
4967+
* First, evaluate expressions whose inputs come from the old
4968+
* tuple.
49654969
*/
49664970
econtext->ecxt_scantuple = oldslot;
49674971

49684972
foreach(l, tab->newvals)
49694973
{
49704974
NewColumnValue *ex = lfirst(l);
49714975

4976+
if (ex->is_generated)
4977+
continue;
4978+
49724979
newslot->tts_values[ex->attnum - 1]
49734980
= ExecEvalExpr(ex->exprstate,
49744981
econtext,
@@ -4977,6 +4984,26 @@ ATRewriteTable(AlteredTableInfo *tab, Oid OIDNewHeap, LOCKMODE lockmode)
49774984

49784985
ExecStoreVirtualTuple(newslot);
49794986

4987+
/*
4988+
* Now, evaluate any expressions whose inputs come from the
4989+
* new tuple. We assume these columns won't reference each
4990+
* other, so that there's no ordering dependency.
4991+
*/
4992+
econtext->ecxt_scantuple = newslot;
4993+
4994+
foreach(l, tab->newvals)
4995+
{
4996+
NewColumnValue *ex = lfirst(l);
4997+
4998+
if (!ex->is_generated)
4999+
continue;
5000+
5001+
newslot->tts_values[ex->attnum - 1]
5002+
= ExecEvalExpr(ex->exprstate,
5003+
econtext,
5004+
&newslot->tts_isnull[ex->attnum - 1]);
5005+
}
5006+
49805007
/*
49815008
* Constraints might reference the tableoid column, so
49825009
* initialize t_tableOid before evaluating them.
@@ -5892,6 +5919,7 @@ ATExecAddColumn(List **wqueue, AlteredTableInfo *tab, Relation rel,
58925919
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
58935920
newval->attnum = attribute.attnum;
58945921
newval->expr = expression_planner(defval);
5922+
newval->is_generated = (colDef->generated != '\0');
58955923

58965924
tab->newvals = lappend(tab->newvals, newval);
58975925
}
@@ -10379,6 +10407,7 @@ ATPrepAlterColumnType(List **wqueue,
1037910407
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
1038010408
newval->attnum = attnum;
1038110409
newval->expr = (Expr *) transform;
10410+
newval->is_generated = false;
1038210411

1038310412
tab->newvals = lappend(tab->newvals, newval);
1038410413
if (ATColumnChangeRequiresRewrite(transform, attnum))

src/test/regress/expected/generated.out

+76-21
Original file line numberDiff line numberDiff line change
@@ -593,40 +593,95 @@ ERROR: cannot use generated column "b" in column generation expression
593593
DETAIL: A generated column cannot reference another generated column.
594594
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error
595595
ERROR: column "z" does not exist
596+
ALTER TABLE gtest25 ADD COLUMN c int DEFAULT 42,
597+
ADD COLUMN x int GENERATED ALWAYS AS (c * 4) STORED;
598+
ALTER TABLE gtest25 ADD COLUMN d int DEFAULT 101;
599+
ALTER TABLE gtest25 ALTER COLUMN d SET DATA TYPE float8,
600+
ADD COLUMN y float8 GENERATED ALWAYS AS (d * 4) STORED;
601+
SELECT * FROM gtest25 ORDER BY a;
602+
a | b | c | x | d | y
603+
---+----+----+-----+-----+-----
604+
3 | 9 | 42 | 168 | 101 | 404
605+
4 | 12 | 42 | 168 | 101 | 404
606+
(2 rows)
607+
608+
\d gtest25
609+
Table "public.gtest25"
610+
Column | Type | Collation | Nullable | Default
611+
--------+------------------+-----------+----------+------------------------------------------------------
612+
a | integer | | not null |
613+
b | integer | | | generated always as (a * 3) stored
614+
c | integer | | | 42
615+
x | integer | | | generated always as (c * 4) stored
616+
d | double precision | | | 101
617+
y | double precision | | | generated always as (d * 4::double precision) stored
618+
Indexes:
619+
"gtest25_pkey" PRIMARY KEY, btree (a)
620+
596621
-- ALTER TABLE ... ALTER COLUMN
597622
CREATE TABLE gtest27 (
598623
a int,
599-
b int GENERATED ALWAYS AS (a * 2) STORED
624+
b int,
625+
x int GENERATED ALWAYS AS ((a + b) * 2) STORED
600626
);
601-
INSERT INTO gtest27 (a) VALUES (3), (4);
627+
INSERT INTO gtest27 (a, b) VALUES (3, 7), (4, 11);
602628
ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error
603629
ERROR: cannot alter type of a column used by a generated column
604-
DETAIL: Column "a" is used by generated column "b".
605-
ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric;
630+
DETAIL: Column "a" is used by generated column "x".
631+
ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric;
606632
\d gtest27
607-
Table "public.gtest27"
608-
Column | Type | Collation | Nullable | Default
609-
--------+---------+-----------+----------+--------------------------------------
633+
Table "public.gtest27"
634+
Column | Type | Collation | Nullable | Default
635+
--------+---------+-----------+----------+--------------------------------------------
610636
a | integer | | |
611-
b | numeric | | | generated always as ((a * 2)) stored
637+
b | integer | | |
638+
x | numeric | | | generated always as (((a + b) * 2)) stored
612639

613640
SELECT * FROM gtest27;
614-
a | b
615-
---+---
616-
3 | 6
617-
4 | 8
641+
a | b | x
642+
---+----+----
643+
3 | 7 | 20
644+
4 | 11 | 30
618645
(2 rows)
619646

620-
ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0; -- error
621-
ERROR: generation expression for column "b" cannot be cast automatically to type boolean
622-
ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error
623-
ERROR: column "b" of relation "gtest27" is a generated column
647+
ALTER TABLE gtest27 ALTER COLUMN x TYPE boolean USING x <> 0; -- error
648+
ERROR: generation expression for column "x" cannot be cast automatically to type boolean
649+
ALTER TABLE gtest27 ALTER COLUMN x DROP DEFAULT; -- error
650+
ERROR: column "x" of relation "gtest27" is a generated column
651+
-- It's possible to alter the column types this way:
652+
ALTER TABLE gtest27
653+
DROP COLUMN x,
654+
ALTER COLUMN a TYPE bigint,
655+
ALTER COLUMN b TYPE bigint,
656+
ADD COLUMN x bigint GENERATED ALWAYS AS ((a + b) * 2) STORED;
624657
\d gtest27
625-
Table "public.gtest27"
626-
Column | Type | Collation | Nullable | Default
627-
--------+---------+-----------+----------+--------------------------------------
628-
a | integer | | |
629-
b | numeric | | | generated always as ((a * 2)) stored
658+
Table "public.gtest27"
659+
Column | Type | Collation | Nullable | Default
660+
--------+--------+-----------+----------+------------------------------------------
661+
a | bigint | | |
662+
b | bigint | | |
663+
x | bigint | | | generated always as ((a + b) * 2) stored
664+
665+
-- Ideally you could just do this, but not today (and should x change type?):
666+
ALTER TABLE gtest27
667+
ALTER COLUMN a TYPE float8,
668+
ALTER COLUMN b TYPE float8; -- error
669+
ERROR: cannot alter type of a column used by a generated column
670+
DETAIL: Column "a" is used by generated column "x".
671+
\d gtest27
672+
Table "public.gtest27"
673+
Column | Type | Collation | Nullable | Default
674+
--------+--------+-----------+----------+------------------------------------------
675+
a | bigint | | |
676+
b | bigint | | |
677+
x | bigint | | | generated always as ((a + b) * 2) stored
678+
679+
SELECT * FROM gtest27;
680+
a | b | x
681+
---+----+----
682+
3 | 7 | 20
683+
4 | 11 | 30
684+
(2 rows)
630685

631686
-- triggers
632687
CREATE TABLE gtest26 (

src/test/regress/sql/generated.sql

+25-6
Original file line numberDiff line numberDiff line change
@@ -316,21 +316,40 @@ ALTER TABLE gtest25 ADD COLUMN b int GENERATED ALWAYS AS (a * 3) STORED;
316316
SELECT * FROM gtest25 ORDER BY a;
317317
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error
318318
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error
319+
ALTER TABLE gtest25 ADD COLUMN c int DEFAULT 42,
320+
ADD COLUMN x int GENERATED ALWAYS AS (c * 4) STORED;
321+
ALTER TABLE gtest25 ADD COLUMN d int DEFAULT 101;
322+
ALTER TABLE gtest25 ALTER COLUMN d SET DATA TYPE float8,
323+
ADD COLUMN y float8 GENERATED ALWAYS AS (d * 4) STORED;
324+
SELECT * FROM gtest25 ORDER BY a;
325+
\d gtest25
319326

320327
-- ALTER TABLE ... ALTER COLUMN
321328
CREATE TABLE gtest27 (
322329
a int,
323-
b int GENERATED ALWAYS AS (a * 2) STORED
330+
b int,
331+
x int GENERATED ALWAYS AS ((a + b) * 2) STORED
324332
);
325-
INSERT INTO gtest27 (a) VALUES (3), (4);
333+
INSERT INTO gtest27 (a, b) VALUES (3, 7), (4, 11);
326334
ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error
327-
ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric;
335+
ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric;
328336
\d gtest27
329337
SELECT * FROM gtest27;
330-
ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0; -- error
331-
332-
ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error
338+
ALTER TABLE gtest27 ALTER COLUMN x TYPE boolean USING x <> 0; -- error
339+
ALTER TABLE gtest27 ALTER COLUMN x DROP DEFAULT; -- error
340+
-- It's possible to alter the column types this way:
341+
ALTER TABLE gtest27
342+
DROP COLUMN x,
343+
ALTER COLUMN a TYPE bigint,
344+
ALTER COLUMN b TYPE bigint,
345+
ADD COLUMN x bigint GENERATED ALWAYS AS ((a + b) * 2) STORED;
333346
\d gtest27
347+
-- Ideally you could just do this, but not today (and should x change type?):
348+
ALTER TABLE gtest27
349+
ALTER COLUMN a TYPE float8,
350+
ALTER COLUMN b TYPE float8; -- error
351+
\d gtest27
352+
SELECT * FROM gtest27;
334353

335354
-- triggers
336355
CREATE TABLE gtest26 (

0 commit comments

Comments
 (0)