-- 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.
SET timezone TO PST8PDT;
\ir include/cagg_refresh_common.sql
-- 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.
CREATE TABLE conditions (time timestamptz NOT NULL, device int, temp float);
SELECT create_hypertable('conditions', 'time');
    create_hypertable    
-------------------------
 (1,public,conditions,t)

-- Test refresh on a cagg built on an empty table
CREATE MATERIALIZED VIEW daily_temp
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket('1 day', time) AS day, device, avg(temp) AS avg_temp
FROM conditions
GROUP BY 1,2 WITH NO DATA;
CALL refresh_continuous_aggregate('daily_temp', NULL, NULL);
psql:include/cagg_refresh_common.sql:17: NOTICE:  continuous aggregate "daily_temp" is already up-to-date
CALL refresh_continuous_aggregate('daily_temp', NULL, NULL, force => true);
psql:include/cagg_refresh_common.sql:18: NOTICE:  continuous aggregate "daily_temp" is already up-to-date
SELECT setseed(.12);
 setseed 
---------
 

INSERT INTO conditions
SELECT t, ceil(abs(timestamp_hash(t::timestamp))%4)::int, abs(timestamp_hash(t::timestamp))%40
FROM generate_series('2020-05-01', '2020-05-05', '10 minutes'::interval) t;
-- Show the most recent data
SELECT * FROM conditions
ORDER BY time DESC, device
LIMIT 10;
             time             | device | temp 
------------------------------+--------+------
 Tue May 05 00:00:00 2020 PDT |      2 |   30
 Mon May 04 23:50:00 2020 PDT |      2 |   10
 Mon May 04 23:40:00 2020 PDT |      0 |   20
 Mon May 04 23:30:00 2020 PDT |      1 |    1
 Mon May 04 23:20:00 2020 PDT |      2 |   34
 Mon May 04 23:10:00 2020 PDT |      1 |   37
 Mon May 04 23:00:00 2020 PDT |      0 |    4
 Mon May 04 22:50:00 2020 PDT |      2 |   10
 Mon May 04 22:40:00 2020 PDT |      1 |   37
 Mon May 04 22:30:00 2020 PDT |      0 |    8

-- The continuous aggregate should be empty
SELECT * FROM daily_temp
ORDER BY day DESC, device;
 day | device | avg_temp 
-----+--------+----------

-- Refresh one bucket (1 day):
SHOW timezone;
 TimeZone 
----------
 PST8PDT

-- The refresh of a single bucket must align with the start of the day
-- in the bucket's time zone (which is UTC, since time_bucket doesn't
-- support time zone arg)
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 00:00 UTC', '2020-05-04 00:00 UTC');
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 17:00 PDT', '2020-05-04 17:00 PDT');
\set ON_ERROR_STOP 0
\set VERBOSITY default
-- These refreshes will fail since they don't align with the bucket's
-- time zone
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', '2020-05-04');
psql:include/cagg_refresh_common.sql:47: ERROR:  refresh window too small
DETAIL:  The refresh window must cover at least one bucket of data.
HINT:  Align the refresh window with the bucket time zone or use at least two buckets.
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 00:00 PDT', '2020-05-04 00:00 PDT');
psql:include/cagg_refresh_common.sql:48: ERROR:  refresh window too small
DETAIL:  The refresh window must cover at least one bucket of data.
HINT:  Align the refresh window with the bucket time zone or use at least two buckets.
-- Refresh window less than one bucket
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 00:00 UTC', '2020-05-03 23:59 UTC');
psql:include/cagg_refresh_common.sql:51: ERROR:  refresh window too small
DETAIL:  The refresh window must cover at least one bucket of data.
HINT:  Align the refresh window with the bucket time zone or use at least two buckets.
-- Refresh window bigger than one bucket, but failing since it is not
-- aligned with bucket boundaries so that it covers a full bucket:
--
-- Refresh window:    [----------)
-- Buckets:          [------|------]
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 01:00 UTC', '2020-05-04 08:00 UTC');
psql:include/cagg_refresh_common.sql:57: ERROR:  refresh window too small
DETAIL:  The refresh window must cover at least one bucket of data.
HINT:  Align the refresh window with the bucket time zone or use at least two buckets.
\set VERBOSITY terse
\set ON_ERROR_STOP 1
-- Refresh the most recent few days:
CALL refresh_continuous_aggregate('daily_temp', '2020-05-02', '2020-05-05 17:00');
SELECT * FROM daily_temp
ORDER BY day DESC, device;
             day              | device |     avg_temp     
