From 50eb953c7def5ce380bbe6664b4e56edd0ca1d57 Mon Sep 17 00:00:00 2001 From: Kirill Reshke Date: Sat, 24 Oct 2026 16:05:13 +0500 Subject: [PATCH] Restore fault hooks and legacy TOAST chunk reads Restore the TOAST chunk-size, WindowAgg spool, and diskquota error hooks used by regression tests. Read legacy TOAST values using their actual chunk size, and make tests check that the requested faults fired. Reject malformed and out-of-range GPFDIST_WATCHDOG_TIMER values before converting them to int. Add CLI regression coverage and update affected expected results, including the PAX test copies. Validation on the prepared server build: toast, misc_jiras, statement_mem_for_windowagg, test_fetch_table_stat, and the gpfdist watchdog target passed. Additional checks covered 936 TOAST slices and compressed values. A clean build of the current main branch was not run. --- .../src/test/regress/expected/misc_jiras.out | 3 + .../src/test/regress/expected/toast.out | 21 +++++ .../src/test/regress/sql/toast.sql | 11 +++ gpcontrib/diskquota/src/gp_activetable.c | 4 +- .../expected/test_fetch_table_stat.out | 14 ++-- .../regress/sql/test_fetch_table_stat.sql | 6 +- src/backend/access/common/toast_internals.c | 10 ++- src/backend/access/heap/heaptoast.c | 77 +++++++++++++++---- src/backend/executor/nodeWindowAgg.c | 15 ++++ src/bin/gpfdist/gpfdist.c | 6 +- src/bin/gpfdist/regress/Makefile | 1 + .../gpfdist/regress/test_watchdog_options.py | 44 +++++++++++ src/test/regress/expected/misc_jiras.out | 3 + .../expected/statement_mem_for_windowagg.out | 22 ++++++ src/test/regress/expected/toast.out | 20 +++++ .../sql/statement_mem_for_windowagg.sql | 8 ++ src/test/regress/sql/toast.sql | 11 +++ 17 files changed, 251 insertions(+), 25 deletions(-) create mode 100644 src/bin/gpfdist/regress/test_watchdog_options.py diff --git a/contrib/pax_storage/src/test/regress/expected/misc_jiras.out b/contrib/pax_storage/src/test/regress/expected/misc_jiras.out index 8cca65187ec..7024a5fc63c 100644 --- a/contrib/pax_storage/src/test/regress/expected/misc_jiras.out +++ b/contrib/pax_storage/src/test/regress/expected/misc_jiras.out @@ -39,6 +39,9 @@ select sum(cc) from ( from misc_jiras.t1 group by 1, 2 ) tt; +NOTICE: winagg: tuplestore spilled to disk (seg0 slice1 127.0.0.1:7002 pid=54719) +NOTICE: winagg: tuplestore spilled to disk (seg1 slice1 127.0.0.1:7003 pid=54720) +NOTICE: winagg: tuplestore spilled to disk (seg2 slice1 127.0.0.1:7004 pid=54721) sum --------- 30006.5 diff --git a/contrib/pax_storage/src/test/regress/expected/toast.out b/contrib/pax_storage/src/test/regress/expected/toast.out index 7cd65f9c21f..9227a993c73 100644 --- a/contrib/pax_storage/src/test/regress/expected/toast.out +++ b/contrib/pax_storage/src/test/regress/expected/toast.out @@ -159,6 +159,14 @@ SELECT DISTINCT gp_inject_fault('decrease_toast_max_chunk_size', 'skip', dbid) (1 row) INSERT INTO toast_chunk_test VALUES (repeat('abcdefghijklmnopqrstuvwxyz', 1000)::bytea); +SELECT bool_or(gp_inject_fault('decrease_toast_max_chunk_size', 'status', dbid) + LIKE '%num times hit:''1''%') AS toast_chunk_fault_fired + FROM gp_segment_configuration WHERE role='p'; + toast_chunk_fault_fired +------------------------- + t +(1 row) + SELECT DISTINCT gp_inject_fault('decrease_toast_max_chunk_size', 'reset', dbid) FROM pg_catalog.gp_segment_configuration WHERE role = 'p'; @@ -180,3 +188,16 @@ SELECT encode(substring(a from 521*26+1 for 26), 'escape') FROM toast_chunk_test abcdefghijklmnopqrstuvwxyz (1 row) +-- Check prefixes, chunk boundaries, the final chunk, and slices past the end. +SELECT bool_and(substring(a FROM pos FOR len) = + substring(repeat('abcdefghijklmnopqrstuvwxyz', 1000)::bytea FROM pos FOR len)) + AS toast_slices_match + FROM toast_chunk_test, + (VALUES (1, 9000), (10, 16300), (1980, 30), (8130, 30), + (13000, 40), (25980, 40), (27000, 2)) AS slices(pos, len); + toast_slices_match +-------------------- + t +(1 row) + + diff --git a/contrib/pax_storage/src/test/regress/sql/toast.sql b/contrib/pax_storage/src/test/regress/sql/toast.sql index 1baae856767..31d37773d8b 100644 --- a/contrib/pax_storage/src/test/regress/sql/toast.sql +++ b/contrib/pax_storage/src/test/regress/sql/toast.sql @@ -86,6 +86,9 @@ SELECT DISTINCT gp_inject_fault('decrease_toast_max_chunk_size', 'skip', dbid) FROM pg_catalog.gp_segment_configuration WHERE role = 'p'; INSERT INTO toast_chunk_test VALUES (repeat('abcdefghijklmnopqrstuvwxyz', 1000)::bytea); +SELECT bool_or(gp_inject_fault('decrease_toast_max_chunk_size', 'status', dbid) + LIKE '%num times hit:''1''%') AS toast_chunk_fault_fired + FROM gp_segment_configuration WHERE role='p'; SELECT DISTINCT gp_inject_fault('decrease_toast_max_chunk_size', 'reset', dbid) FROM pg_catalog.gp_segment_configuration WHERE role = 'p'; @@ -95,3 +98,11 @@ SELECT * FROM toast_chunk_test WHERE a <> repeat('abcdefghijklmnopqrstuvwxyz', 1 -- Random access into the toast table should work equally well. SELECT encode(substring(a from 521*26+1 for 26), 'escape') FROM toast_chunk_test; + +-- Check prefixes, chunk boundaries, the final chunk, and slices past the end. +SELECT bool_and(substring(a FROM pos FOR len) = + substring(repeat('abcdefghijklmnopqrstuvwxyz', 1000)::bytea FROM pos FOR len)) + AS toast_slices_match + FROM toast_chunk_test, + (VALUES (1, 9000), (10, 16300), (1980, 30), (8130, 30), + (13000, 40), (25980, 40), (27000, 2)) AS slices(pos, len); diff --git a/gpcontrib/diskquota/src/gp_activetable.c b/gpcontrib/diskquota/src/gp_activetable.c index b774e538364..dd8e3930ac2 100644 --- a/gpcontrib/diskquota/src/gp_activetable.c +++ b/gpcontrib/diskquota/src/gp_activetable.c @@ -428,6 +428,8 @@ diskquota_fetch_table_stat(PG_FUNCTION_ARGS) DiskQuotaSetOFCache *cache = NULL; DiskQuotaActiveTableEntry *results_entry = NULL; + SIMPLE_FAULT_INJECTOR("diskquota_fetch_table_stat"); + #ifdef FAULT_INJECTOR if (SIMPLE_FAULT_INJECTOR("ereport_warning_from_segment") == FaultInjectorTypeSkip) { @@ -1198,4 +1200,4 @@ pull_active_table_size_from_seg(HTAB *local_table_stats_map, char *active_oid_ar } cdbdisp_clearCdbPgResults(&cdb_pgresults); return; -} \ No newline at end of file +} diff --git a/gpcontrib/diskquota/tests/regress/expected/test_fetch_table_stat.out b/gpcontrib/diskquota/tests/regress/expected/test_fetch_table_stat.out index 3fbde382f86..175a4290078 100644 --- a/gpcontrib/diskquota/tests/regress/expected/test_fetch_table_stat.out +++ b/gpcontrib/diskquota/tests/regress/expected/test_fetch_table_stat.out @@ -3,8 +3,6 @@ -- the error message is preserved for us to debug. -- CREATE TABLE t_error_handling (i int) DISTRIBUTED BY (i); -NOTICE: Table doesn't have 'DISTRIBUTED BY' clause -- Using column named 'i' as the Apache Cloudberry data distribution key for this table. -HINT: The 'DISTRIBUTED BY' clause determines the distribution of data. Make sure column(s) chosen are the optimal data distribution key to minimize skew. -- Inject an error to a segment server, since this UDF is only called on segments. SELECT gp_inject_fault_infinite('diskquota_fetch_table_stat', 'error', dbid) FROM gp_segment_configuration WHERE role='p' AND content=0; @@ -14,15 +12,19 @@ SELECT gp_inject_fault_infinite('diskquota_fetch_table_stat', 'error', dbid) (1 row) -- Dispatch diskquota_fetch_table_stat to segments. --- There should be a warning message from segment server saying: +-- There should be an error message from the segment server saying: -- fault triggered, fault name:'diskquota_fetch_table_stat' fault type:'error' -- We're not interested in the oid here, we aggregate the result by COUNT(*). SELECT COUNT(*) FROM (SELECT diskquota.diskquota_fetch_table_stat(1, array[(SELECT oid FROM pg_class WHERE relname='t_error_handling')]) FROM gp_dist_random('gp_id') WHERE gp_segment_id=0) AS count; - count -------- - 1 +ERROR: fault triggered, fault name:'diskquota_fetch_table_stat' fault type:'error' (seg0 slice1 127.0.0.1:7002 pid=1000) +SELECT bool_and(gp_inject_fault('diskquota_fetch_table_stat', 'status', dbid) + ~ 'num times hit:''[1-9][0-9]*''') AS diskquota_fault_fired + FROM gp_segment_configuration WHERE role='p' AND content=0; + diskquota_fault_fired +----------------------- + t (1 row) -- Reset the fault injector to prevent future failure. diff --git a/gpcontrib/diskquota/tests/regress/sql/test_fetch_table_stat.sql b/gpcontrib/diskquota/tests/regress/sql/test_fetch_table_stat.sql index 0eabbdaf536..751093c3f6d 100644 --- a/gpcontrib/diskquota/tests/regress/sql/test_fetch_table_stat.sql +++ b/gpcontrib/diskquota/tests/regress/sql/test_fetch_table_stat.sql @@ -9,13 +9,17 @@ SELECT gp_inject_fault_infinite('diskquota_fetch_table_stat', 'error', dbid) FROM gp_segment_configuration WHERE role='p' AND content=0; -- Dispatch diskquota_fetch_table_stat to segments. --- There should be a warning message from segment server saying: +-- There should be an error message from the segment server saying: -- fault triggered, fault name:'diskquota_fetch_table_stat' fault type:'error' -- We're not interested in the oid here, we aggregate the result by COUNT(*). SELECT COUNT(*) FROM (SELECT diskquota.diskquota_fetch_table_stat(1, array[(SELECT oid FROM pg_class WHERE relname='t_error_handling')]) FROM gp_dist_random('gp_id') WHERE gp_segment_id=0) AS count; +SELECT bool_and(gp_inject_fault('diskquota_fetch_table_stat', 'status', dbid) + ~ 'num times hit:''[1-9][0-9]*''') AS diskquota_fault_fired + FROM gp_segment_configuration WHERE role='p' AND content=0; + -- Reset the fault injector to prevent future failure. SELECT gp_inject_fault_infinite('diskquota_fetch_table_stat', 'reset', dbid) FROM gp_segment_configuration WHERE role='p' AND content=0; diff --git a/src/backend/access/common/toast_internals.c b/src/backend/access/common/toast_internals.c index 1ab773ee23a..aa0c1282123 100644 --- a/src/backend/access/common/toast_internals.c +++ b/src/backend/access/common/toast_internals.c @@ -23,6 +23,7 @@ #include "catalog/catalog.h" #include "common/pg_lzcompress.h" #include "miscadmin.h" +#include "utils/faultinjector.h" #include "utils/fmgroids.h" #include "utils/rel.h" #include "utils/snapmgr.h" @@ -139,6 +140,7 @@ toast_save_datum(Relation rel, Datum value, int32 align_it; } chunk_data; int32 chunk_size; + int32 max_chunk_size = TOAST_MAX_CHUNK_SIZE; int32 chunk_seq = 0; char *data_p; int32 data_todo; @@ -300,6 +302,12 @@ toast_save_datum(Relation rel, Datum value, t_isnull[1] = false; t_isnull[2] = false; +#ifdef FAULT_INJECTOR + /* Exercise reading values written with a different chunk size before upgrade. */ + if (SIMPLE_FAULT_INJECTOR("decrease_toast_max_chunk_size") == FaultInjectorTypeSkip) + max_chunk_size--; +#endif + /* * Split up the item into chunks */ @@ -312,7 +320,7 @@ toast_save_datum(Relation rel, Datum value, /* * Calculate the size of this chunk */ - chunk_size = Min(TOAST_MAX_CHUNK_SIZE, data_todo); + chunk_size = Min(max_chunk_size, data_todo); /* * Build a tuple and store it diff --git a/src/backend/access/heap/heaptoast.c b/src/backend/access/heap/heaptoast.c index e4315401f14..e2ab7bc6c03 100644 --- a/src/backend/access/heap/heaptoast.c +++ b/src/backend/access/heap/heaptoast.c @@ -689,7 +689,9 @@ heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize, SysScanDesc toastscan; HeapTuple ttup; int32 expectedchunk; - int32 totalchunks = ((attrsize - 1) / TOAST_MAX_CHUNK_SIZE) + 1; + int32 totalchunks; + int32 max_chunk_size = TOAST_MAX_CHUNK_SIZE; + bool fetch_whole_value = sliceoffset == 0 && slicelength == attrsize; int startchunk; int endchunk; int num_indexes; @@ -702,21 +704,61 @@ heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize, &toastidxs, &num_indexes); - startchunk = sliceoffset / TOAST_MAX_CHUNK_SIZE; - endchunk = (sliceoffset + slicelength - 1) / TOAST_MAX_CHUNK_SIZE; - Assert(endchunk <= totalchunks); - /* Set up a scan key to fetch from the index. */ ScanKeyInit(&toastkey[0], (AttrNumber) 1, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(valueid)); + init_toast_snapshot(&SnapshotToast); + /* - * No additional condition if fetching all chunks. Otherwise, use an - * equality condition for one chunk, and a range condition otherwise. + * Older clusters can have used a different TOAST_MAX_CHUNK_SIZE. A slice + * starting in the middle must inspect chunk zero before computing its + * scan bounds. Reads starting at zero learn the size in the main scan. */ - if (startchunk == 0 && endchunk == totalchunks - 1) + if (sliceoffset > 0) + { + Pointer chunk; + int32 chunksize; + bool isnull; + + ScanKeyInit(&toastkey[1], (AttrNumber) 2, + BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(0)); + toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex], + &SnapshotToast, 2, toastkey); + ttup = systable_getnext_ordered(toastscan, ForwardScanDirection); + if (ttup == NULL) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg_internal("missing chunk number 0 for toast value %u in %s", + valueid, RelationGetRelationName(toastrel)))); + chunk = DatumGetPointer(fastgetattr(ttup, 3, toasttupDesc, &isnull)); + Assert(!isnull); + if (VARATT_IS_EXTENDED(chunk) && !VARATT_IS_SHORT(chunk)) + elog(ERROR, "found toasted toast chunk for toast value %u in %s", + valueid, RelationGetRelationName(toastrel)); + chunksize = VARSIZE_ANY_EXHDR(chunk); + if (chunksize <= 0) + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg_internal("invalid chunk size %d for toast value %u in %s", + chunksize, valueid, RelationGetRelationName(toastrel)))); + if (chunksize < attrsize) + max_chunk_size = chunksize; + systable_endscan_ordered(toastscan); + } + + totalchunks = ((attrsize - 1) / max_chunk_size) + 1; + startchunk = sliceoffset / max_chunk_size; + endchunk = (sliceoffset + slicelength - 1) / max_chunk_size; + Assert(endchunk < totalchunks); + + /* + * Reads starting at chunk zero cannot limit the end until its size is + * known. Otherwise, use equality for one chunk, and a range for several. + */ + if (startchunk == 0) nscankeys = 1; else if (startchunk == endchunk) { @@ -740,7 +782,6 @@ heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize, } /* Prepare for scan */ - init_toast_snapshot(&SnapshotToast); toastscan = systable_beginscan_ordered(toastrel, toastidxs[validIndex], &SnapshotToast, nscankeys, toastkey); @@ -804,8 +845,14 @@ heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize, curchunk, startchunk, endchunk, valueid, RelationGetRelationName(toastrel)))); - expected_size = curchunk < totalchunks - 1 ? TOAST_MAX_CHUNK_SIZE - : attrsize - ((totalchunks - 1) * TOAST_MAX_CHUNK_SIZE); + if (curchunk == 0 && chunksize > 0 && chunksize < attrsize) + { + max_chunk_size = chunksize; + totalchunks = ((attrsize - 1) / max_chunk_size) + 1; + endchunk = (sliceoffset + slicelength - 1) / max_chunk_size; + } + expected_size = curchunk < totalchunks - 1 ? max_chunk_size + : attrsize - ((totalchunks - 1) * max_chunk_size); if (chunksize != expected_size) ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), @@ -820,16 +867,18 @@ heap_fetch_toast_slice(Relation toastrel, Oid valueid, int32 attrsize, chcpystrt = 0; chcpyend = chunksize - 1; if (curchunk == startchunk) - chcpystrt = sliceoffset % TOAST_MAX_CHUNK_SIZE; + chcpystrt = sliceoffset % max_chunk_size; if (curchunk == endchunk) - chcpyend = (sliceoffset + slicelength - 1) % TOAST_MAX_CHUNK_SIZE; + chcpyend = (sliceoffset + slicelength - 1) % max_chunk_size; memcpy(VARDATA(result) + - (curchunk * TOAST_MAX_CHUNK_SIZE - sliceoffset) + chcpystrt, + (curchunk * max_chunk_size - sliceoffset) + chcpystrt, chunkdata + chcpystrt, (chcpyend - chcpystrt) + 1); expectedchunk++; + if (!fetch_whole_value && expectedchunk > endchunk) + break; } /* diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c index f0dd67ca7ee..2a2852a0d22 100644 --- a/src/backend/executor/nodeWindowAgg.c +++ b/src/backend/executor/nodeWindowAgg.c @@ -2489,6 +2489,21 @@ ExecWindowAgg(PlanState *pstate) */ spool_tuples(winstate, winstate->currentpos); +#ifdef FAULT_INJECTOR + /* Report the completed partition before releasing its tuplestore. */ + if (winstate->partition_spooled && + winstate->currentpos >= winstate->spooled_rows && + SIMPLE_FAULT_INJECTOR("winagg_after_spool_tuples") == FaultInjectorTypeSkip) + { + if (winstate->buffer == NULL) + ereport(NOTICE, (errmsg("winagg: no input rows"))); + else if (tuplestore_in_memory(winstate->buffer)) + ereport(NOTICE, (errmsg("winagg: tuplestore fitted in memory"))); + else + ereport(NOTICE, (errmsg("winagg: tuplestore spilled to disk"))); + } +#endif + /* Move to the next partition if we reached the end of this partition */ if (winstate->partition_spooled && winstate->currentpos >= winstate->spooled_rows) diff --git a/src/bin/gpfdist/gpfdist.c b/src/bin/gpfdist/gpfdist.c index b9adb5f6035..3f9522141a7 100644 --- a/src/bin/gpfdist/gpfdist.c +++ b/src/bin/gpfdist/gpfdist.c @@ -3990,9 +3990,11 @@ int gpfdist_init(int argc, const char* const argv[]) errno = 0; val = strtol(wd, &endptr, 10); - if (errno || endptr == wd || val > INT_MAX) + if (errno || endptr == wd || *endptr != '\0' || + val < 0 || val > INT_MAX) { - fprintf(stderr, "incorrect watchdog timer: %s\n", strerror(errno)); + fprintf(stderr, "incorrect watchdog timer \"%s\": expected an integer between 0 and %d\n", + wd, INT_MAX); return -1; } diff --git a/src/bin/gpfdist/regress/Makefile b/src/bin/gpfdist/regress/Makefile index bf7b0a9ad1b..bb0c469cab5 100644 --- a/src/bin/gpfdist/regress/Makefile +++ b/src/bin/gpfdist/regress/Makefile @@ -38,6 +38,7 @@ endif $(top_builddir)/src/test/regress/pg_regress --dbname=gpfdist_regression $(REGRESS) $(REGRESS_OPTS) watchdog: + python3 test_watchdog_options.py sh test_watchdog.sh ipv4v6_ports: diff --git a/src/bin/gpfdist/regress/test_watchdog_options.py b/src/bin/gpfdist/regress/test_watchdog_options.py new file mode 100644 index 00000000000..f150d12b4b7 --- /dev/null +++ b/src/bin/gpfdist/regress/test_watchdog_options.py @@ -0,0 +1,44 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +import os +import socket +import subprocess +import tempfile +import unittest + + +class WatchdogOptionsTest(unittest.TestCase): + def test_invalid_timers(self): + with tempfile.TemporaryDirectory() as directory: + for value in ("", "abc", "2junk", "2.5", "-1", "-4294967295", + "2147483648", "9223372036854775808"): + with self.subTest(value=value): + # Avoid depending on the default port being free. + with socket.socket() as sock: + sock.bind(("127.0.0.1", 0)) + port = sock.getsockname()[1] + env = dict(os.environ, GPFDIST_WATCHDOG_TIMER=value) + result = subprocess.run( + ["gpfdist", "-p", str(port), "-d", directory], + env=env, capture_output=True, text=True, timeout=10) + self.assertNotEqual(result.returncode, 0) + self.assertIn("incorrect watchdog timer", result.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/test/regress/expected/misc_jiras.out b/src/test/regress/expected/misc_jiras.out index 8cca65187ec..7024a5fc63c 100644 --- a/src/test/regress/expected/misc_jiras.out +++ b/src/test/regress/expected/misc_jiras.out @@ -39,6 +39,9 @@ select sum(cc) from ( from misc_jiras.t1 group by 1, 2 ) tt; +NOTICE: winagg: tuplestore spilled to disk (seg0 slice1 127.0.0.1:7002 pid=54719) +NOTICE: winagg: tuplestore spilled to disk (seg1 slice1 127.0.0.1:7003 pid=54720) +NOTICE: winagg: tuplestore spilled to disk (seg2 slice1 127.0.0.1:7004 pid=54721) sum --------- 30006.5 diff --git a/src/test/regress/expected/statement_mem_for_windowagg.out b/src/test/regress/expected/statement_mem_for_windowagg.out index 102e94274d4..1e50a1b942c 100644 --- a/src/test/regress/expected/statement_mem_for_windowagg.out +++ b/src/test/regress/expected/statement_mem_for_windowagg.out @@ -17,6 +17,9 @@ SELECT gp_inject_fault('winagg_after_spool_tuples', 'skip', dbid) (3 rows) EXPLAIN ANALYZE SELECT AVG(x) OVER (PARTITION BY y) FROM dummy_table; +NOTICE: winagg: tuplestore fitted in memory (seg0 slice1 127.0.0.1:7002 pid=43472) +NOTICE: winagg: tuplestore fitted in memory (seg1 slice1 127.0.0.1:7003 pid=43473) +NOTICE: winagg: tuplestore fitted in memory (seg2 slice1 127.0.0.1:7004 pid=43474) QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (cost=0.00..431.00 rows=1 width=8) (actual time=6.520..15.872 rows=30003 loops=1) @@ -34,6 +37,14 @@ EXPLAIN ANALYZE SELECT AVG(x) OVER (PARTITION BY y) FROM dummy_table; Execution Time: 17.225 ms (13 rows) +SELECT bool_and(gp_inject_fault('winagg_after_spool_tuples', 'status', dbid) + LIKE '%num times hit:''1''%') AS winagg_fault_fired + FROM gp_segment_configuration WHERE role='p' AND content>=0; + winagg_fault_fired +-------------------- + t +(1 row) + SELECT gp_inject_fault('winagg_after_spool_tuples', 'reset', dbid) FROM gp_segment_configuration WHERE role='p' AND content>=0; gp_inject_fault @@ -56,6 +67,9 @@ SELECT gp_inject_fault('winagg_after_spool_tuples', 'skip', dbid) (3 rows) EXPLAIN ANALYZE SELECT AVG(x) OVER (PARTITION BY y) FROM dummy_table; +NOTICE: winagg: tuplestore spilled to disk (seg0 slice1 127.0.0.1:7002 pid=43472) +NOTICE: winagg: tuplestore spilled to disk (seg1 slice1 127.0.0.1:7003 pid=43473) +NOTICE: winagg: tuplestore spilled to disk (seg2 slice1 127.0.0.1:7004 pid=43474) QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------- Gather Motion 3:1 (slice1; segments: 3) (cost=0.00..431.00 rows=1 width=8) (actual time=8.784..13.923 rows=30003 loops=1) @@ -73,6 +87,14 @@ EXPLAIN ANALYZE SELECT AVG(x) OVER (PARTITION BY y) FROM dummy_table; Execution Time: 15.235 ms (13 rows) +SELECT bool_and(gp_inject_fault('winagg_after_spool_tuples', 'status', dbid) + LIKE '%num times hit:''1''%') AS winagg_fault_fired + FROM gp_segment_configuration WHERE role='p' AND content>=0; + winagg_fault_fired +-------------------- + t +(1 row) + SELECT gp_inject_fault('winagg_after_spool_tuples', 'reset', dbid) FROM gp_segment_configuration WHERE role='p' AND content>=0; gp_inject_fault diff --git a/src/test/regress/expected/toast.out b/src/test/regress/expected/toast.out index 78affbd6e87..781aa001b6f 100644 --- a/src/test/regress/expected/toast.out +++ b/src/test/regress/expected/toast.out @@ -172,6 +172,14 @@ SELECT DISTINCT gp_inject_fault('decrease_toast_max_chunk_size', 'skip', dbid) (1 row) INSERT INTO toast_chunk_test VALUES (repeat('abcdefghijklmnopqrstuvwxyz', 1000)::bytea); +SELECT bool_or(gp_inject_fault('decrease_toast_max_chunk_size', 'status', dbid) + LIKE '%num times hit:''1''%') AS toast_chunk_fault_fired + FROM gp_segment_configuration WHERE role='p'; + toast_chunk_fault_fired +------------------------- + t +(1 row) + SELECT DISTINCT gp_inject_fault('decrease_toast_max_chunk_size', 'reset', dbid) FROM pg_catalog.gp_segment_configuration WHERE role = 'p'; @@ -193,6 +201,18 @@ SELECT encode(substring(a from 521*26+1 for 26), 'escape') FROM toast_chunk_test abcdefghijklmnopqrstuvwxyz (1 row) +-- Check prefixes, chunk boundaries, the final chunk, and slices past the end. +SELECT bool_and(substring(a FROM pos FOR len) = + substring(repeat('abcdefghijklmnopqrstuvwxyz', 1000)::bytea FROM pos FOR len)) + AS toast_slices_match + FROM toast_chunk_test, + (VALUES (1, 9000), (10, 16300), (1980, 30), (8130, 30), + (13000, 40), (25980, 40), (27000, 2)) AS slices(pos, len); + toast_slices_match +-------------------- + t +(1 row) + -- Test for Github Issue 16906 create table t_16906(a int, b text) distributed by(a); -- Insert two rows and make sure they are in the same segment (same dist key) diff --git a/src/test/regress/sql/statement_mem_for_windowagg.sql b/src/test/regress/sql/statement_mem_for_windowagg.sql index a471690c6be..227d36c57c8 100644 --- a/src/test/regress/sql/statement_mem_for_windowagg.sql +++ b/src/test/regress/sql/statement_mem_for_windowagg.sql @@ -14,6 +14,10 @@ SELECT gp_inject_fault('winagg_after_spool_tuples', 'skip', dbid) EXPLAIN ANALYZE SELECT AVG(x) OVER (PARTITION BY y) FROM dummy_table; +SELECT bool_and(gp_inject_fault('winagg_after_spool_tuples', 'status', dbid) + LIKE '%num times hit:''1''%') AS winagg_fault_fired + FROM gp_segment_configuration WHERE role='p' AND content>=0; + SELECT gp_inject_fault('winagg_after_spool_tuples', 'reset', dbid) FROM gp_segment_configuration WHERE role='p' AND content>=0; @@ -26,6 +30,10 @@ SELECT gp_inject_fault('winagg_after_spool_tuples', 'skip', dbid) EXPLAIN ANALYZE SELECT AVG(x) OVER (PARTITION BY y) FROM dummy_table; +SELECT bool_and(gp_inject_fault('winagg_after_spool_tuples', 'status', dbid) + LIKE '%num times hit:''1''%') AS winagg_fault_fired + FROM gp_segment_configuration WHERE role='p' AND content>=0; + SELECT gp_inject_fault('winagg_after_spool_tuples', 'reset', dbid) FROM gp_segment_configuration WHERE role='p' AND content>=0; diff --git a/src/test/regress/sql/toast.sql b/src/test/regress/sql/toast.sql index bcf070acceb..a3fd788292f 100644 --- a/src/test/regress/sql/toast.sql +++ b/src/test/regress/sql/toast.sql @@ -98,6 +98,9 @@ SELECT DISTINCT gp_inject_fault('decrease_toast_max_chunk_size', 'skip', dbid) FROM pg_catalog.gp_segment_configuration WHERE role = 'p'; INSERT INTO toast_chunk_test VALUES (repeat('abcdefghijklmnopqrstuvwxyz', 1000)::bytea); +SELECT bool_or(gp_inject_fault('decrease_toast_max_chunk_size', 'status', dbid) + LIKE '%num times hit:''1''%') AS toast_chunk_fault_fired + FROM gp_segment_configuration WHERE role='p'; SELECT DISTINCT gp_inject_fault('decrease_toast_max_chunk_size', 'reset', dbid) FROM pg_catalog.gp_segment_configuration WHERE role = 'p'; @@ -108,6 +111,14 @@ SELECT * FROM toast_chunk_test WHERE a <> repeat('abcdefghijklmnopqrstuvwxyz', 1 -- Random access into the toast table should work equally well. SELECT encode(substring(a from 521*26+1 for 26), 'escape') FROM toast_chunk_test; +-- Check prefixes, chunk boundaries, the final chunk, and slices past the end. +SELECT bool_and(substring(a FROM pos FOR len) = + substring(repeat('abcdefghijklmnopqrstuvwxyz', 1000)::bytea FROM pos FOR len)) + AS toast_slices_match + FROM toast_chunk_test, + (VALUES (1, 9000), (10, 16300), (1980, 30), (8130, 30), + (13000, 40), (25980, 40), (27000, 2)) AS slices(pos, len); + -- Test for Github Issue 16906 create table t_16906(a int, b text) distributed by(a);