]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Create TOAST table for partitions made by MERGE/SPLIT PARTITION
authorAlexander Korotkov <akorotkov@postgresql.org>
Thu, 18 Jun 2026 07:30:14 +0000 (10:30 +0300)
committerAlexander Korotkov <akorotkov@postgresql.org>
Thu, 18 Jun 2026 07:30:14 +0000 (10:30 +0300)
ALTER TABLE ... MERGE PARTITIONS / SPLIT PARTITION builds a new
partition via createPartitionTable(), but never gives it a TOAST table.
When the source rows carried out-of-line varlena values, the move
into the new partition entered heap_toast_insert_or_update() with
reltoastrelid = InvalidOid: the externalization step is skipped, the
value falls back to inline storage and heap_insert() fails with
"row is too big" error.  Also, TOAST table is needed if the new partition
receives out-of-line varlena values after the DDL operation is complete.

Call NewRelationCreateToastTable() right after the new partition is
created in createPartitionTable(), mirroring what DefineRelation()
does for regular CREATE TABLE.  NewRelationCreateToastTable() decides
on its own whether a TOAST table is actually required, so partitions
with no toast-eligible columns are unaffected.

Reported-by: Justin Pryzby <pryzby@telsasoft.com>
Discussion: https://postgr.es/m/ai_c4-v8iLA2kXFV%40pryzbyj2023
Reviewed-by: Pavel Borisov <pashkin.elfe@gmail.com>
Reviewed-by: Jian He <jian.universality@gmail.com>
src/backend/commands/tablecmds.c
src/test/regress/expected/partition_merge.out
src/test/regress/expected/partition_split.out
src/test/regress/sql/partition_merge.sql
src/test/regress/sql/partition_split.sql

