diff --git a/.unreleased/bugfix_6660 b/.unreleased/bugfix_6660 new file mode 100644 index 00000000000..6a9fc996347 --- /dev/null +++ b/.unreleased/bugfix_6660 @@ -0,0 +1 @@ +Fixes: #6660 Fix refresh on empty CAgg with variable bucket diff --git a/tsl/src/continuous_aggs/refresh.c b/tsl/src/continuous_aggs/refresh.c index 7f713bc4ed8..0a2818bd909 100644 --- a/tsl/src/continuous_aggs/refresh.c +++ b/tsl/src/continuous_aggs/refresh.c @@ -824,9 +824,18 @@ continuous_agg_refresh_internal(const ContinuousAgg *cagg, if (refresh_window.end > invalidation_threshold) refresh_window.end = invalidation_threshold; - /* Capping the end might have made the window 0, or negative, so - * nothing to refresh in that case */ - if (refresh_window.start >= refresh_window.end) + /* Capping the end might have made the window 0, or negative, so nothing to refresh in that + * case. + * + * For variable width buckets we use a refresh_window.start value that is lower than the + * -infinity value (ts_time_get_nobegin < ts_time_get_min). Therefore, the first check in the + * following if statement is not enough. If the invalidation_threshold returns the min_value for + * the data type, we end up with [nobegin, min_value] which is an invalid time interval. + * Therefore, we have also to check if the invalidation_threshold is defined. If not, no refresh + * is needed. */ + if ((refresh_window.start >= refresh_window.end) || + (IS_TIMESTAMP_TYPE(refresh_window.type) && + invalidation_threshold == ts_time_get_min(refresh_window.type))) { emit_up_to_date_notice(cagg, callctx); diff --git a/tsl/test/expected/cagg_invalidation.out b/tsl/test/expected/cagg_invalidation.out index 8e837f817f7..ebf886607eb 100644 --- a/tsl/test/expected/cagg_invalidation.out +++ b/tsl/test/expected/cagg_invalidation.out @@ -1226,3 +1226,52 @@ CALL refresh_continuous_aggregate('cond_10', 0, 200); WARNING: invalid value for session variable "timescaledb.materializations_per_refresh_window" DETAIL: Expected an integer but current value is "-". \set VERBOSITY terse +-- Test refresh with undefined invalidation threshold and variable sized buckets +CREATE TABLE timestamp_ht ( + time timestamptz NOT NULL, + value float +); +SELECT create_hypertable('timestamp_ht', 'time'); + create_hypertable +--------------------------- + (9,public,timestamp_ht,t) +(1 row) + +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: continuous aggregate "temperature_4h" is already up-to-date +-- We also treat time_buckets with an hourly interval that uses a time-zone +-- as a variable see caggtimebucket_validate(). +CREATE MATERIALIZED VIEW temperature_4h_2 + WITH (timescaledb.continuous) AS + SELECT time_bucket('4 hour', time, 'Europe/Berlin') AS bucket_4h, avg(value) AS average + FROM timestamp_ht + GROUP BY 1 ORDER BY 1; +NOTICE: continuous aggregate "temperature_4h_2" is already up-to-date +CREATE MATERIALIZED VIEW temperature_1month + WITH (timescaledb.continuous) AS + SELECT time_bucket('1 month', time), avg(value) + FROM timestamp_ht + GROUP BY 1 ORDER BY 1; +NOTICE: continuous aggregate "temperature_1month" is already up-to-date +CREATE MATERIALIZED VIEW temperature_1month_ts + WITH (timescaledb.continuous) AS + SELECT time_bucket('1 month', time, 'Europe/Berlin'), avg(value) + FROM timestamp_ht + GROUP BY 1 ORDER BY 1; +NOTICE: continuous aggregate "temperature_1month_ts" is already up-to-date +CREATE MATERIALIZED VIEW temperature_1month_hierarchical + WITH (timescaledb.continuous) AS + SELECT time_bucket('1 month', bucket_4h), avg(average) + FROM temperature_4h_2 + GROUP BY 1 ORDER BY 1; +NOTICE: continuous aggregate "temperature_1month_hierarchical" is already up-to-date +CREATE MATERIALIZED VIEW temperature_1month_hierarchical_ts + WITH (timescaledb.continuous) AS + SELECT time_bucket('1 month', bucket_4h, 'Europe/Berlin'), avg(average) + FROM temperature_4h_2 + GROUP BY 1 ORDER BY 1; +NOTICE: continuous aggregate "temperature_1month_hierarchical_ts" is already up-to-date diff --git a/tsl/test/sql/cagg_invalidation.sql b/tsl/test/sql/cagg_invalidation.sql index d3fc2f77fc3..133c2b2420a 100644 --- a/tsl/test/sql/cagg_invalidation.sql +++ b/tsl/test/sql/cagg_invalidation.sql @@ -719,3 +719,49 @@ SET timescaledb.materializations_per_refresh_window='-'; INSERT INTO conditions VALUES (140, 1, 1.0); CALL refresh_continuous_aggregate('cond_10', 0, 200); \set VERBOSITY terse + +-- Test refresh with undefined invalidation threshold and variable sized buckets +CREATE TABLE timestamp_ht ( + time timestamptz NOT NULL, + value float +); + +SELECT create_hypertable('timestamp_ht', '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; + +-- We also treat time_buckets with an hourly interval that uses a time-zone +-- as a variable see caggtimebucket_validate(). +CREATE MATERIALIZED VIEW temperature_4h_2 + WITH (timescaledb.continuous) AS + SELECT time_bucket('4 hour', time, 'Europe/Berlin') AS bucket_4h, avg(value) AS average + FROM timestamp_ht + GROUP BY 1 ORDER BY 1; + +CREATE MATERIALIZED VIEW temperature_1month + WITH (timescaledb.continuous) AS + SELECT time_bucket('1 month', time), avg(value) + FROM timestamp_ht + GROUP BY 1 ORDER BY 1; + +CREATE MATERIALIZED VIEW temperature_1month_ts + WITH (timescaledb.continuous) AS + SELECT time_bucket('1 month', time, 'Europe/Berlin'), avg(value) + FROM timestamp_ht + GROUP BY 1 ORDER BY 1; + +CREATE MATERIALIZED VIEW temperature_1month_hierarchical + WITH (timescaledb.continuous) AS + SELECT time_bucket('1 month', bucket_4h), avg(average) + FROM temperature_4h_2 + GROUP BY 1 ORDER BY 1; + +CREATE MATERIALIZED VIEW temperature_1month_hierarchical_ts + WITH (timescaledb.continuous) AS + SELECT time_bucket('1 month', bucket_4h, 'Europe/Berlin'), avg(average) + FROM temperature_4h_2 + GROUP BY 1 ORDER BY 1;