use crate::query::{Query, QueryItem};
use super::SegmentIndex;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Access {
Index,
Scan,
}
pub fn estimate_matches<I: SegmentIndex>(index: &I, query: &Query, width: u64) -> u64 {
match query {
Query::All => width,
Query::Items(items) => {
let mut total: u64 = 0;
for item in items {
total = total.saturating_add(estimate_item(index, item, width));
if total >= width {
return width; }
}
total.min(width)
}
}
}
pub fn estimate_item<I: SegmentIndex>(index: &I, item: &QueryItem, width: u64) -> u64 {
if item.tags.is_empty() {
return width;
}
let mut smallest = u64::MAX;
for tag in item.tags.iter() {
match index.term_len(tag.as_str()) {
None => return 0,
Some(len) => smallest = smallest.min(u64::from(len)),
}
}
smallest.min(width)
}
pub fn choose(estimate: u64, width: u64, scan_bias: u32) -> Access {
if estimate.saturating_mul(u64::from(scan_bias)) <= width {
Access::Index
} else {
Access::Scan
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Position;
use crate::event::{Event, EventType, Tag, Tags};
use crate::index::ActiveTail;
use smallvec::SmallVec;
fn ty(s: &str) -> EventType {
EventType::new(s).unwrap()
}
fn tags(items: &[&str]) -> Tags {
Tags::new(
items
.iter()
.map(|s| Tag::new(*s).unwrap())
.collect::<SmallVec<[Tag; 4]>>(),
)
.unwrap()
}
fn event(type_str: &str, tag_strs: &[&str]) -> Event {
Event::new(&ty(type_str), &tags(tag_strs), b"").unwrap()
}
fn fixture() -> ActiveTail {
let events = [
event("Registered", &[]),
event("Enrolled", &["course:c1"]),
event("Enrolled", &["course:c1", "student:s1"]),
event("Renamed", &["student:s1"]),
event("Registered", &["course:c1"]),
];
let index = ActiveTail::new(Position::new(1));
for (i, ev) in events.iter().enumerate() {
index
.push(Position::new(1 + i as u64), ev.as_ref())
.unwrap();
}
index
}
const WIDTH: u64 = 5;
fn estimate(query: &Query) -> u64 {
estimate_matches(&fixture().view_full(), query, WIDTH)
}
#[test]
fn single_tag_estimates_its_posting_length() {
let q = Query::item(QueryItem::with_tags(tags(&["course:c1"])));
assert_eq!(estimate(&q), 3);
let q = Query::item(QueryItem::with_tags(tags(&["student:s1"])));
assert_eq!(estimate(&q), 2);
}
#[test]
fn tag_and_estimates_the_shortest_list() {
let q = Query::item(QueryItem::with_tags(tags(&["course:c1", "student:s1"])));
assert_eq!(estimate(&q), 2);
}
#[test]
fn absent_tag_makes_the_item_empty() {
let q = Query::item(QueryItem::with_tags(tags(&["ghost:x"])));
assert_eq!(estimate(&q), 0);
let q = Query::item(QueryItem::with_tags(tags(&["course:c1", "ghost:x"])));
assert_eq!(estimate(&q), 0);
}
#[test]
fn type_only_and_empty_item_are_broad() {
let q = Query::item(QueryItem::of_types(vec![ty("Registered")]));
assert_eq!(estimate(&q), WIDTH);
let q = Query::item(QueryItem::default());
assert_eq!(estimate(&q), WIDTH);
}
#[test]
fn all_is_full_width_and_empty_items_is_zero() {
assert_eq!(estimate(&Query::all()), WIDTH);
assert_eq!(estimate(&Query::items(Vec::new())), 0);
}
#[test]
fn or_sums_items_and_caps_at_width() {
let q = Query::items(vec![
QueryItem::with_tags(tags(&["course:c1"])),
QueryItem::with_tags(tags(&["student:s1"])),
]);
assert_eq!(estimate(&q), WIDTH);
let q = Query::items(vec![QueryItem::with_tags(tags(&["student:s1"]))]);
assert_eq!(estimate(&q), 2);
}
#[test]
fn choose_biases_toward_scanning_at_the_margin() {
assert_eq!(choose(3, 5, 1), Access::Index);
assert_eq!(choose(5, 5, 1), Access::Index);
assert_eq!(choose(6, 5, 1), Access::Scan);
assert_eq!(choose(2, 8, 4), Access::Index); assert_eq!(choose(3, 8, 4), Access::Scan); assert_eq!(choose(1, 5, u32::MAX), Access::Scan);
assert_eq!(choose(0, 5, u32::MAX), Access::Index);
}
}