diff --git a/src/backend/commands/tablecmds.c b/src/backend/commands/tablecmds.c index 38f9ffcd04f..265dcfe7fda 100644 --- a/src/backend/commands/tablecmds.c +++ b/src/backend/commands/tablecmds.c @@ -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. diff --git a/src/test/regress/expected/partition_merge.out b/src/test/regress/expected/partition_merge.out index d3818f1bf9b..4f42afc3dc7 100644 --- a/src/test/regress/expected/partition_merge.out +++ b/src/test/regress/expected/partition_merge.out @@ -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; -- diff --git a/src/test/regress/expected/partition_split.out b/src/test/regress/expected/partition_split.out index ff6027af658..faaf32ed20a 100644 --- a/src/test/regress/expected/partition_split.out +++ b/src/test/regress/expected/partition_split.out @@ -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; -- diff --git a/src/test/regress/sql/partition_merge.sql b/src/test/regress/sql/partition_merge.sql index 1e14ed40f5c..4c8c625f97b 100644 --- a/src/test/regress/sql/partition_merge.sql +++ b/src/test/regress/sql/partition_merge.sql @@ -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; diff --git a/src/test/regress/sql/partition_split.sql b/src/test/regress/sql/partition_split.sql index 05de24152d1..9e44aa9caf0 100644 --- a/src/test/regress/sql/partition_split.sql +++ b/src/test/regress/sql/partition_split.sql @@ -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;