-- Convert the legacy rollup schema without dropping its data. The explicit -- transaction makes lock/statement timeout failures leave the old table intact. BEGIN; LOCK TABLE reporting_events_rollups IN ACCESS EXCLUSIVE MODE; DO $$ DECLARE invalid_periods BIGINT; invalid_dimensions BIGINT; invalid_counts BIGINT; oversized_dimensions BIGINT; collision_groups BIGINT; BEGIN SELECT count(*) INTO invalid_periods FROM reporting_events_rollups WHERE NOT pg_input_is_valid(period, 'date'); IF invalid_periods > 0 THEN RAISE EXCEPTION 'migration 000048: % rollup period value(s) cannot convert to date', invalid_periods USING HINT = 'Repair or remove the reported legacy rows explicitly, then retry the migration.'; END IF; SELECT count(*) INTO invalid_dimensions FROM reporting_events_rollups WHERE NOT pg_input_is_valid(dimension_value, 'bigint'); IF invalid_dimensions > 0 THEN RAISE EXCEPTION 'migration 000048: % rollup dimension_value(s) cannot convert to bigint', invalid_dimensions USING HINT = 'Map non-numeric legacy dimensions explicitly, then retry the migration.'; END IF; SELECT count(*) INTO invalid_counts FROM reporting_events_rollups WHERE metric_name LIKE '%count%' AND NOT pg_input_is_valid(value::text, 'bigint'); IF invalid_counts > 0 THEN RAISE EXCEPTION 'migration 000048: % count value(s) cannot convert losslessly to bigint', invalid_counts USING HINT = 'Repair fractional, non-finite, or out-of-range count values, then retry the migration.'; END IF; SELECT count(*) INTO oversized_dimensions FROM reporting_events_rollups WHERE length(dimension) > 20; IF oversized_dimensions > 0 THEN RAISE EXCEPTION 'migration 000048: % dimension value(s) exceed the new 20-character limit', oversized_dimensions USING HINT = 'Shorten or explicitly map oversized dimensions, then retry the migration.'; END IF; SELECT count(*) INTO collision_groups FROM ( SELECT 1 FROM reporting_events_rollups GROUP BY account_id, period::date, dimension, dimension_value::BIGINT, metric_name HAVING count(*) > 1 ) collisions; IF collision_groups > 0 THEN RAISE EXCEPTION 'migration 000048: % unique-key collision group(s) appear after type conversion', collision_groups USING HINT = 'Merge or choose one legacy row in each collision group explicitly, then retry the migration.'; END IF; END $$; CREATE TABLE reporting_events_rollups_v2 ( id SERIAL PRIMARY KEY, account_id INTEGER NOT NULL, date DATE NOT NULL, dimension_type VARCHAR(20) NOT NULL, dimension_id BIGINT NOT NULL, metric VARCHAR(50) NOT NULL, count BIGINT NOT NULL DEFAULT 0, sum_value DOUBLE PRECISION NOT NULL DEFAULT 0, sum_value_business_hours DOUBLE PRECISION NOT NULL DEFAULT 0, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), deleted_at TIMESTAMP WITH TIME ZONE, UNIQUE(account_id, date, dimension_type, dimension_id, metric) ); INSERT INTO reporting_events_rollups_v2 ( id, account_id, date, dimension_type, dimension_id, metric, count, sum_value, sum_value_business_hours, created_at, updated_at, deleted_at ) SELECT id, account_id, period::date, dimension, dimension_value::BIGINT, metric_name, CASE WHEN metric_name LIKE '%count%' THEN value::BIGINT ELSE 1 END, value, COALESCE(value_in_business_hours, 0), created_at, updated_at, deleted_at FROM reporting_events_rollups; SELECT setval( pg_get_serial_sequence('reporting_events_rollups_v2', 'id'), COALESCE((SELECT MAX(id) FROM reporting_events_rollups_v2), 1), EXISTS (SELECT 1 FROM reporting_events_rollups_v2) ); DROP TABLE reporting_events_rollups; ALTER TABLE reporting_events_rollups_v2 RENAME TO reporting_events_rollups; ALTER INDEX reporting_events_rollups_v2_pkey RENAME TO reporting_events_rollups_pkey; CREATE INDEX idx_reporting_events_rollups_deleted_at ON reporting_events_rollups(deleted_at); CREATE INDEX idx_reporting_events_rollups_account_id ON reporting_events_rollups(account_id) WHERE deleted_at IS NULL; CREATE INDEX idx_reporting_events_rollups_date ON reporting_events_rollups(date) WHERE deleted_at IS NULL; CREATE INDEX idx_reporting_events_rollups_dimension ON reporting_events_rollups(dimension_type, dimension_id) WHERE deleted_at IS NULL; COMMIT;