------------------------------+--------+------------------
 Mon May 04 17:00:00 2020 PDT |      0 | 19.3846153846154
 Mon May 04 17:00:00 2020 PDT |      1 | 16.5555555555556
 Mon May 04 17:00:00 2020 PDT |      2 | 18.5714285714286
 Mon May 04 17:00:00 2020 PDT |      3 | 23.5714285714286
 Sun May 03 17:00:00 2020 PDT |      0 | 15.7647058823529
 Sun May 03 17:00:00 2020 PDT |      1 | 24.3142857142857
 Sun May 03 17:00:00 2020 PDT |      2 | 14.8205128205128
 Sun May 03 17:00:00 2020 PDT |      3 | 18.1111111111111
 Sat May 02 17:00:00 2020 PDT |      0 |               17
 Sat May 02 17:00:00 2020 PDT |      1 |            18.75
 Sat May 02 17:00:00 2020 PDT |      2 |               20
 Sat May 02 17:00:00 2020 PDT |      3 | 21.5217391304348

-- Refresh the rest
CALL refresh_continuous_aggregate('daily_temp', '2020-04-30', '2020-05-04');
-- Compare the aggregate to the equivalent query on the source table
SELECT * FROM daily_temp
ORDER BY day DESC, device;
             day              | device |     avg_temp     
------------------------------+--------+------------------
 Mon May 04 17:00:00 2020 PDT |      0 | 19.3846153846154
 Mon May 04 17:00:00 2020 PDT |      1 | 16.5555555555556
 Mon May 04 17:00:00 2020 PDT |      2 | 18.5714285714286
 Mon May 04 17:00:00 2020 PDT |      3 | 23.5714285714286
 Sun May 03 17:00:00 2020 PDT |      0 | 15.7647058823529
 Sun May 03 17:00:00 2020 PDT |      1 | 24.3142857142857
 Sun May 03 17:00:00 2020 PDT |      2 | 14.8205128205128
 Sun May 03 17:00:00 2020 PDT |      3 | 18.1111111111111
 Sat May 02 17:00:00 2020 PDT |      0 |               17
 Sat May 02 17:00:00 2020 PDT |      1 |            18.75
 Sat May 02 17:00:00 2020 PDT |      2 |               20
 Sat May 02 17:00:00 2020 PDT |      3 | 21.5217391304348
 Fri May 01 17:00:00 2020 PDT |      0 |               19
 Fri May 01 17:00:00 2020 PDT |      1 | 15.1463414634146
 Fri May 01 17:00:00 2020 PDT |      2 | 19.7674418604651
 Fri May 01 17:00:00 2020 PDT |      3 |            22.25
 Thu Apr 30 17:00:00 2020 PDT |      0 | 17.6666666666667
 Thu Apr 30 17:00:00 2020 PDT |      1 | 18.8333333333333
 Thu Apr 30 17:00:00 2020 PDT |      2 | 16.7586206896552
 Thu Apr 30 17:00:00 2020 PDT |      3 |            20.76

SELECT time_bucket('1 day', time) AS day, device, avg(temp) AS avg_temp
FROM conditions
GROUP BY 1,2
ORDER BY 1 DESC,2;
             day              | device |     avg_temp     
------------------------------+--------+------------------
 Mon May 04 17:00:00 2020 PDT |      0 | 19.3846153846154
 Mon May 04 17:00:00 2020 PDT |      1 | 16.5555555555556
 Mon May 04 17:00:00 2020 PDT |      2 | 18.5714285714286
 Mon May 04 17:00:00 2020 PDT |      3 | 23.5714285714286
 Sun May 03 17:00:00 2020 PDT |      0 | 15.7647058823529
 Sun May 03 17:00:00 2020 PDT |      1 | 24.3142857142857
 Sun May 03 17:00:00 2020 PDT |      2 | 14.8205128205128
 Sun May 03 17:00:00 2020 PDT |      3 | 18.1111111111111
 Sat May 02 17:00:00 2020 PDT |      0 |               17
 Sat May 02 17:00:00 2020 PDT |      1 |            18.75
 Sat May 02 17:00:00 2020 PDT |      2 |               20
 Sat May 02 17:00:00 2020 PDT |      3 | 21.5217391304348
 Fri May 01 17:00:00 2020 PDT |      0 |               19
 Fri May 01 17:00:00 2020 PDT |      1 | 15.1463414634146
 Fri May 01 17:00:00 2020 PDT |      2 | 19.7674418604651
 Fri May 01 17:00:00 2020 PDT |      3 |            22.25
 Thu Apr 30 17:00:00 2020 PDT |      0 | 17.6666666666667
 Thu Apr 30 17:00:00 2020 PDT |      1 | 18.8333333333333
 Thu Apr 30 17:00:00 2020 PDT |      2 | 16.7586206896552
 Thu Apr 30 17:00:00 2020 PDT |      3 |            20.76

