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