-- 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 search_path TO public, _timescaledb_functions;
SET timezone TO PST8PDT;
CREATE TABLE devices (
    id INTEGER,
    name TEXT
);
CREATE TABLE metrics (
    "time" TIMESTAMPTZ NOT NULL,
    device_id INTEGER,
    value FLOAT8
);
SELECT table_name FROM create_hypertable('metrics', 'time');
 table_name 
------------
 metrics

-- fixed bucket size
CREATE MATERIALIZED VIEW metrics_by_hour WITH (timescaledb.continuous) AS
SELECT time_bucket('1 hour', time) AS bucket, count(*)
FROM metrics
GROUP BY 1
WITH NO DATA;
-- variable bucket size
CREATE MATERIALIZED VIEW metrics_by_month WITH (timescaledb.continuous) AS
SELECT time_bucket('1 month', bucket) AS bucket, sum(count) AS count
FROM metrics_by_hour
GROUP BY 1
WITH NO DATA;
--
-- ERRORS
--
-- return NULL
SELECT * FROM cagg_validate_query(NULL);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
          |             |            |               |              | 

-- nothing to parse
SELECT * FROM cagg_validate_query('');
 is_valid | error_level | error_code |     error_message     | error_detail | error_hint 
----------+-------------+------------+-----------------------+--------------+------------
 f        | ERROR       | XX000      | failed to parse query |              | 

SELECT * FROM cagg_validate_query('--');
 is_valid | error_level | error_code |     error_message     | error_detail | error_hint 
----------+-------------+------------+-----------------------+--------------+------------
 f        | ERROR       | XX000      | failed to parse query |              | 

SELECT * FROM cagg_validate_query(';');
 is_valid | error_level | error_code |     error_message     | error_detail | error_hint 
----------+-------------+------------+-----------------------+--------------+------------
 f        | ERROR       | XX000      | failed to parse query |              | 

-- syntax error
SELECT * FROM cagg_validate_query('blahh');
 is_valid | error_level | error_code |          error_message          | error_detail | error_hint 
----------+-------------+------------+---------------------------------+--------------+------------
 f        | ERROR       | 42601      | syntax error at or near "blahh" |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket(blahh "time") FROM metrics GROUP BY 1 $$);
 is_valid | error_level | error_code |          error_message           | error_detail | error_hint 
----------+-------------+------------+----------------------------------+--------------+------------
 f        | ERROR       | 42601      | syntax error at or near ""time"" |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour' "time") FROM metrics GROUP BY $$);
 is_valid | error_level | error_code |          error_message           | error_detail | error_hint 
----------+-------------+------------+----------------------------------+--------------+------------
 f        | ERROR       | 42601      | syntax error at or near ""time"" |              | 

-- multiple statements are not allowed
SELECT * FROM cagg_validate_query($$ SELECT 1; SELECT 2; $$);
 is_valid | error_level | error_code |             error_message             | error_detail | error_hint 
----------+-------------+------------+---------------------------------------+--------------+------------
 f        | WARNING     | 0A000      | multiple statements are not supported |              | 

-- only SELECT queries are allowed
SELECT * FROM cagg_validate_query($$ DELETE FROM pg_catalog.pg_class $$);
 is_valid | error_level | error_code |            error_message             | error_detail | error_hint 
----------+-------------+------------+--------------------------------------+--------------+------------
 f        | WARNING     | 0A000      | only select statements are supported |              | 

SELECT * FROM cagg_validate_query($$ UPDATE pg_catalog.pg_class SET relkind = 'r' $$);
 is_valid | error_level | error_code |            error_message             | error_detail | error_hint 
----------+-------------+------------+--------------------------------------+--------------+------------
 f        | WARNING     | 0A000      | only select statements are supported |              | 

SELECT * FROM cagg_validate_query($$ DELETE FROM pg_catalog.pg_class $$);
 is_valid | error_level | error_code |            error_message             | error_detail | error_hint 
----------+-------------+------------+--------------------------------------+--------------+------------
 f        | WARNING     | 0A000      | only select statements are supported |              | 

SELECT * FROM cagg_validate_query($$ VACUUM (ANALYZE) $$);
 is_valid | error_level | error_code |            error_message             | error_detail | error_hint 
