use crate::velesql::ast::{IntervalUnit, IntervalValue, TemporalExpr, Value};
use crate::velesql::{Condition, Parser};
#[test]
fn test_singular_unit_second() {
assert_interval_unit("1 second", 1, IntervalUnit::Seconds);
}
#[test]
fn test_singular_unit_minute() {
assert_interval_unit("1 minute", 1, IntervalUnit::Minutes);
}
#[test]
fn test_singular_unit_hour() {
assert_interval_unit("1 hour", 1, IntervalUnit::Hours);
}
#[test]
fn test_singular_unit_day() {
assert_interval_unit("1 day", 1, IntervalUnit::Days);
}
#[test]
fn test_singular_unit_week() {
assert_interval_unit("1 week", 1, IntervalUnit::Weeks);
}
#[test]
fn test_singular_unit_month() {
assert_interval_unit("1 month", 1, IntervalUnit::Months);
}
#[test]
fn test_plural_unit_seconds() {
assert_interval_unit("30 seconds", 30, IntervalUnit::Seconds);
}
#[test]
fn test_plural_unit_minutes() {
assert_interval_unit("5 minutes", 5, IntervalUnit::Minutes);
}
#[test]
fn test_plural_unit_hours() {
assert_interval_unit("24 hours", 24, IntervalUnit::Hours);
}
#[test]
fn test_plural_unit_days() {
assert_interval_unit("7 days", 7, IntervalUnit::Days);
}
#[test]
fn test_plural_unit_weeks() {
assert_interval_unit("4 weeks", 4, IntervalUnit::Weeks);
}
#[test]
fn test_plural_unit_months() {
assert_interval_unit("6 months", 6, IntervalUnit::Months);
}
#[test]
fn test_shorthand_s() {
assert_interval_unit("30 s", 30, IntervalUnit::Seconds);
}
#[test]
fn test_shorthand_sec() {
assert_interval_unit("1 sec", 1, IntervalUnit::Seconds);
}
#[test]
fn test_shorthand_min() {
assert_interval_unit("5 min", 5, IntervalUnit::Minutes);
}
#[test]
fn test_shorthand_m() {
assert_interval_unit("5 m", 5, IntervalUnit::Minutes);
}
#[test]
fn test_shorthand_h() {
assert_interval_unit("2 h", 2, IntervalUnit::Hours);
}
#[test]
fn test_shorthand_d() {
assert_interval_unit("7 d", 7, IntervalUnit::Days);
}
#[test]
fn test_shorthand_w() {
assert_interval_unit("2 w", 2, IntervalUnit::Weeks);
}
#[test]
fn test_temporal_comparison_gt() {
let sql = "SELECT * FROM events WHERE timestamp > NOW() - INTERVAL '7 days'";
let query = Parser::parse(sql).expect("temporal comparison should parse");
assert!(
query.select.where_clause.is_some(),
"WHERE clause must be present"
);
match query.select.where_clause.as_ref() {
Some(Condition::Comparison(cmp)) => {
assert_eq!(cmp.column, "timestamp");
assert!(
matches!(&cmp.value, Value::Temporal(TemporalExpr::Subtract(_, _))),
"Expected Temporal(Subtract), got {:?}",
cmp.value
);
}
other => panic!("Expected Comparison, got {other:?}"),
}
}
#[test]
fn test_temporal_between() {
let sql = "SELECT * FROM events WHERE ts BETWEEN NOW() - INTERVAL '7 days' AND NOW()";
let query = Parser::parse(sql).expect("BETWEEN with temporal should parse");
match query.select.where_clause.as_ref() {
Some(Condition::Between(b)) => {
assert_eq!(b.column, "ts");
assert!(
matches!(&b.low, Value::Temporal(TemporalExpr::Subtract(_, _))),
"low should be Temporal(Subtract), got {:?}",
b.low
);
assert!(
matches!(&b.high, Value::Temporal(TemporalExpr::Now)),
"high should be Temporal(Now), got {:?}",
b.high
);
}
other => panic!("Expected Between condition, got {other:?}"),
}
}
#[test]
fn test_temporal_with_and_filter() {
let sql = "SELECT * FROM logs WHERE ts > NOW() - INTERVAL '24 hours' AND status = 'active'";
let query = Parser::parse(sql).expect("temporal + AND filter should parse");
assert!(
matches!(query.select.where_clause, Some(Condition::And(_, _))),
"Root condition should be AND"
);
}
#[test]
fn test_multiple_temporal_in_one_query() {
let sql = "SELECT * FROM events WHERE start > NOW() - INTERVAL '1 hour' AND end_time < NOW() + INTERVAL '1 hour'";
let query = Parser::parse(sql).expect("multiple temporal expressions should parse");
match query.select.where_clause.as_ref() {
Some(Condition::And(left, right)) => {
assert!(
matches!(left.as_ref(), Condition::Comparison(_)),
"Left should be Comparison, got {left:?}"
);
assert!(
matches!(right.as_ref(), Condition::Comparison(_)),
"Right should be Comparison, got {right:?}"
);
if let Condition::Comparison(cmp) = left.as_ref() {
assert!(
matches!(&cmp.value, Value::Temporal(TemporalExpr::Subtract(_, _))),
"Left value should be Subtract, got {:?}",
cmp.value
);
}
if let Condition::Comparison(cmp) = right.as_ref() {
assert!(
matches!(&cmp.value, Value::Temporal(TemporalExpr::Add(_, _))),
"Right value should be Add, got {:?}",
cmp.value
);
}
}
other => panic!("Expected AND, got {other:?}"),
}
}
#[test]
fn test_large_magnitude() {
assert_interval_unit("365 days", 365, IntervalUnit::Days);
}
#[test]
fn test_zero_magnitude() {
assert_interval_unit("0 seconds", 0, IntervalUnit::Seconds);
}
#[test]
fn test_case_insensitivity_interval_keyword() {
let sql = "SELECT * FROM events WHERE ts > interval '7 days'";
let result = Parser::parse(sql);
assert!(
result.is_ok(),
"Lowercase 'interval' should parse: {:?}",
result.err()
);
}
#[test]
fn test_case_insensitivity_unit_uppercase() {
assert_interval_unit("7 DAYS", 7, IntervalUnit::Days);
}
#[test]
fn test_to_seconds_large_interval() {
let iv = IntervalValue {
magnitude: 365,
unit: IntervalUnit::Days,
};
assert_eq!(iv.to_seconds(), 365 * 86400);
}
#[test]
fn test_to_seconds_zero() {
let iv = IntervalValue {
magnitude: 0,
unit: IntervalUnit::Seconds,
};
assert_eq!(iv.to_seconds(), 0);
}
#[test]
fn test_invalid_unit_fortnights() {
let sql = "SELECT * FROM events WHERE ts > INTERVAL '7 fortnights'";
let result = Parser::parse(sql);
assert!(
result.is_err(),
"'fortnights' is not a valid interval unit, should fail"
);
}
#[test]
fn test_missing_magnitude() {
let sql = "SELECT * FROM events WHERE ts > INTERVAL 'days'";
let result = Parser::parse(sql);
assert!(
result.is_err(),
"INTERVAL without magnitude should fail to parse"
);
}
#[test]
fn test_empty_interval() {
let sql = "SELECT * FROM events WHERE ts > INTERVAL ''";
let result = Parser::parse(sql);
assert!(
result.is_err(),
"Empty INTERVAL string should fail to parse"
);
}
fn assert_interval_unit(interval_str: &str, expected_mag: i64, expected_unit: IntervalUnit) {
let sql = format!("SELECT * FROM events WHERE ts > INTERVAL '{interval_str}'");
let query = Parser::parse(&sql)
.unwrap_or_else(|e| panic!("Failed to parse interval '{interval_str}': {e:?}"));
let where_clause = query
.select
.where_clause
.as_ref()
.expect("WHERE clause must be present");
match where_clause {
Condition::Comparison(cmp) => match &cmp.value {
Value::Temporal(TemporalExpr::Interval(iv)) => {
assert_eq!(
iv.magnitude, expected_mag,
"Wrong magnitude for '{interval_str}': expected {expected_mag}, got {}",
iv.magnitude
);
assert_eq!(
iv.unit, expected_unit,
"Wrong unit for '{interval_str}': expected {expected_unit:?}, got {:?}",
iv.unit
);
}
other => panic!("Expected Temporal(Interval) for '{interval_str}', got {other:?}"),
},
other => panic!("Expected Comparison for '{interval_str}', got {other:?}"),
}
}