index 38f9ffcd04f7e7a95a4917bc7a2733ef866bb949..265dcfe7fda45df28e5ee76c0506a1e514ca2540 100644 (file)
@@ -22815,6 +22815,15 @@ createPartitionTable(List **wqueue, RangeVar *newPartName,
         */
        CommandCounterIncrement();
 
+       /*
+        * Create a TOAST table if the table needs one.  MERGE/SPLIT PARTITION
+        * moves rows from existing partition(s) into new partition(s), which may
+        * carry out-of-line varlena values that the new relation must be able to
+        * store.  Also, the new partition must be able to receive out-of-line
+        * varlena values after the DDL operation is complete.
+        */
+       NewRelationCreateToastTable(newRelId, (Datum) 0);
+
        /*
         * Open the new partition with no lock, because we already have an
         * AccessExclusiveLock placed there after creation.
index d3818f1bf9bf2d5399d25eb39f01e942dddb3a24..4f42afc3dc7fde5fc0e838c9a3ff5ccc1ae5a006 100644 (file)
@@ -1088,6 +1088,32 @@ SELECT count(*) FROM t WHERE i = 15 AND g IN (SELECT g + 10 FROM t WHERE i = 5);
      1
 (1 row)
 
+DROP TABLE t;
+-- A merged partition needs its own TOAST table; otherwise an out-of-line
+-- varlena value carried over from one of the merging partitions has
+-- nowhere to be stored.  SET STORAGE EXTERNAL forces externalization
+-- for any value over the TOAST threshold, so a string over that threshold
+-- suffices to exercise the toast-table dependency.
+CREATE TABLE t (a text) PARTITION BY RANGE(a);
+ALTER TABLE t ALTER COLUMN a SET STORAGE EXTERNAL;
+CREATE TABLE tp_def PARTITION OF t DEFAULT;
+CREATE TABLE tp_2_3 PARTITION OF t FOR VALUES FROM ('2') TO ('3');
+INSERT INTO t SELECT repeat('1', 10000);
+ALTER TABLE t MERGE PARTITIONS (tp_def, tp_2_3) INTO tp_merged;
+SELECT reltoastrelid <> 0 AS has_toast,
+       pg_relation_size(reltoastrelid) > 0 AS toast_used
+  FROM pg_class WHERE relname = 'tp_merged';
+ has_toast | toast_used 
+-----------+------------
+ t         | t
+(1 row)
+
+SELECT length(a) FROM t;
+ length 
+--------
+  10000
+(1 row)
+
 DROP TABLE t;
 RESET search_path;
 --
index ff6027af6580995d2e8f26c73582549fbfd1555c..faaf32ed20a56f7ab83a0baa99e10108a6dbd8e1 100644 (file)
@@ -1653,6 +1653,36 @@ SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i =
      0
 (1 row)
 
+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
+-- can be stored.  SET STORAGE EXTERNAL forces externalization for any
+-- value over the TOAST threshold, so a string over that threshold
+-- suffices to exercise the toast-table dependency.
+CREATE TABLE t (a text) PARTITION BY RANGE(a);
+ALTER TABLE t ALTER COLUMN a SET STORAGE EXTERNAL;
+CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (MINVALUE) TO (MAXVALUE);
+INSERT INTO t SELECT repeat('1', 10000);
+ALTER TABLE t SPLIT PARTITION tp_all INTO (
+    PARTITION tp_lo FOR VALUES FROM (MINVALUE) TO ('2'),
+    PARTITION tp_hi FOR VALUES FROM ('2') TO (MAXVALUE)
+);
+SELECT relname,
+       reltoastrelid <> 0 AS has_toast,
+       pg_relation_size(reltoastrelid) > 0 AS toast_used
+  FROM pg_class WHERE relname IN ('tp_lo', 'tp_hi') ORDER BY relname;
+ relname | has_toast | toast_used 
+---------+-----------+------------
+ tp_hi   | t         | f
+ tp_lo   | t         | t
+(2 rows)
+
+SELECT length(a) FROM t;
+ length 
+--------
+  10000
+(1 row)
+
 DROP TABLE t;
 RESET search_path;
 --
index 1e14ed40f5c9a1d6a6e4fc20418d0f7fecd17d64..4c8c625f97b6a74cff5869abbb4f71fbc08f3e52 100644 (file)
@@ -781,6 +781,23 @@ SELECT count(*) FROM t WHERE i = 15 AND g IN (SELECT g + 10 FROM t WHERE i = 5);
 
 DROP TABLE t;
 
+-- A merged partition needs its own TOAST table; otherwise an out-of-line
+-- varlena value carried over from one of the merging partitions has
+-- nowhere to be stored.  SET STORAGE EXTERNAL forces externalization
+-- for any value over the TOAST threshold, so a string over that threshold
+-- suffices to exercise the toast-table dependency.
+CREATE TABLE t (a text) PARTITION BY RANGE(a);
+ALTER TABLE t ALTER COLUMN a SET STORAGE EXTERNAL;
+CREATE TABLE tp_def PARTITION OF t DEFAULT;
+CREATE TABLE tp_2_3 PARTITION OF t FOR VALUES FROM ('2') TO ('3');
+INSERT INTO t SELECT repeat('1', 10000);
+ALTER TABLE t MERGE PARTITIONS (tp_def, tp_2_3) INTO tp_merged;
+SELECT reltoastrelid <> 0 AS has_toast,
+       pg_relation_size(reltoastrelid) > 0 AS toast_used
+  FROM pg_class WHERE relname = 'tp_merged';
+SELECT length(a) FROM t;
+DROP TABLE t;
+
 
 RESET search_path;
 
index 05de24152d1739bf3b422f18de64bd69e6195f51..9e44aa9caf045612f71453ca99fc2ce454c8d787 100644 (file)
@@ -1184,6 +1184,25 @@ SELECT count(*) FROM t WHERE i = 0 AND tab_id IN (SELECT tab_id FROM t WHERE i =
 
 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
+-- can be stored.  SET STORAGE EXTERNAL forces externalization for any
+-- value over the TOAST threshold, so a string over that threshold
+-- suffices to exercise the toast-table dependency.
+CREATE TABLE t (a text) PARTITION BY RANGE(a);
+ALTER TABLE t ALTER COLUMN a SET STORAGE EXTERNAL;
+CREATE TABLE tp_all PARTITION OF t FOR VALUES FROM (MINVALUE) TO (MAXVALUE);
+INSERT INTO t SELECT repeat('1', 10000);
+ALTER TABLE t SPLIT PARTITION tp_all INTO (
+    PARTITION tp_lo FOR VALUES FROM (MINVALUE) TO ('2'),
+    PARTITION tp_hi FOR VALUES FROM ('2') TO (MAXVALUE)
+);
+SELECT relname,
+       reltoastrelid <> 0 AS has_toast,
+       pg_relation_size(reltoastrelid) > 0 AS toast_used
+  FROM pg_class WHERE relname IN ('tp_lo', 'tp_hi') ORDER BY relname;
+SELECT length(a) FROM t;
+DROP TABLE t;
 
 RESET search_path;