From 77541f961f12318a6f365efb2b93f974541cdc71 Mon Sep 17 00:00:00 2001 From: Michael Paquier Date: Thu, 14 Aug 2025 13:43:19 +0900 Subject: [PATCH v9 14/15] Add tests for TOAST relations with bigint as value type This adds coverage for relations created with default_toast_type = 'int8', for external TOAST pointers both compressed and uncompressed. --- src/test/regress/expected/strings.out | 231 ++++++++++++++++++---- src/test/regress/expected/type_sanity.out | 6 +- src/test/regress/sql/strings.sql | 134 +++++++++---- src/test/regress/sql/type_sanity.sql | 6 +- 4 files changed, 296 insertions(+), 81 deletions(-) diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out index 727304f60e74..ed1921b32280 100644 --- a/src/test/regress/expected/strings.out +++ b/src/test/regress/expected/strings.out @@ -2012,21 +2012,37 @@ SELECT text 'text' || varchar ' and varchar' AS "Concat text to varchar"; (1 row) -- --- test substr with toasted text values +-- Test substr with toasted text values, for all types of TOAST relations +-- supported. -- -CREATE TABLE toasttest(f1 text); -insert into toasttest values(repeat('1234567890',10000)); -insert into toasttest values(repeat('1234567890',10000)); +CREATE TABLE toasttest_oid(f1 text) with (toast_value_type = 'oid'); +CREATE TABLE toasttest_oid8(f1 text) with (toast_value_type = 'oid8'); +insert into toasttest_oid values(repeat('1234567890',10000)); +insert into toasttest_oid values(repeat('1234567890',10000)); +insert into toasttest_oid8 values(repeat('1234567890',10000)); +insert into toasttest_oid8 values(repeat('1234567890',10000)); -- -- Ensure that some values are uncompressed, to test the faster substring -- operation used in that case -- -alter table toasttest alter column f1 set storage external; -insert into toasttest values(repeat('1234567890',10000)); -insert into toasttest values(repeat('1234567890',10000)); +alter table toasttest_oid alter column f1 set storage external; +insert into toasttest_oid values(repeat('1234567890',10000)); +insert into toasttest_oid values(repeat('1234567890',10000)); +alter table toasttest_oid8 alter column f1 set storage external; +insert into toasttest_oid8 values(repeat('1234567890',10000)); +insert into toasttest_oid8 values(repeat('1234567890',10000)); -- If the starting position is zero or less, then return from the start of the string -- adjusting the length to be consistent with the "negative start" per SQL. -SELECT substr(f1, -1, 5) from toasttest; +SELECT substr(f1, -1, 5) from toasttest_oid; + substr +-------- + 123 + 123 + 123 + 123 +(4 rows) + +SELECT substr(f1, -1, 5) from toasttest_oid8; substr -------- 123 @@ -2036,11 +2052,22 @@ SELECT substr(f1, -1, 5) from toasttest; (4 rows) -- If the length is less than zero, an ERROR is thrown. -SELECT substr(f1, 5, -1) from toasttest; +SELECT substr(f1, 5, -1) from toasttest_oid; +ERROR: negative substring length not allowed +SELECT substr(f1, 5, -1) from toasttest_oid8; ERROR: negative substring length not allowed -- If no third argument (length) is provided, the length to the end of the -- string is assumed. -SELECT substr(f1, 99995) from toasttest; +SELECT substr(f1, 99995) from toasttest_oid; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +SELECT substr(f1, 99995) from toasttest_oid8; substr -------- 567890 @@ -2051,7 +2078,7 @@ SELECT substr(f1, 99995) from toasttest; -- If start plus length is > string length, the result is truncated to -- string length -SELECT substr(f1, 99995, 10) from toasttest; +SELECT substr(f1, 99995, 10) from toasttest_oid; substr -------- 567890 @@ -2060,50 +2087,105 @@ SELECT substr(f1, 99995, 10) from toasttest; 567890 (4 rows) -TRUNCATE TABLE toasttest; -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); +SELECT substr(f1, 99995, 10) from toasttest_oid8; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +-- TRUNCATE cases for TOAST relations with OID values. +TRUNCATE TABLE toasttest_oid; +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); -- expect >0 blocks SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty - FROM pg_class where relname = 'toasttest'; + FROM pg_class where relname = 'toasttest_oid'; is_empty ---------- f (1 row) -TRUNCATE TABLE toasttest; -ALTER TABLE toasttest set (toast_tuple_target = 4080); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); +TRUNCATE TABLE toasttest_oid; +ALTER TABLE toasttest_oid set (toast_tuple_target = 4080); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); -- expect 0 blocks SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty - FROM pg_class where relname = 'toasttest'; + FROM pg_class where relname = 'toasttest_oid'; is_empty ---------- t (1 row) -DROP TABLE toasttest; +DROP TABLE toasttest_oid; +-- TRUNCATE cases for TOAST relation with int8 values. +TRUNCATE TABLE toasttest_oid8; +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +-- expect >0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest_oid8'; + is_empty +---------- + f +(1 row) + +TRUNCATE TABLE toasttest_oid8; +ALTER TABLE toasttest_oid8 set (toast_tuple_target = 4080); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +-- expect 0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest_oid8'; + is_empty +---------- + t +(1 row) + +DROP TABLE toasttest_oid8; -- --- test substr with toasted bytea values +-- test substr with toasted bytea values, for all types of TOAST relations +-- supported. Do not drop these two relations, for pg_upgrade. -- -CREATE TABLE toasttest(f1 bytea); -insert into toasttest values(decode(repeat('1234567890',10000),'escape')); -insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +CREATE TABLE toasttest_oid(f1 bytea) WITH (toast_value_type = 'oid'); +CREATE TABLE toasttest_oid8(f1 bytea) WITH (toast_value_type = 'oid8'); +insert into toasttest_oid values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid8 values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid8 values(decode(repeat('1234567890',10000),'escape')); -- -- Ensure that some values are uncompressed, to test the faster substring -- operation used in that case -- -alter table toasttest alter column f1 set storage external; -insert into toasttest values(decode(repeat('1234567890',10000),'escape')); -insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +alter table toasttest_oid alter column f1 set storage external; +insert into toasttest_oid values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid values(decode(repeat('1234567890',10000),'escape')); +alter table toasttest_oid8 alter column f1 set storage external; +insert into toasttest_oid8 values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid8 values(decode(repeat('1234567890',10000),'escape')); -- If the starting position is zero or less, then return from the start of the string -- adjusting the length to be consistent with the "negative start" per SQL. -SELECT substr(f1, -1, 5) from toasttest; +SELECT substr(f1, -1, 5) from toasttest_oid; + substr +-------- + 123 + 123 + 123 + 123 +(4 rows) + +SELECT substr(f1, -1, 5) from toasttest_oid8; substr -------- 123 @@ -2113,11 +2195,22 @@ SELECT substr(f1, -1, 5) from toasttest; (4 rows) -- If the length is less than zero, an ERROR is thrown. -SELECT substr(f1, 5, -1) from toasttest; +SELECT substr(f1, 5, -1) from toasttest_oid; +ERROR: negative substring length not allowed +SELECT substr(f1, 5, -1) from toasttest_oid8; ERROR: negative substring length not allowed -- If no third argument (length) is provided, the length to the end of the -- string is assumed. -SELECT substr(f1, 99995) from toasttest; +SELECT substr(f1, 99995) from toasttest_oid; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +SELECT substr(f1, 99995) from toasttest_oid8; substr -------- 567890 @@ -2128,7 +2221,72 @@ SELECT substr(f1, 99995) from toasttest; -- If start plus length is > string length, the result is truncated to -- string length -SELECT substr(f1, 99995, 10) from toasttest; +SELECT substr(f1, 99995, 10) from toasttest_oid; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +SELECT substr(f1, 99995, 10) from toasttest_oid8; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +-- A relation rewrite leaves the TOAST value attributes unchanged. +VACUUM FULL toasttest_oid; +VACUUM FULL toasttest_oid8; +SELECT c1.relname, a.atttypid::regtype + FROM pg_attribute AS a, + pg_class AS c1, + pg_class AS c2 + WHERE + c1.relname IN ('toasttest_oid', 'toasttest_oid8') AND + c1.reltoastrelid = c2.oid AND + a.attrelid = c2.oid AND + a.attname = 'chunk_id' + ORDER BY c1.relname COLLATE "C"; + relname | atttypid +----------------+---------- + toasttest_oid | oid + toasttest_oid8 | oid8 +(2 rows) + +-- Check that data slices are still accessible. +SELECT substr(f1, 99995) from toasttest_oid; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +SELECT substr(f1, 99995) from toasttest_oid8; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +SELECT substr(f1, 99995, 10) from toasttest_oid; + substr +-------- + 567890 + 567890 + 567890 + 567890 +(4 rows) + +SELECT substr(f1, 99995, 10) from toasttest_oid8; substr -------- 567890 @@ -2137,7 +2295,6 @@ SELECT substr(f1, 99995, 10) from toasttest; 567890 (4 rows) -DROP TABLE toasttest; -- test internally compressing datums -- this tests compressing a datum to a very small size which exercises a -- corner case in packed-varlena handling: even though small, the compressed diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out index 9ddcacec6bf4..88faa57772c3 100644 --- a/src/test/regress/expected/type_sanity.out +++ b/src/test/regress/expected/type_sanity.out @@ -578,15 +578,15 @@ WHERE c1.relnatts != (SELECT count(*) FROM pg_attribute AS a1 (0 rows) -- Cross-check against pg_type entry --- NOTE: we allow attstorage to be 'plain' even when typstorage is not; --- this is mainly for toast tables. +-- NOTE: we allow attstorage to be 'plain' or 'external' even when typstorage +-- is not; this is mainly for toast tables. SELECT a1.attrelid, a1.attname, t1.oid, t1.typname FROM pg_attribute AS a1, pg_type AS t1 WHERE a1.atttypid = t1.oid AND (a1.attlen != t1.typlen OR a1.attalign != t1.typalign OR a1.attbyval != t1.typbyval OR - (a1.attstorage != t1.typstorage AND a1.attstorage != 'p')); + (a1.attstorage != t1.typstorage AND a1.attstorage NOT IN ('e', 'p'))); attrelid | attname | oid | typname ----------+---------+-----+--------- (0 rows) diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql index 88aa4c2983ba..5c97f5e72eb5 100644 --- a/src/test/regress/sql/strings.sql +++ b/src/test/regress/sql/strings.sql @@ -572,89 +572,147 @@ SELECT text 'text' || char(20) ' and characters' AS "Concat text to char"; SELECT text 'text' || varchar ' and varchar' AS "Concat text to varchar"; -- --- test substr with toasted text values +-- Test substr with toasted text values, for all types of TOAST relations +-- supported. -- -CREATE TABLE toasttest(f1 text); +CREATE TABLE toasttest_oid(f1 text) with (toast_value_type = 'oid'); +CREATE TABLE toasttest_oid8(f1 text) with (toast_value_type = 'oid8'); -insert into toasttest values(repeat('1234567890',10000)); -insert into toasttest values(repeat('1234567890',10000)); +insert into toasttest_oid values(repeat('1234567890',10000)); +insert into toasttest_oid values(repeat('1234567890',10000)); +insert into toasttest_oid8 values(repeat('1234567890',10000)); +insert into toasttest_oid8 values(repeat('1234567890',10000)); -- -- Ensure that some values are uncompressed, to test the faster substring -- operation used in that case -- -alter table toasttest alter column f1 set storage external; -insert into toasttest values(repeat('1234567890',10000)); -insert into toasttest values(repeat('1234567890',10000)); +alter table toasttest_oid alter column f1 set storage external; +insert into toasttest_oid values(repeat('1234567890',10000)); +insert into toasttest_oid values(repeat('1234567890',10000)); +alter table toasttest_oid8 alter column f1 set storage external; +insert into toasttest_oid8 values(repeat('1234567890',10000)); +insert into toasttest_oid8 values(repeat('1234567890',10000)); -- If the starting position is zero or less, then return from the start of the string -- adjusting the length to be consistent with the "negative start" per SQL. -SELECT substr(f1, -1, 5) from toasttest; +SELECT substr(f1, -1, 5) from toasttest_oid; +SELECT substr(f1, -1, 5) from toasttest_oid8; -- If the length is less than zero, an ERROR is thrown. -SELECT substr(f1, 5, -1) from toasttest; +SELECT substr(f1, 5, -1) from toasttest_oid; +SELECT substr(f1, 5, -1) from toasttest_oid8; -- If no third argument (length) is provided, the length to the end of the -- string is assumed. -SELECT substr(f1, 99995) from toasttest; +SELECT substr(f1, 99995) from toasttest_oid; +SELECT substr(f1, 99995) from toasttest_oid8; -- If start plus length is > string length, the result is truncated to -- string length -SELECT substr(f1, 99995, 10) from toasttest; +SELECT substr(f1, 99995, 10) from toasttest_oid; +SELECT substr(f1, 99995, 10) from toasttest_oid8; -TRUNCATE TABLE toasttest; -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); +-- TRUNCATE cases for TOAST relations with OID values. +TRUNCATE TABLE toasttest_oid; +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); -- expect >0 blocks SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty - FROM pg_class where relname = 'toasttest'; - -TRUNCATE TABLE toasttest; -ALTER TABLE toasttest set (toast_tuple_target = 4080); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); -INSERT INTO toasttest values (repeat('1234567890',300)); + FROM pg_class where relname = 'toasttest_oid'; +TRUNCATE TABLE toasttest_oid; +ALTER TABLE toasttest_oid set (toast_tuple_target = 4080); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); +INSERT INTO toasttest_oid values (repeat('1234567890',300)); -- expect 0 blocks SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty - FROM pg_class where relname = 'toasttest'; + FROM pg_class where relname = 'toasttest_oid'; +DROP TABLE toasttest_oid; -DROP TABLE toasttest; +-- TRUNCATE cases for TOAST relation with int8 values. +TRUNCATE TABLE toasttest_oid8; +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +-- expect >0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest_oid8'; +TRUNCATE TABLE toasttest_oid8; +ALTER TABLE toasttest_oid8 set (toast_tuple_target = 4080); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +INSERT INTO toasttest_oid8 values (repeat('1234567890',300)); +-- expect 0 blocks +SELECT pg_relation_size(reltoastrelid) = 0 AS is_empty + FROM pg_class where relname = 'toasttest_oid8'; +DROP TABLE toasttest_oid8; -- --- test substr with toasted bytea values +-- test substr with toasted bytea values, for all types of TOAST relations +-- supported. Do not drop these two relations, for pg_upgrade. -- -CREATE TABLE toasttest(f1 bytea); +CREATE TABLE toasttest_oid(f1 bytea) WITH (toast_value_type = 'oid'); +CREATE TABLE toasttest_oid8(f1 bytea) WITH (toast_value_type = 'oid8'); -insert into toasttest values(decode(repeat('1234567890',10000),'escape')); -insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid8 values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid8 values(decode(repeat('1234567890',10000),'escape')); -- -- Ensure that some values are uncompressed, to test the faster substring -- operation used in that case -- -alter table toasttest alter column f1 set storage external; -insert into toasttest values(decode(repeat('1234567890',10000),'escape')); -insert into toasttest values(decode(repeat('1234567890',10000),'escape')); +alter table toasttest_oid alter column f1 set storage external; +insert into toasttest_oid values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid values(decode(repeat('1234567890',10000),'escape')); +alter table toasttest_oid8 alter column f1 set storage external; +insert into toasttest_oid8 values(decode(repeat('1234567890',10000),'escape')); +insert into toasttest_oid8 values(decode(repeat('1234567890',10000),'escape')); -- If the starting position is zero or less, then return from the start of the string -- adjusting the length to be consistent with the "negative start" per SQL. -SELECT substr(f1, -1, 5) from toasttest; +SELECT substr(f1, -1, 5) from toasttest_oid; +SELECT substr(f1, -1, 5) from toasttest_oid8; -- If the length is less than zero, an ERROR is thrown. -SELECT substr(f1, 5, -1) from toasttest; +SELECT substr(f1, 5, -1) from toasttest_oid; +SELECT substr(f1, 5, -1) from toasttest_oid8; -- If no third argument (length) is provided, the length to the end of the -- string is assumed. -SELECT substr(f1, 99995) from toasttest; +SELECT substr(f1, 99995) from toasttest_oid; +SELECT substr(f1, 99995) from toasttest_oid8; -- If start plus length is > string length, the result is truncated to -- string length -SELECT substr(f1, 99995, 10) from toasttest; +SELECT substr(f1, 99995, 10) from toasttest_oid; +SELECT substr(f1, 99995, 10) from toasttest_oid8; -DROP TABLE toasttest; +-- A relation rewrite leaves the TOAST value attributes unchanged. +VACUUM FULL toasttest_oid; +VACUUM FULL toasttest_oid8; +SELECT c1.relname, a.atttypid::regtype + FROM pg_attribute AS a, + pg_class AS c1, + pg_class AS c2 + WHERE + c1.relname IN ('toasttest_oid', 'toasttest_oid8') AND + c1.reltoastrelid = c2.oid AND + a.attrelid = c2.oid AND + a.attname = 'chunk_id' + ORDER BY c1.relname COLLATE "C"; +-- Check that data slices are still accessible. +SELECT substr(f1, 99995) from toasttest_oid; +SELECT substr(f1, 99995) from toasttest_oid8; +SELECT substr(f1, 99995, 10) from toasttest_oid; +SELECT substr(f1, 99995, 10) from toasttest_oid8; -- test internally compressing datums diff --git a/src/test/regress/sql/type_sanity.sql b/src/test/regress/sql/type_sanity.sql index c2496823d90e..a0d2e8bcf00b 100644 --- a/src/test/regress/sql/type_sanity.sql +++ b/src/test/regress/sql/type_sanity.sql @@ -420,8 +420,8 @@ WHERE c1.relnatts != (SELECT count(*) FROM pg_attribute AS a1 WHERE a1.attrelid = c1.oid AND a1.attnum > 0); -- Cross-check against pg_type entry --- NOTE: we allow attstorage to be 'plain' even when typstorage is not; --- this is mainly for toast tables. +-- NOTE: we allow attstorage to be 'plain' or 'external' even when typstorage +-- is not; this is mainly for toast tables. SELECT a1.attrelid, a1.attname, t1.oid, t1.typname FROM pg_attribute AS a1, pg_type AS t1 @@ -429,7 +429,7 @@ WHERE a1.atttypid = t1.oid AND (a1.attlen != t1.typlen OR a1.attalign != t1.typalign OR a1.attbyval != t1.typbyval OR - (a1.attstorage != t1.typstorage AND a1.attstorage != 'p')); + (a1.attstorage != t1.typstorage AND a1.attstorage NOT IN ('e', 'p'))); -- Look for IsCatalogTextUniqueIndexOid() omissions. -- 2.51.0