From b3ccff2f1d371dd7f7b550b784454fed30431971 Mon Sep 17 00:00:00 2001
From: Alexander Korotkov <akorotkov@postgresql.org>
Date: Mon, 3 Aug 2026 00:19:19 +0200
Subject: [PATCH v1 3/3] Don't recalculate generated columns during MERGE/SPLIT
 PARTITION(S)

ALTER TABLE ... MERGE/SPLIT PARTITION unconditionally recomputed every stored
generated column of the moved rows using the partitioned table's generation
expression.  When a leaf partition's generation expression -- or a function it
calls -- differed from the partitioned table's, this silently rewrote
already-stored values, and could even break constraints.

Relocating a row between partitions never changes a user column, so a stored
generated column defined over user columns yields the same value; move it as-is
instead of recomputing, as every other command preserves generated column
values.  This alone removes the silent data changes and constraint violations
reported for such columns.

The one value that legitimately changes on the move is a stored generated
column whose expression references a system column (only tableoid is allowed
there).  Recomputing it during the move is not safe: unlike a normal insert,
the row-movement path does not re-verify NOT NULL, foreign-key, or
generated-column-dependent CHECK constraints, so a recomputed value could
silently violate them.  Rather than recompute without those checks, reject the
operation for such columns and let the user handle them explicitly.

As nothing is recomputed anymore, the machinery that evaluated generated
expressions during the row move is gone: createTableConstraints() no longer
records generated columns in AlteredTableInfo.newvals, and the two row-move
helpers are reduced to preparing and checking CHECK constraints (and renamed
buildPartitionCheckExprStates()/checkPartitionRowConstraints() accordingly).

Document the behavior and add regression coverage.  Existing MERGE/SPLIT
tests that relied on recomputation now assert the rejection, and a
function-change test shows a plain generated column's value preserved.

Discussion: https://postgr.es/m/CAN4CZFNCU=t09M=+r2t9hHLJuujdM4oQ8hCK_Sx-GpfiwMAicw@mail.gmail.com
---
 doc/src/sgml/ref/alter_table.sgml             |  22 ++++
 src/backend/commands/tablecmds.c              | 114 ++++++++++--------
 src/test/regress/expected/partition_merge.out |  58 +++++----
 src/test/regress/expected/partition_split.out |  32 ++---
 src/test/regress/sql/partition_merge.sql      |  40 +++---
 src/test/regress/sql/partition_split.sql      |  20 +--
 6 files changed, 159 insertions(+), 127 deletions(-)

diff --git a/doc/src/sgml/ref/alter_table.sgml b/doc/src/sgml/ref/alter_table.sgml
index c034745365c..fdf18c264e3 100644
--- a/doc/src/sgml/ref/alter_table.sgml
+++ b/doc/src/sgml/ref/alter_table.sgml
@@ -1281,6 +1281,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       dependencies are not silently lost during merge.
      </para>
 
+     <para>
+      Stored generated columns keep their existing values; the merge does not
+      recompute them from the partitioned table's generation expression, which
+      is consistent with how other commands preserve generated column values.
+      As an exception, if a stored generated column's expression references a
+      system column such as <structfield>tableoid</structfield> (whose value
+      would change when a row is moved to another partition), the command is
+      rejected, because the row-movement path cannot safely recompute the value
+      while re-verifying all of the table's constraints against it.
+     </para>
+
      <para>
       The new partition takes its replica identity from the merged partitions
       when they all use the same simple setting
@@ -1411,6 +1422,17 @@ WITH ( MODULUS <replaceable class="parameter">numeric_literal</replaceable>, REM
       from the source partition's indexes.
      </para>
 
+     <para>
+      Stored generated columns keep their existing values; the split does not
+      recompute them from the partitioned table's generation expression, which
+      is consistent with how other commands preserve generated column values.
+      As an exception, if a stored generated column's expression references a
+      system column such as <structfield>tableoid</structfield> (whose value
+      would change when a row is moved to another partition), the command is
+      rejected, because the row-movement path cannot safely recompute the value
+      while re-verifying all of the table's constraints against it.
+     </para>
+
      <para>
       The new partitions take their replica identity from the split partition,
       unless it uses <literal>REPLICA IDENTITY USING INDEX</literal>, in which
diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c
index 5fd6173b533..7b9dcf47207 100644
--- a/src/backend/commands/tablecmds.c
+++ b/src/backend/commands/tablecmds.c
@@ -22860,18 +22860,15 @@ GetAttributeStorage(Oid atttypid, const char *storagemode)
 }
 
 /*
- * buildExpressionExecutionStates: build the needed expression execution states
- * for new partition (newPartRel) checks and initialize expressions for
- * generated columns. All expressions should be created in "tab"
- * (AlteredTableInfo structure).
+ * buildPartitionCheckExprStates: build the expression execution states for the
+ * CHECK constraints of the new partition (newPartRel), stored in "tab"
+ * (AlteredTableInfo structure), so they can be verified against the relocated
+ * rows in checkPartitionRowConstraints().
  */
 static void