-- Test unusual, but valid input
CALL refresh_continuous_aggregate('daily_temp', '2020-05-01'::timestamptz, '2020-05-03'::date);
psql:include/cagg_refresh_common.sql:80: NOTICE:  continuous aggregate "daily_temp" is already up-to-date
CALL refresh_continuous_aggregate('daily_temp', '2020-05-01'::date, '2020-05-03'::date);
psql:include/cagg_refresh_common.sql:81: NOTICE:  continuous aggregate "daily_temp" is already up-to-date
-- Unbounded window forward in time
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', NULL);
psql:include/cagg_refresh_common.sql:84: NOTICE:  continuous aggregate "daily_temp" is already up-to-date
CALL refresh_continuous_aggregate('daily_temp', NULL, NULL);
-- Unbounded window back in time
CALL refresh_continuous_aggregate('daily_temp', NULL, '2020-05-01');
psql:include/cagg_refresh_common.sql:88: NOTICE:  continuous aggregate "daily_temp" is already up-to-date
-- Test bad input
\set ON_ERROR_STOP 0
-- Bad continuous aggregate name
CALL refresh_continuous_aggregate(NULL, '2020-05-03', '2020-05-05');
psql:include/cagg_refresh_common.sql:93: ERROR:  invalid continuous aggregate
CALL refresh_continuous_aggregate('xyz', '2020-05-03', '2020-05-05');
psql:include/cagg_refresh_common.sql:94: ERROR:  relation "xyz" does not exist at character 35
-- Valid object, but not a continuous aggregate
CALL refresh_continuous_aggregate('conditions', '2020-05-03', '2020-05-05');
psql:include/cagg_refresh_common.sql:96: ERROR:  relation "conditions" is not a continuous aggregate
-- Object ID with no object
CALL refresh_continuous_aggregate(1, '2020-05-03', '2020-05-05');
psql:include/cagg_refresh_common.sql:98: ERROR:  continuous aggregate does not exist
-- Lacking arguments
CALL refresh_continuous_aggregate('daily_temp');
psql:include/cagg_refresh_common.sql:100: ERROR:  procedure refresh_continuous_aggregate(unknown) does not exist at character 6
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03');
psql:include/cagg_refresh_common.sql:101: ERROR:  procedure refresh_continuous_aggregate(unknown, unknown) does not exist at character 6
-- Bad time ranges
CALL refresh_continuous_aggregate('daily_temp', 'xyz', '2020-05-05');
psql:include/cagg_refresh_common.sql:103: ERROR:  invalid input syntax for type timestamp with time zone: "xyz"
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', 'xyz');
psql:include/cagg_refresh_common.sql:104: ERROR:  invalid input syntax for type timestamp with time zone: "xyz"
CALL refresh_continuous_aggregate('daily_temp', '2020-05-03', '2020-05-01');
psql:include/cagg_refresh_common.sql:105: ERROR:  refresh window too small
-- Bad time input
CALL refresh_continuous_aggregate('daily_temp', '2020-05-01'::text, '2020-05-03'::text);
psql:include/cagg_refresh_common.sql:107: ERROR:  invalid time argument type "text"
CALL refresh_continuous_aggregate('daily_temp', 0, '2020-05-01');
psql:include/cagg_refresh_common.sql:108: ERROR:  invalid time argument type "integer"
\set ON_ERROR_STOP 1
-- Test forceful refreshment. Here we simulate the situation that we've seen
-- with tiered data when `timescaledb.enable_tiered_reads` were disabled on the
-- server level. In that case we would not see materialized tiered data and
-- we wouldn't be able to re-materialize the data using a normal refresh call
-- because it would skip previously materialized ranges, but it should be
-- possible with `force=>true` parameter. To simulate this use-case we clear
-- the materialization hypertable and forefully re-materialize it.
SELECT format('%I.%I', ht.schema_name, ht.table_name) AS mat_ht, mat_hypertable_id FROM _timescaledb_catalog.continuous_agg cagg
JOIN _timescaledb_catalog.hypertable ht ON cagg.mat_hypertable_id = ht.id
WHERE user_view_name = 'daily_temp' \gset
-- Delete the data from the materialization hypertable
DELETE FROM :mat_ht;
-- Run regular refresh, it should not touch previously materialized range
CALL refresh_continuous_aggregate('daily_temp', '2020-05-02', '2020-05-05 17:00');
psql:include/cagg_refresh_common.sql:126: NOTICE:  continuous aggregate "daily_temp" is already up-to-date
SELECT * FROM daily_temp
ORDER BY day DESC, device;
 day | device | avg_temp 
