From 9406c69fad810a2f83e008b363d0dea537dfa53e Mon Sep 17 00:00:00 2001 From: Mats Kindahl Date: Thu, 4 Jul 2024 09:59:17 +0200 Subject: [PATCH] Use brief explain stats Use a more compact and easy to read version for the decompression and arrow array cache stats. Also simplify the code and the tests to remove unnecessary parts. --- tsl/src/hypercore/arrow_cache_explain.c | 103 ++++---- tsl/test/expected/hypercore_columnar.out | 96 +++----- tsl/test/expected/hypercore_constraints.out | 40 ++-- tsl/test/expected/hypercore_copy.out | 40 ++-- tsl/test/expected/hypercore_create.out | 40 ++-- tsl/test/expected/hypercore_cursor.out | 40 ++-- tsl/test/expected/hypercore_index_btree.out | 141 ++++------- tsl/test/expected/hypercore_index_hash.out | 82 +++---- tsl/test/expected/hypercore_insert.out | 40 ++-- tsl/test/expected/hypercore_join.out | 47 ++-- tsl/test/expected/hypercore_merge.out | 54 ++--- tsl/test/expected/hypercore_parallel.out | 40 ++-- tsl/test/expected/hypercore_scans.out | 250 +++++++++++++------- tsl/test/expected/hypercore_stats.out | 40 ++-- tsl/test/expected/hypercore_trigger.out | 40 ++-- tsl/test/expected/hypercore_types.out | 40 ++-- tsl/test/expected/hypercore_update.out | 40 ++-- tsl/test/expected/hypercore_vacuum_full.out | 40 ++-- tsl/test/sql/hypercore_scans.sql | 5 + tsl/test/sql/include/hypercore_helpers.sql | 41 ++-- 20 files changed, 657 insertions(+), 602 deletions(-) diff --git a/tsl/src/hypercore/arrow_cache_explain.c b/tsl/src/hypercore/arrow_cache_explain.c index a86c0d0c38e..7216df392d3 100644 --- a/tsl/src/hypercore/arrow_cache_explain.c +++ b/tsl/src/hypercore/arrow_cache_explain.c @@ -61,43 +61,12 @@ standard_ExplainOneQuery(Query *query, int cursorOptions, IntoClause *into, Expl } #endif -static struct +static inline void +append_if_positive(StringInfo info, const char *key, long long val) { - const char *hits_text; /* Number of cache hits */ - const char *miss_text; /* Number of cache misses */ - const char *evict_text; /* Number of cache evictions */ - const char *decompress_text; /* Number of arrays decompressed */ - const char *decompress_calls_text; /* Number of calls to decompress an array */ -} format_texts[] = { - [EXPLAIN_FORMAT_TEXT] = { - .hits_text = "Array Cache Hits", - .miss_text = "Array Cache Misses", - .evict_text = "Array Cache Evictions", - .decompress_text = "Array Decompressions", - .decompress_calls_text = "Array Decompression Calls", - }, - [EXPLAIN_FORMAT_XML]= { - .hits_text = "hits", - .miss_text = "misses", - .evict_text = "evictions", - .decompress_text = "decompressions", - .decompress_calls_text = "decompression calls", - }, - [EXPLAIN_FORMAT_JSON] = { - .hits_text = "hits", - .miss_text = "misses", - .evict_text = "evictions", - .decompress_text = "decompressions", - .decompress_calls_text = "decompression calls", - }, - [EXPLAIN_FORMAT_YAML] = { - .hits_text = "hits", - .miss_text = "misses", - .evict_text = "evictions", - .decompress_text = "decompressions", - .decompress_calls_text = "decompression calls", - }, -}; + if (val > 0) + appendStringInfo(info, " %s=%lld", key, val); +} static void explain_decompression(Query *query, int cursorOptions, IntoClause *into, ExplainState *es, @@ -106,33 +75,41 @@ explain_decompression(Query *query, int cursorOptions, IntoClause *into, Explain standard_ExplainOneQuery(query, cursorOptions, into, es, queryString, params, queryEnv); if (decompress_cache_print) { - Assert(es->format < sizeof(format_texts) / sizeof(*format_texts)); - - ExplainOpenGroup("Array cache", "Arrow Array Cache", true, es); - ExplainPropertyInteger(format_texts[es->format].hits_text, - NULL, - decompress_cache_stats.hits, - es); - ExplainPropertyInteger(format_texts[es->format].miss_text, - NULL, - decompress_cache_stats.misses, - es); - ExplainPropertyInteger(format_texts[es->format].evict_text, - NULL, - decompress_cache_stats.evictions, - es); - ExplainPropertyInteger(format_texts[es->format].decompress_text, - NULL, - decompress_cache_stats.decompressions, - es); - - if (es->verbose) - ExplainPropertyInteger(format_texts[es->format].decompress_calls_text, - NULL, - decompress_cache_stats.decompress_calls, - es); - - ExplainCloseGroup("Array cache", "Arrow Array Cache", true, es); + const bool has_decompress_data = decompress_cache_stats.decompressions > 0 || + decompress_cache_stats.decompress_calls > 0; + const bool has_cache_data = decompress_cache_stats.hits > 0 || + decompress_cache_stats.misses > 0 || + decompress_cache_stats.evictions > 0; + if (has_decompress_data || has_cache_data) + { + if (es->format == EXPLAIN_FORMAT_TEXT) + { + appendStringInfoString(es->str, "Array:"); + if (has_cache_data) + appendStringInfoString(es->str, " cache"); + append_if_positive(es->str, "hits", decompress_cache_stats.hits); + append_if_positive(es->str, "misses", decompress_cache_stats.misses); + append_if_positive(es->str, "evictions", decompress_cache_stats.evictions); + if (has_decompress_data) + appendStringInfoString(es->str, ", decompress"); + append_if_positive(es->str, "count", decompress_cache_stats.decompressions); + append_if_positive(es->str, "calls", decompress_cache_stats.decompress_calls); + appendStringInfoChar(es->str, '\n'); + } + else + { + ExplainOpenGroup("Array Cache", "Arrow Array Cache", true, es); + ExplainPropertyInteger("hits", NULL, decompress_cache_stats.hits, es); + ExplainPropertyInteger("misses", NULL, decompress_cache_stats.misses, es); + ExplainPropertyInteger("evictions", NULL, decompress_cache_stats.evictions, es); + ExplainCloseGroup("Array Cache", "Arrow Array Cache", true, es); + + ExplainOpenGroup("Array Decompress", "Arrow Array Decompress", true, es); + ExplainPropertyInteger("count", NULL, decompress_cache_stats.decompressions, es); + ExplainPropertyInteger("calls", NULL, decompress_cache_stats.decompress_calls, es); + ExplainCloseGroup("Array Decompress", "Arrow Array Decompress", true, es); + } + } decompress_cache_print = false; memset(&decompress_cache_stats, 0, sizeof(struct DecompressCacheStats)); diff --git a/tsl/test/expected/hypercore_columnar.out b/tsl/test/expected/hypercore_columnar.out index d672a93a372..768f7fe846f 100644 --- a/tsl/test/expected/hypercore_columnar.out +++ b/tsl/test/expected/hypercore_columnar.out @@ -9,6 +9,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -18,17 +35,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -41,14 +54,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -101,11 +107,8 @@ $$, :'chunk')); Scankey: (device < 4) Vectorized Filter: (location = 2) Rows Removed by Filter: 16 - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 3 -(9 rows) + Array: cache misses=N, decompress count=N calls=N +(6 rows) -- Save away all data from the chunk so that we can compare. create table saved as select * from :chunk; @@ -136,11 +139,8 @@ $$, :'chunk')); -> Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Vectorized Filter: (humidity > '110'::double precision) Rows Removed by Filter: 204 - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 30 -(8 rows) + Array: cache misses=N, decompress count=N calls=N +(5 rows) select count(*) from :chunk where humidity > 110; count @@ -159,11 +159,8 @@ $$, :'chunk')); -> Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Vectorized Filter: (humidity > '50'::double precision) Rows Removed by Filter: 87 - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 30 -(8 rows) + Array: cache misses=N, decompress count=N calls=N +(5 rows) select lhs.count, rhs.count from (select count(*) from :chunk where humidity > 50) lhs, @@ -194,11 +191,8 @@ $$, :'chunk')); -> Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Filter: (temp > '50'::numeric) Rows Removed by Filter: 204 - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 30 -(8 rows) + Array: cache misses=N, decompress count=N calls=N +(5 rows) select count(*) from :chunk where temp > 50; count @@ -216,11 +210,8 @@ $$, :'chunk')); -> Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Filter: (temp > '20'::numeric) Rows Removed by Filter: 98 - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 30 -(8 rows) + Array: cache misses=N, decompress count=N calls=N +(5 rows) select lhs.count, rhs.count from (select count(*) from :chunk where temp > 20) lhs, @@ -251,11 +242,8 @@ select count(*) from :chunk where humidity > 40 and temp > 20; Filter: (temp > '20'::numeric) Rows Removed by Filter: 132 Vectorized Filter: (humidity > '40'::double precision) - Array Cache Hits: 0 - Array Cache Misses: 30 - Array Cache Evictions: 0 - Array Decompressions: 60 -(9 rows) + Array: cache misses=30, decompress count=60 calls=165 +(6 rows) select count(*) from :chunk where humidity > 40 and temp > 20; count @@ -284,11 +272,8 @@ $$, :'chunk')); Rows Removed by Filter: 3 Scankey: (device = 3) Vectorized Filter: (humidity > '40'::double precision) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 2 -(10 rows) + Array: cache misses=N, decompress count=N calls=N +(7 rows) select count(*) from :chunk where humidity > 40 and temp > 20 and device = 3; count @@ -318,11 +303,8 @@ $$, :'chunk')); -> Seq Scan on _hyper_I_N_chunk (actual rows=N loops=N) Filter: (device < 4) Rows Removed by Filter: 184 - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 96 -(11 rows) + Array: cache misses=N, decompress count=N calls=N +(8 rows) drop table readings; drop table saved; diff --git a/tsl/test/expected/hypercore_constraints.out b/tsl/test/expected/hypercore_constraints.out index f430699a12f..462574ec1ec 100644 --- a/tsl/test/expected/hypercore_constraints.out +++ b/tsl/test/expected/hypercore_constraints.out @@ -15,6 +15,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -24,17 +41,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -47,14 +60,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_copy.out b/tsl/test/expected/hypercore_copy.out index 39ee9865a9c..64bb468b505 100644 --- a/tsl/test/expected/hypercore_copy.out +++ b/tsl/test/expected/hypercore_copy.out @@ -14,6 +14,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -23,17 +40,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -46,14 +59,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_create.out b/tsl/test/expected/hypercore_create.out index b290b7cd494..ef60e8529a5 100644 --- a/tsl/test/expected/hypercore_create.out +++ b/tsl/test/expected/hypercore_create.out @@ -9,6 +9,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -18,17 +35,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -41,14 +54,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_cursor.out b/tsl/test/expected/hypercore_cursor.out index ca4249fa332..aec74cb5ef9 100644 --- a/tsl/test/expected/hypercore_cursor.out +++ b/tsl/test/expected/hypercore_cursor.out @@ -14,6 +14,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -23,17 +40,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -46,14 +59,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_index_btree.out b/tsl/test/expected/hypercore_index_btree.out index 7e0982cdd5d..6df931d5474 100644 --- a/tsl/test/expected/hypercore_index_btree.out +++ b/tsl/test/expected/hypercore_index_btree.out @@ -17,6 +17,23 @@ set role :ROLE_DEFAULT_PERM_USER; -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -26,17 +43,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -49,14 +62,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -351,11 +357,7 @@ select explain_analyze_anonymize(format('select * from %s where owner_id = 3', : Scankey: (owner_id = 3) -> Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Scankey: (owner_id = 3) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(17 rows) +(13 rows) -- TODO(timescale/timescaledb-private#1117): the Decompress Count here -- is not correct, but the result shows correctly. @@ -364,11 +366,7 @@ select explain_analyze_anonymize(format('select * from %s where owner_id = 3', : ------------------------------------------------------------------------ Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Scankey: (owner_id = 3) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(6 rows) +(2 rows) reset enable_indexscan; -- Test index scan on non-segmentby column @@ -393,11 +391,8 @@ $$, :'hypertable')); Index Cond: ((device_id >= 10) AND (device_id <= 20)) -> Index Scan using _hyper_I_N_chunk_hypertable_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: ((device_id >= 10) AND (device_id <= 20)) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 560 -(19 rows) + Array: cache hits=N misses=N, decompress count=N calls=N +(16 rows) select explain_analyze_anonymize(format($$ select device_id, avg(temp) from %s where device_id between 10 and 20 @@ -408,11 +403,8 @@ $$, :'chunk1')); GroupAggregate (actual rows=N loops=N) -> Index Scan using _hyper_I_N_chunk_hypertable_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: ((device_id >= 10) AND (device_id <= 20)) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 149 -(7 rows) + Array: cache hits=N misses=N, decompress count=N calls=N +(4 rows) -- Test index scan on segmentby column select explain_analyze_anonymize(format($$ @@ -433,11 +425,8 @@ $$, :'hypertable')); Index Cond: ((location_id >= 5) AND (location_id <= 10)) -> Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Scankey: ((location_id >= 5) AND (location_id <= 10)) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 360 -(17 rows) + Array: cache misses=N, decompress count=N calls=N +(14 rows) select explain_analyze_anonymize(format($$ select created_at, location_id, temp from %s where location_id between 5 and 10 @@ -446,11 +435,8 @@ $$, :'chunk1')); ------------------------------------------------------------------------ Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Scankey: ((location_id >= 5) AND (location_id <= 10)) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 60 -(6 rows) + Array: cache misses=N, decompress count=N calls=N +(3 rows) -- These should generate decompressions as above, but for all columns. select explain_analyze_anonymize(format($$ @@ -471,11 +457,7 @@ $$, :'hypertable')); Index Cond: ((location_id >= 5) AND (location_id <= 10)) -> Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Scankey: ((location_id >= 5) AND (location_id <= 10)) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(17 rows) +(13 rows) select explain_analyze_anonymize(format($$ select * from %s where location_id between 5 and 10 @@ -484,11 +466,7 @@ $$, :'chunk1')); ------------------------------------------------------------------------ Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Scankey: ((location_id >= 5) AND (location_id <= 10)) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(6 rows) +(2 rows) -- -- Test index only scan @@ -518,11 +496,7 @@ $$, :'hypertable')); Index Cond: ((location_id >= 5) AND (location_id <= 10)) -> Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Scankey: ((location_id >= 5) AND (location_id <= 10)) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(17 rows) +(13 rows) -- We just compare the counts here, not the full content. select heapam.count as heapam, hypercore.count as hypercore @@ -558,11 +532,7 @@ $$, :'hypertable')); -> Index Only Scan using _hyper_I_N_chunk_hypertable_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: ((device_id >= 5) AND (device_id <= 10)) Heap Fetches: N - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(23 rows) +(19 rows) select explain_analyze_anonymize(format($$ select location_id from %s where location_id between 5 and 10 @@ -571,11 +541,7 @@ $$, :'chunk1')); ------------------------------------------------------------------------ Custom Scan (ColumnarScan) on _hyper_I_N_chunk (actual rows=N loops=N) Scankey: ((location_id >= 5) AND (location_id <= 10)) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(6 rows) +(2 rows) select explain_analyze_anonymize(format($$ select device_id from %s where device_id between 5 and 10 @@ -585,11 +551,7 @@ $$, :'chunk1')); Index Only Scan using _hyper_I_N_chunk_hypertable_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: ((device_id >= 5) AND (device_id <= 10)) Heap Fetches: N - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(7 rows) +(3 rows) -- Test index only scan with covering indexes. -- @@ -620,11 +582,8 @@ $$, :'hypertable')); Index Cond: ((location_id >= 5) AND (location_id <= 10)) -> Index Scan using _hyper_I_N_chunk_hypertable_location_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: ((location_id >= 5) AND (location_id <= 10)) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 150 -(20 rows) + Array: cache misses=N, decompress count=N calls=N +(17 rows) select explain_analyze_anonymize(format($$ select device_id, avg(humidity) from %s where device_id between 5 and 10 @@ -653,11 +612,7 @@ $$, :'hypertable')); -> Index Only Scan using _hyper_I_N_chunk_hypertable_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: ((device_id >= 5) AND (device_id <= 10)) Heap Fetches: N - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(25 rows) +(21 rows) select explain_analyze_anonymize(format($$ select location_id, avg(humidity) from %s where location_id between 5 and 10 @@ -669,11 +624,7 @@ $$, :'chunk1')); -> Index Only Scan using _hyper_I_N_chunk_hypertable_location_id_include_humidity_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: ((location_id >= 5) AND (location_id <= 10)) Heap Fetches: N - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(8 rows) +(4 rows) select explain_analyze_anonymize(format($$ select device_id, avg(humidity) from %s where device_id between 5 and 10 @@ -685,11 +636,7 @@ $$, :'chunk1')); -> Index Only Scan using _hyper_I_N_chunk_hypertable_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: ((device_id >= 5) AND (device_id <= 10)) Heap Fetches: N - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 0 -(8 rows) +(4 rows) ------------------------------------- -- Test UNIQUE and Partial indexes -- diff --git a/tsl/test/expected/hypercore_index_hash.out b/tsl/test/expected/hypercore_index_hash.out index a3a82d6b8d3..48223b96171 100644 --- a/tsl/test/expected/hypercore_index_hash.out +++ b/tsl/test/expected/hypercore_index_hash.out @@ -14,6 +14,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -23,17 +40,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -46,14 +59,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -261,11 +267,8 @@ $$, :'hypertable')); -> Partial GroupAggregate (actual rows=N loops=N) -> Index Scan using _hyper_I_N_chunk_hypertable_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: (device_id = 10) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 320 -(24 rows) + Array: cache hits=N misses=N, decompress count=N calls=N +(21 rows) select explain_analyze_anonymize(format($$ select device_id, avg(temp) from %s where device_id = 10 @@ -276,11 +279,8 @@ $$, :'chunk1')); GroupAggregate (actual rows=N loops=N) -> Index Scan using _hyper_I_N_chunk_hypertable_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: (device_id = 10) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 17 -(7 rows) + Array: cache misses=N, decompress count=N calls=N +(4 rows) -- Test index scan on segmentby column select explain_analyze_anonymize(format($$ @@ -301,11 +301,8 @@ $$, :'hypertable')); Index Cond: (location_id = 5) -> Index Scan using _hyper_I_N_chunk_hypertable_location_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: (location_id = 5) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 60 -(17 rows) + Array: cache misses=N, decompress count=N calls=N +(14 rows) select explain_analyze_anonymize(format($$ select created_at, location_id, temp from %s where location_id = 5 @@ -314,11 +311,8 @@ $$, :'chunk1')); ---------------------------------------------------------------------------------------------------------- Index Scan using _hyper_I_N_chunk_hypertable_location_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: (location_id = 5) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 10 -(6 rows) + Array: cache misses=N, decompress count=N calls=N +(3 rows) -- These should generate decompressions as above, but for all columns. select explain_analyze_anonymize(format($$ @@ -339,11 +333,8 @@ $$, :'hypertable')); Index Cond: (location_id = 5) -> Index Scan using _hyper_I_N_chunk_hypertable_location_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: (location_id = 5) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 60 -(17 rows) + Array: cache misses=N, decompress count=N calls=N +(14 rows) select explain_analyze_anonymize(format($$ select * from %s where location_id = 5 @@ -352,11 +343,8 @@ $$, :'chunk1')); ---------------------------------------------------------------------------------------------------------- Index Scan using _hyper_I_N_chunk_hypertable_location_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) Index Cond: (location_id = 5) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 10 -(6 rows) + Array: cache misses=N, decompress count=N calls=N +(3 rows) drop table :hypertable cascade; NOTICE: drop cascades to view chunk_indexes diff --git a/tsl/test/expected/hypercore_insert.out b/tsl/test/expected/hypercore_insert.out index 87a2c660bf1..fe2efd39c62 100644 --- a/tsl/test/expected/hypercore_insert.out +++ b/tsl/test/expected/hypercore_insert.out @@ -14,6 +14,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -23,17 +40,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -46,14 +59,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_join.out b/tsl/test/expected/hypercore_join.out index d901dc4c9c5..b55d547f772 100644 --- a/tsl/test/expected/hypercore_join.out +++ b/tsl/test/expected/hypercore_join.out @@ -14,6 +14,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -23,17 +40,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -46,14 +59,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -176,11 +182,8 @@ $$, :'chunk1')); -> Index Scan using _hyper_I_N_chunk_the_hypercore_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) -> Index Scan using _hyper_I_N_chunk_the_hypercore_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) -> Index Scan using _hyper_I_N_chunk_hypertable_device_id_idx on _hyper_I_N_chunk (actual rows=N loops=N) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 9 -(12 rows) + Array: cache hits=N misses=N, decompress count=N calls=N +(9 rows) -- Check that it generates the right result select * into :inner from :chunk1 join the_hypercore using (device_id); diff --git a/tsl/test/expected/hypercore_merge.out b/tsl/test/expected/hypercore_merge.out index c45432f2b09..f468c2c65f3 100644 --- a/tsl/test/expected/hypercore_merge.out +++ b/tsl/test/expected/hypercore_merge.out @@ -15,6 +15,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -24,17 +41,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -47,14 +60,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -190,11 +196,8 @@ $$, :'hypertable')); Index Cond: (created_at = sd.created_at) -> Index Scan using "6_6_readings_created_at_key" on _hyper_I_N_chunk ht_7 (actual rows=N loops=N) Index Cond: (created_at = sd.created_at) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 1 -(32 rows) + Array: cache misses=N, decompress count=N calls=N +(29 rows) -- Now, the inserted rows should show up, but not the ones that -- already exist. @@ -280,11 +283,8 @@ $$, :'hypertable')); Index Cond: (created_at = sd.created_at) -> Index Scan using "6_6_readings_created_at_key" on _hyper_I_N_chunk ht_7 (actual rows=N loops=N) Index Cond: (created_at = sd.created_at) - Array Cache Hits: N - Array Cache Misses: N - Array Cache Evictions: N - Array Decompressions: 2 -(32 rows) + Array: cache hits=N misses=N, decompress count=N calls=N +(29 rows) \x on select * from :hypertable where not _timescaledb_debug.is_compressed_tid(ctid); diff --git a/tsl/test/expected/hypercore_parallel.out b/tsl/test/expected/hypercore_parallel.out index 7219ebcd3a5..406e55c6e82 100644 --- a/tsl/test/expected/hypercore_parallel.out +++ b/tsl/test/expected/hypercore_parallel.out @@ -14,6 +14,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -23,17 +40,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -46,14 +59,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_scans.out b/tsl/test/expected/hypercore_scans.out index dd4f37f39d0..67bb6dbfa84 100644 --- a/tsl/test/expected/hypercore_scans.out +++ b/tsl/test/expected/hypercore_scans.out @@ -54,11 +54,7 @@ select * from :chunk where ctid = :'ctid'; ------------------------------------------------------ Tid Scan on _hyper_1_1_chunk (actual rows=1 loops=1) TID Cond: (ctid = '(2147483650,1)'::tid) - Array Cache Hits: 0 - Array Cache Misses: 0 - Array Cache Evictions: 0 - Array Decompressions: 0 -(6 rows) +(2 rows) select * from :chunk where ctid = :'ctid'; time | location | device | temp | humidity @@ -75,11 +71,7 @@ select * from :chunk where ctid = :'ctid'; ------------------------------------------------------ Tid Scan on _hyper_1_1_chunk (actual rows=1 loops=1) TID Cond: (ctid = '(0,1)'::tid) - Array Cache Hits: 0 - Array Cache Misses: 0 - Array Cache Evictions: 0 - Array Decompressions: 0 -(6 rows) +(2 rows) select * from :chunk where ctid = :'ctid'; time | location | device | temp | humidity @@ -123,11 +115,147 @@ select time, temp + humidity from readings where device between 5 and 10 and hum Index Cond: ((device >= 5) AND (device <= 10)) Filter: (humidity > '5'::double precision) Rows Removed by Filter: 6 - Array Cache Hits: 0 - Array Cache Misses: 6 - Array Cache Evictions: 0 - Array Decompressions: 18 -(30 rows) + Array: cache misses=6, decompress count=18 calls=105 +(27 rows) + +-- Testing JSON format to make sure it works and to get coverage for +-- those parts of the code. +explain (analyze, costs off, timing off, summary off, decompress_cache_stats, format json) +select time, temp + humidity from readings where device between 5 and 10 and humidity > 5; + QUERY PLAN +--------------------------------------------------------------------- + [ + + { + + "Plan": { + + "Node Type": "Result", + + "Parallel Aware": false, + + "Async Capable": false, + + "Actual Rows": 1624, + + "Actual Loops": 1, + + "Plans": [ + + { + + "Node Type": "Append", + + "Parent Relationship": "Outer", + + "Parallel Aware": false, + + "Async Capable": false, + + "Actual Rows": 1624, + + "Actual Loops": 1, + + "Subplans Removed": 0, + + "Plans": [ + + { + + "Node Type": "Index Scan", + + "Parent Relationship": "Member", + + "Parallel Aware": false, + + "Async Capable": false, + + "Scan Direction": "Forward", + + "Index Name": "_hyper_1_1_chunk_readings_device_idx",+ + "Relation Name": "_hyper_1_1_chunk", + + "Alias": "_hyper_1_1_chunk", + + "Actual Rows": 34, + + "Actual Loops": 1, + + "Index Cond": "((device >= 5) AND (device <= 10))", + + "Rows Removed by Index Recheck": 0, + + "Filter": "(humidity > '5'::double precision)", + + "Rows Removed by Filter": 1 + + }, + + { + + "Node Type": "Index Scan", + + "Parent Relationship": "Member", + + "Parallel Aware": false, + + "Async Capable": false, + + "Scan Direction": "Forward", + + "Index Name": "_hyper_1_2_chunk_readings_device_idx",+ + "Relation Name": "_hyper_1_2_chunk", + + "Alias": "_hyper_1_2_chunk", + + "Actual Rows": 404, + + "Actual Loops": 1, + + "Index Cond": "((device >= 5) AND (device <= 10))", + + "Rows Removed by Index Recheck": 0, + + "Filter": "(humidity > '5'::double precision)", + + "Rows Removed by Filter": 17 + + }, + + { + + "Node Type": "Index Scan", + + "Parent Relationship": "Member", + + "Parallel Aware": false, + + "Async Capable": false, + + "Scan Direction": "Forward", + + "Index Name": "_hyper_1_3_chunk_readings_device_idx",+ + "Relation Name": "_hyper_1_3_chunk", + + "Alias": "_hyper_1_3_chunk", + + "Actual Rows": 380, + + "Actual Loops": 1, + + "Index Cond": "((device >= 5) AND (device <= 10))", + + "Rows Removed by Index Recheck": 0, + + "Filter": "(humidity > '5'::double precision)", + + "Rows Removed by Filter": 23 + + }, + + { + + "Node Type": "Index Scan", + + "Parent Relationship": "Member", + + "Parallel Aware": false, + + "Async Capable": false, + + "Scan Direction": "Forward", + + "Index Name": "_hyper_1_4_chunk_readings_device_idx",+ + "Relation Name": "_hyper_1_4_chunk", + + "Alias": "_hyper_1_4_chunk", + + "Actual Rows": 359, + + "Actual Loops": 1, + + "Index Cond": "((device >= 5) AND (device <= 10))", + + "Rows Removed by Index Recheck": 0, + + "Filter": "(humidity > '5'::double precision)", + + "Rows Removed by Filter": 18 + + }, + + { + + "Node Type": "Index Scan", + + "Parent Relationship": "Member", + + "Parallel Aware": false, + + "Async Capable": false, + + "Scan Direction": "Forward", + + "Index Name": "_hyper_1_5_chunk_readings_device_idx",+ + "Relation Name": "_hyper_1_5_chunk", + + "Alias": "_hyper_1_5_chunk", + + "Actual Rows": 379, + + "Actual Loops": 1, + + "Index Cond": "((device >= 5) AND (device <= 10))", + + "Rows Removed by Index Recheck": 0, + + "Filter": "(humidity > '5'::double precision)", + + "Rows Removed by Filter": 16 + + }, + + { + + "Node Type": "Index Scan", + + "Parent Relationship": "Member", + + "Parallel Aware": false, + + "Async Capable": false, + + "Scan Direction": "Forward", + + "Index Name": "_hyper_1_6_chunk_readings_device_idx",+ + "Relation Name": "_hyper_1_6_chunk", + + "Alias": "_hyper_1_6_chunk", + + "Actual Rows": 68, + + "Actual Loops": 1, + + "Index Cond": "((device >= 5) AND (device <= 10))", + + "Rows Removed by Index Recheck": 0, + + "Filter": "(humidity > '5'::double precision)", + + "Rows Removed by Filter": 6 + + } + + ] + + } + + ] + + }, + + "Triggers": [ + + ] + + }, + + "Arrow Array Cache": { + + "hits": 0, + + "misses": 6, + + "evictions": 0 + + }, + + "Arrow Array Decompress": { + + "count": 18, + + "calls": 105 + + } + + ] +(1 row) -- Check the explain cache information output. -- @@ -163,11 +291,8 @@ select time, temp + humidity from readings where device between 5 and 10 and hum Index Cond: ((device >= 5) AND (device <= 10)) Filter: (humidity > '5'::double precision) Rows Removed by Filter: 6 - Array Cache Hits: 0 - Array Cache Misses: 6 - Array Cache Evictions: 0 - Array Decompressions: 18 -(30 rows) + Array: cache misses=6, decompress count=18 calls=105 +(27 rows) -- Check the explain cache information output. Query 1 and 3 should -- show the same explain plan, and the plan in the middle should not @@ -178,11 +303,7 @@ select * from :chunk where device between 5 and 10; ---------------------------------------------------------------------------------------------------- Index Scan using _hyper_1_1_chunk_readings_device_idx on _hyper_1_1_chunk (actual rows=35 loops=1) Index Cond: ((device >= 5) AND (device <= 10)) - Array Cache Hits: 0 - Array Cache Misses: 0 - Array Cache Evictions: 0 - Array Decompressions: 0 -(6 rows) +(2 rows) explain (analyze, costs off, timing off, summary off, decompress_cache_stats) select * from :chunk where device between 5 and 10; @@ -190,11 +311,7 @@ select * from :chunk where device between 5 and 10; ---------------------------------------------------------------------------------------------------- Index Scan using _hyper_1_1_chunk_readings_device_idx on _hyper_1_1_chunk (actual rows=35 loops=1) Index Cond: ((device >= 5) AND (device <= 10)) - Array Cache Hits: 0 - Array Cache Misses: 0 - Array Cache Evictions: 0 - Array Decompressions: 0 -(6 rows) +(2 rows) -- Queries that will select just a few columns set max_parallel_workers_per_gather to 0; @@ -215,11 +332,8 @@ select device, humidity from readings where device between 5 and 10; Index Cond: ((device >= 5) AND (device <= 10)) -> Index Scan using _hyper_1_6_chunk_readings_device_idx on _hyper_1_6_chunk (actual rows=74 loops=1) Index Cond: ((device >= 5) AND (device <= 10)) - Array Cache Hits: 0 - Array Cache Misses: 6 - Array Cache Evictions: 0 - Array Decompressions: 6 -(17 rows) + Array: cache misses=6, decompress count=6 calls=35 +(14 rows) explain (analyze, costs off, timing off, summary off, decompress_cache_stats) select device, avg(humidity) from readings where device between 5 and 10 @@ -242,11 +356,8 @@ group by device; Index Cond: ((device >= 5) AND (device <= 10)) -> Index Scan using _hyper_1_6_chunk_readings_device_idx on _hyper_1_6_chunk (actual rows=74 loops=1) Index Cond: ((device >= 5) AND (device <= 10)) - Array Cache Hits: 0 - Array Cache Misses: 6 - Array Cache Evictions: 0 - Array Decompressions: 6 -(20 rows) + Array: cache misses=6, decompress count=6 calls=35 +(17 rows) -- Test on conflict: insert the same data as before, but throw away -- the updates. @@ -266,11 +377,8 @@ on conflict (location, device, time) do nothing; -> Custom Scan (ChunkDispatch) (actual rows=8641 loops=1) -> Subquery Scan on "*SELECT*" (actual rows=8641 loops=1) -> Function Scan on generate_series t (actual rows=8641 loops=1) - Array Cache Hits: 0 - Array Cache Misses: 2 - Array Cache Evictions: 0 - Array Decompressions: 4 -(13 rows) + Array: cache misses=2, decompress count=4 calls=4 +(10 rows) -- This should show values for all columns explain (analyze, costs off, timing off, summary off, decompress_cache_stats) @@ -299,11 +407,7 @@ select time, temp + humidity from readings where device between 5 and 10 and hum -> Index Scan using _hyper_1_6_chunk_readings_device_idx on _hyper_1_6_chunk (never executed) Index Cond: ((device >= 5) AND (device <= 10)) Filter: (humidity > '5'::double precision) - Array Cache Hits: 0 - Array Cache Misses: 0 - Array Cache Evictions: 0 - Array Decompressions: 0 -(26 rows) +(22 rows) select time, temp + humidity from readings where device between 5 and 10 and humidity > 5 limit 5; time | ?column? @@ -342,11 +446,8 @@ order by time desc; -> Custom Scan (ColumnarScan) on _hyper_1_1_chunk (actual rows=88 loops=1) Vectorized Filter: (location = '1'::text) Rows Removed by Filter: 319 - Array Cache Hits: 0 - Array Cache Misses: 30 - Array Cache Evictions: 0 - Array Decompressions: 84 -(10 rows) + Array: cache misses=30, decompress count=84 calls=242 +(7 rows) -- Save the data for comparison with seqscan create temp table chunk_saved as @@ -404,11 +505,7 @@ select count(*) from :chunk where device = 1; Aggregate (actual rows=1 loops=1) -> Custom Scan (ColumnarScan) on _hyper_1_1_chunk (actual rows=17 loops=1) Scankey: (device = 1) - Array Cache Hits: 0 - Array Cache Misses: 0 - Array Cache Evictions: 0 - Array Decompressions: 0 -(7 rows) +(3 rows) explain (analyze, costs off, timing off, summary off, decompress_cache_stats) select device from :chunk where device = 1; @@ -416,11 +513,7 @@ select device from :chunk where device = 1; ------------------------------------------------------------------------- Custom Scan (ColumnarScan) on _hyper_1_1_chunk (actual rows=17 loops=1) Scankey: (device = 1) - Array Cache Hits: 0 - Array Cache Misses: 0 - Array Cache Evictions: 0 - Array Decompressions: 0 -(6 rows) +(2 rows) -- Using a non-segmentby column will decompress that column explain (analyze, costs off, timing off, summary off, decompress_cache_stats) @@ -431,11 +524,8 @@ select count(*) from :chunk where location = 1::text; -> Custom Scan (ColumnarScan) on _hyper_1_1_chunk (actual rows=89 loops=1) Vectorized Filter: (location = '1'::text) Rows Removed by Filter: 320 - Array Cache Hits: 0 - Array Cache Misses: 30 - Array Cache Evictions: 0 - Array Decompressions: 30 -(8 rows) + Array: cache misses=30, decompress count=30 calls=30 +(5 rows) -- Testing same thing with SeqScan. It still decompresses in the -- count(*) case, although it shouldn't have to. So, probably an @@ -449,11 +539,8 @@ select count(*) from :chunk where device = 1; -> Seq Scan on _hyper_1_1_chunk (actual rows=17 loops=1) Filter: (device = 1) Rows Removed by Filter: 392 - Array Cache Hits: 0 - Array Cache Misses: 30 - Array Cache Evictions: 0 - Array Decompressions: 62 -(8 rows) + Array: cache misses=30, decompress count=62 calls=410 +(5 rows) explain (analyze, costs off, timing off, summary off, decompress_cache_stats) select device from :chunk where device = 1; @@ -462,11 +549,7 @@ select device from :chunk where device = 1; Seq Scan on _hyper_1_1_chunk (actual rows=17 loops=1) Filter: (device = 1) Rows Removed by Filter: 392 - Array Cache Hits: 0 - Array Cache Misses: 0 - Array Cache Evictions: 0 - Array Decompressions: 0 -(7 rows) +(3 rows) explain (analyze, costs off, timing off, summary off, decompress_cache_stats) select count(*) from :chunk where location = 1::text; @@ -476,11 +559,8 @@ select count(*) from :chunk where location = 1::text; -> Seq Scan on _hyper_1_1_chunk (actual rows=89 loops=1) Filter: (location = '1'::text) Rows Removed by Filter: 320 - Array Cache Hits: 0 - Array Cache Misses: 30 - Array Cache Evictions: 0 - Array Decompressions: 62 -(8 rows) + Array: cache misses=30, decompress count=62 calls=410 +(5 rows) -- ColumnarScan declares itself as projection capable. This query -- would add a Result node on top if ColumnarScan couldn't project. diff --git a/tsl/test/expected/hypercore_stats.out b/tsl/test/expected/hypercore_stats.out index 6ac8d9ef03c..eb901217a41 100644 --- a/tsl/test/expected/hypercore_stats.out +++ b/tsl/test/expected/hypercore_stats.out @@ -14,6 +14,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -23,17 +40,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -46,14 +59,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_trigger.out b/tsl/test/expected/hypercore_trigger.out index 3de60ad3cdd..4ac4daeab42 100644 --- a/tsl/test/expected/hypercore_trigger.out +++ b/tsl/test/expected/hypercore_trigger.out @@ -14,6 +14,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -23,17 +40,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -46,14 +59,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_types.out b/tsl/test/expected/hypercore_types.out index 4de32b94590..b76c66945d5 100644 --- a/tsl/test/expected/hypercore_types.out +++ b/tsl/test/expected/hypercore_types.out @@ -9,6 +9,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -18,17 +35,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -41,14 +54,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_update.out b/tsl/test/expected/hypercore_update.out index 3426606c135..bd45a0e7c58 100644 --- a/tsl/test/expected/hypercore_update.out +++ b/tsl/test/expected/hypercore_update.out @@ -15,6 +15,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -24,17 +41,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -47,14 +60,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/expected/hypercore_vacuum_full.out b/tsl/test/expected/hypercore_vacuum_full.out index 99643f1d882..6866229a1c2 100644 --- a/tsl/test/expected/hypercore_vacuum_full.out +++ b/tsl/test/expected/hypercore_vacuum_full.out @@ -14,6 +14,23 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -23,17 +40,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -46,14 +59,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; diff --git a/tsl/test/sql/hypercore_scans.sql b/tsl/test/sql/hypercore_scans.sql index 63beaef30f3..b7cc7f276c7 100644 --- a/tsl/test/sql/hypercore_scans.sql +++ b/tsl/test/sql/hypercore_scans.sql @@ -70,6 +70,11 @@ select * from :chunk where device between 5 and 10; explain (analyze, costs off, timing off, summary off, decompress_cache_stats) select time, temp + humidity from readings where device between 5 and 10 and humidity > 5; +-- Testing JSON format to make sure it works and to get coverage for +-- those parts of the code. +explain (analyze, costs off, timing off, summary off, decompress_cache_stats, format json) +select time, temp + humidity from readings where device between 5 and 10 and humidity > 5; + -- Check the explain cache information output. -- -- Query 1 and 3 should show the same explain plan, and the plan in diff --git a/tsl/test/sql/include/hypercore_helpers.sql b/tsl/test/sql/include/hypercore_helpers.sql index c8a1bd49d0f..2fb9e0ec9ac 100644 --- a/tsl/test/sql/include/hypercore_helpers.sql +++ b/tsl/test/sql/include/hypercore_helpers.sql @@ -6,6 +6,24 @@ -- emitted plan. This is intended to be used when the structure of the -- plan is important, but not the specific chunks scanned nor the -- number of heap fetches, rows, loops, etc. +create function anonymize(ln text) returns text language plpgsql as +$$ +begin + ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); + ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); + ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); + ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); + + if trim(both from ln) like 'Array: %' then + ln := regexp_replace(ln, 'hits=\d+', 'hits=N'); + ln := regexp_replace(ln, 'misses=\d+', 'misses=N'); + ln := regexp_replace(ln, 'count=\d+', 'count=N'); + ln := regexp_replace(ln, 'calls=\d+', 'calls=N'); + end if; + return ln; +end +$$; + create function explain_analyze_anonymize(text) returns setof text language plpgsql as $$ @@ -15,17 +33,13 @@ begin for ln in execute format('explain (analyze, costs off, summary off, timing off, decompress_cache_stats) %s', $1) loop - if trim(both from ln) like 'Group Key:%' then + -- Group keys are shown for plans in PG15 but not others, so + -- we remove these lines to avoid having to have + -- version-sensible tests. + if trim(both from ln) like 'Group Key:%' then continue; end if; - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$; @@ -39,14 +53,7 @@ begin for ln in execute format('explain (costs off, summary off, timing off) %s', $1) loop - ln := regexp_replace(ln, 'Array Cache Hits: \d+', 'Array Cache Hits: N'); - ln := regexp_replace(ln, 'Array Cache Misses: \d+', 'Array Cache Misses: N'); - ln := regexp_replace(ln, 'Array Cache Evictions: \d+', 'Array Cache Evictions: N'); - ln := regexp_replace(ln, 'Heap Fetches: \d+', 'Heap Fetches: N'); - ln := regexp_replace(ln, 'Workers Launched: \d+', 'Workers Launched: N'); - ln := regexp_replace(ln, 'actual rows=\d+ loops=\d+', 'actual rows=N loops=N'); - ln := regexp_replace(ln, '_hyper_\d+_\d+_chunk', '_hyper_I_N_chunk', 1, 0); - return next ln; + return next anonymize(ln); end loop; end; $$;