1#[derive(Debug, Clone)]
11pub enum Condition {
12 Pk(Vec<u8>),
14 BitmapEq { column_id: u16, value: Vec<u8> },
16 BitmapIn {
21 column_id: u16,
22 values: Vec<Vec<u8>>,
23 },
24 BytesPrefix { column_id: u16, prefix: Vec<u8> },
29 Ann {
31 column_id: u16,
32 query: Vec<f32>,
33 k: usize,
34 },
35 FmContains { column_id: u16, pattern: Vec<u8> },
37 FmContainsAll {
42 column_id: u16,
43 patterns: Vec<Vec<u8>>,
44 },
45 Range { column_id: u16, lo: i64, hi: i64 },
49 RangeF64 {
52 column_id: u16,
53 lo: f64,
54 lo_inclusive: bool,
55 hi: f64,
56 hi_inclusive: bool,
57 },
58 SparseMatch {
61 column_id: u16,
62 query: Vec<(u32, f32)>,
63 k: usize,
64 },
65 MinHashSimilar {
69 column_id: u16,
70 query: Vec<u64>,
71 k: usize,
72 },
73 IsNull { column_id: u16 },
77 IsNotNull { column_id: u16 },
80}
81
82#[derive(Debug, Default, Clone)]
84pub struct Query {
85 pub conditions: Vec<Condition>,
86}
87
88impl Query {
89 pub fn new() -> Self {
90 Self::default()
91 }
92 pub fn and(mut self, c: Condition) -> Self {
93 self.conditions.push(c);
94 self
95 }
96 pub fn pk(key: Vec<u8>) -> Self {
97 Self::new().and(Condition::Pk(key))
98 }
99}
100
101pub fn canonical_query_key(
110 conditions: &[Condition],
111 projection: Option<&[u16]>,
112 epoch: u64,
113) -> u64 {
114 let fold = |seed: u64, b: u64| -> u64 { seed.wrapping_mul(0x9E3779B97F4A7C15).wrapping_add(b) };
115 let mut acc = fold(0xA5A5_A5A5_A5A5_A5A5, epoch);
116 let mut digests: Vec<u64> = conditions.iter().map(hash_condition).collect();
118 digests.sort_unstable();
119 let n = digests.len() as u64;
120 acc = fold(acc, n);
121 for d in digests {
122 acc = fold(acc, d);
123 }
124 match projection {
127 Some(p) => {
128 let mut p = p.to_vec();
129 p.sort_unstable();
130 p.dedup();
131 acc = fold(acc, 0x5E);
132 acc = fold(acc, p.len() as u64);
133 for id in p {
134 acc = fold(acc, id as u64);
135 }
136 }
137 None => {
138 acc = fold(acc, 0xA5);
139 }
140 }
141 acc
142}
143
144fn hash_condition(c: &Condition) -> u64 {
148 use std::hash::{Hash, Hasher};
149 let mut h = std::collections::hash_map::DefaultHasher::new();
150 match c {
151 Condition::Pk(k) => {
152 0u8.hash(&mut h);
153 k.hash(&mut h);
154 }
155 Condition::BitmapEq { column_id, value } => {
156 1u8.hash(&mut h);
157 column_id.hash(&mut h);
158 value.hash(&mut h);
159 }
160 Condition::BitmapIn { column_id, values } => {
161 2u8.hash(&mut h);
162 column_id.hash(&mut h);
163 let mut v: Vec<&Vec<u8>> = values.iter().collect();
164 v.sort();
165 v.dedup();
166 v.len().hash(&mut h);
167 for b in v {
168 b.hash(&mut h);
169 }
170 }
171 Condition::Ann {
172 column_id,
173 query,
174 k,
175 } => {
176 3u8.hash(&mut h);
177 column_id.hash(&mut h);
178 k.hash(&mut h);
179 for f in query {
180 f.to_bits().hash(&mut h);
181 }
182 }
183 Condition::FmContains { column_id, pattern } => {
184 4u8.hash(&mut h);
185 column_id.hash(&mut h);
186 pattern.hash(&mut h);
187 }
188 Condition::FmContainsAll {
189 column_id,
190 patterns,
191 } => {
192 10u8.hash(&mut h);
193 column_id.hash(&mut h);
194 let mut sorted: Vec<&[u8]> = patterns.iter().map(|p| p.as_slice()).collect();
195 sorted.sort();
196 sorted.len().hash(&mut h);
197 for p in sorted {
198 p.hash(&mut h);
199 }
200 }
201 Condition::Range { column_id, lo, hi } => {
202 5u8.hash(&mut h);
203 column_id.hash(&mut h);
204 lo.hash(&mut h);
205 hi.hash(&mut h);
206 }
207 Condition::RangeF64 {
208 column_id,
209 lo,
210 lo_inclusive,
211 hi,
212 hi_inclusive,
213 } => {
214 6u8.hash(&mut h);
215 column_id.hash(&mut h);
216 lo.to_bits().hash(&mut h);
217 lo_inclusive.hash(&mut h);
218 hi.to_bits().hash(&mut h);
219 hi_inclusive.hash(&mut h);
220 }
221 Condition::SparseMatch {
222 column_id,
223 query,
224 k,
225 } => {
226 7u8.hash(&mut h);
227 column_id.hash(&mut h);
228 k.hash(&mut h);
229 let mut q: Vec<(u32, u32)> = query.iter().map(|(t, w)| (*t, w.to_bits())).collect();
230 q.sort_by_key(|(t, _)| *t);
231 for (t, wb) in q {
232 t.hash(&mut h);
233 wb.hash(&mut h);
234 }
235 }
236 Condition::MinHashSimilar {
237 column_id,
238 query,
239 k,
240 } => {
241 10u8.hash(&mut h);
242 column_id.hash(&mut h);
243 k.hash(&mut h);
244 let mut q = query.clone();
245 q.sort_unstable();
246 for t in q {
247 t.hash(&mut h);
248 }
249 }
250 Condition::IsNull { column_id } => {
251 8u8.hash(&mut h);
252 column_id.hash(&mut h);
253 }
254 Condition::IsNotNull { column_id } => {
255 9u8.hash(&mut h);
256 column_id.hash(&mut h);
257 }
258 Condition::BytesPrefix { column_id, prefix } => {
259 11u8.hash(&mut h);
260 column_id.hash(&mut h);
261 prefix.hash(&mut h);
262 }
263 }
264 h.finish()
265}
266
267pub fn condition_columns(conditions: &[Condition]) -> Vec<u16> {
272 let mut cols: Vec<u16> = conditions
273 .iter()
274 .filter_map(|c| match c {
275 Condition::Pk(_) => None,
276 Condition::BitmapEq { column_id, .. }
277 | Condition::BitmapIn { column_id, .. }
278 | Condition::BytesPrefix { column_id, .. }
279 | Condition::Ann { column_id, .. }
280 | Condition::FmContains { column_id, .. }
281 | Condition::FmContainsAll { column_id, .. }
282 | Condition::Range { column_id, .. }
283 | Condition::RangeF64 { column_id, .. }
284 | Condition::SparseMatch { column_id, .. }
285 | Condition::MinHashSimilar { column_id, .. }
286 | Condition::IsNull { column_id }
287 | Condition::IsNotNull { column_id } => Some(*column_id),
288 })
289 .collect();
290 cols.sort_unstable();
291 cols.dedup();
292 cols
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
300 fn builder_chains() {
301 let q = Query::pk(b"k".to_vec()).and(Condition::Range {
302 column_id: 1,
303 lo: 0,
304 hi: 10,
305 });
306 assert_eq!(q.conditions.len(), 2);
307 }
308
309 #[test]
313 fn canonical_key_is_order_independent() {
314 let e = 7u64;
315 let a = Query::new()
316 .and(Condition::Range {
317 column_id: 1,
318 lo: 0,
319 hi: 10,
320 })
321 .and(Condition::BitmapEq {
322 column_id: 2,
323 value: b"x".to_vec(),
324 });
325 let b = Query::new()
326 .and(Condition::BitmapEq {
327 column_id: 2,
328 value: b"x".to_vec(),
329 })
330 .and(Condition::Range {
331 column_id: 1,
332 lo: 0,
333 hi: 10,
334 });
335 assert_eq!(
336 canonical_query_key(&a.conditions, None, e),
337 canonical_query_key(&b.conditions, None, e),
338 "condition order must not affect the key"
339 );
340
341 let ordered = Condition::BitmapIn {
343 column_id: 3,
344 values: vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()],
345 };
346 let shuffled = Condition::BitmapIn {
347 column_id: 3,
348 values: vec![b"c".to_vec(), b"a".to_vec(), b"a".to_vec(), b"b".to_vec()],
349 };
350 assert_eq!(
351 canonical_query_key(std::slice::from_ref(&ordered), None, e),
352 canonical_query_key(&[shuffled], None, e),
353 "BitmapIn values must dedup+sort"
354 );
355
356 assert_ne!(
358 canonical_query_key(&a.conditions, None, e),
359 canonical_query_key(&a.conditions, None, e + 1),
360 "epoch must fold into the key"
361 );
362
363 let proj = vec![1u16, 2];
365 assert_ne!(
366 canonical_query_key(&a.conditions, None, e),
367 canonical_query_key(&a.conditions, Some(&proj), e),
368 "None projection must differ from an explicit projection"
369 );
370 let proj_rev = vec![2u16, 1];
372 assert_eq!(
373 canonical_query_key(&a.conditions, Some(&proj), e),
374 canonical_query_key(&a.conditions, Some(&proj_rev), e),
375 );
376 }
377}