A generated column cannot be written to directly. In
<command>INSERT</command> or <command>UPDATE</command> commands, a value
cannot be specified for a generated column, but the keyword
- <literal>DEFAULT</literal> may be specified.
+ <literal>DEFAULT</literal> may be specified. Also, a generated column
+ cannot be used in the <literal>FOR PORTION OF</literal> clause of an
+ <command>UPDATE</command> or <command>DELETE</command> command.
</para>
<para>
*/
transform_MERGE_to_join(parse);
+ /*
+ * Reject FOR PORTION OF on a generated column. We can't write to a
+ * virtual generated column, and a stored generated column should be
+ * written by its own expression.
+ *
+ * We do this in the planner rather than parse analysis so that updatable
+ * views have been rewritten; otherwise they would mask which columns are
+ * generated. We need to check before preprocess_relation_rtes(), so that
+ * for virtual generated columns we still have the rangeVar. After that
+ * it is replaced by the column's expression.
+ *
+ * XXX: We plan to implement PERIODs as stored generated columns, so later
+ * we will loosen this restriction if the column belongs to a PERIOD.
+ */
+ if (parse->forPortionOf)
+ {
+ ForPortionOfExpr *forPortionOf = parse->forPortionOf;
+ RangeTblEntry *rte = rt_fetch(parse->resultRelation, parse->rtable);
+
+ if (get_attgenerated(rte->relid, forPortionOf->rangeVar->varattno))
+ ereport(ERROR,
+ (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+ errmsg("cannot use generated column \"%s\" in FOR PORTION OF",
+ forPortionOf->range_name)));
+ }
+
/*
* Scan the rangetable for relation RTEs and retrieve the necessary
* catalog information for each relation. Using this information, clear
(2 rows)
DROP TABLE fpo_rule;
+-- UPDATE/DELETE FOR PORTION OF on a GENERATED VIRTUAL range column:
+CREATE TABLE fpo_gen_virtual (
+ a int,
+ b int4range GENERATED ALWAYS AS (int4range(a, a + 1)) VIRTUAL
+);
+INSERT INTO fpo_gen_virtual VALUES (1);
+DELETE FROM fpo_gen_virtual FOR PORTION OF b FROM 1 TO 2; -- fails
+ERROR: cannot use generated column "b" in FOR PORTION OF
+UPDATE fpo_gen_virtual FOR PORTION OF b FROM 1 TO 2 SET a = 5; -- fails
+ERROR: column "b" can only be updated to DEFAULT
+DETAIL: Column "b" is a generated column.
+DROP TABLE fpo_gen_virtual;
+-- UPDATE/DELETE FOR PORTION OF on a GENERATED STORED range column:
+CREATE TABLE fpo_gen_stored (
+ a int,
+ b int4range GENERATED ALWAYS AS (int4range(a, a + 1)) STORED
+);
+INSERT INTO fpo_gen_stored VALUES (1);
+DELETE FROM fpo_gen_stored FOR PORTION OF b FROM 1 TO 2; -- fails
+ERROR: cannot use generated column "b" in FOR PORTION OF
+UPDATE fpo_gen_stored FOR PORTION OF b FROM 1 TO 2 SET a = 5; -- fails
+ERROR: column "b" can only be updated to DEFAULT
+DETAIL: Column "b" is a generated column.
+DROP TABLE fpo_gen_stored;
+-- FOR PORTION OF a generated column reached through an updatable view.
+-- The view hides that b is generated during parse analysis, so the check
+-- must happen later (in the planner), after the view is rewritten to its
+-- underlying table.
+CREATE TABLE fpo_gen_view (
+ a int,
+ b int4range GENERATED ALWAYS AS (int4range(a, a + 1)) STORED
+);
+INSERT INTO fpo_gen_view VALUES (1);
+CREATE VIEW fpo_gen_view_v AS SELECT * FROM fpo_gen_view;
+DELETE FROM fpo_gen_view_v FOR PORTION OF b FROM 1 TO 2; -- fails
+ERROR: cannot use generated column "b" in FOR PORTION OF
+UPDATE fpo_gen_view_v FOR PORTION OF b FROM 1 TO 2 SET a = 5; -- fails
+ERROR: column "b" can only be updated to DEFAULT
+DETAIL: Column "b" is a generated column.
+DROP VIEW fpo_gen_view_v;
+DROP TABLE fpo_gen_view;
+-- A new-style SQL function is parsed at CREATE FUNCTION time, but our
+-- generated-column check is in the planner, so it sees the column's
+-- current attgenerated when the function's plan is built at run time.
+CREATE TABLE fpo_func_test (
+ a int,
+ b int4range GENERATED ALWAYS AS (int4range(a, a + 1)) STORED
+);
+INSERT INTO fpo_func_test VALUES (1);
+-- Definition succeeds even though b is a generated column today.
+CREATE FUNCTION fpo_delete() RETURNS void
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ DELETE FROM fpo_func_test FOR PORTION OF b FROM 1 TO 2;
+ END;
+SELECT fpo_delete(); -- fails: b is generated
+ERROR: cannot use generated column "b" in FOR PORTION OF
+CONTEXT: SQL function "fpo_delete" statement 1
+-- Drop the generation expression and the same function now succeeds.
+ALTER TABLE fpo_func_test ALTER COLUMN b DROP EXPRESSION;
+SELECT fpo_delete();
+ fpo_delete
+------------
+
+(1 row)
+
+TABLE fpo_func_test ORDER BY a, b;
+ a | b
+---+---
+(0 rows)
+
+DROP FUNCTION fpo_delete();
+DROP TABLE fpo_func_test;
-- UPDATE/DELETE FOR PORTION OF with table inheritance
-- Leftover rows must stay in the child table, preserving child-specific columns.
CREATE TABLE fpo_inh_parent (
DROP TABLE fpo_rule;
+-- UPDATE/DELETE FOR PORTION OF on a GENERATED VIRTUAL range column:
+CREATE TABLE fpo_gen_virtual (
+ a int,
+ b int4range GENERATED ALWAYS AS (int4range(a, a + 1)) VIRTUAL
+);
+INSERT INTO fpo_gen_virtual VALUES (1);
+DELETE FROM fpo_gen_virtual FOR PORTION OF b FROM 1 TO 2; -- fails
+UPDATE fpo_gen_virtual FOR PORTION OF b FROM 1 TO 2 SET a = 5; -- fails
+DROP TABLE fpo_gen_virtual;
+
+-- UPDATE/DELETE FOR PORTION OF on a GENERATED STORED range column:
+CREATE TABLE fpo_gen_stored (
+ a int,
+ b int4range GENERATED ALWAYS AS (int4range(a, a + 1)) STORED
+);
+INSERT INTO fpo_gen_stored VALUES (1);
+DELETE FROM fpo_gen_stored FOR PORTION OF b FROM 1 TO 2; -- fails
+UPDATE fpo_gen_stored FOR PORTION OF b FROM 1 TO 2 SET a = 5; -- fails
+DROP TABLE fpo_gen_stored;
+
+-- FOR PORTION OF a generated column reached through an updatable view.
+-- The view hides that b is generated during parse analysis, so the check
+-- must happen later (in the planner), after the view is rewritten to its
+-- underlying table.
+CREATE TABLE fpo_gen_view (
+ a int,
+ b int4range GENERATED ALWAYS AS (int4range(a, a + 1)) STORED
+);
+INSERT INTO fpo_gen_view VALUES (1);
+CREATE VIEW fpo_gen_view_v AS SELECT * FROM fpo_gen_view;
+DELETE FROM fpo_gen_view_v FOR PORTION OF b FROM 1 TO 2; -- fails
+UPDATE fpo_gen_view_v FOR PORTION OF b FROM 1 TO 2 SET a = 5; -- fails
+DROP VIEW fpo_gen_view_v;
+DROP TABLE fpo_gen_view;
+
+-- A new-style SQL function is parsed at CREATE FUNCTION time, but our
+-- generated-column check is in the planner, so it sees the column's
+-- current attgenerated when the function's plan is built at run time.
+CREATE TABLE fpo_func_test (
+ a int,
+ b int4range GENERATED ALWAYS AS (int4range(a, a + 1)) STORED
+);
+INSERT INTO fpo_func_test VALUES (1);
+-- Definition succeeds even though b is a generated column today.
+CREATE FUNCTION fpo_delete() RETURNS void
+ LANGUAGE SQL
+ BEGIN ATOMIC
+ DELETE FROM fpo_func_test FOR PORTION OF b FROM 1 TO 2;
+ END;
+SELECT fpo_delete(); -- fails: b is generated
+-- Drop the generation expression and the same function now succeeds.
+ALTER TABLE fpo_func_test ALTER COLUMN b DROP EXPRESSION;
+SELECT fpo_delete();
+TABLE fpo_func_test ORDER BY a, b;
+DROP FUNCTION fpo_delete();
+DROP TABLE fpo_func_test;
+
-- UPDATE/DELETE FOR PORTION OF with table inheritance
-- Leftover rows must stay in the child table, preserving child-specific columns.
CREATE TABLE fpo_inh_parent (