-----+--------+----------

-- Run it again with force=>true, the data should be rematerialized
CALL refresh_continuous_aggregate('daily_temp', '2020-05-02', '2020-05-05 17:00', force=>true);
SELECT * FROM daily_temp
ORDER BY day DESC, device;
             day              | device |     avg_temp     
------------------------------+--------+------------------
 Mon May 04 17:00:00 2020 PDT |      0 | 19.3846153846154
 Mon May 04 17:00:00 2020 PDT |      1 | 16.5555555555556
 Mon May 04 17:00:00 2020 PDT |      2 | 18.5714285714286
 Mon May 04 17:00:00 2020 PDT |      3 | 23.5714285714286
 Sun May 03 17:00:00 2020 PDT |      0 | 15.7647058823529
 Sun May 03 17:00:00 2020 PDT |      1 | 24.3142857142857
 Sun May 03 17:00:00 2020 PDT |      2 | 14.8205128205128
 Sun May 03 17:00:00 2020 PDT |      3 | 18.1111111111111
 Sat May 02 17:00:00 2020 PDT |      0 |               17
 Sat May 02 17:00:00 2020 PDT |      1 |            18.75
 Sat May 02 17:00:00 2020 PDT |      2 |               20
 Sat May 02 17:00:00 2020 PDT |      3 | 21.5217391304348

-- Test different time types
CREATE TABLE conditions_date (time date NOT NULL, device int, temp float);
SELECT create_hypertable('conditions_date', 'time');
      create_hypertable       
------------------------------
 (3,public,conditions_date,t)

CREATE MATERIALIZED VIEW daily_temp_date
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket('1 day', time) AS day, device, avg(temp) AS avg_temp
FROM conditions_date
GROUP BY 1,2 WITH NO DATA;
CALL refresh_continuous_aggregate('daily_temp_date', '2020-05-01', '2020-05-03');
-- Try max refresh window size
CALL refresh_continuous_aggregate('daily_temp_date', NULL, NULL);
-- Test smallint-based continuous aggregate
CREATE TABLE conditions_smallint (time smallint NOT NULL, device int, temp float);
SELECT create_hypertable('conditions_smallint', 'time', chunk_time_interval => 20);
        create_hypertable         
----------------------------------
 (5,public,conditions_smallint,t)

INSERT INTO conditions_smallint
SELECT t, ceil(abs(timestamp_hash(to_timestamp(t)::timestamp))%4)::smallint, abs(timestamp_hash(to_timestamp(t)::timestamp))%40
FROM generate_series(1, 100, 1) t;
CREATE OR REPLACE FUNCTION smallint_now()
RETURNS smallint LANGUAGE SQL STABLE AS
$$
    SELECT coalesce(max(time), 0)::smallint
    FROM conditions_smallint
$$;
\set ON_ERROR_STOP 0
-- First try to create an integer-based continuous aggregate without
-- an now function. This should not be allowed.
CREATE MATERIALIZED VIEW cond_20_smallint
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket(SMALLINT '20', time) AS bucket, device, avg(temp) AS avg_temp
FROM conditions_smallint c
GROUP BY 1,2 WITH NO DATA;
psql:include/cagg_refresh_common.sql:175: ERROR:  custom time function required on hypertable "conditions_smallint"
\set ON_ERROR_STOP 1
SELECT set_integer_now_func('conditions_smallint', 'smallint_now');
 set_integer_now_func 