----------+-------------+------------+--------------------------------------+--------------+------------
 f        | WARNING     | 0A000      | only select statements are supported |              | 

-- invalid queries
SELECT * FROM cagg_validate_query($$ SELECT 1 $$);
 is_valid | error_level | error_code |           error_message            | error_detail |            error_hint            
----------+-------------+------------+------------------------------------+--------------+----------------------------------
 f        | ERROR       | 0A000      | invalid continuous aggregate query |              | FROM clause missing in the query

SELECT * FROM cagg_validate_query($$ SELECT 1 FROM pg_catalog.pg_class $$);
 is_valid | error_level | error_code |           error_message            | error_detail |                                   error_hint                                    
----------+-------------+------------+------------------------------------+--------------+---------------------------------------------------------------------------------
 f        | ERROR       | 0A000      | invalid continuous aggregate query |              | Include at least one aggregate function and a GROUP BY clause with time bucket.

SELECT * FROM cagg_validate_query($$ SELECT relkind, count(*) FROM pg_catalog.pg_class GROUP BY 1 $$);
 is_valid | error_level | error_code |           error_message           |                          error_detail                          | error_hint 
----------+-------------+------------+-----------------------------------+----------------------------------------------------------------+------------
 f        | ERROR       | 0A000      | invalid continuous aggregate view | At least one hypertable should be used in the view definition. | 

-- time_bucket with offset is not allowed
SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time", "offset" => '-1 minute'::interval), count(*) FROM metrics GROUP BY 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

-- time_bucket with origin is not allowed
SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time", origin => '2023-01-01'::timestamptz), count(*) FROM metrics GROUP BY 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

-- time_bucket with origin is not allowed
SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time", origin => '2023-01-01'::timestamptz), count(*) FROM metrics GROUP BY 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

-- time_bucket_gapfill is not allowed
SELECT * FROM cagg_validate_query($$ SELECT time_bucket_gapfill('1 hour', "time"), count(*) FROM metrics GROUP BY 1 $$);
 is_valid | error_level | error_code |                            error_message                            | error_detail | error_hint 
----------+-------------+------------+---------------------------------------------------------------------+--------------+------------
 f        | ERROR       | 0A000      | continuous aggregate view must include a valid time bucket function |              | 

-- invalid join queries
SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', a."time"), count(*) FROM metrics a, metrics b GROUP BY 1 $$);
 is_valid | error_level | error_code |           error_message           |                         error_detail                         | error_hint 
----------+-------------+------------+-----------------------------------+--------------------------------------------------------------+------------
 f        | ERROR       | 0A000      | invalid continuous aggregate view | Only one hypertable is allowed in continuous aggregate view. | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), count(*) FROM metrics, devices a, devices b GROUP BY 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), device_id, count(*) FROM metrics LEFT JOIN devices ON id = device_id GROUP BY 1, 2 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), device_id, count(*) FROM metrics JOIN devices ON id = device_id AND name = 'foo' GROUP BY 1, 2 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), device_id, count(*) FROM metrics JOIN devices ON id < device_id GROUP BY 1, 2 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

-- invalid caggs on caggs
SELECT * FROM cagg_validate_query($$ SELECT time_bucket('60 days', bucket) AS bucket, sum(count) AS count FROM metrics_by_month GROUP BY 1 $$);
 is_valid | error_level | error_code |                                            error_message                                             |                                                                    error_detail                                                                     | error_hint 
----------+-------------+------------+------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------------------------------------------+------------
 f        | ERROR       | 0A000      | cannot create continuous aggregate with fixed-width bucket on top of one using variable-width bucket | Continuous aggregate with a fixed time bucket width (e.g. 61 days) cannot be created on top of one using variable time bucket width (e.g. 1 month).+| 
          |             |            |                                                                                                      | The variance can lead to the fixed width one not being a multiple of the variable width one.                                                        | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 day 33 minutes', bucket) AS bucket, sum(count) AS count FROM metrics_by_hour GROUP BY 1 $$);
 is_valid | error_level | error_code |                           error_message                           |                                                                   error_detail                                                                    | error_hint 
