use cql2::{Expr, ToDuckSQL};
use duckdb::{params, Connection, Result};
use serde_json::{json, Value};
use std::fs;
#[test]
fn operators_duckdb_filter() -> Result<()> {
let conn = Connection::open_in_memory()?;
conn.execute_batch(r"
SET TimeZone='UTC';
INSTALL SPATIAL;
LOAD SPATIAL;
CREATE TABLE test AS SELECT * REPLACE (st_geomfromgeojson(geom) as geom) from 'tests/cql2testdata.ndjson';
")?;
let tests =
fs::read_to_string("tests/operators_expected.txt").expect("Failed to read operators tests");
let mut lines = tests.lines();
while let Some(query) = lines.next() {
let expected_line = lines
.next()
.unwrap_or_else(|| panic!("Missing expected output for query: {}", query));
let expr: Expr = query
.parse()
.unwrap_or_else(|_| panic!("Failed to parse query '{}'", query));
let where_clause = expr.to_ducksql().expect("to_ducksql failed");
let sql = format!(
"select array_to_string(array_agg(intfield::text), ' ') from test where {}",
where_clause
);
let mut stmt = conn.prepare(&sql)?;
let mut rows = stmt.query([])?;
let ids: String = rows
.next()?
.expect("aggregate query always returns one row")
.get::<_, Option<String>>(0)
.expect("Failed to get result")
.unwrap_or_default();
assert_eq!(
ids, expected_line,
"Query '{}' returned '{}', expected '{}'",
query, ids, expected_line
);
}
Ok(())
}
fn both_engines(
conn: &Connection,
table: &str,
items: &[Value],
query: &str,
) -> Result<(Vec<i64>, Vec<i64>)> {
let expr: Expr = query
.parse()
.unwrap_or_else(|e| panic!("'{query}' does not parse: {e}"));
let evaluated: Vec<i64> = expr
.filter(items)
.expect("the evaluator runs the filter")
.iter()
.map(|item| item["id"].as_i64().expect("every record has an id"))
.collect();
let where_clause = expr.to_ducksql().expect("expression renders as DuckDB SQL");
let sql = format!("select id from {table} where {where_clause} order by id");
let mut statement = conn
.prepare(&sql)
.unwrap_or_else(|e| panic!("DuckDB rejected '{sql}': {e}"));
let queried: Vec<i64> = statement
.query_map([], |row| row.get(0))?
.collect::<Result<_>>()?;
Ok((evaluated, queried))
}
#[test]
fn like_reads_one_pattern_in_both_engines() -> Result<()> {
const ROWS: [(i64, &str); 6] = [
(1, "item_1"),
(2, "itemX1"),
(3, "item%1"),
(4, r"item\1"),
(5, "itemq1"),
(6, "item_2"),
];
let conn = Connection::open_in_memory()?;
conn.execute_batch("CREATE TABLE strings (id BIGINT, textfield VARCHAR)")?;
for (id, text) in ROWS {
conn.execute("INSERT INTO strings VALUES (?, ?)", params![id, text])?;
}
let items: Vec<Value> = ROWS
.iter()
.map(|(id, text)| json!({"id": id, "textfield": text}))
.collect();
for (query, expected) in [
(r"like(textfield, 'item\_1')", vec![1]),
(r"like(textfield, 'item\%1')", vec![3]),
(r"like(textfield, 'item\\1')", vec![4]),
("like(textfield, 'item_1')", vec![1, 2, 3, 4, 5]),
("like(textfield, 'item%')", vec![1, 2, 3, 4, 5, 6]),
] {
let (evaluated, queried) = both_engines(&conn, "strings", &items, query)?;
assert_eq!(evaluated, expected, "the evaluator disagrees on {query}");
assert_eq!(queried, expected, "DuckDB disagrees on {query}");
}
Ok(())
}
#[test]
fn a_equals_is_set_equality_in_both_engines() -> Result<()> {
const ROWS: [(i64, &str); 4] = [
(1, "[1, 2, 3]"),
(2, "[3, 2, 1]"),
(3, "[1, 2, 2, 3]"),
(4, "[1, 2]"),
];
let conn = Connection::open_in_memory()?;
conn.execute_batch("CREATE TABLE arrays (id BIGINT, intarrayfield BIGINT[])")?;
for (id, elements) in ROWS {
conn.execute_batch(&format!("INSERT INTO arrays VALUES ({id}, {elements})"))?;
}
let items: Vec<Value> = ROWS
.iter()
.map(|(id, elements)| {
json!({"id": id, "intarrayfield": serde_json::from_str::<Value>(elements).unwrap()})
})
.collect();
for (query, expected) in [
("a_equals(intarrayfield, (3, 2, 1))", vec![1, 2, 3]),
("a_equals((3, 2, 1), intarrayfield)", vec![1, 2, 3]),
("a_equals(intarrayfield, (1, 2, 2, 3))", vec![1, 2, 3]),
("a_equals(intarrayfield, (1, 2))", vec![4]),
("a_equals(intarrayfield, (1, 2, 3, 4))", vec![]),
("a_contains(intarrayfield, (2, 3))", vec![1, 2, 3]),
("a_containedby(intarrayfield, (1, 2, 3))", vec![1, 2, 3, 4]),
("a_overlaps(intarrayfield, (3, 9))", vec![1, 2, 3]),
] {
let (evaluated, queried) = both_engines(&conn, "arrays", &items, query)?;
assert_eq!(evaluated, expected, "the evaluator disagrees on {query}");
assert_eq!(queried, expected, "DuckDB disagrees on {query}");
}
Ok(())
}