-buildExpressionExecutionStates(AlteredTableInfo *tab, Relation newPartRel, EState *estate)
+buildPartitionCheckExprStates(AlteredTableInfo *tab, Relation newPartRel, EState *estate)
 {
-	/*
-	 * Build the needed expression execution states. Here, we expect only NOT
-	 * NULL and CHECK constraint.
-	 */
+	/* Here, we expect only NOT NULL and CHECK constraints. */
 	foreach_ptr(NewConstraint, con, tab->constraints)
 	{
 		switch (con->contype)
@@ -22892,36 +22889,22 @@ buildExpressionExecutionStates(AlteredTableInfo *tab, Relation newPartRel, EStat
 					 (int) con->contype);
 		}
 	}
-
-	/* Expression already planned in createTableConstraints */
-	foreach_ptr(NewColumnValue, ex, tab->newvals)
-		ex->exprstate = ExecInitExpr((Expr *) ex->expr, NULL);
 }
 
 /*
- * evaluateGeneratedExpressionsAndCheckConstraints: evaluate any generated
- * expressions for "tab" (AlteredTableInfo structure) whose inputs come from
- * the new tuple (insertslot) of the new partition (newPartRel).
+ * checkPartitionRowConstraints: verify the new partition's CHECK constraints
+ * against a relocated row (insertslot).  Stored generated columns are moved
+ * as-is (never recomputed; see createTableConstraints), so there are no
+ * generated expressions to evaluate here.
  */
 static void
