v1-0001-fpo-generated-stored-fix.patch
application/octet-stream
Filename: v1-0001-fpo-generated-stored-fix.patch
Type: application/octet-stream
Part: 0
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v1-0001
| File | + | − |
|---|---|---|
| src/backend/executor/nodeModifyTable.c | 32 | 0 |
| src/test/regress/expected/for_portion_of.out | 58 | 0 |
| src/test/regress/sql/for_portion_of.sql | 40 | 0 |
diff --git a/src/backend/executor/nodeModifyTable.c b/src/backend/executor/nodeModifyTable.c
index dfd7b33a..c0250e65 100644
--- a/src/backend/executor/nodeModifyTable.c
+++ b/src/backend/executor/nodeModifyTable.c
@@ -475,6 +475,38 @@ ExecInitGenerated(ResultRelInfo *resultRelInfo,
else
updatedCols = NULL;
+ /*
+ * For UPDATE ... FOR PORTION OF, the range column is also being modified
+ * (narrowed via intersection), but it is not included in updatedCols
+ * because the user does not need UPDATE permission on it. We must
+ * account for it here so that generated columns referencing the range
+ * column are recomputed.
+ */
+ if (updatedCols)
+ {
+ ForPortionOfState *fpoState = resultRelInfo->ri_forPortionOf;
+
+ if (fpoState == NULL && resultRelInfo->ri_RootResultRelInfo)
+ fpoState = resultRelInfo->ri_RootResultRelInfo->ri_forPortionOf;
+ if (fpoState != NULL)
+ {
+ int rangeAttno = fpoState->fp_rangeAttno;
+
+ /* Map from root attno to child attno if needed */
+ if (resultRelInfo->ri_RootResultRelInfo)
+ {
+ TupleConversionMap *map = ExecGetRootToChildMap(resultRelInfo,
+ estate);
+
+ if (map)
+ rangeAttno = map->attrMap->attnums[rangeAttno - 1];
+ }
+
+ updatedCols = bms_add_member(bms_copy(updatedCols),
+ rangeAttno - FirstLowInvalidHeapAttributeNumber);
+ }
+ }
+
/*
* Make sure these data structures are built in the per-query memory
* context so they'll survive throughout the query.
diff --git a/src/test/regress/expected/for_portion_of.out b/src/test/regress/expected/for_portion_of.out
index 31f772c7..a9917b4a 100644
--- a/src/test/regress/expected/for_portion_of.out
+++ b/src/test/regress/expected/for_portion_of.out
@@ -2097,4 +2097,62 @@ SELECT * FROM temporal_partitioned_5 ORDER BY id, valid_at;
(4 rows)
DROP TABLE temporal_partitioned;
+-- UPDATE FOR PORTION OF with generated stored columns
+-- The generated column depends on the range column, so it must be
+-- recomputed when FOR PORTION OF narrows the range.
+CREATE TABLE fpo_generated_stored (
+ id int,
+ valid_at int4range,
+ range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED
+);
+INSERT INTO fpo_generated_stored (id, valid_at) VALUES
+ (1, '[10,100)');
+SELECT * FROM fpo_generated_stored ORDER BY valid_at;
+ id | valid_at | range_len
+----+----------+-----------
+ 1 | [10,100) | 90
+(1 row)
+
+-- After FPO, all three rows (leftover-before, updated, leftover-after)
+-- must have correct range_len values.
+UPDATE fpo_generated_stored
+ FOR PORTION OF valid_at FROM 30 TO 70
+ SET id = 2;
+SELECT * FROM fpo_generated_stored ORDER BY valid_at;
+ id | valid_at | range_len
+----+----------+-----------
+ 1 | [10,30) | 20
+ 2 | [30,70) | 40
+ 1 | [70,100) | 30
+(3 rows)
+
+-- Also test with a generated column that references both a SET column
+-- and the range column.
+TRUNCATE fpo_generated_stored;
+DROP TABLE fpo_generated_stored;
+CREATE TABLE fpo_generated_stored (
+ id int,
+ valid_at int4range,
+ id_plus_len int GENERATED ALWAYS AS (id + upper(valid_at) - lower(valid_at)) STORED
+);
+INSERT INTO fpo_generated_stored (id, valid_at) VALUES
+ (1, '[10,100)');
+SELECT * FROM fpo_generated_stored ORDER BY valid_at;
+ id | valid_at | id_plus_len
+----+----------+-------------
+ 1 | [10,100) | 91
+(1 row)
+
+UPDATE fpo_generated_stored
+ FOR PORTION OF valid_at FROM 30 TO 70
+ SET id = 2;
+SELECT * FROM fpo_generated_stored ORDER BY valid_at;
+ id | valid_at | id_plus_len
+----+----------+-------------
+ 1 | [10,30) | 21
+ 2 | [30,70) | 42
+ 1 | [70,100) | 31
+(3 rows)
+
+DROP TABLE fpo_generated_stored;
RESET datestyle;
diff --git a/src/test/regress/sql/for_portion_of.sql b/src/test/regress/sql/for_portion_of.sql
index d4062acf..f1e3937c 100644
--- a/src/test/regress/sql/for_portion_of.sql
+++ b/src/test/regress/sql/for_portion_of.sql
@@ -1365,4 +1365,44 @@ SELECT * FROM temporal_partitioned_5 ORDER BY id, valid_at;
DROP TABLE temporal_partitioned;
+-- UPDATE FOR PORTION OF with generated stored columns
+-- The generated column depends on the range column, so it must be
+-- recomputed when FOR PORTION OF narrows the range.
+
+CREATE TABLE fpo_generated_stored (
+ id int,
+ valid_at int4range,
+ range_len int GENERATED ALWAYS AS (upper(valid_at) - lower(valid_at)) STORED
+);
+INSERT INTO fpo_generated_stored (id, valid_at) VALUES
+ (1, '[10,100)');
+SELECT * FROM fpo_generated_stored ORDER BY valid_at;
+
+-- After FPO, all three rows (leftover-before, updated, leftover-after)
+-- must have correct range_len values.
+UPDATE fpo_generated_stored
+ FOR PORTION OF valid_at FROM 30 TO 70
+ SET id = 2;
+SELECT * FROM fpo_generated_stored ORDER BY valid_at;
+
+-- Also test with a generated column that references both a SET column
+-- and the range column.
+TRUNCATE fpo_generated_stored;
+DROP TABLE fpo_generated_stored;
+CREATE TABLE fpo_generated_stored (
+ id int,
+ valid_at int4range,
+ id_plus_len int GENERATED ALWAYS AS (id + upper(valid_at) - lower(valid_at)) STORED
+);
+INSERT INTO fpo_generated_stored (id, valid_at) VALUES
+ (1, '[10,100)');
+SELECT * FROM fpo_generated_stored ORDER BY valid_at;
+
+UPDATE fpo_generated_stored
+ FOR PORTION OF valid_at FROM 30 TO 70
+ SET id = 2;
+SELECT * FROM fpo_generated_stored ORDER BY valid_at;
+
+DROP TABLE fpo_generated_stored;
+
RESET datestyle;