1#[cfg(feature = "native")]
11use crate::dsl::Schema;
12#[cfg(feature = "native")]
13use crate::error::Result;
14#[cfg(feature = "native")]
15use std::sync::Arc;
16
17mod searcher;
18pub use searcher::Searcher;
19
20#[cfg(feature = "native")]
21mod primary_key;
22#[cfg(feature = "native")]
23mod reader;
24#[cfg(feature = "native")]
25mod vector_builder;
26#[cfg(all(feature = "wasm", not(feature = "native")))]
27mod wasm_writer;
28#[cfg(feature = "native")]
29mod writer;
30#[cfg(feature = "native")]
31pub use primary_key::PrimaryKeyIndex;
32#[cfg(feature = "native")]
33pub use reader::IndexReader;
34#[cfg(all(feature = "wasm", not(feature = "native")))]
35pub use wasm_writer::IndexWriter as WasmIndexWriter;
36#[cfg(feature = "native")]
37pub use writer::{IndexWriter, PreparedCommit};
38
39mod metadata;
40pub use metadata::{FieldVectorMeta, INDEX_META_FILENAME, IndexMetadata, VectorIndexState};
41
42#[cfg(feature = "native")]
43mod helpers;
44#[cfg(feature = "native")]
45pub use helpers::{
46 IndexingStats, SchemaConfig, SchemaFieldConfig, create_index_at_path, create_index_from_sdl,
47 index_documents_from_reader, index_json_document, parse_schema,
48};
49
50pub const SLICE_CACHE_FILENAME: &str = "index.slicecache";
52
53#[derive(Debug, Clone)]
55pub struct IndexConfig {
56 pub num_threads: usize,
58 pub num_indexing_threads: usize,
60 pub num_compression_threads: usize,
62 pub term_cache_blocks: usize,
64 pub store_cache_blocks: usize,
66 pub max_indexing_memory_bytes: usize,
68 pub merge_policy: Box<dyn crate::merge::MergePolicy>,
70 pub optimization: crate::structures::IndexOptimization,
72 pub reload_interval_ms: u64,
74 pub max_concurrent_merges: usize,
76 pub merge_bp_time_budget: Option<std::time::Duration>,
83 pub bp_memory_budget_bytes: usize,
90}
91
92impl Default for IndexConfig {
93 fn default() -> Self {
94 #[cfg(feature = "native")]
95 let indexing_threads = crate::default_indexing_threads();
96 #[cfg(not(feature = "native"))]
97 let indexing_threads = 1;
98
99 #[cfg(feature = "native")]
100 let compression_threads = crate::default_compression_threads();
101 #[cfg(not(feature = "native"))]
102 let compression_threads = 1;
103
104 Self {
105 num_threads: indexing_threads,
106 num_indexing_threads: 1, num_compression_threads: compression_threads,
108 term_cache_blocks: 256,
109 store_cache_blocks: 32,
110 max_indexing_memory_bytes: 256 * 1024 * 1024, merge_policy: Box::new(crate::merge::TieredMergePolicy::large_scale()),
116 optimization: crate::structures::IndexOptimization::default(),
117 reload_interval_ms: 1000, max_concurrent_merges: 4,
119 merge_bp_time_budget: Some(std::time::Duration::from_secs(600)),
120 #[cfg(target_pointer_width = "64")]
128 bp_memory_budget_bytes: 8 * 1024 * 1024 * 1024,
129 #[cfg(not(target_pointer_width = "64"))]
130 bp_memory_budget_bytes: usize::MAX,
131 }
132 }
133}
134
135#[cfg(feature = "native")]
144pub struct Index<D: crate::directories::DirectoryWriter + 'static> {
145 directory: Arc<D>,
146 schema: Arc<Schema>,
147 config: IndexConfig,
148 segment_manager: Arc<crate::merge::SegmentManager<D>>,
150 cached_reader: tokio::sync::OnceCell<IndexReader<D>>,
152}
153
154#[cfg(feature = "native")]
155impl<D: crate::directories::DirectoryWriter + 'static> Index<D> {
156 pub async fn create(directory: D, schema: Schema, config: IndexConfig) -> Result<Self> {
158 let directory = Arc::new(directory);
159 let schema = Arc::new(schema);
160 directory.set_index_label(schema.index_label());
162 let metadata = IndexMetadata::new((*schema).clone());
163
164 let segment_manager = Arc::new(crate::merge::SegmentManager::new(
165 Arc::clone(&directory),
166 Arc::clone(&schema),
167 metadata,
168 config.merge_policy.clone_box(),
169 config.term_cache_blocks,
170 config.max_concurrent_merges,
171 config.merge_bp_time_budget,
172 config.bp_memory_budget_bytes,
173 ));
174
175 segment_manager.update_metadata(|_| {}).await?;
177
178 Ok(Self {
179 directory,
180 schema,
181 config,
182 segment_manager,
183 cached_reader: tokio::sync::OnceCell::new(),
184 })
185 }
186
187 pub async fn open(directory: D, config: IndexConfig) -> Result<Self> {
189 let directory = Arc::new(directory);
190
191 let metadata = IndexMetadata::load(directory.as_ref()).await?;
193 let schema = Arc::new(metadata.schema.clone());
194 directory.set_index_label(schema.index_label());
196
197 let segment_manager = Arc::new(crate::merge::SegmentManager::new(
198 Arc::clone(&directory),
199 Arc::clone(&schema),
200 metadata,
201 config.merge_policy.clone_box(),
202 config.term_cache_blocks,
203 config.max_concurrent_merges,
204 config.merge_bp_time_budget,
205 config.bp_memory_budget_bytes,
206 ));
207
208 segment_manager.load_and_publish_trained().await;
210
211 Ok(Self {
212 directory,
213 schema,
214 config,
215 segment_manager,
216 cached_reader: tokio::sync::OnceCell::new(),
217 })
218 }
219
220 pub fn schema(&self) -> &Schema {
222 &self.schema
223 }
224
225 pub fn schema_arc(&self) -> &Arc<Schema> {
227 &self.schema
228 }
229
230 pub fn directory(&self) -> &D {
232 &self.directory
233 }
234
235 pub fn segment_manager(&self) -> &Arc<crate::merge::SegmentManager<D>> {
237 &self.segment_manager
238 }
239
240 pub async fn reader(&self) -> Result<&IndexReader<D>> {
245 self.cached_reader
246 .get_or_try_init(|| async {
247 IndexReader::from_segment_manager(
248 Arc::clone(&self.schema),
249 Arc::clone(&self.segment_manager),
250 self.config.term_cache_blocks,
251 self.config.reload_interval_ms,
252 )
253 .await
254 })
255 .await
256 }
257
258 pub fn config(&self) -> &IndexConfig {
260 &self.config
261 }
262
263 pub async fn segment_readers(&self) -> Result<Vec<Arc<crate::segment::SegmentReader>>> {
265 let reader = self.reader().await?;
266 let searcher = reader.searcher().await?;
267 Ok(searcher.segment_readers().to_vec())
268 }
269
270 pub async fn num_docs(&self) -> Result<u32> {
272 let reader = self.reader().await?;
273 let searcher = reader.searcher().await?;
274 Ok(searcher.num_docs())
275 }
276
277 pub fn default_fields(&self) -> Vec<crate::Field> {
279 if !self.schema.default_fields().is_empty() {
280 self.schema.default_fields().to_vec()
281 } else {
282 self.schema
283 .fields()
284 .filter(|(_, entry)| {
285 entry.indexed && entry.field_type == crate::dsl::FieldType::Text
286 })
287 .map(|(field, _)| field)
288 .collect()
289 }
290 }
291
292 pub fn tokenizers(&self) -> Arc<crate::tokenizer::TokenizerRegistry> {
294 Arc::new(crate::tokenizer::TokenizerRegistry::default())
295 }
296
297 pub fn query_parser(&self) -> crate::dsl::QueryLanguageParser {
299 let default_fields = self.default_fields();
300 let tokenizers = self.tokenizers();
301
302 let query_routers = self.schema.query_routers();
303 if !query_routers.is_empty()
304 && let Ok(router) = crate::dsl::QueryFieldRouter::from_rules(query_routers)
305 {
306 return crate::dsl::QueryLanguageParser::with_router(
307 Arc::clone(&self.schema),
308 default_fields,
309 tokenizers,
310 router,
311 );
312 }
313
314 crate::dsl::QueryLanguageParser::new(Arc::clone(&self.schema), default_fields, tokenizers)
315 }
316
317 pub async fn query(
319 &self,
320 query_str: &str,
321 limit: usize,
322 ) -> Result<crate::query::SearchResponse> {
323 self.query_offset(query_str, limit, 0).await
324 }
325
326 pub async fn query_offset(
328 &self,
329 query_str: &str,
330 limit: usize,
331 offset: usize,
332 ) -> Result<crate::query::SearchResponse> {
333 let parser = self.query_parser();
334 let query = parser
335 .parse(query_str)
336 .map_err(crate::error::Error::Query)?;
337 self.search_offset(query.as_ref(), limit, offset).await
338 }
339
340 pub async fn search(
342 &self,
343 query: &dyn crate::query::Query,
344 limit: usize,
345 ) -> Result<crate::query::SearchResponse> {
346 self.search_offset(query, limit, 0).await
347 }
348
349 pub async fn search_offset(
351 &self,
352 query: &dyn crate::query::Query,
353 limit: usize,
354 offset: usize,
355 ) -> Result<crate::query::SearchResponse> {
356 let reader = self.reader().await?;
357 let searcher = reader.searcher().await?;
358
359 #[cfg(feature = "sync")]
360 let (results, total_seen) = {
361 let runtime_flavor = tokio::runtime::Handle::current().runtime_flavor();
365 if runtime_flavor == tokio::runtime::RuntimeFlavor::MultiThread {
366 tokio::task::block_in_place(|| {
367 searcher.search_with_offset_and_count_sync(query, limit, offset)
368 })?
369 } else {
370 searcher.search_with_offset_and_count_sync(query, limit, offset)?
371 }
372 };
373
374 #[cfg(not(feature = "sync"))]
375 let (results, total_seen) = {
376 searcher
377 .search_with_offset_and_count(query, limit, offset)
378 .await?
379 };
380
381 let total_hits = total_seen;
382 let hits: Vec<crate::query::SearchHit> = results
383 .into_iter()
384 .map(|result| crate::query::SearchHit {
385 address: crate::query::DocAddress::new(result.segment_id, result.doc_id),
386 score: result.score,
387 matched_fields: result.extract_ordinals(),
388 })
389 .collect();
390
391 Ok(crate::query::SearchResponse { hits, total_hits })
392 }
393
394 pub async fn get_document(
396 &self,
397 address: &crate::query::DocAddress,
398 ) -> Result<Option<crate::dsl::Document>> {
399 let reader = self.reader().await?;
400 let searcher = reader.searcher().await?;
401 searcher.get_document(address).await
402 }
403
404 pub async fn get_postings(
406 &self,
407 field: crate::Field,
408 term: &[u8],
409 ) -> Result<
410 Vec<(
411 Arc<crate::segment::SegmentReader>,
412 crate::structures::BlockPostingList,
413 )>,
414 > {
415 let segments = self.segment_readers().await?;
416 let mut results = Vec::new();
417
418 for segment in segments {
419 if let Some(postings) = segment.get_postings(field, term).await? {
420 results.push((segment, postings));
421 }
422 }
423
424 Ok(results)
425 }
426}
427
428#[cfg(feature = "native")]
430impl<D: crate::directories::DirectoryWriter + 'static> Index<D> {
431 pub fn writer(&self) -> writer::IndexWriter<D> {
433 writer::IndexWriter::from_index(self)
434 }
435}
436
437#[cfg(test)]
438mod tests;
439
440