use super::*;
use crate::matches::{run_pass, Matches};
use rushdb_lmpg::plan::FieldChange;
use serde_json::json;
fn store() -> Lmpg {
let lmpg = Lmpg::new(
Arc::new(rushdb_storage::Store::new_in_memory()),
rushdb_types::ids::TenantId::from_ordinal(9),
);
let mut tx = lmpg.begin_batch();
for n in 0u64..130 {
let (label, mut fields) = match n {
0..60 => {
let mut fields = vec![(
"g".to_owned(),
FieldChange::Set(json!(if n % 3 == 0 { "hit" } else { "miss" })),
)];
if n % 5 != 0 {
let value = if n % 7 == 0 {
json!(null)
} else {
json!(n % 9)
};
fields.push(("p".to_owned(), FieldChange::Set(value)));
}
("A", fields)
}
60..100 => ("B", vec![("q".to_owned(), FieldChange::Set(json!(n)))]),
_ => (
"C",
vec![(
"p".to_owned(),
FieldChange::Set(json!(format!("s{}", n % 4))),
)],
),
};
let label = match n % 3 {
0 if label == "B" => "A",
_ => label,
};
fields.sort_by(|a, b| a.0.cmp(&b.0));
tx.ops.push(rushdb_lmpg::BatchOp::CreateRecord {
label: label.into(),
explicit_id: None,
fields,
});
}
lmpg.run_batch(tx).unwrap();
for (name, label, property) in [("a_g", "A", "g"), ("a_p", "A", "p"), ("c_p", "C", "p")] {
let def = lmpg
.define_index(
name,
label,
property,
rushdb_types::index::IndexKind::Ordered,
)
.unwrap();
while !matches!(
lmpg.index_step(def.id, 10_000).unwrap(),
rushdb_lmpg::index::IndexStep::Finished | rushdb_lmpg::index::IndexStep::Idle
) {}
}
lmpg
}
fn both_ways(lmpg: &Lmpg, req: &SearchRequest, skip: usize, limit: usize) -> [(Vec<u64>, u64); 2] {
let snap: QuerySnapshot = Arc::new(lmpg.store.read_view().unwrap());
let page = |matches: Matches| {
let roots = matches
.retained
.into_iter()
.skip(skip.saturating_sub(matches.offset))
.take(limit)
.collect();
(roots, matches.total)
};
[
page(run_matches(lmpg, req, &|| false, &snap, skip, limit, None).unwrap()),
page(
run_pass(lmpg, req, &|| false, &snap, (skip, limit), None, false)
.unwrap()
.unwrap(),
),
]
}
#[test]
fn order_first_pages_equal_predicate_first_pages() {
let lmpg = store();
let snap: QuerySnapshot = Arc::new(lmpg.store.read_view().unwrap());
let mut sources = std::collections::BTreeSet::new();
for labels in [
json!([]),
json!(["A"]),
json!(["A", "B"]),
json!(["C"]),
json!(["B"]),
] {
for filter in [json!({}), json!({"g": "hit"})] {
for order in [
json!({}),
json!("asc"),
json!("desc"),
json!({"p": "asc"}),
json!({"p": "desc"}),
] {
for (skip, limit) in [
(0, 5),
(3, 4),
(20, 7),
(55, 10),
(95, 5),
(126, 9),
(400, 5),
] {
let document = json!({"labels": labels, "where": filter,
"orderBy": order, "skip": skip, "limit": limit});
let req = document::parse_document(&document).unwrap().search;
let [planned, oracle] = both_ways(&lmpg, &req, skip, limit);
assert_eq!(planned, oracle, "{document}");
if let Some(order) = explain_order(&lmpg, &req, &snap, skip, limit).unwrap() {
sources.insert((order.source, order.reverse_walk));
}
}
}
}
}
for source in [
("storage", true),
("id", false),
("id", true),
("property", false),
("index", false),
] {
assert!(
sources.contains(&source),
"{source:?} never planned: {sources:?}"
);
}
}