-- Found by: commit 038822925 agent
-- Location: tsl/src/continuous_aggs/insert.c:632-639, 712-718
--
-- When every row has a NULL tenant, the buffer is empty so the foreach
-- loop over hypertable_ids never executes. tenant_buffer_unencodable is
-- set but mark_invalid never fires. Correctness maintained via seqnum=0
-- fallback, but the code path is unusual.
\set ON_ERROR_STOP on
SET timezone TO 'UTC';
SET client_min_messages TO notice;
DROP TABLE IF EXISTS null_compress CASCADE;
SELECT (CURRENT_DATE - DATE '2019-01-01')::text || ' days' AS lookback \gset
CREATE TABLE null_compress(time timestamptz NOT NULL, sensor_id text, value float)
WITH (tsdb.hypertable, tsdb.orderby = 'time');
ALTER TABLE null_compress SET (
timescaledb.compress,
timescaledb.compress_orderby = 'time',
timescaledb.granular_refresh_column = 'sensor_id',
timescaledb.granular_refresh_start_offset = :'lookback',
timescaledb.granular_refresh_end_offset = '1 day'
);
CREATE MATERIALIZED VIEW null_daily
WITH (timescaledb.continuous) AS
SELECT time_bucket('1 day', time) AS bucket, sensor_id, sum(value) AS total
FROM null_compress
GROUP BY bucket, sensor_id
WITH NO DATA;
ALTER MATERIALIZED VIEW null_daily SET (timescaledb.enable_granular_refresh = true);
-- Prime
CALL refresh_continuous_aggregate('null_daily', NULL, NULL);
-- Enable direct compress
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_sort_batches = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
-- Insert ALL-NULL tenants via direct compress (1000 rows to trigger direct compress)
INSERT INTO null_compress
SELECT '2020-01-01 00:00+00'::timestamptz + (g || ' seconds')::interval,
NULL,
g::float
FROM generate_series(1, 1000) g;
RESET timescaledb.enable_direct_compress_insert;
RESET timescaledb.enable_direct_compress_insert_sort_batches;
RESET timescaledb.enable_direct_compress_insert_client_sorted;
-- Refresh should still work (full-refresh fallback via seqnum=0)
CALL refresh_continuous_aggregate('null_daily', NULL, NULL);
DO $$
DECLARE
cagg_sum float;
raw_sum float;
BEGIN
SELECT coalesce(sum(total), 0) INTO cagg_sum FROM null_daily;
SELECT coalesce(sum(value), 0) INTO raw_sum FROM null_compress;
IF abs(cagg_sum - raw_sum) < 0.001 THEN
RAISE NOTICE 'RESULT: PASS — all-NULL tenant via direct compress correctly falls back (cagg=%, raw=%)',
cagg_sum, raw_sum;
ELSE
RAISE WARNING 'RESULT: BUG CONFIRMED — all-NULL tenant data not materialized (cagg=%, raw=%)',
cagg_sum, raw_sum;
END IF;
END $$;
-- Also test: mix of NULL and valid tenants in same direct-compress batch
SET timescaledb.enable_direct_compress_insert = true;
SET timescaledb.enable_direct_compress_insert_sort_batches = true;
SET timescaledb.enable_direct_compress_insert_client_sorted = false;
INSERT INTO null_compress
SELECT '2020-02-01 00:00+00'::timestamptz + (g || ' seconds')::interval,
CASE WHEN g % 2 = 0 THEN NULL ELSE 'valid_sensor' END,
g::float
FROM generate_series(1, 1000) g;
RESET timescaledb.enable_direct_compress_insert;
RESET timescaledb.enable_direct_compress_insert_sort_batches;
RESET timescaledb.enable_direct_compress_insert_client_sorted;
CALL refresh_continuous_aggregate('null_daily', NULL, NULL);
DO $$
DECLARE
cagg_sum float;
raw_sum float;
BEGIN
SELECT coalesce(sum(total), 0) INTO cagg_sum FROM null_daily
WHERE bucket >= '2020-02-01' AND bucket < '2020-03-01';
SELECT coalesce(sum(value), 0) INTO raw_sum FROM null_compress
WHERE time >= '2020-02-01' AND time < '2020-03-01';
IF abs(cagg_sum - raw_sum) < 0.001 THEN
RAISE NOTICE 'RESULT: PASS — mixed NULL/valid tenant via direct compress (cagg=%, raw=%)',
cagg_sum, raw_sum;
ELSE
RAISE WARNING 'RESULT: BUG CONFIRMED — mixed NULL/valid data mismatch (cagg=%, raw=%)',
cagg_sum, raw_sum;
END IF;
END $$;
DROP MATERIALIZED VIEW null_daily;
DROP TABLE null_compress;