use std::collections::HashSet;
use std::ops::Bound;
use nodedb_types::value::Value;
use super::catalog::IndexDef;
use super::document::fold_case;
use super::key;
use super::maintain::ids_under;
use super::store::IndexCatalog;
type ClassRange = (u8, Option<Vec<u8>>, Option<Vec<u8>>);
fn dedup(ids: impl Iterator<Item = String>) -> Vec<String> {
let mut seen = HashSet::new();
ids.filter(|id| seen.insert(id.clone())).collect()
}
impl IndexCatalog {
pub(crate) fn lookup_eq(&self, def: &IndexDef, probe: &Value) -> Vec<String> {
let probe = fold_case(def, probe.clone());
let prefix = def.entry_prefix();
let state = self.lock();
let mut ids = Vec::new();
for encoded in key::encode_coercible(&probe) {
let mut scan = prefix.clone();
scan.extend_from_slice(&encoded);
ids.extend(ids_under(&state, &prefix, scan).map(str::to_string));
}
dedup(ids.into_iter())
}
pub(crate) fn lookup_range(
&self,
def: &IndexDef,
lower: Option<&Value>,
upper: Option<&Value>,
) -> Option<Vec<String>> {
let classes = |bound: Option<&Value>| -> Option<Vec<Vec<u8>>> {
bound.map(|v| key::encode_coercible(&fold_case(def, v.clone())))
};
let lower_keys = classes(lower);
let upper_keys = classes(upper);
let tag_of = |k: &Vec<u8>| k.first().copied();
let mut ranges: Vec<ClassRange> = Vec::new();
match (&lower_keys, &upper_keys) {
(Some(lo), Some(hi)) => {
for l in lo {
if let Some(h) = hi.iter().find(|h| tag_of(h) == tag_of(l))
&& let Some(tag) = tag_of(l)
{
ranges.push((tag, Some(l.clone()), Some(h.clone())));
}
}
}
(Some(lo), None) => {
for l in lo {
if let Some(tag) = tag_of(l) {
ranges.push((tag, Some(l.clone()), None));
}
}
}
(None, Some(hi)) => {
for h in hi {
if let Some(tag) = tag_of(h) {
ranges.push((tag, None, Some(h.clone())));
}
}
}
(None, None) => return None,
}
if ranges.is_empty() {
return None;
}
let prefix = def.entry_prefix();
let state = self.lock();
let mut ids = Vec::new();
for (tag, lo, hi) in ranges {
let with_prefix = |bytes: &[u8]| {
let mut k = prefix.clone();
k.extend_from_slice(bytes);
k
};
let start = with_prefix(&lo.unwrap_or_else(|| vec![tag]));
let end = match hi {
Some(h) => {
let mut k = with_prefix(&h);
k.push(0xFF);
Bound::Included(k)
}
None => Bound::Excluded(with_prefix(&[tag + 1])),
};
let crossed = match &end {
Bound::Included(e) => start > *e,
Bound::Excluded(e) => start >= *e,
Bound::Unbounded => false,
};
if crossed {
continue;
}
let start = Bound::Included(start);
ids.extend(
state
.entries
.range::<Vec<u8>, _>((start, end))
.filter_map(|k| key::entry_doc_id(k, &prefix))
.map(str::to_string),
);
}
Some(dedup(ids.into_iter()))
}
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::sync::Arc;
use super::*;
use crate::index::catalog::{IndexEngine, canonical_field};
fn catalog_with(values: &[(&str, Value)]) -> (IndexCatalog, Arc<IndexDef>) {
let (path, is_array) = canonical_field("n");
let def = Arc::new(IndexDef {
name: "idx_n".into(),
collection: "c".into(),
path,
unique: false,
case_insensitive: false,
is_array,
predicate: None,
engine: IndexEngine::Document,
});
let rows = values.iter().map(|(id, v)| {
(
(*id).to_string(),
Value::Object(HashMap::from([("n".to_string(), v.clone())])),
)
});
let catalog = IndexCatalog::new();
catalog.install(Arc::clone(&def), rows).expect("install");
(catalog, def)
}
#[test]
fn a_numeric_range_follows_numeric_order() {
let (catalog, def) = catalog_with(&[
("a", Value::Integer(100)),
("b", Value::Integer(2)),
("c", Value::Integer(10)),
("d", Value::Float(5.5)),
]);
let ids = catalog
.lookup_range(&def, Some(&Value::Integer(3)), Some(&Value::Integer(50)))
.expect("shared class");
assert_eq!(ids, vec!["d", "c"]);
let open = catalog
.lookup_range(&def, Some(&Value::Integer(10)), None)
.expect("class");
assert_eq!(open, vec!["c", "a"]);
let crossed = catalog
.lookup_range(&def, Some(&Value::Integer(50)), Some(&Value::Integer(3)))
.expect("shared class");
assert!(crossed.is_empty());
}
#[test]
fn an_equality_probe_finds_coercible_values() {
let (catalog, def) = catalog_with(&[
("a", Value::String("7".into())),
("b", Value::Integer(7)),
("c", Value::Integer(8)),
]);
let mut ids = catalog.lookup_eq(&def, &Value::Integer(7));
ids.sort();
assert_eq!(ids, vec!["a", "b"]);
}
}