#[derive(Debug, Clone)]
pub enum Condition {
Pk(Vec<u8>),
BitmapEq { column_id: u16, value: Vec<u8> },
BitmapIn {
column_id: u16,
values: Vec<Vec<u8>>,
},
Ann {
column_id: u16,
query: Vec<f32>,
k: usize,
},
FmContains { column_id: u16, pattern: Vec<u8> },
FmContainsAll {
column_id: u16,
patterns: Vec<Vec<u8>>,
},
Range { column_id: u16, lo: i64, hi: i64 },
RangeF64 {
column_id: u16,
lo: f64,
lo_inclusive: bool,
hi: f64,
hi_inclusive: bool,
},
SparseMatch {
column_id: u16,
query: Vec<(u32, f32)>,
k: usize,
},
MinHashSimilar {
column_id: u16,
query: Vec<u64>,
k: usize,
},
IsNull { column_id: u16 },
IsNotNull { column_id: u16 },
}
#[derive(Debug, Default, Clone)]
pub struct Query {
pub conditions: Vec<Condition>,
}
impl Query {
pub fn new() -> Self {
Self::default()
}
pub fn and(mut self, c: Condition) -> Self {
self.conditions.push(c);
self
}
pub fn pk(key: Vec<u8>) -> Self {
Self::new().and(Condition::Pk(key))
}
}
pub fn canonical_query_key(
conditions: &[Condition],
projection: Option<&[u16]>,
epoch: u64,
) -> u64 {
let fold = |seed: u64, b: u64| -> u64 { seed.wrapping_mul(0x9E3779B97F4A7C15).wrapping_add(b) };
let mut acc = fold(0xA5A5_A5A5_A5A5_A5A5, epoch);
let mut digests: Vec<u64> = conditions.iter().map(hash_condition).collect();
digests.sort_unstable();
let n = digests.len() as u64;
acc = fold(acc, n);
for d in digests {
acc = fold(acc, d);
}
match projection {
Some(p) => {
let mut p = p.to_vec();
p.sort_unstable();
p.dedup();
acc = fold(acc, 0x5E);
acc = fold(acc, p.len() as u64);
for id in p {
acc = fold(acc, id as u64);
}
}
None => {
acc = fold(acc, 0xA5);
}
}
acc
}
fn hash_condition(c: &Condition) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
match c {
Condition::Pk(k) => {
0u8.hash(&mut h);
k.hash(&mut h);
}
Condition::BitmapEq { column_id, value } => {
1u8.hash(&mut h);
column_id.hash(&mut h);
value.hash(&mut h);
}
Condition::BitmapIn { column_id, values } => {
2u8.hash(&mut h);
column_id.hash(&mut h);
let mut v: Vec<&Vec<u8>> = values.iter().collect();
v.sort();
v.dedup();
v.len().hash(&mut h);
for b in v {
b.hash(&mut h);
}
}
Condition::Ann {
column_id,
query,
k,
} => {
3u8.hash(&mut h);
column_id.hash(&mut h);
k.hash(&mut h);
for f in query {
f.to_bits().hash(&mut h);
}
}
Condition::FmContains { column_id, pattern } => {
4u8.hash(&mut h);
column_id.hash(&mut h);
pattern.hash(&mut h);
}
Condition::FmContainsAll {
column_id,
patterns,
} => {
10u8.hash(&mut h);
column_id.hash(&mut h);
let mut sorted: Vec<&[u8]> = patterns.iter().map(|p| p.as_slice()).collect();
sorted.sort();
sorted.len().hash(&mut h);
for p in sorted {
p.hash(&mut h);
}
}
Condition::Range { column_id, lo, hi } => {
5u8.hash(&mut h);
column_id.hash(&mut h);
lo.hash(&mut h);
hi.hash(&mut h);
}
Condition::RangeF64 {
column_id,
lo,
lo_inclusive,
hi,
hi_inclusive,
} => {
6u8.hash(&mut h);
column_id.hash(&mut h);
lo.to_bits().hash(&mut h);
lo_inclusive.hash(&mut h);
hi.to_bits().hash(&mut h);
hi_inclusive.hash(&mut h);
}
Condition::SparseMatch {
column_id,
query,
k,
} => {
7u8.hash(&mut h);
column_id.hash(&mut h);
k.hash(&mut h);
let mut q: Vec<(u32, u32)> = query.iter().map(|(t, w)| (*t, w.to_bits())).collect();
q.sort_by_key(|(t, _)| *t);
for (t, wb) in q {
t.hash(&mut h);
wb.hash(&mut h);
}
}
Condition::MinHashSimilar {
column_id,
query,
k,
} => {
10u8.hash(&mut h);
column_id.hash(&mut h);
k.hash(&mut h);
let mut q = query.clone();
q.sort_unstable();
for t in q {
t.hash(&mut h);
}
}
Condition::IsNull { column_id } => {
8u8.hash(&mut h);
column_id.hash(&mut h);
}
Condition::IsNotNull { column_id } => {
9u8.hash(&mut h);
column_id.hash(&mut h);
}
}
h.finish()
}
pub fn condition_columns(conditions: &[Condition]) -> Vec<u16> {
let mut cols: Vec<u16> = conditions
.iter()
.filter_map(|c| match c {
Condition::Pk(_) => None,
Condition::BitmapEq { column_id, .. }
| Condition::BitmapIn { column_id, .. }
| Condition::Ann { column_id, .. }
| Condition::FmContains { column_id, .. }
| Condition::FmContainsAll { column_id, .. }
| Condition::Range { column_id, .. }
| Condition::RangeF64 { column_id, .. }
| Condition::SparseMatch { column_id, .. }
| Condition::MinHashSimilar { column_id, .. }
| Condition::IsNull { column_id }
| Condition::IsNotNull { column_id } => Some(*column_id),
})
.collect();
cols.sort_unstable();
cols.dedup();
cols
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builder_chains() {
let q = Query::pk(b"k".to_vec()).and(Condition::Range {
column_id: 1,
lo: 0,
hi: 10,
});
assert_eq!(q.conditions.len(), 2);
}
#[test]
fn canonical_key_is_order_independent() {
let e = 7u64;
let a = Query::new()
.and(Condition::Range {
column_id: 1,
lo: 0,
hi: 10,
})
.and(Condition::BitmapEq {
column_id: 2,
value: b"x".to_vec(),
});
let b = Query::new()
.and(Condition::BitmapEq {
column_id: 2,
value: b"x".to_vec(),
})
.and(Condition::Range {
column_id: 1,
lo: 0,
hi: 10,
});
assert_eq!(
canonical_query_key(&a.conditions, None, e),
canonical_query_key(&b.conditions, None, e),
"condition order must not affect the key"
);
let ordered = Condition::BitmapIn {
column_id: 3,
values: vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()],
};
let shuffled = Condition::BitmapIn {
column_id: 3,
values: vec![b"c".to_vec(), b"a".to_vec(), b"a".to_vec(), b"b".to_vec()],
};
assert_eq!(
canonical_query_key(std::slice::from_ref(&ordered), None, e),
canonical_query_key(&[shuffled], None, e),
"BitmapIn values must dedup+sort"
);
assert_ne!(
canonical_query_key(&a.conditions, None, e),
canonical_query_key(&a.conditions, None, e + 1),
"epoch must fold into the key"
);
let proj = vec![1u16, 2];
assert_ne!(
canonical_query_key(&a.conditions, None, e),
canonical_query_key(&a.conditions, Some(&proj), e),
"None projection must differ from an explicit projection"
);
let proj_rev = vec![2u16, 1];
assert_eq!(
canonical_query_key(&a.conditions, Some(&proj), e),
canonical_query_key(&a.conditions, Some(&proj_rev), e),
);
}
}