----------------------
 

CREATE MATERIALIZED VIEW cond_20_smallint
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket(SMALLINT '20', time) AS bucket, device, avg(temp) AS avg_temp
FROM conditions_smallint c
GROUP BY 1,2 WITH NO DATA;
CALL refresh_continuous_aggregate('cond_20_smallint', 0::smallint, 70::smallint);
SELECT * FROM cond_20_smallint
ORDER BY 1,2;
 bucket | device |     avg_temp     
--------+--------+------------------
      0 |      0 |                6
      0 |      1 |               19
      0 |      2 |             14.5
      0 |      3 |             21.4
     20 |      0 |               15
     20 |      1 |               16
     20 |      2 | 23.3333333333333
     20 |      3 | 13.6666666666667
     40 |      0 |               21
     40 |      1 |             19.4
     40 |      2 |               22
     40 |      3 |             21.4

-- Try max refresh window size
CALL refresh_continuous_aggregate('cond_20_smallint', NULL, NULL);
-- Test int-based continuous aggregate
CREATE TABLE conditions_int (time int NOT NULL, device int, temp float);
SELECT create_hypertable('conditions_int', 'time', chunk_time_interval => 20);
      create_hypertable      
-----------------------------
 (7,public,conditions_int,t)

INSERT INTO conditions_int
SELECT t, ceil(abs(timestamp_hash(to_timestamp(t)::timestamp))%4)::int, abs(timestamp_hash(to_timestamp(t)::timestamp))%40
FROM generate_series(1, 100, 1) t;
CREATE OR REPLACE FUNCTION int_now()
RETURNS int LANGUAGE SQL STABLE AS
$$
    SELECT coalesce(max(time), 0)
    FROM conditions_int
$$;
SELECT set_integer_now_func('conditions_int', 'int_now');
 set_integer_now_func 
----------------------
 

CREATE MATERIALIZED VIEW cond_20_int
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket(INT '20', time) AS bucket, device, avg(temp) AS avg_temp
FROM conditions_int
GROUP BY 1,2 WITH NO DATA;
CALL refresh_continuous_aggregate('cond_20_int', 0, 65);
SELECT * FROM cond_20_int
ORDER BY 1,2;
 bucket | device |     avg_temp     
--------+--------+------------------
      0 |      0 |                6
      0 |      1 |               19
      0 |      2 |             14.5
      0 |      3 |             21.4
     20 |      0 |               15
     20 |      1 |               16
     20 |      2 | 23.3333333333333
     20 |      3 | 13.6666666666667
     40 |      0 |               21
     40 |      1 |             19.4
     40 |      2 |               22
     40 |      3 |             21.4

-- Try max refresh window size
CALL refresh_continuous_aggregate('cond_20_int', NULL, NULL);
-- Test bigint-based continuous aggregate
CREATE TABLE conditions_bigint (time bigint NOT NULL, device int, temp float);
SELECT create_hypertable('conditions_bigint', 'time', chunk_time_interval => 20);
       create_hypertable        
--------------------------------
 (9,public,conditions_bigint,t)

INSERT INTO conditions_bigint
SELECT t, ceil(abs(timestamp_hash(to_timestamp(t)::timestamp))%4)::bigint, abs(timestamp_hash(to_timestamp(t)::timestamp))%40
FROM generate_series(1, 100, 1) t;
CREATE OR REPLACE FUNCTION bigint_now()
RETURNS bigint LANGUAGE SQL STABLE AS
$$
    SELECT coalesce(max(time), 0)::bigint
    FROM conditions_bigint
$$;
SELECT set_integer_now_func('conditions_bigint', 'bigint_now');
 set_integer_now_func 
----------------------
 

CREATE MATERIALIZED VIEW cond_20_bigint
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket(BIGINT '20', time) AS bucket, device, avg(temp) AS avg_temp
FROM conditions_bigint
GROUP BY 1,2 WITH NO DATA;
CALL refresh_continuous_aggregate('cond_20_bigint', 0, 75);
SELECT * FROM cond_20_bigint
ORDER BY 1,2;
 bucket | device |     avg_temp     
