mirror of
https://github.com/postgres/postgres.git
synced 2026-07-08 01:01:01 -04:00
Forbid generated columns in FOR PORTION OF
With virtual generated columns there is no column to assign to, and we shouldn't assign directly to stored generated columns either. (Once we have PERIODs, we will allow a stored generated column here, but we will assign to its start/end inputs.) We can't do this in parse analysis, because views haven't yet been rewritten, so they mask generated columns. Author: Paul A. Jungwirth <pj@illuminatedcomputing.com> Discussion: https://www.postgresql.org/message-id/agOOykf2HV26yVfU%40nathan
This commit is contained in:
parent
98d5d7ee64
commit
c8d49ffb00
4 changed files with 159 additions and 1 deletions
|
|
@ -382,7 +382,9 @@ CREATE TABLE people (
|
|||
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>
|
||||
|
|
|
|||
|
|
@ -848,6 +848,32 @@ subquery_planner(PlannerGlobal *glob, Query *parse, char *plan_name,
|
|||
*/
|
||||
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
|
||||
|
|
|
|||
|
|
@ -2208,6 +2208,79 @@ SELECT * FROM fpo_rule ORDER BY f1;
|
|||
(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 (
|
||||
|
|
|
|||
|
|
@ -1448,6 +1448,63 @@ SELECT * FROM fpo_rule ORDER BY f1;
|
|||
|
||||
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 (
|
||||
|
|
|
|||
Loading…
Reference in a new issue