use super::projection::should_merge_projected_scan;
use super::*;
fn doc<const N: usize>(pairs: [(&str, Value); N]) -> Document {
pairs.into_iter().map(|(k, v)| (k.to_string(), v)).collect()
}
#[test]
fn put_get_round_trip() {
let mut s = MemoryDocumentStore::new();
s.put(1, doc([("title", Value::Str("rust".into()))]))
.unwrap();
let got = s.get(1).unwrap().unwrap();
assert_eq!(got.get("title"), Some(&Value::Str("rust".into())));
}
#[test]
fn tuple_metadata_survives_field_replacements_patches_and_snapshots() {
let mut store = MemoryDocumentStore::new();
store
.put_stored(
1,
StoredDocument::with_metadata(
doc([("title", Value::Str("rust".into()))]),
DocumentMetadata::with_tuple_xmin(37),
),
)
.unwrap();
store
.put(1, doc([("title", Value::Str("engine".into()))]))
.unwrap();
store
.patch_fields(
1,
&BTreeMap::from([("title".into(), Value::Str("UQA".into()))]),
)
.unwrap();
let stored = store.get_stored(1).unwrap().unwrap();
assert_eq!(stored.metadata().tuple_xmin(), Some(37));
assert_eq!(
stored.fields().get("title"),
Some(&Value::Str("UQA".into()))
);
let snapshot = store.snapshot().unwrap();
assert_eq!(
snapshot.get_metadata(1).unwrap().unwrap().tuple_xmin(),
Some(37)
);
}
#[test]
fn get_field_returns_value() {
let mut s = MemoryDocumentStore::new();
s.put(1, doc([("year", Value::Int(2026))])).unwrap();
assert_eq!(s.get_field(1, "year").unwrap(), Some(Value::Int(2026)));
assert_eq!(s.get_field(1, "missing").unwrap(), None);
assert_eq!(s.get_field(99, "year").unwrap(), None);
}
#[test]
fn delete_removes_doc() {
let mut s = MemoryDocumentStore::new();
s.put(1, doc([("a", Value::Int(1))])).unwrap();
s.delete(1).unwrap();
assert!(s.get(1).unwrap().is_none());
assert_eq!(s.len().unwrap(), 0);
}
#[test]
fn doc_ids_returns_all() {
let mut s = MemoryDocumentStore::new();
s.put(2, Document::new()).unwrap();
s.put(1, Document::new()).unwrap();
s.put(3, Document::new()).unwrap();
assert_eq!(s.doc_ids().unwrap(), vec![1, 2, 3]);
}
#[test]
fn get_fields_bulk_returns_value_per_id_with_null_for_missing() {
let mut s = MemoryDocumentStore::new();
s.put(1, doc([("year", Value::Int(2026))])).unwrap();
s.put(2, doc([("year", Value::Int(2025))])).unwrap();
let got = s.get_fields_bulk(&[1, 2, 99], "year").unwrap();
assert_eq!(got.get(&1), Some(&Value::Int(2026)));
assert_eq!(got.get(&2), Some(&Value::Int(2025)));
assert_eq!(got.get(&99), Some(&Value::Null));
}
#[test]
fn get_fields_multi_projects_in_requested_order() {
let mut s = MemoryDocumentStore::new();
s.put(
1,
doc([
("year", Value::Int(2026)),
("title", Value::Str("rust".into())),
("unused", Value::Bool(true)),
]),
)
.unwrap();
let got = s
.get_fields_multi(&[1, 99], &["title", "missing", "year"])
.unwrap();
assert_eq!(
got.get(&1),
Some(&vec![
Value::Str("rust".into()),
Value::Null,
Value::Int(2026)
])
);
assert!(!got.contains_key(&99));
}
#[test]
fn for_each_fields_multi_streams_doc_id_order_and_can_stop() {
let mut s = MemoryDocumentStore::new();
s.put(2, doc([("value", Value::Int(20))])).unwrap();
s.put(1, doc([("value", Value::Int(10))])).unwrap();
let mut visited = Vec::new();
s.for_each_fields_multi(&[2, 99, 1], &["value"], &mut |doc_id, values| {
visited.push((doc_id, values));
doc_id != 99
})
.unwrap();
assert_eq!(
visited,
vec![(2, vec![Value::Int(20)]), (99, vec![Value::Null])]
);
}
#[test]
fn for_each_fields_multi_ref_borrows_memory_values_and_reuses_nulls() {
let mut s = MemoryDocumentStore::new();
s.put(2, doc([("value", Value::Str("twenty".into()))]))
.unwrap();
let stored = std::ptr::from_ref(s.field(&s.state.documents[&2], "value").unwrap());
let mut visited = Vec::new();
s.for_each_fields_multi_ref(&[2, 99], &["value"], &mut |doc_id, values| {
if doc_id == 2 {
assert_eq!(std::ptr::from_ref(values[0]), stored);
}
visited.push((doc_id, values[0].clone()));
true
})
.unwrap();
assert_eq!(
visited,
vec![(2, Value::Str("twenty".into())), (99, Value::Null),]
);
}
#[test]
fn for_each_fields_multi_ref_preserves_projection_order_across_scan_paths() {
let mut s = MemoryDocumentStore::new();
for doc_id in 1..=10 {
s.put(
doc_id,
doc([
("alpha", Value::Int(doc_id as i64)),
("middle", Value::Bool(true)),
("zulu", Value::Int((doc_id * 10) as i64)),
]),
)
.unwrap();
}
let mut dense = Vec::new();
s.for_each_fields_multi_ref(
&[2, 3, 4, 99],
&["alpha", "missing", "zulu"],
&mut |doc_id, values| {
dense.push((
doc_id,
values.iter().map(|value| (*value).clone()).collect(),
));
true
},
)
.unwrap();
assert_eq!(
dense,
vec![
(2, vec![Value::Int(2), Value::Null, Value::Int(20)]),
(3, vec![Value::Int(3), Value::Null, Value::Int(30)]),
(4, vec![Value::Int(4), Value::Null, Value::Int(40)]),
(99, vec![Value::Null, Value::Null, Value::Null]),
]
);
let mut unsorted = Vec::new();
s.for_each_fields_multi_ref(&[10, 1], &["zulu", "alpha"], &mut |doc_id, values| {
unsorted.push((
doc_id,
values.iter().map(|value| (*value).clone()).collect(),
));
true
})
.unwrap();
assert_eq!(
unsorted,
vec![
(10, vec![Value::Int(100), Value::Int(10)]),
(1, vec![Value::Int(10), Value::Int(1)]),
]
);
}
#[test]
fn projected_scan_uses_merge_only_for_dense_or_table_wide_id_ranges() {
assert!(should_merge_projected_scan(&[100, 101, 102, 103], 20_000));
assert!(should_merge_projected_scan(&[1, 10_000], 10));
assert!(!should_merge_projected_scan(&[1, 10_000], 20_000));
assert!(!should_merge_projected_scan(&[2, 1], 2));
assert!(!should_merge_projected_scan(&[1], 1));
}
#[test]
fn for_each_fields_multi_ref_uses_each_documents_layout() {
let mut s = MemoryDocumentStore::new();
s.put(
1,
doc([("alpha", Value::Int(1)), ("zulu", Value::Str("one".into()))]),
)
.unwrap();
s.put(
2,
doc([
("alpha", Value::Int(2)),
("middle", Value::Str("two".into())),
]),
)
.unwrap();
let mut projected = Vec::new();
s.for_each_fields_multi_ref(&[1, 2], &["alpha", "zulu"], &mut |doc_id, values| {
projected.push((
doc_id,
values.iter().map(|value| (*value).clone()).collect(),
));
true
})
.unwrap();
assert_eq!(
projected,
vec![
(1, vec![Value::Int(1), Value::Str("one".into())]),
(2, vec![Value::Int(2), Value::Null]),
]
);
s.patch_fields(
2,
&BTreeMap::from([
("middle".to_string(), Value::Null),
("zulu".to_string(), Value::Str("patched".into())),
]),
)
.unwrap();
let mut patched = Vec::new();
s.for_each_fields_multi_ref(&[2], &["alpha", "zulu"], &mut |_doc_id, values| {
patched.extend(values.iter().map(|value| (*value).clone()));
true
})
.unwrap();
assert_eq!(patched, vec![Value::Int(2), Value::Str("patched".into())]);
}
#[test]
fn shared_projection_reuses_the_stored_value_vector() {
let mut s = MemoryDocumentStore::new();
s.put(
1,
doc([
("alpha", Value::Str("kept".into())),
("zulu", Value::Int(9)),
]),
)
.unwrap();
let stored = Arc::clone(&s.state.documents[&1].values);
let mut rows = s
.get_shared_fields(&[1], &["zulu", "missing", "alpha"])
.unwrap()
.unwrap();
let shared = rows.pop().unwrap().unwrap();
let projected = shared.with_projected(|values| {
values
.iter()
.map(|value| (*value).clone())
.collect::<Vec<_>>()
});
let (values, _) = shared.into_parts();
assert!(Arc::ptr_eq(&values, &stored));
assert_eq!(
projected,
vec![Value::Int(9), Value::Null, Value::Str("kept".into())]
);
}
#[test]
fn shared_cursor_combines_id_scan_and_projection() {
let mut store = MemoryDocumentStore::new();
store
.put(1, doc([("alpha", Value::Int(1)), ("zulu", Value::Int(2))]))
.unwrap();
store
.put(3, doc([("alpha", Value::Int(3)), ("zulu", Value::Int(4))]))
.unwrap();
let rows = store
.next_shared_fields(Some(1), 1, &["zulu", "alpha"])
.unwrap()
.unwrap();
assert_eq!(rows.len(), 1);
assert_eq!(rows[0].0, 3);
assert_eq!(
rows[0].1.with_projected(|values| {
values
.iter()
.map(|value| (*value).clone())
.collect::<Vec<_>>()
}),
vec![Value::Int(4), Value::Int(3)]
);
}
#[test]
fn borrowed_cursor_projects_stored_values_without_shared_row_handles() {
let mut store = MemoryDocumentStore::new();
store
.put(1, doc([("alpha", Value::Int(1)), ("zulu", Value::Int(2))]))
.unwrap();
store
.put(3, doc([("alpha", Value::Int(3)), ("zulu", Value::Int(4))]))
.unwrap();
let mut seen = Vec::new();
let visited = store
.for_each_next_fields(
Some(1),
2,
&["zulu", "missing", "alpha"],
&mut |doc_id, values| {
seen.push((
doc_id,
values.iter().map(|value| (*value).clone()).collect(),
));
true
},
)
.unwrap();
assert_eq!(visited, Some(1));
assert_eq!(
seen,
vec![(3, vec![Value::Int(4), Value::Null, Value::Int(3)])]
);
}
#[test]
fn borrowed_cursor_honors_zero_limit_and_visitor_stop() {
let mut store = MemoryDocumentStore::new();
for doc_id in 1..=3 {
store
.put(doc_id, doc([("value", Value::Int(doc_id as i64))]))
.unwrap();
}
let mut invoked = false;
let visited = store
.for_each_next_fields(None, 0, &["value"], &mut |_, _| {
invoked = true;
true
})
.unwrap();
assert_eq!(visited, Some(0));
assert!(!invoked);
let mut seen = Vec::new();
let visited = store
.for_each_next_fields(None, 3, &["value"], &mut |doc_id, values| {
seen.push((doc_id, (*values[0]).clone()));
false
})
.unwrap();
assert_eq!(visited, Some(1));
assert_eq!(seen, vec![(1, Value::Int(1))]);
}
#[test]
fn shared_snapshot_remains_isolated_after_a_write() {
let mut s = MemoryDocumentStore::new();
s.put(1, doc([("value", Value::Int(1))])).unwrap();
let snapshot = s.snapshot().unwrap();
s.patch_fields(1, &BTreeMap::from([("value".into(), Value::Int(2))]))
.unwrap();
assert_eq!(snapshot.get_field(1, "value").unwrap(), Some(Value::Int(1)));
assert_eq!(s.get_field(1, "value").unwrap(), Some(Value::Int(2)));
}
#[test]
fn snapshot_creation_does_not_scale_with_document_count() {
let allocations = [1, 1024].map(|count| {
let mut store = MemoryDocumentStore::new();
for id in 0..count {
store
.put(
id,
doc([("body", Value::Str("snapshot payload".repeat(16)))]),
)
.unwrap();
}
let mut snapshots = None;
let allocation = allocation_counter::measure(|| {
snapshots = Some((
store.snapshot().unwrap(),
store.writable_snapshot().unwrap(),
));
});
let (read, writable) = snapshots.unwrap();
store.clear().unwrap();
assert_eq!(read.len().unwrap(), count as usize);
assert_eq!(writable.len().unwrap(), count as usize);
allocation
});
assert_eq!(allocations[0].bytes_total, allocations[1].bytes_total);
assert_eq!(allocations[0].count_total, allocations[1].count_total);
}
#[test]
fn writable_snapshots_keep_rows_layouts_and_metadata_independent() {
let mut store = MemoryDocumentStore::new();
store
.put_stored(
1,
StoredDocument::with_metadata(
doc([("original", Value::Int(1))]),
DocumentMetadata::with_tuple_xmin(37),
),
)
.unwrap();
store.put(2, doc([("other", Value::Int(2))])).unwrap();
let read = store.snapshot().unwrap();
let mut writable = store.writable_snapshot().unwrap();
writable
.patch_fields(1, &doc([("original", Value::Null), ("new", Value::Int(3))]))
.unwrap();
writable.delete(2).unwrap();
store.put(1, doc([("source", Value::Int(4))])).unwrap();
assert_eq!(
read.get(1).unwrap(),
Some(doc([("original", Value::Int(1))]))
);
assert_eq!(read.get(2).unwrap(), Some(doc([("other", Value::Int(2))])));
assert_eq!(
writable.get(1).unwrap(),
Some(doc([("new", Value::Int(3))]))
);
assert_eq!(
writable.get_metadata(1).unwrap().unwrap().tuple_xmin(),
Some(37)
);
assert_eq!(
store.get(1).unwrap(),
Some(doc([("source", Value::Int(4))]))
);
assert!(store.contains_doc_id(2).unwrap());
let second = writable.snapshot().unwrap();
writable.clear().unwrap();
writable.put(1, doc([("reused", Value::Int(5))])).unwrap();
store.clear().unwrap();
assert_eq!(second.get(1).unwrap(), Some(doc([("new", Value::Int(3))])));
assert_eq!(
writable.get(1).unwrap(),
Some(doc([("reused", Value::Int(5))]))
);
assert_eq!(read.len().unwrap(), 2);
}
#[test]
fn has_value_returns_true_when_any_doc_matches() {
let mut s = MemoryDocumentStore::new();
s.put(1, doc([("color", Value::Str("red".into()))]))
.unwrap();
s.put(2, doc([("color", Value::Str("blue".into()))]))
.unwrap();
assert!(s.has_value("color", &Value::Str("red".into())).unwrap());
assert!(!s.has_value("color", &Value::Str("green".into())).unwrap());
}
#[test]
fn find_doc_id_by_field_returns_first_match() {
let mut s = MemoryDocumentStore::new();
s.put(3, doc([("public_id", Value::Str("m-3".into()))]))
.unwrap();
s.put(7, doc([("public_id", Value::Str("m-7".into()))]))
.unwrap();
assert_eq!(
s.find_doc_id_by_field("public_id", &Value::Str("m-7".into()))
.unwrap(),
Some(7)
);
assert_eq!(
s.find_doc_id_by_field("public_id", &Value::Str("missing".into()))
.unwrap(),
None
);
}
#[test]
fn patch_fields_updates_and_removes_top_level_values() {
let mut s = MemoryDocumentStore::new();
s.put(
1,
doc([
("public_id", Value::Str("m-1".into())),
("content", Value::Str("old".into())),
("token_count", Value::Int(4)),
]),
)
.unwrap();
let updates = BTreeMap::from([
("content".to_string(), Value::Str("new".into())),
("token_count".to_string(), Value::Null),
]);
assert!(s.patch_fields(1, &updates).unwrap());
let got = s.get(1).unwrap().unwrap();
assert_eq!(got.get("public_id"), Some(&Value::Str("m-1".into())));
assert_eq!(got.get("content"), Some(&Value::Str("new".into())));
assert!(!got.contains_key("token_count"));
}
#[test]
fn eval_path_walks_nested_map() {
let mut s = MemoryDocumentStore::new();
let mut nested = BTreeMap::new();
nested.insert("name".to_string(), Value::Str("alice".into()));
s.put(1, doc([("user", Value::Map(nested))])).unwrap();
let path = vec![
uqa_core::PathSegment::Key("user".into()),
uqa_core::PathSegment::Key("name".into()),
];
assert_eq!(
s.eval_path(1, &path).unwrap(),
Some(Value::Str("alice".into()))
);
}
#[test]
fn iter_all_yields_in_id_order() {
let mut s = MemoryDocumentStore::new();
s.put(3, doc([("k", Value::Int(3))])).unwrap();
s.put(1, doc([("k", Value::Int(1))])).unwrap();
s.put(2, doc([("k", Value::Int(2))])).unwrap();
let collected: Vec<u64> = s.iter_all().unwrap().map(|(id, _)| id).collect();
assert_eq!(collected, vec![1, 2, 3]);
}