use cql2::Expr;
use rstest::rstest;
use serde_json::{json, Value};
use std::path::Path;
fn read_lines(filename: impl AsRef<Path>) -> Vec<String> {
std::fs::read_to_string(filename)
.unwrap() .lines() .map(String::from) .collect() }
fn validate_reduction(a: String, b: String) {
let properties: Value = json!(
{
"properties": {
"eo:cloud_cover": 10,
"boolfalse": false,
"booltrue": true,
"stringfield": "string",
"tsfield": {"timestamp": "2020-01-01 00:00:00Z"},
"tstarr": [1,2,3]
},
"geometry": {"type": "Point", "coordinates": [-93.0, 45]},
"datetime": "2020-01-01 00:00:00Z"
}
);
let inexpr: Expr = a.parse().unwrap();
let reduced = inexpr.reduce(Some(&properties)).unwrap();
let outexpr: Expr = b.parse().unwrap();
assert_eq!(reduced, outexpr);
}
#[rstest]
fn validate_reduce_fixtures() {
let lines = read_lines("tests/reductions.txt");
let a = lines.clone().into_iter().step_by(2);
let b = lines.clone().into_iter().skip(1).step_by(2);
let zipped = a.zip(b);
for (a, b) in zipped {
validate_reduction(a, b);
}
}
#[test]
fn is_null_not_folded_without_context() {
let expr: Expr = "numeric IS NULL".parse().unwrap();
let reduced = expr.reduce(None).unwrap();
match reduced {
Expr::Operation { op, args } => {
assert_eq!(op, "isNull");
assert_eq!(
*args[0],
Expr::Property {
property: "numeric".to_string()
}
);
}
other => panic!("expected the IS NULL predicate to be preserved, got {other:?}"),
}
}
#[test]
fn is_null_folds_with_known_value() {
let present = json!({"properties": {"numeric": 5}});
let present_null = json!({"properties": {"numeric": null}});
let absent = json!({"properties": {"other": 5}});
let expr: Expr = "numeric IS NULL".parse().unwrap();
assert_eq!(expr.reduce(Some(&present)).unwrap(), Expr::Bool(false));
let expr: Expr = "numeric IS NULL".parse().unwrap();
assert_eq!(expr.reduce(Some(&present_null)).unwrap(), Expr::Bool(true));
let expr: Expr = "numeric IS NULL".parse().unwrap();
assert_eq!(expr.reduce(Some(&absent)).unwrap(), Expr::Bool(true));
let expr: Expr = "null IS NULL".parse().unwrap();
assert_eq!(expr.reduce(None).unwrap(), Expr::Bool(true));
let expr: Expr = "1 IS NULL".parse().unwrap();
assert_eq!(expr.reduce(None).unwrap(), Expr::Bool(false));
}
#[test]
fn in_not_folded_without_context() {
let expr: Expr = "cityName IN ('Toronto','Frankfurt','Tokyo','New York')"
.parse()
.unwrap();
let reduced = expr.reduce(None).unwrap();
match reduced {
Expr::Operation { op, args } => {
assert_eq!(op, "in");
assert_eq!(
*args[0],
Expr::Property {
property: "cityName".to_string()
}
);
}
other => panic!("expected the IN predicate to be preserved, got {other:?}"),
}
}
#[test]
fn in_still_folds_for_known_values() {
let expr: Expr = "'b' IN ('a','b','c')".parse().unwrap();
assert_eq!(expr.reduce(None).unwrap(), Expr::Bool(true));
let expr: Expr = "'z' IN ('a','b','c')".parse().unwrap();
assert_eq!(expr.reduce(None).unwrap(), Expr::Bool(false));
}
#[test]
fn negative_number_literal() {
let expr: Expr = "property > -2".parse().unwrap();
assert_eq!(expr.to_text().unwrap(), "(property > -2)");
match &expr {
Expr::Operation { op, args } => {
assert_eq!(op, ">");
assert_eq!(*args[1], Expr::Float(-2.0));
}
other => panic!("expected a comparison operation, got {other:?}"),
}
let expr: Expr = "property > -3.14".parse().unwrap();
assert_eq!(expr.to_text().unwrap(), "(property > -3.14)");
let expr: Expr = "-foo".parse().unwrap();
assert_eq!(expr.to_text().unwrap(), "-1 * foo");
}