----------+-------------+------------+-------------------------------------------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------+------------
 f        | ERROR       | 0A000      | cannot create continuous aggregate with incompatible bucket width | Time bucket width of "public.cagg_validate" [@ 1 day 33 mins] should be multiple of the time bucket width of "public.metrics_by_hour" [@ 1 hour]. | 

--
-- OK
--
-- valid join queries
SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), device_id, count(*) FROM metrics JOIN devices ON id = device_id GROUP BY 1, 2 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), device_id, count(*) FROM metrics JOIN devices ON id = device_id WHERE devices.name = 'foo' GROUP BY 1, 2 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

-- valid queries
SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time"), count(*) FROM metrics GROUP BY 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time", timezone => 'UTC'), count(*) FROM metrics GROUP BY 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 hour', "time", timezone => 'UTC'), count(*) FROM metrics GROUP BY 1 HAVING count(*) > 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

-- caggs on caggs
SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 day', bucket) AS bucket, sum(count) AS count FROM metrics_by_hour GROUP BY 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 month', bucket) AS bucket, sum(count) AS count FROM metrics_by_hour GROUP BY 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

SELECT * FROM cagg_validate_query($$ SELECT time_bucket('1 year', bucket) AS bucket, sum(count) AS count FROM metrics_by_month GROUP BY 1 $$);
 is_valid | error_level | error_code | error_message | error_detail | error_hint 
----------+-------------+------------+---------------+--------------+------------
 t        |             |            |               |              | 

--get grouping cols for cagg
SELECT _timescaledb_functions.cagg_get_grouping_columns('metrics_by_hour'::regclass);
 cagg_get_grouping_columns 
---------------------------
 {bucket}

SELECT _timescaledb_functions.cagg_get_grouping_columns('metrics_by_month'::regclass);
 cagg_get_grouping_columns 
---------------------------
 {bucket}

CREATE MATERIALIZED VIEW cagg_test_groupingcols
WITH( timescaledb.continuous ) AS
SELECT device_id, count(*), max(value),
       time_bucket('1 day'::interval, time) as bkt,
       min( value)
FROM
metrics
GROUP BY 4, 1 WITH NO DATA;
SELECT unnest(_timescaledb_functions.cagg_get_grouping_columns('cagg_test_groupingcols'::regclass));
  unnest   
-----------
 bkt
 device_id

--
-- Test bucket Oid recovery
--
SET timezone TO PST8PDT;
CREATE TABLE timestamp_ht (
  time timestamp NOT NULL,
  value float
);
SELECT create_hypertable('timestamp_ht', 'time');
WARNING:  column type "timestamp without time zone" used for "time" does not follow best practices
     create_hypertable     
---------------------------
 (5,public,timestamp_ht,t)

INSERT INTO timestamp_ht
  SELECT time, ceil(random() * 100)::int
    FROM generate_series('2000-01-01 0:00:00+0'::timestamptz,
                         '2000-01-01 23:59:59+0','1m') time;
CREATE MATERIALIZED VIEW temperature_4h
  WITH  (timescaledb.continuous) AS
  SELECT time_bucket('4 hour', time), avg(value)
    FROM timestamp_ht
    GROUP BY 1 ORDER BY 1;
NOTICE:  refreshing continuous aggregate "temperature_4h"
CREATE TABLE timestamptz_ht (
  time timestamptz NOT NULL,
  value float
);
SELECT create_hypertable('timestamptz_ht', 'time');
      create_hypertable      
-----------------------------
 (7,public,timestamptz_ht,t)

INSERT INTO timestamptz_ht
  SELECT time, ceil(random() * 100)::int
    FROM generate_series('2000-01-01 0:00:00+0'::timestamptz,
                         '2000-01-01 23:59:59+0','1m') time;
CREATE MATERIALIZED VIEW temperature_tz_4h
  WITH  (timescaledb.continuous) AS
  SELECT time_bucket('4 hour', time), avg(value)
    FROM timestamptz_ht
    GROUP BY 1 ORDER BY 1;
NOTICE:  refreshing continuous aggregate "temperature_tz_4h"
CREATE MATERIALIZED VIEW temperature_tz_4h_ts
  WITH  (timescaledb.continuous) AS
  SELECT time_bucket('4 hour', time, 'Europe/Berlin'), avg(value)
    FROM timestamptz_ht
    GROUP BY 1 ORDER BY 1;
