1pub(crate) mod ann_build;
9mod ann_disk;
10pub use ann_disk::AnnHealth;
11pub(crate) mod bmp_adaptive;
12pub(crate) mod bmp_grid;
13#[cfg(any(feature = "native", feature = "wasm"))]
14mod builder;
15pub mod chunk_map;
16pub(crate) mod format;
17#[cfg(feature = "native")]
18mod merger;
19pub(crate) mod reader;
20#[cfg(feature = "native")]
21pub(crate) mod reorder;
22mod store;
23#[cfg(feature = "native")]
24mod tracker;
25mod types;
26mod vector_data;
27
28#[cfg(test)]
29pub(crate) use builder::graph_bisection::{
30 build_forward_index_from_blocks, build_forward_index_from_bmps, graph_bisection,
31};
32#[cfg(any(feature = "native", feature = "wasm"))]
33pub(crate) use builder::validate_vector_value_counts;
34#[cfg(any(feature = "native", feature = "wasm"))]
35pub use builder::{
36 BpBudget, MemoryBreakdown, SegmentBuilder, SegmentBuilderConfig, SegmentBuilderStats,
37};
38#[cfg(feature = "native")]
39pub(crate) use merger::block_in_place_if_multithread;
40#[cfg(feature = "native")]
41pub use merger::{MergeStats, SegmentMerger, delete_segment};
42pub(crate) use reader::BmpIndex;
43pub(crate) use reader::bmp::BMP_SUPERBLOCK_SIZE;
44pub(crate) use reader::combine_ordinal_results;
45#[cfg(feature = "native")]
46pub mod pin;
47pub use reader::{
48 BmpDimStats, DensePlanCache, SegmentReader, SparseIndex, VectorIndex, VectorSearchResult,
49};
50pub use store::*;
51#[cfg(feature = "native")]
52pub use tracker::{PublishedIndexGeneration, SegmentSnapshot, SegmentTracker};
53pub use types::{
54 FieldStats, ScannTrainedArtifactBytes, SegmentFiles, SegmentId, SegmentMeta,
55 TrainedVectorStructures,
56};
57pub use vector_data::{FlatVectorData, LazyFlatVectorData, dequantize_raw};
58
59#[cfg(any(feature = "native", feature = "wasm"))]
64pub(crate) struct OffsetWriter {
65 inner: Box<dyn crate::directories::StreamingWriter>,
66 offset: u64,
67}
68
69#[cfg(any(feature = "native", feature = "wasm"))]
70impl OffsetWriter {
71 pub(crate) fn new(inner: Box<dyn crate::directories::StreamingWriter>) -> Self {
72 Self { inner, offset: 0 }
73 }
74
75 pub(crate) fn offset(&self) -> u64 {
77 self.offset
78 }
79
80 pub(crate) fn finish(self) -> std::io::Result<()> {
82 self.inner.finish()
83 }
84
85 #[cfg(feature = "native")]
88 pub(crate) fn copy_from_file_range(
89 &mut self,
90 source: &std::fs::File,
91 source_offset: &mut u64,
92 len: usize,
93 ) -> std::io::Result<usize> {
94 let copied = self
95 .inner
96 .copy_from_file_range(source, source_offset, len)?;
97 self.offset = self
98 .offset
99 .checked_add(copied as u64)
100 .ok_or_else(|| std::io::Error::other("offset-writer byte count overflow"))?;
101 Ok(copied)
102 }
103}
104
105#[cfg(any(feature = "native", feature = "wasm"))]
106impl std::io::Write for OffsetWriter {
107 fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
108 let n = self.inner.write(buf)?;
109 self.offset += n as u64;
110 Ok(n)
111 }
112
113 fn flush(&mut self) -> std::io::Result<()> {
114 self.inner.flush()
115 }
116}
117
118#[cfg(test)]
119#[cfg(feature = "native")]
120mod tests {
121 use super::*;
122 use crate::directories::RamDirectory;
123 use crate::dsl::SchemaBuilder;
124 use std::sync::Arc;
125
126 #[tokio::test]
127 async fn test_async_segment_reader() {
128 let mut schema_builder = SchemaBuilder::default();
129 let title = schema_builder.add_text_field("title", true, true);
130 let schema = Arc::new(schema_builder.build());
131
132 let dir = RamDirectory::new();
133 let segment_id = SegmentId::new();
134
135 let config = SegmentBuilderConfig::default();
137 let mut builder = SegmentBuilder::new(Arc::clone(&schema), config).unwrap();
138
139 let mut doc = crate::dsl::Document::new();
140 doc.add_text(title, "Hello World");
141 builder.add_document(doc).unwrap();
142
143 let mut doc = crate::dsl::Document::new();
144 doc.add_text(title, "Goodbye World");
145 builder.add_document(doc).unwrap();
146
147 builder.build(&dir, segment_id, None).await.unwrap();
148
149 let reader = SegmentReader::open(&dir, segment_id, schema.clone(), 16)
151 .await
152 .unwrap();
153
154 assert_eq!(reader.num_docs(), 2);
155
156 let postings = reader.get_postings(title, b"hello").await.unwrap();
158 assert!(postings.is_some());
159 assert_eq!(postings.unwrap().doc_count(), 1);
160
161 let postings = reader.get_postings(title, b"world").await.unwrap();
162 assert!(postings.is_some());
163 assert_eq!(postings.unwrap().doc_count(), 2);
164
165 let doc = reader.doc(0).await.unwrap().unwrap();
167 assert_eq!(doc.get_first(title).unwrap().as_text(), Some("Hello World"));
168 }
169
170 #[tokio::test]
171 async fn test_dense_vector_ordinal_tracking() {
172 use crate::query::MultiValueCombiner;
173
174 let mut schema_builder = SchemaBuilder::default();
175 let embedding = schema_builder.add_dense_vector_field("embedding", 4, true, true);
177 let schema = Arc::new(schema_builder.build());
178
179 let dir = RamDirectory::new();
180 let segment_id = SegmentId::new();
181
182 let config = SegmentBuilderConfig::default();
183 let mut builder = SegmentBuilder::new(Arc::clone(&schema), config).unwrap();
184
185 let mut doc = crate::dsl::Document::new();
187 doc.add_dense_vector(embedding, vec![1.0, 0.0, 0.0, 0.0]);
188 builder.add_document(doc).unwrap();
189
190 let mut doc = crate::dsl::Document::new();
192 doc.add_dense_vector(embedding, vec![0.0, 1.0, 0.0, 0.0]);
193 doc.add_dense_vector(embedding, vec![0.0, 0.0, 1.0, 0.0]);
194 builder.add_document(doc).unwrap();
195
196 let mut doc = crate::dsl::Document::new();
198 doc.add_dense_vector(embedding, vec![0.0, 0.0, 0.0, 1.0]);
199 builder.add_document(doc).unwrap();
200
201 builder.build(&dir, segment_id, None).await.unwrap();
202
203 let reader = SegmentReader::open(&dir, segment_id, schema.clone(), 16)
204 .await
205 .unwrap();
206
207 let query = vec![0.0, 0.9, 0.1, 0.0];
209 let results = reader
210 .search_dense_vector(embedding, &query, 10, 0, 1.0, MultiValueCombiner::Max)
211 .await
212 .unwrap();
213
214 let doc1_result = results.iter().find(|r| r.doc_id == 1);
216 assert!(doc1_result.is_some(), "Doc 1 should be in results");
217
218 let doc1 = doc1_result.unwrap();
219 assert!(
221 doc1.ordinals.len() <= 2,
222 "Doc 1 should have at most 2 ordinals, got {}",
223 doc1.ordinals.len()
224 );
225
226 for (ordinal, _score) in &doc1.ordinals {
228 assert!(*ordinal <= 1, "Ordinal should be 0 or 1, got {}", ordinal);
229 }
230 }
231
232 #[tokio::test]
233 async fn test_sparse_vector_ordinal_tracking() {
234 use crate::query::MultiValueCombiner;
235
236 let mut schema_builder = SchemaBuilder::default();
237 let sparse = schema_builder.add_sparse_vector_field("sparse", true, true);
238 let schema = Arc::new(schema_builder.build());
239
240 let dir = RamDirectory::new();
241 let segment_id = SegmentId::new();
242
243 let config = SegmentBuilderConfig::default();
244 let mut builder = SegmentBuilder::new(Arc::clone(&schema), config).unwrap();
245
246 let mut doc = crate::dsl::Document::new();
248 doc.add_sparse_vector(sparse, vec![(0, 1.0), (1, 0.5)]);
249 builder.add_document(doc).unwrap();
250
251 let mut doc = crate::dsl::Document::new();
253 doc.add_sparse_vector(sparse, vec![(0, 0.8), (2, 0.3)]);
254 doc.add_sparse_vector(sparse, vec![(1, 0.9), (3, 0.4)]);
255 builder.add_document(doc).unwrap();
256
257 let mut doc = crate::dsl::Document::new();
259 doc.add_sparse_vector(sparse, vec![(2, 1.0), (3, 0.5)]);
260 builder.add_document(doc).unwrap();
261
262 builder.build(&dir, segment_id, None).await.unwrap();
263
264 let reader = SegmentReader::open(&dir, segment_id, schema.clone(), 16)
265 .await
266 .unwrap();
267
268 let query = crate::query::SparseVectorQuery::new(sparse, vec![(0, 1.0)])
270 .with_combiner(MultiValueCombiner::Sum);
271 let mut collector = crate::query::TopKCollector::new(10);
272 crate::query::collect_segment(&reader, &query, &mut collector)
273 .await
274 .unwrap();
275 let top_docs = collector.into_sorted_results();
276
277 assert!(top_docs.len() >= 2, "Should have at least 2 results");
279 }
280}