* Phase 3 copy (this could be either a new column with a non-null default, or
* a column that we're changing the type of). Columns without such an entry
* are just copied from the old table during ATRewriteTable. Note that the
- * expr is an expression over *old* table values.
+ * expr is an expression over *old* table values, except when is_generated
+ * is true; then it is an expression over columns of the *new* tuple.
*/
typedef struct NewColumnValue
{
AttrNumber attnum; /* which column */
Expr *expr; /* expression to compute */
ExprState *exprstate; /* execution state */
+ bool is_generated; /* is it a GENERATED expression? */
} NewColumnValue;
/*
/*
* Process supplied expressions to replace selected columns.
- * Expression inputs come from the old tuple.
+ *
+ * First, evaluate expressions whose inputs come from the old
+ * tuple.
*/
econtext->ecxt_scantuple = oldslot;
{
NewColumnValue *ex = lfirst(l);
+ if (ex->is_generated)
+ continue;
+
newslot->tts_values[ex->attnum - 1]
= ExecEvalExpr(ex->exprstate,
econtext,
ExecStoreVirtualTuple(newslot);
+ /*
+ * Now, evaluate any expressions whose inputs come from the
+ * new tuple. We assume these columns won't reference each
+ * other, so that there's no ordering dependency.
+ */
+ econtext->ecxt_scantuple = newslot;
+
+ foreach(l, tab->newvals)
+ {
+ NewColumnValue *ex = lfirst(l);
+
+ if (!ex->is_generated)
+ continue;
+
+ newslot->tts_values[ex->attnum - 1]
+ = ExecEvalExpr(ex->exprstate,
+ econtext,
+ &newslot->tts_isnull[ex->attnum - 1]);
+ }
+
/*
* Constraints might reference the tableoid column, so
* initialize t_tableOid before evaluating them.
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
newval->attnum = attribute.attnum;
newval->expr = expression_planner(defval);
+ newval->is_generated = (colDef->generated != '\0');
tab->newvals = lappend(tab->newvals, newval);
}
newval = (NewColumnValue *) palloc0(sizeof(NewColumnValue));
newval->attnum = attnum;
newval->expr = (Expr *) transform;
+ newval->is_generated = false;
tab->newvals = lappend(tab->newvals, newval);
if (ATColumnChangeRequiresRewrite(transform, attnum))
DETAIL: A generated column cannot reference another generated column.
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error
ERROR: column "z" does not exist
+ALTER TABLE gtest25 ADD COLUMN c int DEFAULT 42,
+ ADD COLUMN x int GENERATED ALWAYS AS (c * 4) STORED;
+ALTER TABLE gtest25 ADD COLUMN d int DEFAULT 101;
+ALTER TABLE gtest25 ALTER COLUMN d SET DATA TYPE float8,
+ ADD COLUMN y float8 GENERATED ALWAYS AS (d * 4) STORED;
+SELECT * FROM gtest25 ORDER BY a;
+ a | b | c | x | d | y
+---+----+----+-----+-----+-----
+ 3 | 9 | 42 | 168 | 101 | 404
+ 4 | 12 | 42 | 168 | 101 | 404
+(2 rows)
+
+\d gtest25
+ Table "public.gtest25"
+ Column | Type | Collation | Nullable | Default
+--------+------------------+-----------+----------+------------------------------------------------------
+ a | integer | | not null |
+ b | integer | | | generated always as (a * 3) stored
+ c | integer | | | 42
+ x | integer | | | generated always as (c * 4) stored
+ d | double precision | | | 101
+ y | double precision | | | generated always as (d * 4::double precision) stored
+Indexes:
+ "gtest25_pkey" PRIMARY KEY, btree (a)
+
-- ALTER TABLE ... ALTER COLUMN
CREATE TABLE gtest27 (
a int,
- b int GENERATED ALWAYS AS (a * 2) STORED
+ b int,
+ x int GENERATED ALWAYS AS ((a + b) * 2) STORED
);
-INSERT INTO gtest27 (a) VALUES (3), (4);
+INSERT INTO gtest27 (a, b) VALUES (3, 7), (4, 11);
ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error
ERROR: cannot alter type of a column used by a generated column
-DETAIL: Column "a" is used by generated column "b".
-ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric;
+DETAIL: Column "a" is used by generated column "x".
+ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric;
\d gtest27
- Table "public.gtest27"
- Column | Type | Collation | Nullable | Default
---------+---------+-----------+----------+--------------------------------------
+ Table "public.gtest27"
+ Column | Type | Collation | Nullable | Default
+--------+---------+-----------+----------+--------------------------------------------
a | integer | | |
- b | numeric | | | generated always as ((a * 2)) stored
+ b | integer | | |
+ x | numeric | | | generated always as (((a + b) * 2)) stored
SELECT * FROM gtest27;
- a | b
----+---
- 3 | 6
- 4 | 8
+ a | b | x
+---+----+----
+ 3 | 7 | 20
+ 4 | 11 | 30
(2 rows)
-ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0; -- error
-ERROR: generation expression for column "b" cannot be cast automatically to type boolean
-ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error
-ERROR: column "b" of relation "gtest27" is a generated column
+ALTER TABLE gtest27 ALTER COLUMN x TYPE boolean USING x <> 0; -- error
+ERROR: generation expression for column "x" cannot be cast automatically to type boolean
+ALTER TABLE gtest27 ALTER COLUMN x DROP DEFAULT; -- error
+ERROR: column "x" of relation "gtest27" is a generated column
+-- It's possible to alter the column types this way:
+ALTER TABLE gtest27
+ DROP COLUMN x,
+ ALTER COLUMN a TYPE bigint,
+ ALTER COLUMN b TYPE bigint,
+ ADD COLUMN x bigint GENERATED ALWAYS AS ((a + b) * 2) STORED;
\d gtest27
- Table "public.gtest27"
- Column | Type | Collation | Nullable | Default
---------+---------+-----------+----------+--------------------------------------
- a | integer | | |
- b | numeric | | | generated always as ((a * 2)) stored
+ Table "public.gtest27"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+------------------------------------------
+ a | bigint | | |
+ b | bigint | | |
+ x | bigint | | | generated always as ((a + b) * 2) stored
+
+-- Ideally you could just do this, but not today (and should x change type?):
+ALTER TABLE gtest27
+ ALTER COLUMN a TYPE float8,
+ ALTER COLUMN b TYPE float8; -- error
+ERROR: cannot alter type of a column used by a generated column
+DETAIL: Column "a" is used by generated column "x".
+\d gtest27
+ Table "public.gtest27"
+ Column | Type | Collation | Nullable | Default
+--------+--------+-----------+----------+------------------------------------------
+ a | bigint | | |
+ b | bigint | | |
+ x | bigint | | | generated always as ((a + b) * 2) stored
+
+SELECT * FROM gtest27;
+ a | b | x
+---+----+----
+ 3 | 7 | 20
+ 4 | 11 | 30
+(2 rows)
-- triggers
CREATE TABLE gtest26 (
SELECT * FROM gtest25 ORDER BY a;
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (b * 4) STORED; -- error
ALTER TABLE gtest25 ADD COLUMN x int GENERATED ALWAYS AS (z * 4) STORED; -- error
+ALTER TABLE gtest25 ADD COLUMN c int DEFAULT 42,
+ ADD COLUMN x int GENERATED ALWAYS AS (c * 4) STORED;
+ALTER TABLE gtest25 ADD COLUMN d int DEFAULT 101;
+ALTER TABLE gtest25 ALTER COLUMN d SET DATA TYPE float8,
+ ADD COLUMN y float8 GENERATED ALWAYS AS (d * 4) STORED;
+SELECT * FROM gtest25 ORDER BY a;
+\d gtest25
-- ALTER TABLE ... ALTER COLUMN
CREATE TABLE gtest27 (
a int,
- b int GENERATED ALWAYS AS (a * 2) STORED
+ b int,
+ x int GENERATED ALWAYS AS ((a + b) * 2) STORED
);
-INSERT INTO gtest27 (a) VALUES (3), (4);
+INSERT INTO gtest27 (a, b) VALUES (3, 7), (4, 11);
ALTER TABLE gtest27 ALTER COLUMN a TYPE text; -- error
-ALTER TABLE gtest27 ALTER COLUMN b TYPE numeric;
+ALTER TABLE gtest27 ALTER COLUMN x TYPE numeric;
\d gtest27
SELECT * FROM gtest27;
-ALTER TABLE gtest27 ALTER COLUMN b TYPE boolean USING b <> 0; -- error
-
-ALTER TABLE gtest27 ALTER COLUMN b DROP DEFAULT; -- error
+ALTER TABLE gtest27 ALTER COLUMN x TYPE boolean USING x <> 0; -- error
+ALTER TABLE gtest27 ALTER COLUMN x DROP DEFAULT; -- error
+-- It's possible to alter the column types this way:
+ALTER TABLE gtest27
+ DROP COLUMN x,
+ ALTER COLUMN a TYPE bigint,
+ ALTER COLUMN b TYPE bigint,
+ ADD COLUMN x bigint GENERATED ALWAYS AS ((a + b) * 2) STORED;
\d gtest27
+-- Ideally you could just do this, but not today (and should x change type?):
+ALTER TABLE gtest27
+ ALTER COLUMN a TYPE float8,
+ ALTER COLUMN b TYPE float8; -- error
+\d gtest27
+SELECT * FROM gtest27;
-- triggers
CREATE TABLE gtest26 (