NOTICE:  refreshing continuous aggregate "temperature_tz_4h_ts"
CREATE MATERIALIZED VIEW temperature_tz_4h_ts_origin
  WITH  (timescaledb.continuous) AS
  SELECT time_bucket('4 hour', time, timezone => 'Europe/Berlin', origin => '2001-01-01'::timestamptz), avg(value)
    FROM timestamptz_ht
    GROUP BY 1 ORDER BY 1;
NOTICE:  refreshing continuous aggregate "temperature_tz_4h_ts_origin"
CREATE MATERIALIZED VIEW temperature_tz_4h_ts_offset
  WITH  (timescaledb.continuous) AS
  SELECT time_bucket('4 hour', time, timezone => 'Europe/Berlin', "offset" => '1 hour'::interval), avg(value)
    FROM timestamptz_ht
    GROUP BY 1 ORDER BY 1;
NOTICE:  refreshing continuous aggregate "temperature_tz_4h_ts_offset"
CREATE TABLE integer_ht(a integer, b integer, c integer);
SELECT table_name FROM create_hypertable('integer_ht', 'a', chunk_time_interval=> 10);
 table_name 
------------
 integer_ht

CREATE OR REPLACE FUNCTION integer_now_integer_ht() returns int LANGUAGE SQL STABLE as $$ SELECT coalesce(max(a), 0) FROM integer_ht $$;
SELECT set_integer_now_func('integer_ht', 'integer_now_integer_ht');
 set_integer_now_func 
----------------------
 

CREATE MATERIALIZED VIEW integer_ht_cagg
  WITH (timescaledb.continuous) AS
  SELECT time_bucket(1, a) AS bucket, a, count(b)
     FROM integer_ht
     GROUP BY bucket, a;
NOTICE:  continuous aggregate "integer_ht_cagg" is already up-to-date
CREATE MATERIALIZED VIEW integer_ht_cagg_offset
  WITH (timescaledb.continuous) AS
  SELECT time_bucket(1, a, "offset" => 10) AS bucket, a, count(b)
     FROM integer_ht
     GROUP BY bucket, a;
NOTICE:  continuous aggregate "integer_ht_cagg_offset" is already up-to-date
-- Get the bucket Oids
SELECT user_view_name, bf.bucket_func
FROM _timescaledb_catalog.continuous_agg, LATERAL _timescaledb_functions.cagg_get_bucket_function_info(mat_hypertable_id) AS bf
WHERE user_view_name IN
  ('temperature_4h', 'temperature_tz_4h', 'temperature_tz_4h_ts', 'temperature_tz_4h_ts_origin', 'temperature_tz_4h_ts_offset', 'integer_ht_cagg', 'integer_ht_cagg_offset')
ORDER BY user_view_name;
       user_view_name        |                                      bucket_func                                      
-----------------------------+---------------------------------------------------------------------------------------
 integer_ht_cagg             | time_bucket(integer,integer)
 integer_ht_cagg_offset      | time_bucket(integer,integer,integer)
 temperature_4h              | time_bucket(interval,timestamp without time zone)
 temperature_tz_4h           | time_bucket(interval,timestamp with time zone)
 temperature_tz_4h_ts        | time_bucket(interval,timestamp with time zone,text,timestamp with time zone,interval)
 temperature_tz_4h_ts_offset | time_bucket(interval,timestamp with time zone,text,timestamp with time zone,interval)
 temperature_tz_4h_ts_origin | time_bucket(interval,timestamp with time zone,text,timestamp with time zone,interval)

-- Get all bucket function information
SELECT user_view_name, bf.*
FROM _timescaledb_catalog.continuous_agg, LATERAL _timescaledb_functions.cagg_get_bucket_function_info(mat_hypertable_id) AS bf
WHERE user_view_name IN
  ('temperature_4h', 'temperature_tz_4h', 'temperature_tz_4h_ts', 'temperature_tz_4h_ts_origin', 'temperature_tz_4h_ts_offset', 'integer_ht_cagg', 'integer_ht_cagg_offset')
