use quarb_csv::CsvAdapter;
const DOC: &str = "\
name,dept,age,salary
Ada,eng,36,120000
Bo,sales,17,45000
Cy,eng,64,95000
Dee,\"ops, misc\",41,
";
fn nodes(query: &str) -> Vec<String> {
let adapter = CsvAdapter::parse(DOC).unwrap();
match quarb::run(query, &adapter).unwrap() {
quarb::QueryResult::Nodes(ns) => ns.into_iter().map(|n| adapter.locator(n)).collect(),
quarb::QueryResult::Values(_) => panic!("expected nodes"),
}
}
fn values(query: &str) -> Vec<String> {
let adapter = CsvAdapter::parse(DOC).unwrap();
match quarb::run(query, &adapter).unwrap() {
quarb::QueryResult::Values(vs) => vs.iter().map(|v| v.to_string()).collect(),
quarb::QueryResult::Nodes(_) => panic!("expected values"),
}
}
#[test]
fn rows_and_columns() {
assert_eq!(nodes("/row").len(), 4);
assert_eq!(values("//row @| count"), vec!["4"]);
assert_eq!(nodes("/row[2]"), vec!["/row[2]"]);
assert_eq!(nodes("/row[-1]"), vec!["/row[4]"]);
assert_eq!(values("/row[1]::name"), vec!["Ada"]);
assert_eq!(values("/row[4]::dept"), vec!["ops, misc"]);
assert_eq!(values("^::;n-rows"), vec!["4"]);
assert_eq!(values("^::;columns"), vec!["name, dept, age, salary"]);
}
#[test]
fn filtering_and_arithmetic() {
assert_eq!(values("/row[::age > 35]::name"), vec!["Ada", "Cy", "Dee"]);
assert_eq!(
values(r#"/row[::dept = "eng" and ::age < 40]::name"#),
vec!["Ada"]
);
assert_eq!(values("/row[::salary div ::age > 3000]::name"), vec!["Ada"]);
assert_eq!(
values("/row[::dept = \"eng\"] | .(::salary + 0) @| sum"),
vec!["215000"]
);
}
#[test]
fn missing_values() {
assert_eq!(values("/row[::name = \"Dee\"]::salary"), vec![""]);
assert_eq!(values("/row::salary @| mean"), vec!["86666.66666666667"]);
assert_eq!(values("/row::salary @| count"), vec!["4"]);
assert_eq!(values("/row[::salary]::name @| count"), vec!["3"]);
assert_eq!(values("/row[::name = \"Dee\"] | ::salary * 2"), vec![""]);
assert_eq!(
values("/row[::name = \"Dee\"] | ::salary | default(0)"),
vec!["0"]
);
assert_eq!(values("/row[1] | ::salary | default(0)"), vec!["120000"]);
}
#[test]
fn aggregation() {
assert_eq!(values("/row::age @| median"), vec!["38.5"]);
assert_eq!(values("/row::age @| max"), vec!["64"]);
assert_eq!(values("/row::name @| sort @| first"), vec!["Ada"]);
assert_eq!(values("/row[::dept = \"eng\"]::age @| mean"), vec!["50"]);
}
#[test]
fn keyed_aggregates() {
assert_eq!(values("/row @| max_by(::salary) | ::name"), vec!["Ada"]);
assert_eq!(values("/row @| min_by(::salary) | ::name"), vec!["Bo"]);
assert_eq!(values("/row @| top(2, ::age) | ::name"), vec!["Cy", "Dee"]);
assert_eq!(values("/row @| bottom(1, ::age) | ::name"), vec!["Bo"]);
assert_eq!(
values("/row @| sort_by(::salary) | ::name"),
vec!["Bo", "Cy", "Ada", "Dee"]
);
assert_eq!(
values("/row @| unique_by(::dept) | ::dept"),
vec!["eng", "sales", "ops, misc"]
);
assert_eq!(
values("/row @| sort_by(::age) @| [..2] | ::name"),
vec!["Bo", "Ada"]
);
assert_eq!(nodes("/row @| max_by(::age)"), vec!["/row[3]"]);
}
#[test]
fn records_and_json() {
assert_eq!(
values(r#"/row[::dept = "eng"] | record(::name, "monthly", ::salary div 12)"#),
vec![
r#"{"name": "Ada", "monthly": 10000}"#,
r#"{"name": "Cy", "monthly": 7916.666666666667}"#
]
);
assert_eq!(values("/row[1]::name | json"), vec![r#""Ada""#]);
assert_eq!(values("/row[-1]::salary | json"), vec!["null"]);
assert_eq!(
values("/row @| max_by(::salary) | record(::name, ::salary)"),
vec![r#"{"name": "Ada", "salary": "120000"}"#]
);
}
#[test]
fn grouping() {
assert_eq!(
values("/row @| group(::dept) | count | rec($.dept, \"n\", $_)"),
vec![
r#"{"dept": "eng", "n": 2}"#,
r#"{"dept": "sales", "n": 1}"#,
r#"{"dept": "ops, misc", "n": 1}"#
]
);
assert_eq!(
values(
"/row::salary @| group(::dept) | mean | .m | rec($.dept, \"mean\", $.m) \
@| sort_by($.m)"
),
vec![
r#"{"dept": "sales", "mean": 45000}"#,
r#"{"dept": "eng", "mean": 107500}"#,
r#"{"dept": "ops, misc", "mean": null}"#
]
);
assert_eq!(
values("/row @| group(\"senior\", ::age idiv 40) | count | rec($.senior, \"n\", $_)"),
vec![r#"{"senior": 0, "n": 2}"#, r#"{"senior": 1, "n": 2}"#]
);
assert_eq!(
values("/row @| group(::salary) | count @| count"),
vec!["3"]
);
}
#[test]
fn group_members() {
assert_eq!(
values(
"/row @| group(::dept) | top(1, ::salary) @| ungroup \
| rec($.dept, ::name, ::salary)"
),
vec![
r#"{"dept": "eng", "name": "Ada", "salary": "120000"}"#,
r#"{"dept": "sales", "name": "Bo", "salary": "45000"}"#,
r#"{"dept": "ops, misc", "name": "Dee", "salary": null}"#
]
);
assert_eq!(
values("/row::salary @| group(::dept) | top(1, $_) | sum | rec($.dept, \"max\", $_)"),
vec![
r#"{"dept": "eng", "max": 120000}"#,
r#"{"dept": "sales", "max": 45000}"#,
r#"{"dept": "ops, misc", "max": null}"#
]
);
assert_eq!(
nodes("/row @| group(::dept) | top(1, ::age) @| ungroup"),
vec!["/row[3]", "/row[2]", "/row[4]"]
);
assert_eq!(
values("/row::name @| group(::dept) | top(1, ::age) @| ungroup"),
vec!["Cy", "Bo", "Dee"]
);
assert_eq!(
values("/row | top(1, ::age) @| ungroup @| count"),
vec!["4"]
);
}
#[test]
fn ordinal() {
assert_eq!(
values("/row @| sort_by(::age) | rec($ord, ::name)"),
vec![
r#"{"ordinal": 1, "name": "Bo"}"#,
r#"{"ordinal": 2, "name": "Ada"}"#,
r#"{"ordinal": 3, "name": "Dee"}"#,
r#"{"ordinal": 4, "name": "Cy"}"#
]
);
assert_eq!(
values("/row | [$ord mod 2 = 1] | ::name"),
vec!["Ada", "Cy"]
);
assert_eq!(
values("/row | [::dept = \"eng\"] | $ord + 0"),
vec!["1", "2"]
);
assert_eq!(values("/row[$ord = 1] @| count"), vec!["0"]);
}
#[test]
fn windows() {
assert_eq!(
values("/row | ::age @| window(-1..0) | sum"),
vec!["36", "53", "81", "105"]
);
assert_eq!(
values("/row | ::age @| window(2) | sum"),
vec!["36", "53", "81", "105"]
);
assert_eq!(
values("/row | ::age @| window(..0) | sum"),
vec!["36", "53", "117", "158"]
);
assert_eq!(
values("/row @| window(-1..1) | count"),
vec!["2", "3", "3", "2"]
);
assert_eq!(
values("/row | ::age @| window(0..1) | max"),
vec!["36", "64", "64", "41"]
);
assert_eq!(
values("/row @| window(..-1) | count"),
vec!["0", "1", "2", "3"]
);
assert_eq!(
values("/row | ::age @| window(-1..0, ::dept) | sum"),
vec!["36", "17", "100", "41"]
);
assert_eq!(
values("/row | ::age @| window(-1..0) | top(1, $_) | sum"),
vec!["36", "36", "64", "64"]
);
assert_eq!(
values("/row | ::age | .a @| window(-1..0) | sum | $_ - $.a"),
vec!["0", "36", "17", "64"]
);
}
#[test]
fn shift() {
assert_eq!(
values("/row | ::age @| shift(1)"),
vec!["", "36", "17", "64"]
);
assert_eq!(
values("/row | ::age @| shift(-1)"),
vec!["17", "64", "41", ""]
);
assert_eq!(
values("/row | ::age | .now @| shift(1) | $.now - $_"),
vec!["", "-19", "47", "-23"]
);
assert_eq!(
values("/row | ::age @| shift(1, ::dept)"),
vec!["", "", "36", ""]
);
}
#[test]
fn interpolation() {
assert_eq!(
values(r#"/row[..2] | "${::name} is ${::age}""#),
vec!["Ada is 36", "Bo is 17"]
);
assert_eq!(
values(r#"/row[1] | ::salary | .s | "\$${$.s}, monthly \$${$_ idiv 12}""#),
vec!["$120000, monthly $10000"]
);
assert_eq!(values(r#"/row[-1] | "[${::salary}]""#), vec!["[]"]);
assert_eq!(values(r#"/row[::dept = "eng"] @| count"#), vec!["2"]);
assert_eq!(
values(r#"/row[1] | rec("who", "${::name}/${::dept}")"#),
vec![r#"{"who": "Ada/eng"}"#]
);
}
#[test]
fn root_anchor_in_subcontexts() {
assert_eq!(
values("/row @| group(::dept) | count | .n | .t(^/row @| count) | $.n * 100 idiv $.t"),
vec!["50", "25", "25"]
);
assert_eq!(values("^/row @| count"), vec!["4"]);
}
#[test]
fn macros_end_to_end() {
assert_eq!(
values(
r#"macro &describe(@cols):
/cols/* | .k(/projection::key)
| "| .${$.k}-mean(/row${::form} @| mean)";
^ | &describe(::age, ::salary) | %."#
),
vec![r#"{"age-mean": 39.5, "salary-mean": 86666.66666666667}"#]
);
assert_eq!(
values(r#"macro &odd($n): ^ | "| [\$ord mod ${$n} = 1]"; /row | &odd(2) | ::name"#),
vec!["Ada", "Cy"]
);
}
#[test]
fn data_aware_macros() {
assert_eq!(
values(
r#"macro &pivot!($col):
/data/row[::age < 40] | $col @| unique
| "| .n-${$_}(/row[${$col} = '${$_}'] @| count)";
^ | &pivot!(::dept) | %."#
),
vec![r#"{"n-eng": 2, "n-sales": 1}"#]
);
assert_eq!(
values(
r#"macro &max!($col):
/data/row | $col @| max | "| '${$_}'";
^ | &max!(::age)"#
),
vec!["64"]
);
}
#[test]
fn window_shapes() {
let err = |q: &str| {
let adapter = CsvAdapter::parse(DOC).unwrap();
quarb::run(q, &adapter).unwrap_err().to_string()
};
assert!(err("/row | ::age | window(-1..0)").contains("'window' uses '@|'"));
assert!(err("/row | ::age | shift(1)").contains("'shift' uses '@|'"));
assert!(err("/row @| window(2..1)").contains("start <= end"));
assert!(err("/row @| window(0)").contains("offset range or a count"));
assert!(err("/row @| shift()").contains("integer distance"));
assert!(err("/row @| top(1..2, ::age)").contains("no range argument"));
}
#[test]
fn register_record_view() {
assert_eq!(
values("/row[::age > 35] | .name(::name) | .age(::age) | %."),
vec![
r#"{"name": "Ada", "age": "36"}"#,
r#"{"name": "Cy", "age": "64"}"#,
r#"{"name": "Dee", "age": "41"}"#
]
);
assert_eq!(
values("/row @| group(::dept) | count | .n | %."),
vec![
r#"{"dept": "eng", "n": 2}"#,
r#"{"dept": "sales", "n": 1}"#,
r#"{"dept": "ops, misc", "n": 1}"#
]
);
assert_eq!(
values("/row[1] | .a(::name) | ::dept | . | .b(::age) | .a(::salary) | %."),
vec![r#"{"a": "120000", "b": "36"}"#]
);
assert_eq!(values("/row[1] | %."), vec!["{}"]);
}
#[test]
fn fragments() {
assert_eq!(
values("def &grown: /row[::age >= 18]; &grown @| count"),
vec!["3"]
);
assert_eq!(
values("def &above($col, $min): /row[$col >= $min]; &above(::salary, 100000) | ::name"),
vec!["Ada"]
);
assert_eq!(
values(
"def &cols: | .name(::name) | .dept(::dept) | %.; \
/row @| top(1, ::salary) | &cols"
),
vec![r#"{"name": "Ada", "dept": "eng"}"#]
);
assert_eq!(
values(
"def &grown: /row[::age >= 18]; \
def &grown-eng: &grown | [::dept = \"eng\"]; \
&grown-eng | ::name"
),
vec!["Ada", "Cy"]
);
assert_eq!(
values("def &col($c): | .v($c) | $.v; /row | &col(::age * 2) @| max"),
vec!["128"]
);
let defs = quarb::parse_defs("def &grown: /row[::age >= 18];").unwrap();
let adapter = CsvAdapter::parse(DOC).unwrap();
match quarb::run_with_defs("&grown @| count", &defs, &adapter).unwrap() {
quarb::QueryResult::Values(vs) => assert_eq!(vs[0].to_string(), "3"),
_ => panic!("expected values"),
}
}
#[test]
fn captures() {
assert_eq!(
values(r#"/row | [::dept =~ /^(\w+)s$/] | rec("stem", $1, ::name)"#),
vec![r#"{"stem": "sale", "name": "Bo"}"#]
);
assert_eq!(
values(r#"/row | [::name =~ /^(\w)/] | .initial($1) | .age(::age) | %. @| [1..2]"#),
vec![
r#"{"initial": "A", "age": "36"}"#,
r#"{"initial": "B", "age": "17"}"#
]
);
assert_eq!(
values(r#"/row | [::dept =~ /^(\w)/] @| group("initial", $1) | count | .n | %."#),
vec![
r#"{"initial": "e", "n": 2}"#,
r#"{"initial": "s", "n": 1}"#,
r#"{"initial": "o", "n": 1}"#
]
);
assert_eq!(
values(r#"/row | [::name =~ /^(\w+)$/] | [::age > 30] | rec("who", $1, "g2", $2)"#),
vec![
r#"{"who": "Ada", "g2": null}"#,
r#"{"who": "Cy", "g2": null}"#,
r#"{"who": "Dee", "g2": null}"#
]
);
assert_eq!(values("/row[1] | rec(\"c\", $1)"), vec![r#"{"c": null}"#]);
}
#[test]
fn property_at_root_is_null() {
use quarb::{AstAdapter, NodeId};
let adapter = CsvAdapter::parse(DOC).unwrap();
assert!(adapter.property(NodeId(0), "age").is_none());
}
#[test]
fn tsv_and_errors() {
let tsv = "a\tb\n1\t2\n";
let adapter = CsvAdapter::parse_with_delimiter(tsv, b'\t').unwrap();
match quarb::run("/row::b", &adapter).unwrap() {
quarb::QueryResult::Values(vs) => assert_eq!(vs.len(), 1),
_ => panic!("expected values"),
}
assert!(CsvAdapter::parse("a,b\n1,2,3\n").is_err());
}
#[test]
fn correlated_subqueries() {
assert_eq!(
values(
"/row @| group(::dept) \
| .adults(^/row[::dept = $$.dept][::age >= 18] @| count) | %."
),
vec![
r#"{"dept": "eng", "adults": 2}"#,
r#"{"dept": "sales", "adults": 0}"#,
r#"{"dept": "ops, misc", "adults": 1}"#
]
);
assert_eq!(
values(
"/row @| [..2] | .d(::dept) \
| .m(^/row[::dept = $$.d]::salary @| mean) | $.m"
),
vec!["107500", "45000"]
);
assert_eq!(
values("/row @| [..2] | ::age | .t(^/row[:::index = $$ord]::name) | $.t"),
vec!["Ada", "Bo"]
);
assert_eq!(values("/row[1] | $$.x"), vec![""]);
assert_eq!(
values(
"/row[1] | .a(::age) | .x(^/row[1] | .b(::dept) | .y(^/row[::age = $$$.a] @| count) | $.y) | $.x"
),
vec!["1"]
);
}