--------+--------+------------------
      0 |      0 |                6
      0 |      1 |               19
      0 |      2 |             14.5
      0 |      3 |             21.4
     20 |      0 |               15
     20 |      1 |               16
     20 |      2 | 23.3333333333333
     20 |      3 | 13.6666666666667
     40 |      0 |               21
     40 |      1 |             19.4
     40 |      2 |               22
     40 |      3 |             21.4

-- Try max refresh window size
CALL refresh_continuous_aggregate('cond_20_bigint', NULL, NULL);
-- Test that WITH NO DATA and WITH DATA works (we use whatever is the
-- default for Postgres, so we do not need to have test for the
-- default).
CREATE MATERIALIZED VIEW weekly_temp_without_data
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket('7 days', time) AS day, device, avg(temp) AS avg_temp
FROM conditions
GROUP BY 1,2 WITH NO DATA;
CREATE MATERIALIZED VIEW weekly_temp_with_data
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket('7 days', time) AS day, device, avg(temp) AS avg_temp
FROM conditions
GROUP BY 1,2 WITH DATA;
psql:include/cagg_refresh_common.sql:280: NOTICE:  refreshing continuous aggregate "weekly_temp_with_data"
SELECT * FROM weekly_temp_without_data;
 day | device | avg_temp 
-----+--------+----------

SELECT * FROM weekly_temp_with_data ORDER BY 1,2;
             day              | device |     avg_temp     
------------------------------+--------+------------------
 Sun Apr 26 17:00:00 2020 PDT |      0 | 17.8181818181818
 Sun Apr 26 17:00:00 2020 PDT |      1 | 17.2474226804124
 Sun Apr 26 17:00:00 2020 PDT |      2 | 18.9803921568627
 Sun Apr 26 17:00:00 2020 PDT |      3 | 21.5631067961165
 Sun May 03 17:00:00 2020 PDT |      0 | 16.7659574468085
 Sun May 03 17:00:00 2020 PDT |      1 | 22.7272727272727
 Sun May 03 17:00:00 2020 PDT |      2 |  15.811320754717
 Sun May 03 17:00:00 2020 PDT |      3 |               19

\set ON_ERROR_STOP 0
-- REFRESH MATERIALIZED VIEW is blocked on continuous aggregates
REFRESH MATERIALIZED VIEW weekly_temp_without_data;
psql:include/cagg_refresh_common.sql:287: ERROR:  operation not supported on continuous aggregate
-- These should fail since we do not allow refreshing inside a
-- transaction, not even as part of CREATE MATERIALIZED VIEW.
DO LANGUAGE PLPGSQL $$ BEGIN
CREATE MATERIALIZED VIEW weekly_conditions
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket('7 days', time) AS day, device, avg(temp) AS avg_temp
FROM conditions
GROUP BY 1,2 WITH DATA;
END $$;
psql:include/cagg_refresh_common.sql:299: ERROR:  CREATE MATERIALIZED VIEW ... WITH DATA cannot be executed from a function
BEGIN;
CREATE MATERIALIZED VIEW weekly_conditions
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket('7 days', time) AS day, device, avg(temp) AS avg_temp
FROM conditions
GROUP BY 1,2 WITH DATA;
psql:include/cagg_refresh_common.sql:308: ERROR:  CREATE MATERIALIZED VIEW ... WITH DATA cannot run inside a transaction block
COMMIT;
\set ON_ERROR_STOP 1
-- This should not fail since we do not refresh the continuous
-- aggregate.
DO LANGUAGE PLPGSQL $$ BEGIN
CREATE MATERIALIZED VIEW weekly_conditions_1
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket('7 days', time) AS day, device, avg(temp) AS avg_temp
FROM conditions
GROUP BY 1,2 WITH NO DATA;
END $$;
BEGIN;
CREATE MATERIALIZED VIEW weekly_conditions_2
WITH (timescaledb.continuous,
      timescaledb.materialized_only=true)
AS
SELECT time_bucket('7 days', time) AS day, device, avg(temp) AS avg_temp
FROM conditions
GROUP BY 1,2 WITH NO DATA;
COMMIT;
-- refresh_continuous_aggregate can run two transactions, thus it cannot be
-- called in a transaction block (from a function, from dynamic SQL) or in a
-- subtransaction (from a procedure block with an EXCEPTION clause). Though it
-- does NOT require a top level context and can be called from a procedure
-- block without an EXCEPTION clause.
-- DO block
DO $$
BEGIN
  CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 00:00 UTC', '2020-05-04 00:00 UTC');
