#![allow(clippy::unwrap_used)]
use chrono::NaiveDate;
use fraiseql_db::{WhereClause, WhereOperator};
use serde_json::json;
use super::*;
fn date(y: i32, m: u32, d: u32) -> NaiveDate {
NaiveDate::from_ymd_opt(y, m, d).unwrap()
}
#[test]
fn test_day_always_aligned() {
assert!(is_period_aligned(date(2024, 3, 15), TemporalGrain::Day));
assert!(is_period_aligned(date(2024, 1, 1), TemporalGrain::Day));
}
#[test]
fn test_week_alignment() {
assert!(is_period_aligned(date(2024, 1, 1), TemporalGrain::Week));
assert!(!is_period_aligned(date(2024, 1, 2), TemporalGrain::Week));
assert!(is_period_aligned(date(2024, 1, 8), TemporalGrain::Week));
assert!(!is_period_aligned(date(2024, 1, 7), TemporalGrain::Week));
}
#[test]
fn test_month_alignment() {
assert!(is_period_aligned(date(2024, 1, 1), TemporalGrain::Month));
assert!(is_period_aligned(date(2024, 2, 1), TemporalGrain::Month));
assert!(!is_period_aligned(date(2024, 1, 15), TemporalGrain::Month));
assert!(!is_period_aligned(date(2024, 12, 31), TemporalGrain::Month));
}
#[test]
fn test_quarter_alignment() {
assert!(is_period_aligned(date(2024, 1, 1), TemporalGrain::Quarter));
assert!(is_period_aligned(date(2024, 4, 1), TemporalGrain::Quarter));
assert!(is_period_aligned(date(2024, 7, 1), TemporalGrain::Quarter));
assert!(is_period_aligned(date(2024, 10, 1), TemporalGrain::Quarter));
assert!(!is_period_aligned(date(2024, 2, 1), TemporalGrain::Quarter));
assert!(!is_period_aligned(date(2024, 3, 1), TemporalGrain::Quarter));
assert!(!is_period_aligned(date(2024, 5, 15), TemporalGrain::Quarter));
}
#[test]
fn test_year_alignment() {
assert!(is_period_aligned(date(2024, 1, 1), TemporalGrain::Year));
assert!(is_period_aligned(date(2025, 1, 1), TemporalGrain::Year));
assert!(!is_period_aligned(date(2024, 1, 2), TemporalGrain::Year));
assert!(!is_period_aligned(date(2024, 6, 1), TemporalGrain::Year));
}
#[test]
fn test_period_start_day() {
assert_eq!(period_start(date(2024, 3, 15), TemporalGrain::Day), date(2024, 3, 15));
}
#[test]
fn test_period_start_week() {
assert_eq!(period_start(date(2024, 1, 3), TemporalGrain::Week), date(2024, 1, 1));
assert_eq!(period_start(date(2024, 1, 1), TemporalGrain::Week), date(2024, 1, 1));
assert_eq!(period_start(date(2024, 1, 7), TemporalGrain::Week), date(2024, 1, 1));
}
#[test]
fn test_period_start_month() {
assert_eq!(period_start(date(2024, 2, 15), TemporalGrain::Month), date(2024, 2, 1));
assert_eq!(period_start(date(2024, 2, 1), TemporalGrain::Month), date(2024, 2, 1));
assert_eq!(period_start(date(2024, 12, 31), TemporalGrain::Month), date(2024, 12, 1));
}
#[test]
fn test_period_start_quarter() {
assert_eq!(period_start(date(2024, 5, 20), TemporalGrain::Quarter), date(2024, 4, 1));
assert_eq!(period_start(date(2024, 1, 1), TemporalGrain::Quarter), date(2024, 1, 1));
assert_eq!(period_start(date(2024, 3, 31), TemporalGrain::Quarter), date(2024, 1, 1));
assert_eq!(period_start(date(2024, 12, 15), TemporalGrain::Quarter), date(2024, 10, 1));
}
#[test]
fn test_period_start_year() {
assert_eq!(period_start(date(2024, 3, 15), TemporalGrain::Year), date(2024, 1, 1));
assert_eq!(period_start(date(2024, 1, 1), TemporalGrain::Year), date(2024, 1, 1));
}
#[test]
fn test_next_period_start_day() {
assert_eq!(next_period_start(date(2024, 1, 15), TemporalGrain::Day), date(2024, 1, 16));
}
#[test]
fn test_next_period_start_week() {
assert_eq!(next_period_start(date(2024, 1, 3), TemporalGrain::Week), date(2024, 1, 8));
}
#[test]
fn test_next_period_start_month() {
assert_eq!(next_period_start(date(2024, 1, 15), TemporalGrain::Month), date(2024, 2, 1));
assert_eq!(next_period_start(date(2024, 12, 1), TemporalGrain::Month), date(2025, 1, 1));
assert_eq!(next_period_start(date(2024, 2, 29), TemporalGrain::Month), date(2024, 3, 1));
}
#[test]
fn test_next_period_start_quarter() {
assert_eq!(next_period_start(date(2024, 2, 15), TemporalGrain::Quarter), date(2024, 4, 1));
assert_eq!(next_period_start(date(2024, 10, 1), TemporalGrain::Quarter), date(2025, 1, 1));
}
#[test]
fn test_next_period_start_year() {
assert_eq!(next_period_start(date(2024, 6, 15), TemporalGrain::Year), date(2025, 1, 1));
}
#[test]
fn test_three_branch_mid_month() {
let plan = determine_branches(date(2024, 1, 15), TemporalGrain::Month, date(2024, 3, 20));
assert_eq!(plan.partial_leading, Some((date(2024, 1, 15), date(2024, 2, 1))));
assert_eq!(plan.complete_middle, Some((date(2024, 2, 1), date(2024, 3, 1))));
assert_eq!(plan.current_period, (date(2024, 3, 1), date(2024, 3, 21)));
}
#[test]
fn test_two_branch_aligned_lower_bound() {
let plan = determine_branches(date(2024, 2, 1), TemporalGrain::Month, date(2024, 4, 10));
assert_eq!(plan.partial_leading, None);
assert_eq!(plan.complete_middle, Some((date(2024, 2, 1), date(2024, 4, 1))));
assert_eq!(plan.current_period, (date(2024, 4, 1), date(2024, 4, 11)));
}
#[test]
fn test_two_branch_lower_in_current_period() {
let plan = determine_branches(date(2024, 3, 15), TemporalGrain::Month, date(2024, 3, 20));
assert_eq!(plan.partial_leading, Some((date(2024, 3, 15), date(2024, 4, 1))));
assert_eq!(plan.complete_middle, None);
assert_eq!(plan.current_period, (date(2024, 3, 1), date(2024, 3, 21)));
}
#[test]
fn test_single_branch_aligned_same_period() {
let plan = determine_branches(date(2024, 3, 1), TemporalGrain::Month, date(2024, 3, 20));
assert_eq!(plan.partial_leading, None);
assert_eq!(plan.complete_middle, None);
assert_eq!(plan.current_period, (date(2024, 3, 1), date(2024, 3, 21)));
}
#[test]
fn test_quarter_grain_three_branches() {
let plan = determine_branches(date(2024, 2, 15), TemporalGrain::Quarter, date(2024, 11, 10));
assert_eq!(plan.partial_leading, Some((date(2024, 2, 15), date(2024, 4, 1))));
assert_eq!(plan.complete_middle, Some((date(2024, 4, 1), date(2024, 10, 1))));
assert_eq!(plan.current_period, (date(2024, 10, 1), date(2024, 11, 11)));
}
#[test]
fn test_year_grain() {
let plan = determine_branches(date(2023, 6, 15), TemporalGrain::Year, date(2025, 3, 10));
assert_eq!(plan.partial_leading, Some((date(2023, 6, 15), date(2024, 1, 1))));
assert_eq!(plan.complete_middle, Some((date(2024, 1, 1), date(2025, 1, 1))));
assert_eq!(plan.current_period, (date(2025, 1, 1), date(2025, 3, 11)));
}
#[test]
fn test_week_grain() {
let plan = determine_branches(date(2024, 1, 3), TemporalGrain::Week, date(2024, 1, 17));
assert_eq!(plan.partial_leading, Some((date(2024, 1, 3), date(2024, 1, 8))));
assert_eq!(plan.complete_middle, Some((date(2024, 1, 8), date(2024, 1, 15))));
assert_eq!(plan.current_period, (date(2024, 1, 15), date(2024, 1, 18)));
}
#[test]
fn test_lower_bound_equals_today() {
let plan = determine_branches(date(2024, 3, 15), TemporalGrain::Month, date(2024, 3, 15));
assert_eq!(plan.partial_leading, Some((date(2024, 3, 15), date(2024, 4, 1))));
assert_eq!(plan.complete_middle, None);
assert_eq!(plan.current_period, (date(2024, 3, 1), date(2024, 3, 16)));
}
#[test]
fn test_extract_gte_simple() {
let wc = WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Gte,
value: json!("2024-01-15"),
};
assert_eq!(extract_lower_date_bound(&wc, "date"), Some(date(2024, 1, 15)));
}
#[test]
fn test_extract_gt_converts_to_next_day() {
let wc = WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Gt,
value: json!("2024-01-14"),
};
assert_eq!(extract_lower_date_bound(&wc, "date"), Some(date(2024, 1, 15)));
}
#[test]
fn test_extract_from_and_chain() {
let wc = WhereClause::And(vec![
WhereClause::Field {
path: vec!["tenant_id".into()],
operator: WhereOperator::Eq,
value: json!("t1"),
},
WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Gte,
value: json!("2024-01-15"),
},
]);
assert_eq!(extract_lower_date_bound(&wc, "date"), Some(date(2024, 1, 15)));
}
#[test]
fn test_extract_or_returns_none() {
let wc = WhereClause::Or(vec![
WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Gte,
value: json!("2024-01-15"),
},
WhereClause::Field {
path: vec!["status".into()],
operator: WhereOperator::Eq,
value: json!("active"),
},
]);
assert!(extract_lower_date_bound(&wc, "date").is_none());
}
#[test]
fn test_extract_not_returns_none() {
let wc = WhereClause::Not(Box::new(WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Lt,
value: json!("2024-01-15"),
}));
assert!(extract_lower_date_bound(&wc, "date").is_none());
}
#[test]
fn test_extract_no_date_column_returns_none() {
let wc = WhereClause::Field {
path: vec!["status".into()],
operator: WhereOperator::Eq,
value: json!("active"),
};
assert!(extract_lower_date_bound(&wc, "date").is_none());
}
#[test]
fn test_extract_wrong_operator_returns_none() {
let wc = WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Lt,
value: json!("2024-01-15"),
};
assert!(extract_lower_date_bound(&wc, "date").is_none());
}
#[test]
fn test_extract_native_field_gte() {
let wc = WhereClause::NativeField {
column: "period_start".into(),
pg_cast: "date".into(),
operator: WhereOperator::Gte,
value: json!("2024-03-01"),
};
assert_eq!(extract_lower_date_bound(&wc, "period_start"), Some(date(2024, 3, 1)));
}
#[test]
fn test_extract_native_field_wrong_column() {
let wc = WhereClause::NativeField {
column: "created_at".into(),
pg_cast: "date".into(),
operator: WhereOperator::Gte,
value: json!("2024-03-01"),
};
assert!(extract_lower_date_bound(&wc, "period_start").is_none());
}
#[test]
fn test_split_single_date_condition() {
let wc = WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Gte,
value: json!("2024-01-15"),
};
let result = split_where_clause(&wc, "date").unwrap();
assert_eq!(result.lower_bound, date(2024, 1, 15));
assert_eq!(result.remaining, None);
}
#[test]
fn test_split_and_chain_three_conditions() {
let wc = WhereClause::And(vec![
WhereClause::Field {
path: vec!["tenant_id".into()],
operator: WhereOperator::Eq,
value: json!("t1"),
},
WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Gte,
value: json!("2024-01-15"),
},
WhereClause::Field {
path: vec!["status".into()],
operator: WhereOperator::Eq,
value: json!("active"),
},
]);
let result = split_where_clause(&wc, "date").unwrap();
assert_eq!(result.lower_bound, date(2024, 1, 15));
assert_eq!(
result.remaining,
Some(WhereClause::And(vec![
WhereClause::Field {
path: vec!["tenant_id".into()],
operator: WhereOperator::Eq,
value: json!("t1"),
},
WhereClause::Field {
path: vec!["status".into()],
operator: WhereOperator::Eq,
value: json!("active"),
},
]))
);
}
#[test]
fn test_split_and_chain_two_conditions_unwraps() {
let wc = WhereClause::And(vec![
WhereClause::Field {
path: vec!["tenant_id".into()],
operator: WhereOperator::Eq,
value: json!("t1"),
},
WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Gte,
value: json!("2024-01-15"),
},
]);
let result = split_where_clause(&wc, "date").unwrap();
assert_eq!(result.lower_bound, date(2024, 1, 15));
assert_eq!(
result.remaining,
Some(WhereClause::Field {
path: vec!["tenant_id".into()],
operator: WhereOperator::Eq,
value: json!("t1"),
})
);
}
#[test]
fn test_split_no_match_returns_none() {
let wc = WhereClause::Field {
path: vec!["status".into()],
operator: WhereOperator::Eq,
value: json!("active"),
};
assert!(split_where_clause(&wc, "date").is_none());
}
#[test]
fn test_split_or_returns_none() {
let wc = WhereClause::Or(vec![
WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Gte,
value: json!("2024-01-15"),
},
WhereClause::Field {
path: vec!["status".into()],
operator: WhereOperator::Eq,
value: json!("active"),
},
]);
assert!(split_where_clause(&wc, "date").is_none());
}
#[test]
fn test_split_native_field() {
let wc = WhereClause::NativeField {
column: "period_start".into(),
pg_cast: "date".into(),
operator: WhereOperator::Gte,
value: json!("2024-03-01"),
};
let result = split_where_clause(&wc, "period_start").unwrap();
assert_eq!(result.lower_bound, date(2024, 3, 1));
assert_eq!(result.remaining, None);
}
#[test]
fn test_split_gt_conversion() {
let wc = WhereClause::And(vec![
WhereClause::Field {
path: vec!["date".into()],
operator: WhereOperator::Gt,
value: json!("2024-01-14"),
},
WhereClause::Field {
path: vec!["status".into()],
operator: WhereOperator::Eq,
value: json!("active"),
},
]);
let result = split_where_clause(&wc, "date").unwrap();
assert_eq!(result.lower_bound, date(2024, 1, 15));
assert_eq!(
result.remaining,
Some(WhereClause::Field {
path: vec!["status".into()],
operator: WhereOperator::Eq,
value: json!("active"),
})
);
}
mod should_use_tests {
use super::*;
use crate::compiler::fact_table::{
DimensionColumn, FactTableMetadata, MeasureColumn, PartialPeriodConfig, SqlType,
TemporalGrain,
};
fn metadata_with_pp() -> FactTableMetadata {
FactTableMetadata {
table_name: "v_events_month".into(),
measures: vec![MeasureColumn {
name: "volume".into(),
sql_type: SqlType::BigInt,
nullable: false,
}],
dimensions: DimensionColumn {
name: "data".into(),
paths: vec![],
},
denormalized_filters: vec![],
calendar_dimensions: vec![],
partial_period: Some(PartialPeriodConfig {
fine_grain_view: "v_events_day".into(),
time_grain_column: "period_start".into(),
time_grain_trunc: TemporalGrain::Month,
}),
native_measures: std::collections::HashMap::new(),
native_dimension_mapping: std::collections::HashMap::new(),
}
}
fn metadata_without_pp() -> FactTableMetadata {
let mut m = metadata_with_pp();
m.partial_period = None;
m
}
#[test]
fn test_triggers_when_conditions_met() {
let m = metadata_with_pp();
let wc = WhereClause::Field {
path: vec!["period_start".into()],
operator: WhereOperator::Gte,
value: json!("2024-01-15"),
};
let today = date(2024, 3, 20);
let result = should_use_partial_period(&m, Some(&wc), today);
assert!(result.is_some());
let (lower, config) = result.unwrap();
assert_eq!(lower, date(2024, 1, 15));
assert_eq!(config.fine_grain_view, "v_events_day");
}
#[test]
fn test_none_without_partial_period_config() {
let m = metadata_without_pp();
let wc = WhereClause::Field {
path: vec!["period_start".into()],
operator: WhereOperator::Gte,
value: json!("2024-01-15"),
};
assert!(should_use_partial_period(&m, Some(&wc), date(2024, 3, 20)).is_none());
}
#[test]
fn test_none_without_where_clause() {
let m = metadata_with_pp();
assert!(should_use_partial_period(&m, None, date(2024, 3, 20)).is_none());
}
#[test]
fn test_none_when_no_date_condition() {
let m = metadata_with_pp();
let wc = WhereClause::Field {
path: vec!["status".into()],
operator: WhereOperator::Eq,
value: json!("active"),
};
assert!(should_use_partial_period(&m, Some(&wc), date(2024, 3, 20)).is_none());
}
#[test]
fn test_none_when_single_branch_shortcircuit() {
let m = metadata_with_pp();
let wc = WhereClause::Field {
path: vec!["period_start".into()],
operator: WhereOperator::Gte,
value: json!("2024-03-01"),
};
assert!(should_use_partial_period(&m, Some(&wc), date(2024, 3, 20)).is_none());
}
#[test]
fn test_triggers_with_aligned_but_multiple_branches() {
let m = metadata_with_pp();
let wc = WhereClause::Field {
path: vec!["period_start".into()],
operator: WhereOperator::Gte,
value: json!("2024-02-01"),
};
let result = should_use_partial_period(&m, Some(&wc), date(2024, 3, 20));
assert!(result.is_some());
}
}