Checks of v17-0001 and v17-0002 (with 0003-0005 where noted) on master 77bba5454f9, --with-lz4 --with-zstd Builds: cassert -O2, and cassert + ASan -O1. With the cassert -O2 build each prefix (0001, 0001-0002, ... 0001-0005) builds without warnings and passes make check, and 0001-0004 passes without 0005. 1. 0001 changes no behavior toast-oracle.sh.txt fills a cluster with pglz and lz4 values (out of line compressed and uncompressed, in line compressed; text and bytea; sizes 1992 and 1996 bytes +-2 times 1 to 3, and up to 1 MB) and reads them with md5(text), length(), octet_length(), substr() and substring() at offsets and lengths around the chunk size, left(), pg_column_size(), pg_column_compression(), pg_column_toast_chunk_id(), verify_heapam(check_toast => true) and test_decoding (INSERT, UPDATE, DELETE with REPLICA IDENTITY FULL). 58,990 output lines. master vs master+0001, cassert build: identical master vs master+0001, ASan build: identical, no ASan report Each of these wrong conversions, put on top of 0001, changes the output (number of lines that differ from master): toast_datum_size() returns rawsize - VARHDRSZ, not extsize 57 amcheck keeps rawsize - VARHDRSZ as the saved size 192 toast_fetch_datum_slice() treats every value as uncompressed 12033 toast_external_info_get() keeps the method bits of an uncompressed value instead of TOAST_INVALID_COMPRESSION_ID 113 2. 0002: which test fails when one check is reverted On 0001-0003, cassert. t/007 is amcheck/t/007_verify_toast_pointer.pl. toast_compression_id_resolve(), plain form: method_bits == VARLENA_COMPRESS_METHOD_LONG t/007 (the Assert in toast_compression_id_needs_cmid_byte() fires) needs_cmid_byte(method_bits) t/007, 2 tests toast_compression_id_resolve(), long form: method_bits != VARLENA_COMPRESS_METHOD_LONG t/007, 2 tests method_byte == TOAST_INVALID_COMPRESSION_ID none method_byte > TOAST_LAST_COMPRESSION_ID t/007, 2 tests !needs_cmid_byte(method_byte) t/007, 2 tests toast_external_info_get(): not compressed -> no method make check (4 tests) and amcheck check_heap toast_get_compression_id(): ERROR for a compressed pointer with no method t/007, 5 tests The byte == TOAST_INVALID_COMPRESSION_ID test cannot matter on 0001-0003, where 3 is above the last ID (2). From 0004 on the last ID is 5, and it is the only thing that rejects a long pointer with byte 3 before toast_compression_id_needs_cmid_byte() asserts on it. On 0001-0005 with that test removed, t/007 still passes, and the byte-3 row of forge-pointers.sh.txt gives (section 3): TRAP: failed Assert("cmid != TOAST_INVALID_COMPRESSION_ID"), File: "src/include/access/toast_compression.h", Line: 76 (8 TRAP lines: one per reader of that row, and one for verify_heapam() on the zstd table, which then returns nothing; the readers of the other 268 rows give the same results as in section 3) Without asserts the function would return 3, which is TOAST_INVALID_COMPRESSION_ID anyway, so only assert-enabled builds see it. A byte-3 case in the long-form part of t/007 would cover it. 3. Forged pointers forge-pointers.sh.txt rewrites, with the server stopped, the method bits, the method byte or the saved size of one pointer per row, and then reads every row. On 0001-0003 and on 0001-0005, cassert + ASan: 269 rows, no crash, no ASan report, no TRAP. On 0001-0005: zstd, method byte 0,1,3,6..255 (253 rows) amcheck: has a long TOAST pointer with invalid compression method id pg_column_compression(): ERROR: compressed data is corrupt text, length(), substr(), left(): the value, intact zstd, method byte 4 or 5 amcheck: nothing pg_column_compression(): zstd_dict / lz4_dict text, length(), substr(), left(): the value, intact zstd, method bits 0, 1, 2 amcheck: has a long TOAST pointer but compression method bits pg_column_compression(): ERROR: compressed data is corrupt text, length(), substr(), left(): the value, intact zstd, saved size = rawsize - 4, or = rawsize ("not compressed") amcheck: ... is not compressed but has a long TOAST pointer (plus the chunk size messages) pg_column_compression(): NULL reads: ERROR: unexpected chunk size zstd, saved size -1 or +1 amcheck and reads: unexpected chunk size pglz and lz4, method bits 2 or 3 amcheck: has invalid compression method id 2 / 3 pg_column_compression(): ERROR: compressed data is corrupt text, length(), substr(), left(): the value, intact On 0001-0003 the same, except that bytes 4 and 5 are rejected like the other unknown bytes. So the pointer checks are seen by amcheck and pg_column_compression() only: the data readers take the method from the header stored with the data, and return the value whatever the pointer says. The one case no check catches is a long pointer naming the other long-form methods (byte 4 or 5 on a plain zstd value); amcheck already reads every chunk of the value and could compare the pointer's method with the stored header's. I have not tried that. 4. Old data upgrade-check.sh.txt, with toast-oracle.sh.txt: master data directory, read by master, then by 0001-0002 and by 0001-0003 (same catalog version): identical, 58,990 lines REL_18_STABLE (18.7) and REL_19_STABLE (19rc2), release builds, pg_upgrade --copy to 0001-0003: identical, 58,752 lines (no logical slot here: pg_upgrade refuses one with pending changes) then zstd values written next to the old ones: verify_heapam() finds nothing in any table, and the new values read back. Both for the cassert and the ASan builds of 0001-0002 / 0001-0003.