END; $$;
psql:include/cagg_refresh_common.sql:345: NOTICE:  continuous aggregate "daily_temp" is already up-to-date
-- Procedure without subtransaction
CREATE OR REPLACE PROCEDURE refresh_cagg_proc_normal()
LANGUAGE PLPGSQL AS
$$
BEGIN
  CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 00:00 UTC', '2020-05-04 00:00 UTC');
END; $$;
CALL refresh_cagg_proc_normal();
psql:include/cagg_refresh_common.sql:355: NOTICE:  continuous aggregate "daily_temp" is already up-to-date
\set ON_ERROR_STOP 0
-- Procedure with subtransaction
CREATE OR REPLACE PROCEDURE refresh_cagg_proc_subtransaction()
LANGUAGE PLPGSQL AS
$$
DECLARE
  errmsg TEXT;
BEGIN
  CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 00:00 UTC', '2020-05-04 00:00 UTC');
EXCEPTION WHEN OTHERS THEN
  GET STACKED DIAGNOSTICS errmsg = MESSAGE_TEXT;
  RAISE EXCEPTION '%', errmsg;
END; $$;
CALL refresh_cagg_proc_subtransaction();
psql:include/cagg_refresh_common.sql:372: ERROR:  refresh_continuous_aggregate() cannot run inside a transaction block
-- Function
CREATE OR REPLACE FUNCTION refresh_cagg_fun()
RETURNS INT LANGUAGE PLPGSQL AS
$$
BEGIN
  CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 00:00 UTC', '2020-05-04 00:00 UTC');
  RETURN 1;
END; $$;
SELECT * from  refresh_cagg_fun();
psql:include/cagg_refresh_common.sql:383: ERROR:  refresh_continuous_aggregate() cannot be executed from a function
-- Dynamic SQL
DO $$
BEGIN
  EXECUTE $inner$
      CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 00:00 UTC', '2020-05-04 00:00 UTC');
  $inner$;
END; $$;
psql:include/cagg_refresh_common.sql:391: ERROR:  refresh_continuous_aggregate() cannot be executed from a function
-- Trigger
CREATE TABLE refresh_cagg_trigger_table(a int);
CREATE FUNCTION refresh_cagg_trigger_fun()
RETURNS TRIGGER LANGUAGE PLPGSQL AS $$
BEGIN
  CALL refresh_continuous_aggregate('daily_temp', '2020-05-03 00:00 UTC', '2020-05-04 00:00 UTC');
END; $$;
CREATE TRIGGER refresh_cagg_trigger AFTER INSERT ON refresh_cagg_trigger_table
EXECUTE FUNCTION refresh_cagg_trigger_fun();
INSERT INTO refresh_cagg_trigger_table VALUES(1);
psql:include/cagg_refresh_common.sql:405: ERROR:  refresh_continuous_aggregate() cannot be executed from a function
\set ON_ERROR_STOP 1
-- check that cagg refresh is not blocked by decompression limit
CREATE TABLE conditions_decompress_limit (time timestamptz NOT NULL, device text, temp float) WITH (tsdb.hypertable, tsdb.partition_column='time');
INSERT INTO conditions_decompress_limit SELECT '2020-01-01','d' || i::text, 1.0 FROM generate_series(1,100) g(i);
CREATE MATERIALIZED VIEW daily_temp_decompress_limit WITH (tsdb.continuous) AS SELECT time_bucket('1 day', time) AS day, device, avg(temp) AS avg_temp FROM conditions_decompress_limit GROUP BY 1,2 WITH NO DATA;
ALTER MATERIALIZED VIEW daily_temp_decompress_limit SET (tsdb.columnstore,tsdb.segmentby = 'device');
psql:include/cagg_refresh_common.sql:413: NOTICE:  defaulting compress_orderby to day
CALL refresh_continuous_aggregate('daily_temp_decompress_limit', NULL, NULL);
SELECT compress_chunk(show_chunks('daily_temp_decompress_limit'));
              compress_chunk              
------------------------------------------
 _timescaledb_internal._hyper_17_26_chunk

INSERT INTO conditions_decompress_limit SELECT '2020-01-01','d' || i::text, 2.0 FROM generate_series(1,100) g(i);
SET timescaledb.max_tuples_decompressed_per_dml_transaction TO 1;
CALL refresh_continuous_aggregate('daily_temp_decompress_limit', NULL, NULL);
SHOW timescaledb.max_tuples_decompressed_per_dml_transaction;
 timescaledb.max_tuples_decompressed_per_dml_transaction 
