Skip to main content

velesdb_core/collection/search/
sparse.rs

1//! Public sparse and hybrid dense+sparse search methods for Collection.
2//!
3//! These methods provide a simpler API than the VelesQL-based internal
4//! `execute_sparse_search` / `execute_hybrid_search_with_strategy` methods,
5//! accepting raw `SparseVector` directly instead of VelesQL AST nodes.
6//! Designed for SDK wiring (Python, TypeScript, Mobile).
7
8use crate::collection::types::Collection;
9use crate::error::{Error, Result};
10use crate::fusion::FusionStrategy;
11use crate::point::SearchResult;
12use crate::sparse_index::{search::sparse_search, SparseVector, DEFAULT_SPARSE_INDEX_NAME};
13
14impl Collection {
15    /// Sparse-only search on the default sparse index.
16    ///
17    /// # Errors
18    ///
19    /// Returns an error if the default sparse index does not exist.
20    pub fn sparse_search_default(
21        &self,
22        query: &SparseVector,
23        k: usize,
24    ) -> Result<Vec<SearchResult>> {
25        self.sparse_search_named(query, k, DEFAULT_SPARSE_INDEX_NAME)
26    }
27
28    /// Sparse-only search on a named sparse index.
29    ///
30    /// # Errors
31    ///
32    /// Returns an error if the named sparse index does not exist.
33    pub fn sparse_search_named(
34        &self,
35        query: &SparseVector,
36        k: usize,
37        index_name: &str,
38    ) -> Result<Vec<SearchResult>> {
39        let indexes = self.sparse_indexes.read();
40        let index = indexes.get(index_name).ok_or_else(|| {
41            Error::Config(format!(
42                "Sparse index '{}' not found",
43                if index_name.is_empty() {
44                    "<default>"
45                } else {
46                    index_name
47                }
48            ))
49        })?;
50        let results = sparse_search(index, query, k);
51        // Explicit drop: `resolve_sparse_results` acquires the payload_storage read-lock,
52        // which is ordered after sparse_indexes in the Collection lock hierarchy.
53        // Releasing sparse_indexes here before entering resolve_sparse_results prevents
54        // a potential lock-ordering violation if the call path ever reacquires sparse_indexes.
55        drop(indexes);
56        Ok(self.resolve_sparse_results(&results, k))
57    }
58
59    /// Hybrid dense+sparse search with RRF fusion on the default sparse index.
60    ///
61    /// Runs both dense (HNSW) and sparse branches, then fuses using the
62    /// provided strategy (typically RRF with k=60).
63    ///
64    /// # Errors
65    ///
66    /// Returns an error if the sparse index does not exist or fusion fails.
67    pub fn hybrid_sparse_search(
68        &self,
69        dense_vector: &[f32],
70        sparse_query: &SparseVector,
71        k: usize,
72        strategy: &FusionStrategy,
73    ) -> Result<Vec<SearchResult>> {
74        let candidate_k = k.saturating_mul(2).max(k + 10);
75
76        let (dense_results, sparse_results) = self.execute_both_branches(
77            dense_vector,
78            sparse_query,
79            DEFAULT_SPARSE_INDEX_NAME,
80            candidate_k,
81            None,
82        );
83
84        if dense_results.is_empty() && sparse_results.is_empty() {
85            return Ok(Vec::new());
86        }
87        if dense_results.is_empty() {
88            let scored: Vec<(u64, f32)> = sparse_results
89                .iter()
90                .map(|sd| (sd.doc_id, sd.score))
91                .collect();
92            return Ok(self.resolve_fused_results(&scored, k));
93        }
94        if sparse_results.is_empty() {
95            return Ok(self.resolve_fused_results(&dense_results, k));
96        }
97
98        let sparse_tuples: Vec<(u64, f32)> = sparse_results
99            .iter()
100            .map(|sd| (sd.doc_id, sd.score))
101            .collect();
102
103        let fused = strategy
104            .fuse(vec![dense_results, sparse_tuples])
105            .map_err(|e| Error::Config(format!("Fusion error: {e}")))?;
106
107        Ok(self.resolve_fused_results(&fused, k))
108    }
109}