use quarb::{QueryResult, Value};
fn doc() -> quarb_json::JsonAdapter {
quarb_json::JsonAdapter::parse(r#"{"rows":[{"k":"a","x":1},{"k":"b","x":3}]}"#).unwrap()
}
fn run(q: &str) -> quarb::Result<Vec<String>> {
let run = quarb::Run::ambient();
let out = quarb::Query::parse(q, &doc())?.run(&doc(), &run)?;
Ok(match out.result {
QueryResult::Values(vs) => vs.iter().map(Value::display_form).collect(),
QueryResult::Nodes(ns) => ns.iter().map(|n| format!("{n:?}")).collect(),
})
}
fn one(q: &str) -> String {
let out = run(q).unwrap_or_else(|e| panic!("{q}: {e}"));
assert_eq!(out.len(), 1, "{q}: {out:?}");
out.into_iter().next().unwrap()
}
fn refuse(q: &str) -> String {
match run(q) {
Err(e) => e.to_string(),
Ok(out) => panic!("{q} passed: {out:?}"),
}
}
#[test]
fn a_literal_after_a_pipe_opens_an_expression() {
assert_eq!(one("= 3 | 0"), "0");
assert_eq!(one("= 3 | 10 + $_"), "13");
assert_eq!(one(r#"= 3 | "x""#), "x");
assert_eq!(one("= 3 | null | type"), "null");
assert_eq!(one("= 3 | true | type"), "bool");
assert_eq!(one("= 3 | -1 * $_"), "-3");
assert_eq!(one("= 3 | 2_km | type"), "quantity");
assert_eq!(one("= 3 | 1.5 * 2"), "3");
assert_eq!(
run("/rows/* | ::x | 10 + $_").unwrap(),
vec!["11", "13"]
);
let e = refuse("= 3 | nosuchcommand");
assert!(e.contains("nosuchcommand"), "{e}");
for q in ["= 12 h", "= 3 | 12 km | type", "= 3 | 12 h"] {
let e = refuse(q);
assert!(e.contains("continues only with an operator") && e.contains("_"), "{q}: {e}");
}
assert!(refuse("= 12 h").contains("12_h"));
let e = refuse("= 3 | 12h");
assert!(e.contains("12_h") && e.contains("\"12h\""), "{e}");
let e = refuse("= 3 | 12 x");
assert!(e.contains("takes a right side"), "{e}");
}
#[test]
fn a_constructor_after_a_pipe_heads_an_expression() {
assert_eq!(one("= 3 | @(0; $_)"), "@(0; 3)");
assert_eq!(one("= 3 | @(0; $_) ++ @(1)"), "@(0; 3; 1)");
assert_eq!(one("= 3 | %(v = $_) ++ %(w = 1)"), "%(v = 3; w = 1)");
assert_eq!(one("= 3 | #(%(1; 2)) | dim"), "@(1; 2)");
assert_eq!(one("= 3 | #(%(1; 2)) ++ %(3; 4) | dim"), "@(2; 2)");
assert_eq!(one("= 3 | %(v = $_; n = 1)"), "%(v = 3; n = 1)");
assert_eq!(one("= %(a = @(1; 2)) | @ | type"), "list");
assert_eq!(one("= @(1; 2) | # | dim"), "@(2; 1)");
}
#[test]
fn after_the_aggregation_pipe_the_expression_runs_once_over_the_context() {
assert_eq!(one("/rows/* | ::x @| @(0; $_)"), "@(0; @(1; 3))");
assert_eq!(one("/rows/* | ::x @| 0"), "0");
assert_eq!(one(r#"/rows/* | ::x @| "x""#), "x");
assert_eq!(one("/rows/* | ::x @| 10 + count"), "12");
assert_eq!(
one("/rows/* | ::x @| %(xs = $_) ++ %(n = 1)"),
"%(xs = @(1; 3); n = 1)"
);
assert_eq!(one("/rows/* | ::x @| #(%(1; 2)) | dim"), "@(1; 2)");
assert_eq!(one("/rows/* | ::x @| %(xs = $_; n = count)"), "%(xs = @(1; 3); n = 2)");
assert_eq!(
one("/rows/* | %(::k; ::x) @| @"),
one("/rows/* | %(::k; ::x) @| # | @")
);
assert_eq!(one("/rows/* | %(::k; ::x) @| @ | type"), "list");
assert_eq!(one("/rows/* | ::x @| @"), "@(1; 3)");
}
#[test]
fn the_table_constructor_takes_its_option_alone() {
assert_eq!(one("= @(1..12) | #(--width=3) | dim"), "@(4; 3)");
assert_eq!(one("= @(1..12) | #(-w=3) | dim"), "@(4; 3)");
assert_eq!(
one("= @(1..6) | #(--header=@(a; b; c)) | header"),
r#"@("a"; "b"; "c")"#
);
assert_eq!(one("= @(1..6) | #(-h=@(a; b; c)) | :b"), "@(2; 5)");
assert_eq!(one("= 1..6 @| #(-w=2) | dim"), "@(3; 2)");
assert_eq!(one("= 1..6 @| #(--header=@(a; b)) | header"), r#"@("a"; "b")"#);
assert_eq!(one("= @(1; 2) | (#(-w=1) | dim)"), "@(2; 1)");
let e = refuse("= @(1..5) | #(-w=3)");
assert!(e.contains("5 items do not divide into rows of 3"), "{e}");
let e = refuse("= @(1..5) | #(-h=@(a; b))");
assert!(e.contains("5 items do not divide into rows of 2"), "{e}");
let e = refuse("= @(1..6) | #(-w=2; %(1; 2))");
assert!(e.contains("takes its option alone") && e.contains("'-w'"), "{e}");
let e = refuse("= @(1..6) | #(%(1; 2); --width=2)");
assert!(e.contains("takes its option alone") && e.contains("'--width'"), "{e}");
let e = refuse("= @(1..6) | #(-w=2 -h=@(a; b))");
assert!(e.contains("both given"), "{e}");
let e = refuse("= @(1..6) | #(--foo=3)");
assert!(e.contains("'--foo' given"), "{e}");
let e = refuse("= @(1; --x=1)");
assert!(e.contains("'@(…)' takes no option ('--x' given)"), "{e}");
let e = refuse("= %(a = 1; --x=1)");
assert!(e.contains("'%(…)' takes no option ('--x' given)"), "{e}");
}
#[test]
fn the_count_and_the_header_alone_are_retired() {
for q in ["= @(1..6) | #(3)", "= 1..6 @| #(3)", "= #(3)"] {
let e = refuse(q);
assert!(e.contains("'#(3)' is retired") && e.contains("#(--width=3)"), "{q}: {e}");
}
let e = refuse("= @(1..6) | #(@(a; b; c))");
assert!(
e.contains("'#(@(a; b; c))' alone is retired") && e.contains("#(--header=@(a; b; c))"),
"{e}"
);
assert_eq!(one("= #(@(a; b)) | header"), r#"@("a"; "b")"#);
}
#[test]
fn the_expression_head_round_trips() {
for q in [
"= 3 | 0 + $_ | @(0; $_) ++ @(1) | %(v = $_) ++ %(w = 1)",
"/rows/* | ::x @| @(0; $_) ++ @(1)",
"/rows/* | ::x @| 0",
"= @(1..12) | #(--width=3) | #(--header=@(a; b; c))",
] {
let first = quarb::Query::parse(q, &doc())
.unwrap_or_else(|e| panic!("{q}: {e}"))
.canonical();
let again = quarb::Query::parse(&first, &doc())
.unwrap_or_else(|e| panic!("{first}: {e}"))
.canonical();
assert_eq!(first, again, "{q}");
}
}