-evaluateGeneratedExpressionsAndCheckConstraints(AlteredTableInfo *tab,
-												Relation newPartRel,
-												TupleTableSlot *insertslot,
-												ExprContext *econtext)
+checkPartitionRowConstraints(AlteredTableInfo *tab,
+							 Relation newPartRel,
+							 TupleTableSlot *insertslot,
+							 ExprContext *econtext)
 {
 	econtext->ecxt_scantuple = insertslot;
 
-	foreach_ptr(NewColumnValue, ex, tab->newvals)
-	{
-		if (!ex->is_generated)
-			continue;
-
-		insertslot->tts_values[ex->attnum - 1]
-			= ExecEvalExpr(ex->exprstate,
-						   econtext,
-						   &insertslot->tts_isnull[ex->attnum - 1]);
-	}
-
 	foreach_ptr(NewConstraint, con, tab->constraints)
 	{
 		switch (con->contype)
@@ -22995,11 +22978,29 @@ getAttributesList(Relation parent_rel)
 	return colList;
 }
 
+/*
+ * expression_references_system_column: walker that returns true if the given
+ * expression references any system column (a Var with a negative attribute
+ * number, such as tableoid).  Used to decide whether a stored generated column
+ * must be recomputed when a row is relocated between partitions.
+ */
+static bool
+expression_references_system_column(Node *node, void *context)
+{
+	if (node == NULL)
+		return false;
+	if (IsA(node, Var) && ((Var *) node)->varattno < 0)
+		return true;
+	return expression_tree_walker(node, expression_references_system_column,
+								  context);
+}
+
 /*
  * createTableConstraints:
- * create check constraints, default values, and generated values for newRel
- * based on parent_rel.  tab is pending-work queue for newRel, we may need it in
- * MergePartitionsMoveRows.
+ * create check constraints and column defaults (including generation
+ * expressions) for newRel based on parent_rel.  The CHECK constraints are also
+ * recorded in "tab" so they can be re-verified against the relocated rows in
+ * MergePartitionsMoveRows()/SplitPartitionMoveRows().
  */
 static void
 createTableConstraints(List **wqueue, AlteredTableInfo *tab,
@@ -23045,7 +23046,6 @@ createTableConstraints(List **wqueue, AlteredTableInfo *tab,
 			bool		found_whole_row;
 			AttrNumber	num;
 			Node	   *def;
-			NewColumnValue *newval;
 
 			if (attribute->attgenerated == ATTRIBUTE_GENERATED_VIRTUAL)
 				this_default = build_generation_expression(parent_rel, attribute->attnum);
@@ -23067,19 +23067,31 @@ createTableConstraints(List **wqueue, AlteredTableInfo *tab,
 			StoreAttrDefault(newRel, num, def, false);
 
 			/*
-			 * Stored generated column expressions in parent_rel might
-			 * reference the tableoid.  newRel, parent_rel tableoid clear is
-			 * not the same. If so, these stored generated columns require
-			 * recomputation for newRel within MergePartitionsMoveRows.
+			 * Relocating a row between partitions never changes a user column,
+			 * so a stored generated column defined over user columns keeps the
+			 * same value; we move it as-is rather than recomputing it (which is
+			 * what every other command does, and which avoids silently
+			 * rewriting stored data when a leaf partition's generation
+			 * expression, or a function it calls, differs from the partitioned
+			 * table's).
+			 *
+			 * A stored generated column whose expression references a system
+			 * column (in practice only tableoid is allowed there) is the one
+			 * case whose value would legitimately change on the move.  We can't
+			 * recompute it safely: the row-movement path does not re-verify NOT
+			 * NULL, foreign-key, or generated-column-dependent CHECK
+			 * constraints the way a normal insert does, so a recomputed value
+			 * could silently violate them.  Rather than risk that, reject the
+			 * operation and let the user handle such columns explicitly.
 			 */
-			if (attribute->attgenerated == ATTRIBUTE_GENERATED_STORED)
-			{
-				newval = palloc0_object(NewColumnValue);
-				newval->attnum = num;
-				newval->expr = expression_planner((Expr *) def);
-				newval->is_generated = (attribute->attgenerated != '\0');
-				tab->newvals = lappend(tab->newvals, newval);
-			}
+			if (attribute->attgenerated == ATTRIBUTE_GENERATED_STORED &&
+				expression_references_system_column(def, NULL))
+				ereport(ERROR,
+						errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+						errmsg("cannot merge or split partitions when a stored generated column depends on a system column"),
+						errdetail("Column \"%s\" of relation \"%s\" is generated from an expression that references a system column such as tableoid.",
+								  NameStr(attribute->attname),
+								  RelationGetRelationName(parent_rel)));
 		}
 	}
 
@@ -23458,7 +23470,7 @@ MergePartitionsMoveRows(List **wqueue, List *mergingPartitions, Relation newPart
 	/* Generate the constraint and default execution states. */
 	estate = CreateExecutorState();
 
-	buildExpressionExecutionStates(tab, newPartRel, estate);
+	buildPartitionCheckExprStates(tab, newPartRel, estate);
 
 	mycid = GetCurrentCommandId(true);
 
@@ -23548,7 +23560,7 @@ MergePartitionsMoveRows(List **wqueue, List *mergingPartitions, Relation newPart
 			 * the new tuple.  We assume these columns won't reference each
 			 * other, so that there's no ordering dependency.
 			 */
-			evaluateGeneratedExpressionsAndCheckConstraints(tab, newPartRel,
+			checkPartitionRowConstraints(tab, newPartRel,
 															insertslot, econtext);
 
 			/* Write the tuple out to the new relation. */
@@ -24154,7 +24166,7 @@ SplitPartitionMoveRows(List **wqueue, Relation rel, Relation splitRel,
 		/* Find the work queue entry for the new partition table: newPartRel. */
 		pc->tab = ATGetQueueEntry(wqueue, pc->partRel);
 
-		buildExpressionExecutionStates(pc->tab, pc->partRel, estate);
+		buildPartitionCheckExprStates(pc->tab, pc->partRel, estate);
 
 		if (sps->bound->is_default)
 		{
@@ -24286,7 +24298,7 @@ SplitPartitionMoveRows(List **wqueue, Relation rel, Relation splitRel,
 		 * new tuple.  We assume these columns won't reference each other, so
 		 * that there's no ordering dependency.
 		 */
-		evaluateGeneratedExpressionsAndCheckConstraints(pc->tab, pc->partRel,
+		checkPartitionRowConstraints(pc->tab, pc->partRel,
 														insertslot, econtext);
 
 		/* Write the tuple out to the new relation. */
diff --git a/src/test/regress/expected/partition_merge.out b/src/test/regress/expected/partition_merge.out
index 75d06beae19..ce87d2b139a 100644
--- a/src/test/regress/expected/partition_merge.out
+++ b/src/test/regress/expected/partition_merge.out
@@ -968,8 +968,8 @@ NOTICE:  trigger(t) called: action = INSERT, when = BEFORE, level = ROW
 SELECT tableoid::regclass, * FROM t ORDER BY b;
  tableoid | i |       t        | b |     d      
 ----------+---+----------------+---+------------
- tp_0_1   | 0 | default_tp_0_1 | 1 | 01-01-2022
- tp_0_1   | 1 | default_tp_1_2 | 2 | 01-01-2022
+ tp_0_1   | 0 | default_tp_0_1 | 1 | 02-02-2022
+ tp_0_1   | 1 | default_tp_1_2 | 2 | 03-03-2022
  tp_0_1   | 1 | default_t      | 3 | 01-01-2022
 (3 rows)
 
@@ -1030,31 +1030,19 @@ ERROR:  insert or update on table "t_fk" violates foreign key constraint "t_fk_i
 DETAIL:  Key (i)=(2) is not present in table "t".
 DROP TABLE t_fk;
 DROP TABLE t;
--- Test for recomputation of stored generated columns.
+-- A stored generated column whose expression references a system column
+-- (tableoid) would have to be recomputed when a row is relocated to another
+-- partition; since the row-movement path cannot re-verify all constraints
+-- against a recomputed value, MERGE PARTITIONS is rejected instead.  (A stored
+-- generated column over user columns only is fine: its value is preserved, as
+-- exercised above.)
 CREATE TABLE t (i int, tab_id int generated always as (tableoid) stored) PARTITION BY RANGE (i);
 CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1);
 CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2);
-ALTER TABLE t ADD CONSTRAINT cc CHECK(tableoid <> 123456789);
 INSERT INTO t VALUES (0), (1);
--- Should be 0 because partition identifier for row with i=0 is different from
--- partition identifier for row with i=1.
-SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
- count 
--------
-     0
-(1 row)
-
--- "tab_id" column (stored generated column) with "tableoid" attribute requires
--- recomputation here.
-ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
--- Should be 1 because partition identifier for row with i=0 is the same as
--- partition identifier for row with i=1.
-SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
- count 
--------
-     1
-(1 row)
-
+ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;	-- fails
+ERROR:  cannot merge or split partitions when a stored generated column depends on a system column
+DETAIL:  Column "tab_id" of relation "t" is generated from an expression that references a system column such as tableoid.
 DROP TABLE t;
 -- Test for generated columns (different order of columns in partitioned table
 -- and partitions).
@@ -1168,6 +1156,30 @@ SELECT reltablespace FROM pg_class WHERE relname = 'tp_merged';
 (1 row)
 
 DROP TABLE t;
+-- MERGE PARTITIONS preserves stored generated column values rather than
+-- recomputing them (here the partitioned table's generation expression differs
+-- from what actually produced the stored rows because the function changed).
+CREATE FUNCTION merge_gen(i int) RETURNS int IMMUTABLE LANGUAGE sql AS 'SELECT i * 2';
+CREATE TABLE t (i int, g int GENERATED ALWAYS AS (merge_gen(i)) STORED)
+  PARTITION BY RANGE (i);
+CREATE TABLE tp_0_10 PARTITION OF t FOR VALUES FROM (0) TO (10);
+CREATE TABLE tp_10_20 PARTITION OF t FOR VALUES FROM (10) TO (20);
+INSERT INTO t VALUES (3), (12);
+CREATE OR REPLACE FUNCTION merge_gen(i int) RETURNS int IMMUTABLE LANGUAGE sql AS 'SELECT i * 100';
+ALTER TABLE t MERGE PARTITIONS (tp_0_10, tp_10_20) INTO tp_0_20;
+-- Existing rows keep g = i * 2 (3->6, 12->24); only a fresh insert uses the new
+-- expression (5->500).
+INSERT INTO t VALUES (5);
+SELECT i, g FROM t ORDER BY i;
+ i  |  g  
+----+-----
+  3 |   6
+  5 | 500
+ 12 |  24
+(3 rows)
+
+DROP TABLE t;
+DROP FUNCTION merge_gen(int);
 -- MERGE PARTITIONS carries over a uniform replica identity ...
 CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i);
 CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1);
diff --git a/src/test/regress/expected/partition_split.out b/src/test/regress/expected/partition_split.out
index 87374ca43ff..e2a5ba55124 100644
--- a/src/test/regress/expected/partition_split.out
+++ b/src/test/regress/expected/partition_split.out
@@ -1620,39 +1620,25 @@ NOTICE:  trigger(t) called: action = INSERT, when = BEFORE, level = ROW
 SELECT tableoid::regclass, * FROM t ORDER BY tableoid::regclass::text COLLATE "C", b;
  tableoid | i |      t       | b |     d      
 ----------+---+--------------+---+------------
- tp_0_1   | 0 | default_tp_x | 1 | 01-01-2022
- tp_x     | 1 | default_tp_x | 2 | 01-01-2022
+ tp_0_1   | 0 | default_tp_x | 1 | 02-02-2022
+ tp_x     | 1 | default_tp_x | 2 | 02-02-2022
  tp_x     | 1 | default_t    | 3 | 01-01-2022
 (3 rows)
 
 DROP TABLE t;
 DROP FUNCTION trigger_function();
--- Test for recomputation of stored generated columns.
+-- A stored generated column whose expression references a system column
+-- (tableoid) would have to be recomputed when a row is relocated to another
+-- partition; since the row-movement path cannot re-verify all constraints
+-- against a recomputed value, SPLIT PARTITION is rejected instead.
 CREATE TABLE t (i int, tab_id int generated always as (tableoid) stored) PARTITION BY RANGE (i);
 CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2);
-ALTER TABLE t ADD CONSTRAINT cc CHECK(tableoid <> 123456789);
 INSERT INTO t VALUES (0), (1);
--- Should be 1 because partition identifier for row with i=0 is the same as
--- partition identifier for row with i=1.
-SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
- count 
--------
-     1
-(1 row)
-
--- "tab_id" column (stored generated column) with "tableoid" attribute requires
--- recomputation here.
 ALTER TABLE t SPLIT PARTITION tp_0_2 INTO
   (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1),
-   PARTITION tp_1_2 FOR VALUES FROM (1) TO (2));
--- Should be 0 because partition identifier for row with i=0 is different from
--- partition identifier for row with i=1.
-SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
- count 
--------
-     0
-(1 row)
-
+   PARTITION tp_1_2 FOR VALUES FROM (1) TO (2));	-- fails
+ERROR:  cannot merge or split partitions when a stored generated column depends on a system column
+DETAIL:  Column "tab_id" of relation "t" is generated from an expression that references a system column such as tableoid.
 DROP TABLE t;
 -- Each new partition produced by SPLIT must get its own TOAST table so
 -- that out-of-line varlena attributes coming from the source partition
diff --git a/src/test/regress/sql/partition_merge.sql b/src/test/regress/sql/partition_merge.sql
index f714a1c64d5..1d44c4cbff4 100644
--- a/src/test/regress/sql/partition_merge.sql
+++ b/src/test/regress/sql/partition_merge.sql
@@ -736,25 +736,17 @@ DROP TABLE t_fk;
 DROP TABLE t;
 
 
--- Test for recomputation of stored generated columns.
+-- A stored generated column whose expression references a system column
+-- (tableoid) would have to be recomputed when a row is relocated to another
+-- partition; since the row-movement path cannot re-verify all constraints
+-- against a recomputed value, MERGE PARTITIONS is rejected instead.  (A stored
+-- generated column over user columns only is fine: its value is preserved, as
+-- exercised above.)
 CREATE TABLE t (i int, tab_id int generated always as (tableoid) stored) PARTITION BY RANGE (i);
 CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1);
 CREATE TABLE tp_1_2 PARTITION OF t FOR VALUES FROM (1) TO (2);
-ALTER TABLE t ADD CONSTRAINT cc CHECK(tableoid <> 123456789);
 INSERT INTO t VALUES (0), (1);
-
--- Should be 0 because partition identifier for row with i=0 is different from
--- partition identifier for row with i=1.
-SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
-
--- "tab_id" column (stored generated column) with "tableoid" attribute requires
--- recomputation here.
-ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;
-
--- Should be 1 because partition identifier for row with i=0 is the same as
--- partition identifier for row with i=1.
-SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
-
+ALTER TABLE t MERGE PARTITIONS (tp_0_1, tp_1_2) INTO tp_0_2;	-- fails
 DROP TABLE t;
 
 
@@ -839,6 +831,24 @@ SELECT reltablespace FROM pg_class WHERE relname = 'tp_merged';
 DROP TABLE t;
 
 
+-- MERGE PARTITIONS preserves stored generated column values rather than
+-- recomputing them (here the partitioned table's generation expression differs
+-- from what actually produced the stored rows because the function changed).
+CREATE FUNCTION merge_gen(i int) RETURNS int IMMUTABLE LANGUAGE sql AS 'SELECT i * 2';
+CREATE TABLE t (i int, g int GENERATED ALWAYS AS (merge_gen(i)) STORED)
+  PARTITION BY RANGE (i);
+CREATE TABLE tp_0_10 PARTITION OF t FOR VALUES FROM (0) TO (10);
+CREATE TABLE tp_10_20 PARTITION OF t FOR VALUES FROM (10) TO (20);
+INSERT INTO t VALUES (3), (12);
+CREATE OR REPLACE FUNCTION merge_gen(i int) RETURNS int IMMUTABLE LANGUAGE sql AS 'SELECT i * 100';
+ALTER TABLE t MERGE PARTITIONS (tp_0_10, tp_10_20) INTO tp_0_20;
+-- Existing rows keep g = i * 2 (3->6, 12->24); only a fresh insert uses the new
+-- expression (5->500).
+INSERT INTO t VALUES (5);
+SELECT i, g FROM t ORDER BY i;
+DROP TABLE t;
+DROP FUNCTION merge_gen(int);
+
 -- MERGE PARTITIONS carries over a uniform replica identity ...
 CREATE TABLE t (i int PRIMARY KEY) PARTITION BY RANGE (i);
 CREATE TABLE tp_0_1 PARTITION OF t FOR VALUES FROM (0) TO (1);
diff --git a/src/test/regress/sql/partition_split.sql b/src/test/regress/sql/partition_split.sql
index 8734419e754..82a2fc50f1b 100644
--- a/src/test/regress/sql/partition_split.sql
+++ b/src/test/regress/sql/partition_split.sql
@@ -1162,26 +1162,16 @@ DROP TABLE t;
 DROP FUNCTION trigger_function();
 
 
--- Test for recomputation of stored generated columns.
+-- A stored generated column whose expression references a system column
+-- (tableoid) would have to be recomputed when a row is relocated to another
+-- partition; since the row-movement path cannot re-verify all constraints
+-- against a recomputed value, SPLIT PARTITION is rejected instead.
 CREATE TABLE t (i int, tab_id int generated always as (tableoid) stored) PARTITION BY RANGE (i);
 CREATE TABLE tp_0_2 PARTITION OF t FOR VALUES FROM (0) TO (2);
-ALTER TABLE t ADD CONSTRAINT cc CHECK(tableoid <> 123456789);
 INSERT INTO t VALUES (0), (1);
-
--- Should be 1 because partition identifier for row with i=0 is the same as
--- partition identifier for row with i=1.
-SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
-
--- "tab_id" column (stored generated column) with "tableoid" attribute requires
--- recomputation here.
 ALTER TABLE t SPLIT PARTITION tp_0_2 INTO
   (PARTITION tp_0_1 FOR VALUES FROM (0) TO (1),
-   PARTITION tp_1_2 FOR VALUES FROM (1) TO (2));
-
--- Should be 0 because partition identifier for row with i=0 is different from
--- partition identifier for row with i=1.
-SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i = 1);
-
+   PARTITION tp_1_2 FOR VALUES FROM (1) TO (2));	-- fails
 DROP TABLE t;
 
 -- Each new partition produced by SPLIT must get its own TOAST table so
-- 
2.50.1 (Apple Git-155)

