-- This file and its contents are licensed under the Timescale License.
-- Please see the included NOTICE for copyright information and
-- LICENSE-TIMESCALE for a copy of the license.
\c :TEST_DBNAME :ROLE_SUPERUSER
--set enable_indexscan=false;
-- Helper function that returns the amount of memory currently allocated in a
-- given memory context.
create or replace function ts_debug_allocated_bytes(text = 'PortalContext') returns bigint
    as :MODULE_PATHNAME, 'ts_debug_allocated_bytes'
    language c strict volatile;
create table mvagg (t int, s0 int, s1 int);
select create_hypertable('mvagg', 't', chunk_time_interval => pow(10, 9)::int);
 create_hypertable  
--------------------
 (1,public,mvagg,t)

insert into mvagg select generate_series(1, 2 * pow(10, 6)::int) t, 1 s0, 1 s1;
-- Need two segmentbys to prevent compressed index scans to force hash aggregation.
-- Otherwise we might get GroupAggregate with Sort which uses linear memory in
-- the number of compressed batches.
alter table mvagg set (timescaledb.compress, timescaledb.compress_segmentby='s0, s1');
-- Need to inflate the estimated cardinalities of segmentby columns, again to
-- force the hash aggregation.
insert into mvagg select -1 - x t, -x s0, -x s1 from generate_series(1, 1000) x;
-- Need two chunks for chunkwise aggregation.
insert into mvagg select -1 t, 1 s0, 1 s1;
select count(compress_chunk(x)) from show_chunks('mvagg') x;
 count 
-------
     2

vacuum analyze mvagg;
-- We are going to log memory usage as a function of number of aggregated elements
-- here.
create table log(n int, bytes int, a bigint, b bigint, c bigint, d bigint, e bigint, f bigint);
-- First, ensure that the underlying decompression has constant memory usage.
explain (buffers off, costs off) select distinct on (s0, s1) ts_debug_allocated_bytes() bytes,
        s0, s1, t
    from mvagg where t >= -1 and t < 1000000 order by s0, s1, t desc;
--- QUERY PLAN ---
 Result
   ->  Unique
         ->  Merge Append
               Sort Key: mvagg.s0, mvagg.s1, mvagg.t DESC
               ->  Custom Scan (ColumnarScan) on _hyper_1_1_chunk
                     Vectorized Filter: ((t >= '-1'::integer) AND (t < 1000000))
                     ->  Index Scan using compress_hyper_2_3_chunk_s0_s1__ts_meta_min_1__ts_meta_max__idx on compress_hyper_2_3_chunk
                           Index Cond: ((_ts_meta_min_1 < 1000000) AND (_ts_meta_max_1 >= '-1'::integer))
               ->  Custom Scan (ColumnarScan) on _hyper_1_2_chunk
                     Vectorized Filter: ((t >= '-1'::integer) AND (t < 1000000))
                     ->  Index Scan using compress_hyper_2_4_chunk_s0_s1__ts_meta_min_1__ts_meta_max__idx on compress_hyper_2_4_chunk
                           Index Cond: ((_ts_meta_min_1 < 1000000) AND (_ts_meta_max_1 >= '-1'::integer))

truncate log;
\set ECHO none
select * from log where (
    -- Ideally the memory usage should be constant, but we have to allow for
    -- small spurious changes to make this test more robust.
    select regr_slope(bytes, n) > 1/65536 from log
);
 n | bytes | a | b | c | d | e | f 
---+-------+---+---+---+---+---+---

-- Test the vectorized aggregation with grouping by segmentby with various number
-- of input row. We expect approximately constant memory usage.
truncate log;
set max_parallel_workers_per_gather = 0;
set timescaledb.debug_require_vector_agg = 'require';
set enable_sort to off;
set enable_indexscan to off;
set enable_bitmapscan to off;
-- We should reliably see HashAggregate here because of the tweaks we made above.
explain (buffers off, costs off) select ts_debug_allocated_bytes() bytes,
        count(*) a, count(t) b, sum(t) c, avg(t) d, min(t) e, max(t) f
            from mvagg where t >= -1 and t < 1000000 group by s1;
--- QUERY PLAN ---
 Finalize HashAggregate
   Group Key: mvagg.s1
   ->  Append
         ->  Custom Scan (VectorAgg)
               ->  Custom Scan (ColumnarScan) on _hyper_1_1_chunk
                     Vectorized Filter: ((t >= '-1'::integer) AND (t < 1000000))
                     ->  Seq Scan on compress_hyper_2_3_chunk
                           Filter: ((_ts_meta_max_1 >= '-1'::integer) AND (_ts_meta_min_1 < 1000000))
         ->  Custom Scan (VectorAgg)
               ->  Custom Scan (ColumnarScan) on _hyper_1_2_chunk
                     Vectorized Filter: ((t >= '-1'::integer) AND (t < 1000000))
                     ->  Seq Scan on compress_hyper_2_4_chunk
                           Filter: ((_ts_meta_max_1 >= '-1'::integer) AND (_ts_meta_min_1 < 1000000))

\set ECHO none
reset timescaledb.debug_require_vector_agg;
reset max_parallel_workers_per_gather;
reset work_mem;
reset enable_sort;
select * from log where (
    -- For aggregation by segmentby, memory usage should be constant regardless
    -- of the number of tuples. Still, we have to allow for small variations
    -- that can be caused by other reasons.
    select regr_slope(bytes, n) > 0.01 from log
);
 n | bytes | a | b | c | d | e | f 
---+-------+---+---+---+---+---+---

reset timescaledb.debug_require_vector_agg;
