use std::collections::HashMap;
use std::ops::Bound;
use crate::catalog::ValType;
use crate::value::ValueTest;
use crate::segment::{Cursor, Segment};
use crate::value::{IndexValue, order_key};
pub struct ScalarClauses<'a> {
pub filters: &'a [(usize, ValueTest)],
pub sort: Option<(usize, bool, ValType)>,
pub distinct: Option<(usize, ValType)>,
pub facets: &'a [(usize, ValType)],
pub fetch: usize,
}
impl ScalarClauses<'_> {
pub fn selects(&self) -> bool {
self.sort.is_some() || self.distinct.is_some() || !self.facets.is_empty()
}
}
pub struct ScalarHit {
pub key: Vec<u8>,
pub value: IndexValue,
pub okey: Option<Vec<u8>>,
pub dkey: Option<Vec<u8>>,
}
pub type FacetBucket = (Vec<u8>, Vec<u8>, u64);
type FacetCounts = HashMap<Vec<u8>, (Vec<u8>, u64)>;
pub struct ClausedPage {
pub hits: Vec<ScalarHit>,
pub facets: Vec<Vec<FacetBucket>>,
pub cursor: Option<Cursor>,
}
pub fn scalar_sorted_order(
a: (Option<&[u8]>, &[u8]),
b: (Option<&[u8]>, &[u8]),
desc: bool,
) -> std::cmp::Ordering {
use std::cmp::Ordering;
match (a.0, b.0) {
(Some(x), Some(y)) => {
let ord = if desc { y.cmp(x) } else { x.cmp(y) };
ord.then_with(|| a.1.cmp(b.1))
}
(Some(_), None) => Ordering::Less,
(None, Some(_)) => Ordering::Greater,
(None, None) => a.1.cmp(b.1),
}
}
impl Segment {
fn passes(&self, key: &[u8], filters: &[(usize, ValueTest)]) -> bool {
if filters.is_empty() {
return true;
}
filters
.iter()
.all(|(f, t)| self.stored(key, *f).is_some_and(|raw| t.passes(raw)))
}
fn clause_key(&self, key: &[u8], field: usize, ty: ValType) -> Option<Vec<u8>> {
self.stored(key, field).and_then(|raw| order_key(ty, raw))
}
pub fn query_claused(
&self,
min: &IndexValue,
max: &IndexValue,
cursor: Option<&Cursor>,
c: &ScalarClauses<'_>,
) -> ClausedPage {
let mut facets: Vec<FacetCounts> = vec![HashMap::new(); c.facets.len()];
let mut hits: Vec<ScalarHit> = Vec::new();
let mut groups: HashMap<Vec<u8>, usize> = HashMap::new();
let full_walk = c.sort.is_some() || !c.facets.is_empty();
for (v, k) in self.range_iter(min, max, cursor) {
if !self.passes(k, c.filters) {
continue;
}
self.count_facets(k, c, &mut facets);
if !full_walk && hits.len() == c.fetch {
break;
}
self.select_hit(v, k, c, &mut hits, &mut groups);
}
if let Some((_, desc, _)) = c.sort {
hits.sort_by(|a, b| {
scalar_sorted_order((a.okey.as_deref(), &a.key), (b.okey.as_deref(), &b.key), desc)
});
}
hits.truncate(c.fetch);
let cursor = self.filter_cursor(c, &hits);
ClausedPage { hits, facets: finish_facets(facets), cursor }
}
fn range_iter<'s>(
&'s self,
min: &IndexValue,
max: &IndexValue,
cursor: Option<&Cursor>,
) -> impl Iterator<Item = (&'s IndexValue, &'s [u8])> {
let lower: Bound<(IndexValue, Vec<u8>)> = match cursor {
Some(c) => Bound::Excluded((c.value.clone(), c.key.clone())),
None => Bound::Included((min.clone(), Vec::new())),
};
let max = max.clone();
self.tree()
.range((lower, Bound::Unbounded))
.take_while(move |(v, _)| *v <= max)
.map(|(v, k)| (v, k.as_slice()))
}
fn count_facets(
&self,
key: &[u8],
c: &ScalarClauses<'_>,
facets: &mut [FacetCounts],
) {
for ((f, ty), counts) in c.facets.iter().zip(facets.iter_mut()) {
let Some(raw) = self.stored(key, *f) else { continue };
let Some(id) = order_key(*ty, raw) else { continue };
let e = counts.entry(id).or_insert_with(|| (raw.to_vec(), 0));
e.1 += 1;
}
}
fn select_hit(
&self,
v: &IndexValue,
k: &[u8],
c: &ScalarClauses<'_>,
hits: &mut Vec<ScalarHit>,
groups: &mut HashMap<Vec<u8>, usize>,
) {
let okey = c.sort.and_then(|(f, _, ty)| self.clause_key(k, f, ty));
let dkey = c.distinct.and_then(|(f, ty)| self.clause_key(k, f, ty));
if let Some(id) = &dkey {
match groups.entry(id.clone()) {
std::collections::hash_map::Entry::Occupied(e) => {
let Some((_, desc, _)) = c.sort else { return };
let prev = &mut hits[*e.get()];
if scalar_sorted_order(
(okey.as_deref(), k),
(prev.okey.as_deref(), &prev.key),
desc,
) == std::cmp::Ordering::Less
{
*prev = ScalarHit { key: k.to_vec(), value: v.clone(), okey, dkey };
}
return;
}
std::collections::hash_map::Entry::Vacant(slot) => {
slot.insert(hits.len());
}
}
}
hits.push(ScalarHit { key: k.to_vec(), value: v.clone(), okey, dkey });
}
fn filter_cursor(&self, c: &ScalarClauses<'_>, hits: &[ScalarHit]) -> Option<Cursor> {
if c.selects() || hits.len() < c.fetch {
return None;
}
hits.last().map(|h| Cursor { value: h.value.clone(), key: h.key.clone() })
}
}
fn finish_facets(facets: Vec<FacetCounts>) -> Vec<Vec<FacetBucket>> {
facets
.into_iter()
.map(|counts| {
let mut out: Vec<FacetBucket> =
counts.into_iter().map(|(id, (label, n))| (id, label, n)).collect();
out.sort_by(|a, b| b.2.cmp(&a.2).then_with(|| a.1.cmp(&b.1)));
out
})
.collect()
}
pub fn merge_claused<T>(
mut all: Vec<(ScalarHit, T)>,
sort_desc: Option<bool>,
grouped: bool,
offset: usize,
limit: usize,
) -> Vec<(ScalarHit, T)> {
match sort_desc {
Some(desc) => all.sort_by(|(a, _), (b, _)| {
scalar_sorted_order((a.okey.as_deref(), &a.key), (b.okey.as_deref(), &b.key), desc)
}),
None => all.sort_by(|(a, _), (b, _)| (&a.value, &a.key).cmp(&(&b.value, &b.key))),
}
if grouped {
let mut seen: std::collections::HashSet<Vec<u8>> = std::collections::HashSet::new();
all.retain(|(h, _)| match &h.dkey {
Some(k) => seen.insert(k.clone()),
None => true,
});
}
if offset > 0 {
all.drain(..offset.min(all.len()));
}
all.truncate(limit);
all
}
pub fn fold_facets(into: &mut [Vec<FacetBucket>], from: Vec<Vec<FacetBucket>>) {
for (acc, part) in into.iter_mut().zip(from) {
for (id, label, n) in part {
match acc.iter_mut().find(|(k, _, _)| *k == id) {
Some(e) => e.2 += n,
None => acc.push((id, label, n)),
}
}
}
}
pub fn sort_facets(facets: &mut [Vec<FacetBucket>]) {
for f in facets.iter_mut() {
f.sort_by(|a, b| b.2.cmp(&a.2).then_with(|| a.1.cmp(&b.1)));
}
}
#[cfg(test)]
#[path = "segment_claused_tests.rs"]
mod tests;