use crate::*; #[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] pub struct AnalyticsDateDimensionEnsureInput { pub date_key: i64, } #[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] pub struct AnalyticsDateDimensionSeedInput { pub start_date: String, pub end_date: String, } #[spacetimedb::table( accessor = analytics_date_dimension, index(accessor = by_analytics_date_dimension_iso_week, btree(columns = [iso_week_key])), index(accessor = by_analytics_date_dimension_month, btree(columns = [month_key])), index(accessor = by_analytics_date_dimension_quarter, btree(columns = [quarter_key])), index(accessor = by_analytics_date_dimension_year, btree(columns = [year_key])) )] pub struct AnalyticsDateDimension { #[primary_key] pub(crate) date_key: i64, // 中文注释:北京时间业务日对应的公历日期,格式 YYYY-MM-DD。 pub(crate) calendar_date: String, // 中文注释:ISO weekday,周一=1,周日=7,方便统计层不重复计算。 pub(crate) weekday: u8, // 中文注释:ISO week 编码为 YYYYWW,跨年周按 ISO 年归属。 pub(crate) iso_week_key: i32, pub(crate) week_start_date_key: i64, pub(crate) week_end_date_key: i64, pub(crate) month_key: i32, pub(crate) month_start_date_key: i64, pub(crate) month_end_date_key: i64, pub(crate) quarter_key: i32, pub(crate) quarter_start_date_key: i64, pub(crate) quarter_end_date_key: i64, pub(crate) year_key: i32, pub(crate) year_start_date_key: i64, pub(crate) year_end_date_key: i64, pub(crate) created_at: Timestamp, pub(crate) updated_at: Timestamp, } #[spacetimedb::reducer] pub fn ensure_analytics_date_dimension_for_date( ctx: &ReducerContext, input: AnalyticsDateDimensionEnsureInput, ) -> Result<(), String> { ensure_analytics_date_dimension_row(ctx, input.date_key)?; Ok(()) } #[spacetimedb::reducer] pub fn seed_analytics_date_dimensions( ctx: &ReducerContext, input: AnalyticsDateDimensionSeedInput, ) -> Result<(), String> { let start_date_key = parse_analytics_calendar_date_key(&input.start_date) .map_err(|error| format!("invalid start_date: {error}"))?; let end_date_key = parse_analytics_calendar_date_key(&input.end_date) .map_err(|error| format!("invalid end_date: {error}"))?; if start_date_key > end_date_key { return Err("start_date must be <= end_date".to_string()); } let seed_days = end_date_key - start_date_key + 1; if seed_days > ANALYTICS_DATE_DIMENSION_MAX_SEED_DAYS { return Err(format!( "date range too large: max {} days", ANALYTICS_DATE_DIMENSION_MAX_SEED_DAYS )); } for date_key in start_date_key..=end_date_key { ensure_analytics_date_dimension_row(ctx, date_key)?; } Ok(()) } pub(crate) fn ensure_analytics_date_dimension_row( ctx: &ReducerContext, date_key: i64, ) -> Result<(), String> { validate_analytics_date_dimension_date_key(date_key) .map_err(|error| format!("invalid date_key: {error}"))?; if ctx .db .analytics_date_dimension() .date_key() .find(&date_key) .is_some() { return Ok(()); } let snapshot = build_analytics_date_dimension_from_date_key(date_key); ctx.db .analytics_date_dimension() .insert(build_analytics_date_dimension_row(snapshot, ctx.timestamp)); Ok(()) } fn build_analytics_date_dimension_row( snapshot: AnalyticsDateDimensionSnapshot, now: Timestamp, ) -> AnalyticsDateDimension { AnalyticsDateDimension { date_key: snapshot.date_key, calendar_date: snapshot.calendar_date, weekday: snapshot.weekday, iso_week_key: snapshot.iso_week_key, week_start_date_key: snapshot.week_start_date_key, week_end_date_key: snapshot.week_end_date_key, month_key: snapshot.month_key, month_start_date_key: snapshot.month_start_date_key, month_end_date_key: snapshot.month_end_date_key, quarter_key: snapshot.quarter_key, quarter_start_date_key: snapshot.quarter_start_date_key, quarter_end_date_key: snapshot.quarter_end_date_key, year_key: snapshot.year_key, year_start_date_key: snapshot.year_start_date_key, year_end_date_key: snapshot.year_end_date_key, created_at: now, updated_at: now, } }