use crate::eval::environment::Environment;
use crate::reverse_eval::domain::Constraint;
use crate::reverse_eval::reverse::reverse_eval;
use crate::value::{TimestampValue, Value};
use chrono::{Duration, TimeZone, Utc};
use hamelin_lib::tree::builder::*;
use hamelin_lib::tree::options::ExpressionTypeCheckOptions;
use hamelin_lib::tree::typed_ast::environment::TypeEnvironment;
use hamelin_lib::type_check_expression;
use hamelin_lib::types::{CALENDAR_INTERVAL, INT, INTERVAL, TIMESTAMP};
use pretty_assertions::assert_eq;
use rstest::rstest;
use std::sync::Arc;
fn ts(timestamp_str: &str) -> FunctionCallBuilder {
call("ts").arg(StringLiteralBuilder::new(timestamp_str))
}
#[rstest]
#[case::timestamp_plus_interval(
add(field_ref("ts"), days(1)),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 16, 0, 0, 0).unwrap()).into()),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 15, 0, 0, 0).unwrap()).into())
)]
#[case::timestamp_minus_interval(
subtract(field_ref("ts"), hours(2)),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 15, 10, 0, 0).unwrap()).into()),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 15, 12, 0, 0).unwrap()).into())
)]
fn test_reverse_eval_timestamp_interval_arithmetic_equals(
#[case] expr_builder: impl ExpressionBuilder,
#[case] output_constraint: Constraint,
#[case] expected_constraint: Constraint,
) {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ts", TIMESTAMP);
trans_env.bind_str("iv", INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
expr_builder.build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let result = reverse_eval(&expr, output_constraint, &env).expect("Reverse eval failed");
assert_eq!(result, Some(expected_constraint));
}
#[test]
fn test_reverse_eval_timestamp_plus_interval_range() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ts", TIMESTAMP);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
add(field_ref("ts"), hours(1)).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let min_output = Utc.with_ymd_and_hms(2024, 1, 15, 14, 0, 0).unwrap();
let max_output = Utc.with_ymd_and_hms(2024, 1, 15, 16, 0, 0).unwrap();
let constraint = Constraint::Range {
min: Some(TimestampValue::utc(min_output).into()),
max: Some(TimestampValue::utc(max_output).into()),
};
let result = reverse_eval(&expr, constraint, &env).expect("Reverse eval failed");
let expected_min = Utc.with_ymd_and_hms(2024, 1, 15, 13, 0, 0).unwrap();
let expected_max = Utc.with_ymd_and_hms(2024, 1, 15, 15, 0, 0).unwrap();
assert_eq!(
result,
Some(Constraint::Range {
min: Some(TimestampValue::utc(expected_min).into()),
max: Some(TimestampValue::utc(expected_max).into()),
})
);
}
#[rstest]
#[case::interval_plus_timestamp(
add(field_ref("iv"), ts("2024-01-15 00:00:00+00:00")),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 20, 0, 0, 0).unwrap()).into()),
Constraint::Equals(Value::Interval(Duration::days(5)))
)]
#[case::timestamp_minus_timestamp(
subtract(field_ref("ts"), ts("2024-01-10 00:00:00+00:00")),
Constraint::Equals(Value::Interval(Duration::days(3))),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 13, 0, 0, 0).unwrap()).into())
)]
#[case::timestamp_constant_minus_variable(
subtract(ts("2024-01-15 00:00:00+00:00"), field_ref("ts")),
Constraint::Equals(Value::Interval(Duration::days(2))),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 13, 0, 0, 0).unwrap()).into())
)]
fn test_reverse_eval_timestamp_with_intervals(
#[case] expr_builder: impl ExpressionBuilder,
#[case] output_constraint: Constraint,
#[case] expected_constraint: Constraint,
) {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ts", TIMESTAMP);
trans_env.bind_str("iv", INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
expr_builder.build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let result = reverse_eval(&expr, output_constraint, &env).expect("Reverse eval failed");
assert_eq!(result, Some(expected_constraint));
}
#[rstest]
#[case::timestamp_plus_calendar_interval_months(
add(field_ref("ts"), months(2)),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 3, 15, 0, 0, 0).unwrap()).into()),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 15, 0, 0, 0).unwrap()).into())
)]
#[case::calendar_interval_plus_timestamp(
add(months(6), field_ref("ts")),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 7, 1, 0, 0, 0).unwrap()).into()),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap()).into())
)]
#[case::timestamp_minus_calendar_interval(
subtract(field_ref("ts"), years(1)),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2023, 6, 15, 0, 0, 0).unwrap()).into()),
Constraint::Equals(TimestampValue::utc(Utc.with_ymd_and_hms(2024, 6, 15, 0, 0, 0).unwrap()).into())
)]
fn test_reverse_eval_calendar_interval_timestamp_arithmetic(
#[case] expr_builder: impl ExpressionBuilder,
#[case] output_constraint: Constraint,
#[case] expected_constraint: Constraint,
) {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ts", TIMESTAMP);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
expr_builder.build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let result = reverse_eval(&expr, output_constraint, &env).expect("Reverse eval failed");
assert_eq!(result, Some(expected_constraint));
}
#[rstest]
#[case::interval_plus_interval(
add(field_ref("iv"), days(2)),
Constraint::Equals(Value::Interval(Duration::days(5))),
Constraint::Equals(Value::Interval(Duration::days(3)))
)]
#[case::interval_minus_interval(
subtract(field_ref("iv"), hours(3)),
Constraint::Equals(Value::Interval(Duration::hours(7))),
Constraint::Equals(Value::Interval(Duration::hours(10)))
)]
#[case::interval_multiply_numeric(
multiply(field_ref("iv"), int(3)),
Constraint::Equals(Value::Interval(Duration::hours(9))),
Constraint::Equals(Value::Interval(Duration::hours(3)))
)]
#[case::numeric_multiply_interval(
multiply(int(2), field_ref("iv")),
Constraint::Equals(Value::Interval(Duration::days(10))),
Constraint::Equals(Value::Interval(Duration::days(5)))
)]
#[case::interval_divide_numeric(
divide(field_ref("iv"), int(4)),
Constraint::Equals(Value::Interval(Duration::hours(6))),
Constraint::Equals(Value::Interval(Duration::hours(24)))
)]
fn test_reverse_eval_interval_arithmetic(
#[case] expr_builder: impl ExpressionBuilder,
#[case] output_constraint: Constraint,
#[case] expected_constraint: Constraint,
) {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("iv", INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
expr_builder.build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let result = reverse_eval(&expr, output_constraint, &env).expect("Reverse eval failed");
assert_eq!(result, Some(expected_constraint));
}
#[test]
fn test_reverse_eval_interval_plus_interval_range() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("iv", INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
add(field_ref("iv"), days(2)).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let min_output = Duration::days(5);
let max_output = Duration::days(10);
let constraint = Constraint::Range {
min: Some(Value::Interval(min_output)),
max: Some(Value::Interval(max_output)),
};
let result = reverse_eval(&expr, constraint, &env).expect("Reverse eval failed");
let expected_min = Duration::days(3);
let expected_max = Duration::days(8);
assert_eq!(
result,
Some(Constraint::Range {
min: Some(Value::Interval(expected_min)),
max: Some(Value::Interval(expected_max)),
})
);
}
#[test]
fn test_reverse_eval_calendar_interval_plus_calendar_interval() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ci", CALENDAR_INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
add(field_ref("ci"), months(6)).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(Value::CalendarInterval(12));
let result = reverse_eval(&expr, output_constraint, &env).expect("Reverse eval failed");
assert_eq!(result, Some(Constraint::Equals(Value::CalendarInterval(6))));
}
#[test]
fn test_reverse_eval_calendar_interval_minus_calendar_interval() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ci", CALENDAR_INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
subtract(field_ref("ci"), months(3)).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(Value::CalendarInterval(9));
let result = reverse_eval(&expr, output_constraint, &env).expect("Reverse eval failed");
assert_eq!(
result,
Some(Constraint::Equals(Value::CalendarInterval(12)))
);
}
#[test]
fn test_reverse_eval_calendar_interval_multiply_numeric() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ci", CALENDAR_INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
multiply(field_ref("ci"), int(3)).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(Value::CalendarInterval(12));
let result = reverse_eval(&expr, output_constraint, &env).expect("Reverse eval failed");
assert_eq!(result, Some(Constraint::Equals(Value::CalendarInterval(4))));
}
#[test]
fn test_reverse_eval_calendar_interval_divide_numeric() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ci", CALENDAR_INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
divide(field_ref("ci"), int(2)).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(Value::CalendarInterval(6));
let result = reverse_eval(&expr, output_constraint, &env).expect("Reverse eval failed");
assert_eq!(
result,
Some(Constraint::Equals(Value::CalendarInterval(12)))
);
}
#[test]
fn test_reverse_interval_multiply_division_by_zero() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("x", INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
multiply(field_ref("x"), int(0)).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(Value::Interval(Duration::hours(1)));
let result = reverse_eval(&expr, output_constraint, &env);
assert!(
result.is_err(),
"Expected error for division by zero in reverse multiplication"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Division by zero"),
"Expected 'Division by zero' error, got: {}",
err_msg
);
}
#[test]
fn test_reverse_calendar_interval_multiply_division_by_zero() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ci", CALENDAR_INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
multiply(field_ref("ci"), int(0)).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(Value::CalendarInterval(6));
let result = reverse_eval(&expr, output_constraint, &env);
assert!(
result.is_err(),
"Expected error for division by zero in reverse multiplication"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Division by zero"),
"Expected 'Division by zero' error, got: {}",
err_msg
);
}
#[test]
fn test_reverse_interval_division_with_zero_output() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("interval_col", INTERVAL);
trans_env.bind_str("x", INT);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
divide(field_ref("interval_col"), field_ref("x")).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(Value::Interval(Duration::zero()));
let result = reverse_eval(&expr, output_constraint, &env);
assert!(
result.is_err(),
"Expected error for division by zero in reverse division"
);
}
#[test]
fn test_reverse_interval_multiply_with_zero_interval() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("x", INT);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
multiply(hours(0), field_ref("x")).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(Value::Interval(Duration::hours(1)));
let result = reverse_eval(&expr, output_constraint, &env);
assert!(
result.is_err(),
"Expected error for division by zero in reverse multiplication"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Division by zero"),
"Expected 'Division by zero' error, got: {}",
err_msg
);
}
#[test]
fn test_reverse_numeric_multiply_interval_with_zero_interval() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("x", INT);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
multiply(field_ref("x"), hours(0)).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(Value::Interval(Duration::hours(1)));
let result = reverse_eval(&expr, output_constraint, &env);
assert!(
result.is_err(),
"Expected error for division by zero in reverse multiplication"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Division by zero"),
"Expected 'Division by zero' error, got: {}",
err_msg
);
}
#[test]
fn test_reverse_calendar_interval_plus_timestamp_unsolvable() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ci", CALENDAR_INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
add(field_ref("ci"), ts("2024-01-15 00:00:00+00:00")).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(
TimestampValue::utc(Utc.with_ymd_and_hms(2024, 6, 15, 0, 0, 0).unwrap()).into(),
);
let result = reverse_eval(&expr, output_constraint, &env);
assert!(
result.is_err(),
"Expected error when solving for calendar interval from timestamp arithmetic, but got: {:?}",
result
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Cannot solve for calendar interval from timestamp arithmetic"),
"Expected 'Cannot solve for calendar interval' error, got: {}",
err_msg
);
}
#[test]
fn test_reverse_timestamp_plus_calendar_interval_unsolvable() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ci", CALENDAR_INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
add(ts("2024-01-01 00:00:00+00:00"), field_ref("ci")).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(
TimestampValue::utc(Utc.with_ymd_and_hms(2024, 7, 1, 0, 0, 0).unwrap()).into(),
);
let result = reverse_eval(&expr, output_constraint, &env);
assert!(
result.is_err(),
"Expected error when solving for calendar interval from timestamp arithmetic, but got: {:?}",
result
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Cannot solve for calendar interval from timestamp arithmetic"),
"Expected 'Cannot solve for calendar interval' error, got: {}",
err_msg
);
}
#[test]
fn test_reverse_timestamp_minus_calendar_interval_unsolvable() {
let env = Environment::new();
let mut trans_env = TypeEnvironment::default();
trans_env.bind_str("ci", CALENDAR_INTERVAL);
let bindings = Arc::new(trans_env);
let expr = type_check_expression(
subtract(ts("2024-06-15 00:00:00+00:00"), field_ref("ci")).build(),
ExpressionTypeCheckOptions::builder()
.bindings(bindings)
.build(),
)
.output;
let output_constraint = Constraint::Equals(
TimestampValue::utc(Utc.with_ymd_and_hms(2024, 1, 15, 0, 0, 0).unwrap()).into(),
);
let result = reverse_eval(&expr, output_constraint, &env);
assert!(
result.is_err(),
"Expected error when solving for calendar interval from timestamp arithmetic"
);
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("Cannot solve for calendar interval from timestamp arithmetic"),
"Expected 'Cannot solve for calendar interval' error, got: {}",
err_msg
);
}