use crate::segment::Segment;
use crate::value::IndexValue;
pub use crate::view_sidecar::{MAX_VIEWS, ViewCatalog};
#[derive(Debug, Clone, PartialEq)]
pub struct Leaf {
pub index: Vec<u8>,
pub min: IndexValue,
pub max: IndexValue,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Tree {
Leaf(Leaf),
And(Box<Tree>, Box<Tree>),
Or(Box<Tree>, Box<Tree>),
Diff(Box<Tree>, Box<Tree>),
}
impl Tree {
pub fn leaves(&self) -> usize {
match self {
Tree::Leaf(_) => 1,
Tree::And(a, b) | Tree::Or(a, b) | Tree::Diff(a, b) => a.leaves() + b.leaves(),
}
}
pub fn depth(&self) -> usize {
match self {
Tree::Leaf(_) => 1,
Tree::And(a, b) | Tree::Or(a, b) | Tree::Diff(a, b) => 1 + a.depth().max(b.depth()),
}
}
pub fn each_leaf<F: FnMut(&Leaf)>(&self, f: &mut F) {
match self {
Tree::Leaf(l) => f(l),
Tree::And(a, b) | Tree::Or(a, b) | Tree::Diff(a, b) => {
a.each_leaf(f);
b.each_leaf(f);
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ViewMode {
Virtual,
Materialized {
top_k: u32,
},
}
#[derive(Debug, Clone, PartialEq)]
pub struct ViewSpec {
pub name: Vec<u8>,
pub tree: Tree,
pub order_by: Vec<u8>,
pub desc: bool,
pub mode: ViewMode,
pub via: Option<Vec<u8>>,
}
pub const MAX_TREE_DEPTH: usize = 3;
pub const MAX_TREE_LEAVES: usize = 4;
impl ViewSpec {
pub fn validate(&self) -> Result<(), &'static str> {
if self.tree.depth() > MAX_TREE_DEPTH {
return Err("ERR view tree deeper than 3");
}
if self.tree.leaves() > MAX_TREE_LEAVES {
return Err("ERR view tree has more than 4 leaves");
}
Ok(())
}
}
pub fn eval_tree<'a>(
tree: &Tree,
seg: &impl Fn(&[u8]) -> Option<&'a Segment>,
) -> Vec<Vec<u8>> {
match tree {
Tree::Leaf(l) => match seg(&l.index) {
Some(s) => {
let (hits, _) = s.range(&l.min, &l.max, None, usize::MAX);
hits.into_iter().map(|(k, _)| k).collect()
}
None => Vec::new(),
},
Tree::And(a, b) => {
let (xa, xb) = (eval_tree(a, seg), eval_tree(b, seg));
let (mut drive, probe) = if xa.len() <= xb.len() { (xa, xb) } else { (xb, xa) };
let set: std::collections::HashSet<&[u8]> =
probe.iter().map(Vec::as_slice).collect();
drive.retain(|k| set.contains(k.as_slice()));
drive
}
Tree::Or(a, b) => {
let mut xa = eval_tree(a, seg);
xa.extend(eval_tree(b, seg));
xa.sort();
xa.dedup();
xa
}
Tree::Diff(a, b) => {
let mut xa = eval_tree(a, seg);
let xb = eval_tree(b, seg);
let set: std::collections::HashSet<&[u8]> = xb.iter().map(Vec::as_slice).collect();
xa.retain(|k| !set.contains(k.as_slice()));
xa
}
}
}
pub fn key_in_tree<'a>(
tree: &Tree,
key: &[u8],
seg: &impl Fn(&[u8]) -> Option<&'a Segment>,
) -> bool {
match tree {
Tree::Leaf(l) => seg(&l.index)
.and_then(|s| s.verify_entry(key))
.is_some_and(|v| *v >= l.min && *v <= l.max),
Tree::And(a, b) => key_in_tree(a, key, seg) && key_in_tree(b, key, seg),
Tree::Or(a, b) => key_in_tree(a, key, seg) || key_in_tree(b, key, seg),
Tree::Diff(a, b) => key_in_tree(a, key, seg) && !key_in_tree(b, key, seg),
}
}
pub fn key_in_tree_vals(
tree: &Tree,
vals: &impl Fn(&[u8]) -> Option<IndexValue>,
) -> bool {
match tree {
Tree::Leaf(l) => vals(&l.index).is_some_and(|v| v >= l.min && v <= l.max),
Tree::And(a, b) => key_in_tree_vals(a, vals) && key_in_tree_vals(b, vals),
Tree::Or(a, b) => key_in_tree_vals(a, vals) || key_in_tree_vals(b, vals),
Tree::Diff(a, b) => key_in_tree_vals(a, vals) && !key_in_tree_vals(b, vals),
}
}
#[derive(Debug, Default)]
pub struct MaterializedSet {
set: std::collections::BTreeSet<(IndexValue, Vec<u8>)>,
back: std::collections::HashMap<Vec<u8>, IndexValue>,
top_k: u32,
desc: bool,
pub order_excluded: u64,
}
impl MaterializedSet {
pub fn new(top_k: u32, desc: bool) -> Self {
Self { top_k, desc, ..Default::default() }
}
fn cap(&self) -> usize {
if self.top_k == 0 {
usize::MAX
} else {
(self.top_k + self.top_k / 4) as usize
}
}
pub fn apply(&mut self, key: &[u8], member: bool, order: Option<IndexValue>) -> bool {
if self.top_k != 0
&& member
&& !self.back.contains_key(key)
&& self.set.len() >= self.cap()
&& let Some(v) = &order
{
let enters = if self.desc {
self.set.iter().next().is_some_and(|(worst, _)| v > worst)
} else {
self.set.iter().next_back().is_some_and(|(worst, _)| v < worst)
};
if !enters {
return false;
}
}
if let Some(old) = self.back.remove(key) {
self.set.remove(&(old, key.to_vec()));
}
match (member, order) {
(true, Some(v)) => {
self.back.insert(key.to_vec(), v.clone());
self.set.insert((v, key.to_vec()));
self.evict_past_cap();
false
}
(true, None) => {
self.order_excluded += 1;
false
}
_ => {
self.top_k != 0 && self.set.len() < self.top_k as usize
}
}
}
fn evict_past_cap(&mut self) {
if self.set.len() > self.cap() {
let worst = if self.desc {
self.set.iter().next().cloned()
} else {
self.set.iter().next_back().cloned()
};
if let Some(w) = worst {
self.set.remove(&w);
self.back.remove(&w.1);
}
}
}
pub fn page(
&self,
after: Option<&(IndexValue, Vec<u8>)>,
limit: usize,
desc: bool,
) -> Vec<(IndexValue, Vec<u8>)> {
if desc {
let iter: Box<dyn Iterator<Item = &(IndexValue, Vec<u8>)>> = match after {
Some(c) => Box::new(
self.set
.range((std::ops::Bound::Unbounded, std::ops::Bound::Excluded(c.clone())))
.rev(),
),
None => Box::new(self.set.iter().rev()),
};
return iter.take(limit).cloned().collect();
}
let iter: Box<dyn Iterator<Item = &(IndexValue, Vec<u8>)>> = match after {
Some(c) => Box::new(self.set.range((
std::ops::Bound::Excluded(c.clone()),
std::ops::Bound::Unbounded,
))),
None => Box::new(self.set.iter()),
};
iter.take(limit).cloned().collect()
}
pub fn len(&self) -> usize {
self.set.len()
}
pub fn is_empty(&self) -> bool {
self.set.is_empty()
}
pub fn clear(&mut self) {
self.set.clear();
self.back.clear();
}
pub fn approx_bytes(&self) -> u64 {
self.set
.iter()
.map(|(v, k)| (v.approx_bytes() + k.len() + 48) as u64)
.sum()
}
}
#[cfg(test)]
#[path = "view_tests.rs"]
mod tests;