ORDER BY user_view_name;
       user_view_name        |                                      bucket_func                                      | bucket_width |        bucket_origin         | bucket_offset | bucket_timezone | bucket_fixed_width 
-----------------------------+---------------------------------------------------------------------------------------+--------------+------------------------------+---------------+-----------------+--------------------
 integer_ht_cagg             | time_bucket(integer,integer)                                                          | 1            |                              |               |                 | t
 integer_ht_cagg_offset      | time_bucket(integer,integer,integer)                                                  | 1            |                              | 10            |                 | t
 temperature_4h              | time_bucket(interval,timestamp without time zone)                                     | @ 4 hours    |                              |               |                 | t
 temperature_tz_4h           | time_bucket(interval,timestamp with time zone)                                        | @ 4 hours    |                              |               |                 | t
 temperature_tz_4h_ts        | time_bucket(interval,timestamp with time zone,text,timestamp with time zone,interval) | @ 4 hours    |                              |               | Europe/Berlin   | f
 temperature_tz_4h_ts_offset | time_bucket(interval,timestamp with time zone,text,timestamp with time zone,interval) | @ 4 hours    |                              | @ 1 hour      | Europe/Berlin   | f
 temperature_tz_4h_ts_origin | time_bucket(interval,timestamp with time zone,text,timestamp with time zone,interval) | @ 4 hours    | Mon Jan 01 00:00:00 2001 PST |               | Europe/Berlin   | f

-- Valid multiple time_bucket usage on view definition
CREATE MATERIALIZED VIEW temperature_tz_4h_2
WITH  (timescaledb.continuous) AS
SELECT (time_bucket('4 hour', time) at time zone 'utc')::date, avg(value)
FROM timestamptz_ht
GROUP BY time_bucket('4 hour', time)
ORDER BY 1
WITH NO DATA;
SELECT user_view_name, bf.bucket_func
FROM _timescaledb_catalog.continuous_agg, LATERAL _timescaledb_functions.cagg_get_bucket_function_info(mat_hypertable_id) AS bf
WHERE user_view_name = 'temperature_tz_4h_2'
ORDER BY user_view_name;
   user_view_name    |                  bucket_func                   
---------------------+------------------------------------------------
 temperature_tz_4h_2 | time_bucket(interval,timestamp with time zone)

-- Corrupt the direct view definition
\c :TEST_DBNAME :ROLE_SUPERUSER
SELECT direct_view_schema, direct_view_name
FROM _timescaledb_catalog.continuous_agg
WHERE user_view_name = 'temperature_tz_4h_2' \gset
CREATE OR REPLACE VIEW :direct_view_schema.:direct_view_name AS
SELECT NULL::date AS timezone, NULL::FLOAT8 AS avg;
\set ON_ERROR_STOP 0
-- Should error because there's no time_bucket function on the view definition
SELECT user_view_name, bf.bucket_func
FROM _timescaledb_catalog.continuous_agg, LATERAL _timescaledb_functions.cagg_get_bucket_function_info(mat_hypertable_id) AS bf
WHERE user_view_name = 'temperature_tz_4h_2'
ORDER BY user_view_name;
ERROR:  time_bucket function not found in CAgg definition for mat_ht_id: 15
\set ON_ERROR_STOP 1
-- Group by another function to make sure it will be ignored
CREATE FUNCTION skip() RETURNS INTEGER AS $$ SELECT 1; $$ IMMUTABLE LANGUAGE SQL;
CREATE MATERIALIZED VIEW temperature_tz_4h_3
WITH  (timescaledb.continuous) AS
SELECT skip(), time_bucket('4 hour', time), avg(value)
FROM timestamptz_ht
GROUP BY 1, 2
ORDER BY 1
WITH NO DATA;
SELECT user_view_name, bf.bucket_func
FROM _timescaledb_catalog.continuous_agg, LATERAL _timescaledb_functions.cagg_get_bucket_function_info(mat_hypertable_id) AS bf
WHERE user_view_name = 'temperature_tz_4h_3'
ORDER BY user_view_name;
   user_view_name    |                  bucket_func                   
---------------------+------------------------------------------------
 temperature_tz_4h_3 | time_bucket(interval,timestamp with time zone)