---------------------------------------------------------
 1

-- More tests for forceful refreshment
TRUNCATE conditions;
INSERT INTO conditions
VALUES
  -- daily bucket 2025-07-04 10:00:00+00
  ('2025-07-04 10:00:00+00', 1, 1),
  ('2025-07-04 10:05:00+00', 1, 1),
  -- daily bucket 2025-07-04 11:00:00+00
  ('2025-07-04 11:00:00+00', 1, 1),
  ('2025-07-04 11:05:00+00', 1, 1),
  -- daily bucket 2025-07-04 12:00:00+00
  ('2025-07-04 12:00:00+00', 1, 1),
  ('2025-07-04 12:05:00+00', 1, 1);
CREATE MATERIALIZED VIEW conditions_by_hour
WITH (timescaledb.continuous) AS
SELECT
  time_bucket(INTERVAL '1 hour', time) AS bucket, -- Fixed bucket size
  device,
  MAX(temp),
  MIN(temp),
  COUNT(*)
FROM conditions
GROUP BY 1, 2
WITH NO DATA;
CALL refresh_continuous_aggregate('conditions_by_hour', '2025-07-04 10:00:00+00'::timestamptz, '2025-07-04 12:00:00+00'::timestamptz);
-- It should return 2 buckets
SELECT * FROM conditions_by_hour ORDER BY bucket;
            bucket            | device | max | min | count 
------------------------------+--------+-----+-----+-------
 Fri Jul 04 03:00:00 2025 PDT |      1 |   1 |   1 |     2
 Fri Jul 04 04:00:00 2025 PDT |      1 |   1 |   1 |     2

CALL refresh_continuous_aggregate('conditions_by_hour', '2025-07-04 10:00:00+00'::timestamptz, '2025-07-04 12:00:00+00'::timestamptz, force=>true);
-- It should return the same 2 buckets of previous query
SELECT * FROM conditions_by_hour ORDER BY bucket;
            bucket            | device | max | min | count 
------------------------------+--------+-----+-----+-------
 Fri Jul 04 03:00:00 2025 PDT |      1 |   1 |   1 |     2
 Fri Jul 04 04:00:00 2025 PDT |      1 |   1 |   1 |     2

-- Monthly buckets
INSERT INTO conditions
VALUES
  -- monthly bucket 2025-05-01 00:00:00+00
  ('2025-05-04 10:00:00+00', 1, 1),
  ('2025-05-04 10:05:00+00', 1, 1),
  -- monthly bucket 2025-06-01 00:00:00+00
  ('2025-06-04 11:00:00+00', 1, 1),
  ('2025-06-04 11:05:00+00', 1, 1);
CREATE MATERIALIZED VIEW conditions_by_month
WITH (timescaledb.continuous) AS
SELECT
  time_bucket(INTERVAL '1 month', time) AS bucket, -- Variable bucket size
  device,
  MAX(temp),
  MIN(temp),
  COUNT(*)
FROM conditions
GROUP BY 1, 2
WITH NO DATA;
CALL refresh_continuous_aggregate('conditions_by_month', '2025-05-01 00:00:00+00'::timestamptz, '2025-07-01 12:00:00+00'::timestamptz);
-- It should return 2 buckets
SELECT * FROM conditions_by_month ORDER BY bucket;
            bucket            | device | max | min | count 
------------------------------+--------+-----+-----+-------
 Wed Apr 30 17:00:00 2025 PDT |      1 |   1 |   1 |     2
 Sat May 31 17:00:00 2025 PDT |      1 |   1 |   1 |     2

CALL refresh_continuous_aggregate('conditions_by_month', '2025-05-01 00:00:00+00'::timestamptz, '2025-07-01 12:00:00+00'::timestamptz, force=>true);
-- It should return the same 2 buckets of previous query
SELECT * FROM conditions_by_month ORDER BY bucket;
            bucket            | device | max | min | count 
------------------------------+--------+-----+-----+-------
 Wed Apr 30 17:00:00 2025 PDT |      1 |   1 |   1 |     2
 Sat May 31 17:00:00 2025 PDT |      1 |   1 |   1 |     2

