//! Compatibility reads for installations whose retained daily totals predate //! the incremental dashboard. Never replay or mutate the activation boundary. use crate::error::SqlxResultExt; use aether_data_contracts::{repository::usage::*, DataLayerError}; use chrono::{DateTime, Utc}; use serde_json::Value; use sqlx::{Postgres, Row, Transaction}; pub(super) struct DashboardHistory { pub since: DateTime, pub metrics: Value, pub days: Vec, } pub(super) async fn read_dashboard_history( tx: &mut Transaction<'_, Postgres>, activation: DateTime, now: DateTime, ) -> Result, DataLayerError> { // Both timestamp and integer dates exist in supported imported schemas. let state = sqlx::query( r#" WITH summary AS ( SELECT CASE WHEN pg_typeof(cutoff_date) IN ('bigint'::regtype,'integer'::regtype) THEN to_timestamp(cutoff_date::text::double precision) ELSE cutoff_date::text::timestamptz END AS cutoff FROM stats_summary ORDER BY updated_at DESC, created_at DESC LIMIT 1 ), daily AS ( SELECT CASE WHEN pg_typeof(date) IN ('bigint'::regtype,'integer'::regtype) THEN to_timestamp(date::text::double precision) ELSE date::text::timestamptz END AS day FROM stats_daily WHERE total_requests>0 ) SELECT cutoff, (SELECT min(day) FROM daily WHERE day>, _>("since") .map_postgres_err()? else { return Ok(None); }; let cutoff: DateTime = state.try_get("cutoff").map_postgres_err()?; // Canonical fact views expand several joins. JIT compilation can cost more // than the small recent range itself on upgraded installations. sqlx::query("SET LOCAL jit=off") .execute(&mut **tx) .await .map_postgres_err()?; // The published summary cutoff is advanced atomically with daily rollups. // Read that same snapshot and use disjoint [history, cutoff), [cutoff, now) // ranges. This keeps working after raw history is pruned or the cutoff moves. let row = sqlx::query(include_str!("queries/dashboard_history.sql")) .bind(cutoff) .bind(now) .fetch_one(&mut **tx) .await .map_postgres_err()?; let days: Value = row.try_get("days").map_postgres_err()?; Ok(Some(DashboardHistory { since, metrics: row.try_get("metrics").map_postgres_err()?, days: serde_json::from_value(days) .map_err(|error| DataLayerError::UnexpectedValue(error.to_string()))?, })) } pub(super) async fn complete_activation_day( tx: &mut Transaction<'_, Postgres>, today: &DashboardSummaryMetrics, today_start: DateTime, activation: DateTime, now: DateTime, ) -> Result { // Match dashboard_request_fact exactly for the missing part of today. // The actor UNION deduplicates users present on both sides of activation. let row = sqlx::query(include_str!("queries/dashboard_activation_day.sql")) .bind(today_start) .bind(activation) .bind(now) .bind( serde_json::to_value(today) .map_err(|error| DataLayerError::UnexpectedValue(error.to_string()))?, ) .fetch_one(&mut **tx) .await .map_postgres_err()?; row.try_get("metrics").map_postgres_err() }