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, Clone)]
85pub enum Retriever {
86 Ann {
87 column_id: u16,
88 query: Vec<f32>,
89 k: usize,
90 },
91 Sparse {
92 column_id: u16,
93 query: Vec<(u32, f32)>,
94 k: usize,
95 },
96 MinHash {
97 column_id: u16,
98 members: Vec<SetMember>,
99 k: usize,
100 },
101}
102
103impl Retriever {
104 pub fn column_id(&self) -> u16 {
105 match self {
106 Self::Ann { column_id, .. }
107 | Self::Sparse { column_id, .. }
108 | Self::MinHash { column_id, .. } => *column_id,
109 }
110 }
111}
112
113pub fn encode_sparse_vector(terms: &[(u32, f32)]) -> crate::Result<Vec<u8>> {
114 Ok(bincode::serialize(terms)?)
115}
116
117#[derive(Debug, Clone, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
118#[serde(untagged)]
119pub enum SetMember {
120 String(String),
121 Number(serde_json::Number),
122 Boolean(bool),
123}
124
125impl SetMember {
126 pub fn hash_v1(&self) -> u64 {
127 let value = match self {
128 Self::String(value) => serde_json::Value::String(value.clone()),
129 Self::Number(value) => serde_json::Value::Number(value.clone()),
130 Self::Boolean(value) => serde_json::Value::Bool(*value),
131 };
132 crate::index::minhash_member_hash_v1(&value).expect("SetMember is always scalar")
133 }
134}
135
136#[derive(Debug, Clone, Copy, PartialEq)]
137pub enum RetrieverScore {
138 AnnHammingDistance(u32),
139 SparseDotProduct(f32),
140 MinHashEstimatedJaccard(f32),
141}
142
143#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum VectorMetric {
145 Cosine,
146 DotProduct,
147 Euclidean,
148}
149
150#[derive(Debug, Clone)]
151pub struct AnnRerankRequest {
152 pub column_id: u16,
153 pub query: Vec<f32>,
154 pub candidate_k: usize,
155 pub limit: usize,
156 pub metric: VectorMetric,
157}
158
159#[derive(Debug, Clone, Copy, PartialEq)]
160pub struct AnnRerankHit {
161 pub row_id: crate::RowId,
162 pub hamming_distance: u32,
163 pub exact_score: f32,
164}
165
166#[derive(Debug, Clone, PartialEq)]
167pub struct RetrieverHit {
168 pub row_id: crate::RowId,
169 pub rank: usize,
171 pub score: RetrieverScore,
172}
173
174#[derive(Debug, Clone)]
175pub struct SetSimilarityRequest {
176 pub column_id: u16,
177 pub members: Vec<SetMember>,
178 pub candidate_k: usize,
179 pub min_jaccard: f32,
180 pub limit: usize,
181}
182
183#[derive(Debug, Clone, PartialEq)]
184pub struct SetSimilarityHit {
185 pub row_id: crate::RowId,
186 pub estimated_jaccard: f32,
187 pub exact_jaccard: f32,
188}
189
190#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
191pub struct SetSimilarityTrace {
192 pub candidate_generation_us: u64,
193 pub gather_us: u64,
194 pub parse_us: u64,
195 pub score_us: u64,
196 pub candidate_count: usize,
197 pub verified_count: usize,
198}
199
200#[derive(Debug, Clone)]
201pub struct SearchRequest {
202 pub must: Vec<Condition>,
203 pub retrievers: Vec<NamedRetriever>,
204 pub fusion: Fusion,
205 pub limit: usize,
206 pub projection: Option<Vec<u16>>,
207}
208
209#[derive(Debug, Clone)]
210pub struct NamedRetriever {
211 pub name: String,
212 pub weight: f64,
213 pub retriever: Retriever,
214}
215
216#[derive(Debug, Clone, Copy)]
217pub enum Fusion {
218 ReciprocalRank { constant: u32 },
219}
220
221#[derive(Debug, Clone, PartialEq)]
222pub struct ComponentScore {
223 pub retriever_name: String,
224 pub rank: usize,
225 pub raw_score: RetrieverScore,
226 pub contribution: f64,
227}
228
229#[derive(Debug, Clone, PartialEq)]
230pub struct SearchHit {
231 pub row_id: crate::RowId,
232 pub cells: Vec<(u16, crate::Value)>,
233 pub components: Vec<ComponentScore>,
234 pub fused_score: f64,
235}
236
237#[derive(Debug, Default, Clone)]
239pub struct Query {
240 pub conditions: Vec<Condition>,
241}
242
243impl Query {
244 pub fn new() -> Self {
245 Self::default()
246 }
247 pub fn and(mut self, c: Condition) -> Self {
248 self.conditions.push(c);
249 self
250 }
251 pub fn pk(key: Vec<u8>) -> Self {
252 Self::new().and(Condition::Pk(key))
253 }
254}
255
256pub fn canonical_query_key(
265 conditions: &[Condition],
266 projection: Option<&[u16]>,
267 epoch: u64,
268) -> u64 {
269 canonical_query_key_with_version(QUERY_CACHE_KEY_VERSION, conditions, projection, epoch)
270}
271
272const QUERY_CACHE_KEY_VERSION: u64 = 2;
273
274fn canonical_query_key_with_version(
275 version: u64,
276 conditions: &[Condition],
277 projection: Option<&[u16]>,
278 epoch: u64,
279) -> u64 {
280 let fold = |seed: u64, b: u64| -> u64 { seed.wrapping_mul(0x9E3779B97F4A7C15).wrapping_add(b) };
281 let mut acc = fold(0xA5A5_A5A5_A5A5_A5A5, version);
282 acc = fold(acc, epoch);
283 let mut digests: Vec<u64> = conditions.iter().map(hash_condition).collect();
285 digests.sort_unstable();
286 let n = digests.len() as u64;
287 acc = fold(acc, n);
288 for d in digests {
289 acc = fold(acc, d);
290 }
291 match projection {
294 Some(p) => {
295 let mut p = p.to_vec();
296 p.sort_unstable();
297 p.dedup();
298 acc = fold(acc, 0x5E);
299 acc = fold(acc, p.len() as u64);
300 for id in p {
301 acc = fold(acc, id as u64);
302 }
303 }
304 None => {
305 acc = fold(acc, 0xA5);
306 }
307 }
308 acc
309}
310
311fn hash_condition(c: &Condition) -> u64 {
315 use std::hash::{Hash, Hasher};
316 let mut h = std::collections::hash_map::DefaultHasher::new();
317 match c {
318 Condition::Pk(k) => {
319 0u8.hash(&mut h);
320 k.hash(&mut h);
321 }
322 Condition::BitmapEq { column_id, value } => {
323 1u8.hash(&mut h);
324 column_id.hash(&mut h);
325 value.hash(&mut h);
326 }
327 Condition::BitmapIn { column_id, values } => {
328 2u8.hash(&mut h);
329 column_id.hash(&mut h);
330 let mut v: Vec<&Vec<u8>> = values.iter().collect();
331 v.sort();
332 v.dedup();
333 v.len().hash(&mut h);
334 for b in v {
335 b.hash(&mut h);
336 }
337 }
338 Condition::Ann {
339 column_id,
340 query,
341 k,
342 } => {
343 3u8.hash(&mut h);
344 column_id.hash(&mut h);
345 k.hash(&mut h);
346 for f in query {
347 f.to_bits().hash(&mut h);
348 }
349 }
350 Condition::FmContains { column_id, pattern } => {
351 4u8.hash(&mut h);
352 column_id.hash(&mut h);
353 pattern.hash(&mut h);
354 }
355 Condition::FmContainsAll {
356 column_id,
357 patterns,
358 } => {
359 5u8.hash(&mut h);
360 column_id.hash(&mut h);
361 let mut sorted: Vec<&[u8]> = patterns.iter().map(|p| p.as_slice()).collect();
362 sorted.sort();
363 sorted.dedup();
364 sorted.len().hash(&mut h);
365 for p in sorted {
366 p.hash(&mut h);
367 }
368 }
369 Condition::Range { column_id, lo, hi } => {
370 6u8.hash(&mut h);
371 column_id.hash(&mut h);
372 lo.hash(&mut h);
373 hi.hash(&mut h);
374 }
375 Condition::RangeF64 {
376 column_id,
377 lo,
378 lo_inclusive,
379 hi,
380 hi_inclusive,
381 } => {
382 7u8.hash(&mut h);
383 column_id.hash(&mut h);
384 lo.to_bits().hash(&mut h);
385 lo_inclusive.hash(&mut h);
386 hi.to_bits().hash(&mut h);
387 hi_inclusive.hash(&mut h);
388 }
389 Condition::SparseMatch {
390 column_id,
391 query,
392 k,
393 } => {
394 8u8.hash(&mut h);
395 column_id.hash(&mut h);
396 k.hash(&mut h);
397 let mut q: Vec<(u32, u32)> = query.iter().map(|(t, w)| (*t, w.to_bits())).collect();
398 q.sort_by_key(|(t, _)| *t);
399 for (t, wb) in q {
400 t.hash(&mut h);
401 wb.hash(&mut h);
402 }
403 }
404 Condition::MinHashSimilar {
405 column_id,
406 query,
407 k,
408 } => {
409 9u8.hash(&mut h);
410 column_id.hash(&mut h);
411 k.hash(&mut h);
412 let mut q = query.clone();
413 q.sort_unstable();
414 q.dedup();
415 for t in q {
416 t.hash(&mut h);
417 }
418 }
419 Condition::IsNull { column_id } => {
420 10u8.hash(&mut h);
421 column_id.hash(&mut h);
422 }
423 Condition::IsNotNull { column_id } => {
424 11u8.hash(&mut h);
425 column_id.hash(&mut h);
426 }
427 Condition::BytesPrefix { column_id, prefix } => {
428 12u8.hash(&mut h);
429 column_id.hash(&mut h);
430 prefix.hash(&mut h);
431 }
432 }
433 h.finish()
434}
435
436pub fn condition_columns(conditions: &[Condition]) -> Vec<u16> {
441 let mut cols: Vec<u16> = conditions
442 .iter()
443 .filter_map(|c| match c {
444 Condition::Pk(_) => None,
445 Condition::BitmapEq { column_id, .. }
446 | Condition::BitmapIn { column_id, .. }
447 | Condition::BytesPrefix { column_id, .. }
448 | Condition::Ann { column_id, .. }
449 | Condition::FmContains { column_id, .. }
450 | Condition::FmContainsAll { column_id, .. }
451 | Condition::Range { column_id, .. }
452 | Condition::RangeF64 { column_id, .. }
453 | Condition::SparseMatch { column_id, .. }
454 | Condition::MinHashSimilar { column_id, .. }
455 | Condition::IsNull { column_id }
456 | Condition::IsNotNull { column_id } => Some(*column_id),
457 })
458 .collect();
459 cols.sort_unstable();
460 cols.dedup();
461 cols
462}
463
464#[cfg(test)]
465mod tests {
466 use super::*;
467
468 #[test]
469 fn builder_chains() {
470 let q = Query::pk(b"k".to_vec()).and(Condition::Range {
471 column_id: 1,
472 lo: 0,
473 hi: 10,
474 });
475 assert_eq!(q.conditions.len(), 2);
476 }
477
478 #[test]
482 fn canonical_key_is_order_independent() {
483 let e = 7u64;
484 let a = Query::new()
485 .and(Condition::Range {
486 column_id: 1,
487 lo: 0,
488 hi: 10,
489 })
490 .and(Condition::BitmapEq {
491 column_id: 2,
492 value: b"x".to_vec(),
493 });
494 let b = Query::new()
495 .and(Condition::BitmapEq {
496 column_id: 2,
497 value: b"x".to_vec(),
498 })
499 .and(Condition::Range {
500 column_id: 1,
501 lo: 0,
502 hi: 10,
503 });
504 assert_eq!(
505 canonical_query_key(&a.conditions, None, e),
506 canonical_query_key(&b.conditions, None, e),
507 "condition order must not affect the key"
508 );
509
510 let minhash = Condition::MinHashSimilar {
511 column_id: 4,
512 query: vec![3, 1, 1, 2],
513 k: 5,
514 };
515 let minhash_canonical = Condition::MinHashSimilar {
516 column_id: 4,
517 query: vec![1, 2, 3],
518 k: 5,
519 };
520 assert_eq!(
521 canonical_query_key(std::slice::from_ref(&minhash), None, e),
522 canonical_query_key(&[minhash_canonical], None, e),
523 );
524
525 let fm = Condition::FmContainsAll {
526 column_id: 4,
527 patterns: vec![b"b".to_vec(), b"a".to_vec(), b"a".to_vec()],
528 };
529 let fm_canonical = Condition::FmContainsAll {
530 column_id: 4,
531 patterns: vec![b"a".to_vec(), b"b".to_vec()],
532 };
533 assert_eq!(
534 canonical_query_key(std::slice::from_ref(&fm), None, e),
535 canonical_query_key(&[fm_canonical], None, e),
536 );
537 assert_ne!(
538 canonical_query_key(std::slice::from_ref(&fm), None, e),
539 canonical_query_key(std::slice::from_ref(&minhash), None, e),
540 );
541 assert_ne!(
542 canonical_query_key_with_version(1, &a.conditions, None, e),
543 canonical_query_key_with_version(2, &a.conditions, None, e),
544 );
545
546 let ordered = Condition::BitmapIn {
548 column_id: 3,
549 values: vec![b"a".to_vec(), b"b".to_vec(), b"c".to_vec()],
550 };
551 let shuffled = Condition::BitmapIn {
552 column_id: 3,
553 values: vec![b"c".to_vec(), b"a".to_vec(), b"a".to_vec(), b"b".to_vec()],
554 };
555 assert_eq!(
556 canonical_query_key(std::slice::from_ref(&ordered), None, e),
557 canonical_query_key(&[shuffled], None, e),
558 "BitmapIn values must dedup+sort"
559 );
560
561 assert_ne!(
563 canonical_query_key(&a.conditions, None, e),
564 canonical_query_key(&a.conditions, None, e + 1),
565 "epoch must fold into the key"
566 );
567
568 let proj = vec![1u16, 2];
570 assert_ne!(
571 canonical_query_key(&a.conditions, None, e),
572 canonical_query_key(&a.conditions, Some(&proj), e),
573 "None projection must differ from an explicit projection"
574 );
575 let proj_rev = vec![2u16, 1];
577 assert_eq!(
578 canonical_query_key(&a.conditions, Some(&proj), e),
579 canonical_query_key(&a.conditions, Some(&proj_rev), e),
580 );
581 }
582}