1#[cfg(feature = "native")]
11use crate::dsl::Schema;
12#[cfg(feature = "native")]
13use crate::error::Result;
14#[cfg(feature = "sync")]
15use std::collections::HashMap;
16#[cfg(feature = "native")]
17use std::sync::Arc;
18#[cfg(feature = "native")]
19use std::sync::{OnceLock, Weak};
20
21mod searcher;
22pub use searcher::Searcher;
23
24#[cfg(feature = "native")]
25mod primary_key;
26#[cfg(feature = "native")]
27mod reader;
28#[cfg(feature = "native")]
29mod vector_builder;
30#[cfg(all(feature = "wasm", not(feature = "native")))]
31mod wasm_writer;
32#[cfg(feature = "native")]
33mod writer;
34#[cfg(feature = "native")]
35pub use primary_key::PrimaryKeyIndex;
36#[cfg(feature = "native")]
37pub use reader::IndexReader;
38#[cfg(feature = "native")]
39pub use vector_builder::{AlterVectorIndexOutcome, AlterVectorIndexState};
40#[cfg(all(feature = "wasm", not(feature = "native")))]
41pub use wasm_writer::IndexWriter as WasmIndexWriter;
42#[cfg(feature = "native")]
43pub use writer::{IndexWriter, PreparedCommit, WRITER_LOCK_FILENAME};
44
45mod metadata;
46pub use metadata::{
47 FieldVectorMeta, INDEX_META_FILENAME, IndexMetadata, SegmentMetaInfo, VectorIndexState,
48};
49
50#[cfg(feature = "native")]
51mod helpers;
52#[cfg(feature = "native")]
53pub use helpers::{
54 IndexingStats, SchemaConfig, SchemaFieldConfig, create_index_at_path, create_index_from_sdl,
55 index_documents_from_reader, index_json_document, parse_schema,
56};
57
58pub const SLICE_CACHE_FILENAME: &str = "index.slicecache";
60
61#[cfg(feature = "native")]
65pub const MAX_CONCURRENT_REORDER_PASSES: usize = 2;
66
67#[cfg(feature = "native")]
68#[derive(Clone, Copy, Debug, Eq, PartialEq)]
69pub(crate) enum ReorderPriority {
70 Optimizer,
75 AutomaticMerge,
77 Foreground,
78}
79
80#[cfg(feature = "native")]
86#[derive(Debug)]
87pub struct ReorderConcurrencyGate {
88 permits: Arc<tokio::sync::Semaphore>,
89 optimizer_permits: Arc<tokio::sync::Semaphore>,
94 automatic_merge_permits: Arc<tokio::sync::Semaphore>,
98 limit: usize,
99 foreground_lock: Arc<tokio::sync::Mutex<()>>,
100 foreground_active: std::sync::atomic::AtomicBool,
101 foreground_finished: tokio::sync::Notify,
102}
103
104#[cfg(feature = "native")]
110#[derive(Debug)]
111pub(crate) struct BmpIoGate {
112 limit: usize,
113 active: parking_lot::Mutex<usize>,
114 available: parking_lot::Condvar,
115 async_available: tokio::sync::Notify,
116}
117
118#[cfg(feature = "native")]
119impl BmpIoGate {
120 fn new(limit: usize) -> Self {
121 Self {
122 limit,
123 active: parking_lot::Mutex::new(0),
124 available: parking_lot::Condvar::new(),
125 async_available: tokio::sync::Notify::new(),
126 }
127 }
128
129 #[cfg(feature = "sync")]
130 fn acquire(&self) -> BmpIoPermit<'_> {
131 let mut active = self.active.lock();
132 while *active >= self.limit {
133 self.available.wait(&mut active);
134 }
135 *active += 1;
136 BmpIoPermit { gate: self }
137 }
138
139 async fn acquire_async(&self) -> BmpIoPermit<'_> {
140 loop {
141 let notified = self.async_available.notified();
144 {
145 let mut active = self.active.lock();
146 if *active < self.limit {
147 *active += 1;
148 return BmpIoPermit { gate: self };
149 }
150 }
151 notified.await;
152 }
153 }
154}
155
156#[cfg(feature = "native")]
157struct BmpIoPermit<'a> {
158 gate: &'a BmpIoGate,
159}
160
161#[cfg(feature = "native")]
162impl Drop for BmpIoPermit<'_> {
163 fn drop(&mut self) {
164 let mut active = self.gate.active.lock();
165 *active -= 1;
166 self.gate.available.notify_one();
167 self.gate.async_available.notify_one();
168 }
169}
170
171#[cfg(feature = "native")]
172impl ReorderConcurrencyGate {
173 pub fn new(requested_limit: usize) -> Self {
174 let limit = requested_limit.clamp(1, MAX_CONCURRENT_REORDER_PASSES);
175 let automatic_merge_limit = limit.saturating_sub(1).max(1);
176 Self {
177 permits: Arc::new(tokio::sync::Semaphore::new(limit)),
178 optimizer_permits: Arc::new(tokio::sync::Semaphore::new(1)),
179 automatic_merge_permits: Arc::new(tokio::sync::Semaphore::new(automatic_merge_limit)),
180 limit,
181 foreground_lock: Arc::new(tokio::sync::Mutex::new(())),
182 foreground_active: std::sync::atomic::AtomicBool::new(false),
183 foreground_finished: tokio::sync::Notify::new(),
184 }
185 }
186
187 pub fn limit(&self) -> usize {
188 self.limit
189 }
190
191 pub(crate) async fn acquire(
192 self: &Arc<Self>,
193 priority: ReorderPriority,
194 ) -> std::result::Result<ReorderPermit, tokio::sync::AcquireError> {
195 match priority {
196 ReorderPriority::Optimizer => {
197 let optimizer_permit = Arc::clone(&self.optimizer_permits).acquire_owned().await?;
198 self.acquire_background(Some(optimizer_permit), None).await
199 }
200 ReorderPriority::AutomaticMerge => {
201 let merge_permit = Arc::clone(&self.automatic_merge_permits)
202 .acquire_owned()
203 .await?;
204 self.acquire_background(None, Some(merge_permit)).await
205 }
206 ReorderPriority::Foreground => self.acquire_foreground().await,
207 }
208 }
209
210 async fn acquire_background(
212 self: &Arc<Self>,
213 optimizer: Option<tokio::sync::OwnedSemaphorePermit>,
214 automatic_merge: Option<tokio::sync::OwnedSemaphorePermit>,
215 ) -> std::result::Result<ReorderPermit, tokio::sync::AcquireError> {
216 loop {
217 if self
218 .foreground_active
219 .load(std::sync::atomic::Ordering::Acquire)
220 {
221 let notified = self.foreground_finished.notified();
222 if self
223 .foreground_active
224 .load(std::sync::atomic::Ordering::Acquire)
225 {
226 notified.await;
227 continue;
228 }
229 }
230
231 let permit = Arc::clone(&self.permits).acquire_owned().await?;
232 if !self
233 .foreground_active
234 .load(std::sync::atomic::Ordering::Acquire)
235 {
236 return Ok(ReorderPermit {
237 _permit: permit,
238 _optimizer: optimizer,
239 _automatic_merge: automatic_merge,
240 });
241 }
242 drop(permit);
245 }
246 }
247
248 async fn acquire_foreground(
250 self: &Arc<Self>,
251 ) -> std::result::Result<ReorderPermit, tokio::sync::AcquireError> {
252 let permit = Arc::clone(&self.permits).acquire_owned().await?;
253 Ok(ReorderPermit {
254 _permit: permit,
255 _optimizer: None,
256 _automatic_merge: None,
257 })
258 }
259
260 pub(crate) async fn begin_foreground(
266 self: &Arc<Self>,
267 ) -> std::result::Result<ForegroundReorderGuard, tokio::sync::AcquireError> {
268 let exclusive = Arc::clone(&self.foreground_lock).lock_owned().await;
269 self.foreground_active
270 .store(true, std::sync::atomic::Ordering::Release);
271
272 let mut guard = ForegroundReorderGuard {
276 gate: Arc::clone(self),
277 reserved: None,
278 _exclusive: exclusive,
279 };
280 if self.limit > 1 {
281 guard.reserved = Some(
282 Arc::clone(&self.permits)
283 .acquire_many_owned((self.limit - 1) as u32)
284 .await?,
285 );
286 }
287 Ok(guard)
288 }
289}
290
291#[cfg(feature = "native")]
292pub(crate) struct ReorderPermit {
293 _permit: tokio::sync::OwnedSemaphorePermit,
294 _optimizer: Option<tokio::sync::OwnedSemaphorePermit>,
295 _automatic_merge: Option<tokio::sync::OwnedSemaphorePermit>,
296}
297
298#[cfg(feature = "native")]
299pub(crate) struct ForegroundReorderGuard {
300 gate: Arc<ReorderConcurrencyGate>,
301 reserved: Option<tokio::sync::OwnedSemaphorePermit>,
302 _exclusive: tokio::sync::OwnedMutexGuard<()>,
303}
304
305#[cfg(feature = "native")]
306impl Drop for ForegroundReorderGuard {
307 fn drop(&mut self) {
308 drop(self.reserved.take());
310 self.gate
311 .foreground_active
312 .store(false, std::sync::atomic::Ordering::Release);
313 self.gate.foreground_finished.notify_waiters();
314 }
315}
316
317#[derive(Debug, Clone)]
319pub struct IndexConfig {
320 pub num_threads: usize,
326 pub bmp_io_concurrency: usize,
331 pub num_indexing_threads: usize,
333 pub num_compression_threads: usize,
337 pub term_cache_blocks: usize,
339 pub store_cache_budget_bytes: usize,
346 pub max_indexing_memory_bytes: usize,
348 pub vector_training_max_samples: usize,
352 pub vector_training_memory_bytes: usize,
354 pub merge_policy: Box<dyn crate::merge::MergePolicy>,
356 pub optimization: crate::structures::IndexOptimization,
361 pub posting_codec: Option<crate::structures::PostingCodec>,
364 pub reload_interval_ms: u64,
366 pub max_concurrent_merges: usize,
368 #[cfg(feature = "native")]
372 pub background_merge_permits: Arc<tokio::sync::Semaphore>,
373 pub merge_bp_time_budget: Option<std::time::Duration>,
380 pub bp_memory_budget_bytes: usize,
387 #[cfg(feature = "native")]
393 pub background_reorder_permits: Arc<ReorderConcurrencyGate>,
394 #[cfg(feature = "native")]
398 pub background_reorder_pool: Option<Arc<rayon::ThreadPool>>,
399}
400
401#[cfg(feature = "sync")]
405static SEARCH_CPU_POOLS: OnceLock<parking_lot::Mutex<HashMap<usize, Weak<rayon::ThreadPool>>>> =
406 OnceLock::new();
407
408#[cfg(feature = "native")]
413static STORE_CACHE_POOLS: OnceLock<
414 parking_lot::Mutex<std::collections::HashMap<usize, Weak<crate::segment::SharedStoreCache>>>,
415> = OnceLock::new();
416
417#[cfg(feature = "native")]
418static BMP_IO_GATES: OnceLock<
419 parking_lot::Mutex<std::collections::HashMap<usize, Weak<BmpIoGate>>>,
420> = OnceLock::new();
421
422#[cfg(feature = "native")]
423pub(crate) fn shared_bmp_io_gate(limit: usize) -> Arc<BmpIoGate> {
424 let mut gates = BMP_IO_GATES
425 .get_or_init(|| parking_lot::Mutex::new(std::collections::HashMap::new()))
426 .lock();
427 if let Some(gate) = gates.get(&limit).and_then(Weak::upgrade) {
428 return gate;
429 }
430 let gate = Arc::new(BmpIoGate::new(limit));
431 gates.retain(|_, gate| gate.strong_count() > 0);
432 gates.insert(limit, Arc::downgrade(&gate));
433 log::info!("[bmp] process-wide random-I/O concurrency={limit}");
434 gate
435}
436
437#[cfg(feature = "native")]
438pub(crate) fn shared_store_cache(budget_bytes: usize) -> Arc<crate::segment::SharedStoreCache> {
439 let mut caches = STORE_CACHE_POOLS
440 .get_or_init(|| parking_lot::Mutex::new(std::collections::HashMap::new()))
441 .lock();
442 if let Some(cache) = caches.get(&budget_bytes).and_then(Weak::upgrade) {
443 return cache;
444 }
445 let cache = Arc::new(crate::segment::SharedStoreCache::new(budget_bytes));
446 caches.retain(|_, cache| cache.strong_count() > 0);
447 caches.insert(budget_bytes, Arc::downgrade(&cache));
448 log::info!(
449 "[store_cache] process-wide budget={}",
450 crate::format_bytes(budget_bytes as u64)
451 );
452 cache
453}
454
455#[cfg(feature = "sync")]
456fn shared_search_pool(num_threads: usize) -> Result<Arc<rayon::ThreadPool>> {
457 if num_threads == 0 {
458 return Err(crate::Error::Internal(
459 "IndexConfig.num_threads must be greater than zero".into(),
460 ));
461 }
462
463 let mut pools = SEARCH_CPU_POOLS
464 .get_or_init(|| parking_lot::Mutex::new(HashMap::new()))
465 .lock();
466 if let Some(pool) = pools.get(&num_threads).and_then(Weak::upgrade) {
467 return Ok(pool);
468 }
469
470 let pool = Arc::new(
474 rayon::ThreadPoolBuilder::new()
475 .num_threads(num_threads)
476 .thread_name(move |idx| format!("hermes-search-{}-{}", num_threads, idx))
477 .build()
478 .map_err(|error| {
479 crate::Error::Internal(format!(
480 "failed to create {num_threads}-thread search pool: {error}"
481 ))
482 })?,
483 );
484 pools.retain(|_, pool| pool.strong_count() > 0);
485 pools.insert(num_threads, Arc::downgrade(&pool));
486 log::info!("[search] process-wide CPU pool: {} thread(s)", num_threads);
487 Ok(pool)
488}
489
490impl Default for IndexConfig {
491 fn default() -> Self {
492 #[cfg(feature = "native")]
493 let compression_threads = crate::default_compression_threads();
494 #[cfg(not(feature = "native"))]
495 let compression_threads = 1;
496
497 #[cfg(feature = "native")]
498 let search_threads = crate::default_search_threads();
499 #[cfg(not(feature = "native"))]
500 let search_threads = 1;
501
502 Self {
503 num_threads: search_threads,
504 bmp_io_concurrency: 4,
505 num_indexing_threads: 1, num_compression_threads: compression_threads,
507 term_cache_blocks: 256,
508 #[cfg(target_pointer_width = "64")]
512 store_cache_budget_bytes: 2 * 1024 * 1024 * 1024,
513 #[cfg(not(target_pointer_width = "64"))]
514 store_cache_budget_bytes: 32 * 1024 * 1024,
515 max_indexing_memory_bytes: 256 * 1024 * 1024, vector_training_max_samples: 10_000_000,
517 #[cfg(target_pointer_width = "64")]
518 vector_training_memory_bytes: 4 * 1024 * 1024 * 1024,
519 #[cfg(not(target_pointer_width = "64"))]
520 vector_training_memory_bytes: usize::MAX,
521 merge_policy: Box::new(crate::merge::TieredMergePolicy::large_scale()),
526 optimization: crate::structures::IndexOptimization::default(),
527 posting_codec: None,
528 reload_interval_ms: 1000, max_concurrent_merges: 4,
530 #[cfg(feature = "native")]
531 background_merge_permits: Arc::new(tokio::sync::Semaphore::new(4)),
532 merge_bp_time_budget: Some(std::time::Duration::from_secs(600)),
533 #[cfg(target_pointer_width = "64")]
542 bp_memory_budget_bytes: 24 * 1024 * 1024 * 1024,
543 #[cfg(not(target_pointer_width = "64"))]
544 bp_memory_budget_bytes: usize::MAX,
545 #[cfg(feature = "native")]
546 background_reorder_permits: Arc::new(ReorderConcurrencyGate::new(2)),
547 #[cfg(feature = "native")]
548 background_reorder_pool: None,
549 }
550 }
551}
552
553impl IndexConfig {
554 pub fn effective_posting_codec(&self) -> crate::structures::PostingCodec {
556 self.posting_codec
557 .unwrap_or_else(|| self.optimization.default_posting_codec())
558 }
559}
560
561#[cfg(feature = "native")]
567fn segment_manager_from_config<D: crate::directories::DirectoryWriter + 'static>(
568 directory: &Arc<D>,
569 schema: &Arc<Schema>,
570 metadata: IndexMetadata,
571 config: &IndexConfig,
572) -> Arc<crate::merge::SegmentManager<D>> {
573 Arc::new(
574 crate::merge::SegmentManager::new(
575 Arc::clone(directory),
576 Arc::clone(schema),
577 metadata,
578 config.merge_policy.clone_box(),
579 config.term_cache_blocks,
580 config.max_concurrent_merges,
581 Arc::clone(&config.background_merge_permits),
582 config.merge_bp_time_budget,
583 config.bp_memory_budget_bytes,
584 Arc::clone(&config.background_reorder_permits),
585 config.background_reorder_pool.clone(),
586 )
587 .with_posting_config(config.optimization, config.effective_posting_codec()),
588 )
589}
590
591#[cfg(feature = "native")]
600pub struct Index<D: crate::directories::DirectoryWriter + 'static> {
601 directory: Arc<D>,
602 config: IndexConfig,
603 search_resources: searcher::SearcherResources,
605 segment_manager: Arc<crate::merge::SegmentManager<D>>,
607 cached_reader: tokio::sync::OnceCell<IndexReader<D>>,
609}
610
611#[cfg(feature = "native")]
612impl<D: crate::directories::DirectoryWriter + 'static> Index<D> {
613 pub async fn create(directory: D, schema: Schema, config: IndexConfig) -> Result<Self> {
615 let search_resources = searcher::SearcherResources::new(
616 config.term_cache_blocks,
617 config.store_cache_budget_bytes,
618 config.num_threads,
619 config.bmp_io_concurrency,
620 )?;
621 let directory = Arc::new(directory);
622 let schema = Arc::new(schema);
623 directory.set_index_label(schema.index_label());
625
626 if directory
630 .exists(std::path::Path::new(INDEX_META_FILENAME))
631 .await?
632 {
633 return Err(crate::Error::Internal(format!(
634 "refusing to create index: {} already exists in this directory; \
635 use Index::open to open the existing index, or delete the \
636 directory first if you really want to start over",
637 INDEX_META_FILENAME
638 )));
639 }
640
641 let metadata = IndexMetadata::new((*schema).clone());
642
643 let segment_manager = segment_manager_from_config(&directory, &schema, metadata, &config);
644
645 segment_manager.update_metadata(|_| {}).await?;
647
648 Ok(Self {
649 directory,
650 config,
651 search_resources,
652 segment_manager,
653 cached_reader: tokio::sync::OnceCell::new(),
654 })
655 }
656
657 pub async fn open(directory: D, config: IndexConfig) -> Result<Self> {
659 let search_resources = searcher::SearcherResources::new(
660 config.term_cache_blocks,
661 config.store_cache_budget_bytes,
662 config.num_threads,
663 config.bmp_io_concurrency,
664 )?;
665 let directory = Arc::new(directory);
666
667 let metadata = IndexMetadata::load(directory.as_ref()).await?;
669 let schema = Arc::new(metadata.schema.clone());
670 directory.set_index_label(schema.index_label());
672
673 let segment_manager = segment_manager_from_config(&directory, &schema, metadata, &config);
674
675 segment_manager.try_load_and_publish_trained().await?;
677
678 Ok(Self {
679 directory,
680 config,
681 search_resources,
682 segment_manager,
683 cached_reader: tokio::sync::OnceCell::new(),
684 })
685 }
686
687 pub fn schema(&self) -> Arc<Schema> {
689 self.schema_arc()
690 }
691
692 pub fn schema_arc(&self) -> Arc<Schema> {
694 self.segment_manager.published_generation().schema.clone()
695 }
696
697 pub fn directory(&self) -> &D {
699 &self.directory
700 }
701
702 pub fn segment_manager(&self) -> &Arc<crate::merge::SegmentManager<D>> {
704 &self.segment_manager
705 }
706
707 pub async fn reader(&self) -> Result<&IndexReader<D>> {
712 self.cached_reader
713 .get_or_try_init(|| async {
714 IndexReader::from_segment_manager_with_resources(
715 self.schema_arc(),
716 Arc::clone(&self.segment_manager),
717 self.config.reload_interval_ms,
718 self.search_resources.clone(),
719 )
720 .await
721 })
722 .await
723 }
724
725 pub fn config(&self) -> &IndexConfig {
727 &self.config
728 }
729
730 pub async fn segment_readers(&self) -> Result<Vec<Arc<crate::segment::SegmentReader>>> {
732 let reader = self.reader().await?;
733 let searcher = reader.searcher().await?;
734 Ok(searcher.segment_readers().to_vec())
735 }
736
737 pub async fn num_docs(&self) -> Result<u32> {
739 let reader = self.reader().await?;
740 let searcher = reader.searcher().await?;
741 Ok(searcher.num_docs())
742 }
743
744 pub fn default_fields(&self) -> Vec<crate::Field> {
746 let schema = self.schema_arc();
747 if !schema.default_fields().is_empty() {
748 schema.default_fields().to_vec()
749 } else {
750 schema
751 .fields()
752 .filter(|(_, entry)| {
753 entry.indexed && entry.field_type == crate::dsl::FieldType::Text
754 })
755 .map(|(field, _)| field)
756 .collect()
757 }
758 }
759
760 pub fn tokenizers(&self) -> Arc<crate::tokenizer::TokenizerRegistry> {
762 Arc::new(crate::tokenizer::TokenizerRegistry::default())
763 }
764
765 pub fn query_parser(&self) -> crate::dsl::QueryLanguageParser {
767 let default_fields = self.default_fields();
768 let tokenizers = self.tokenizers();
769 let schema = self.schema_arc();
770
771 let query_routers = schema.query_routers();
772 if !query_routers.is_empty()
773 && let Ok(router) = crate::dsl::QueryFieldRouter::from_rules(query_routers)
774 {
775 return crate::dsl::QueryLanguageParser::with_router(
776 Arc::clone(&schema),
777 default_fields,
778 tokenizers,
779 router,
780 );
781 }
782
783 crate::dsl::QueryLanguageParser::new(schema, default_fields, tokenizers)
784 }
785
786 pub async fn query(
788 &self,
789 query_str: &str,
790 limit: usize,
791 ) -> Result<crate::query::SearchResponse> {
792 self.query_offset(query_str, limit, 0).await
793 }
794
795 pub async fn query_offset(
797 &self,
798 query_str: &str,
799 limit: usize,
800 offset: usize,
801 ) -> Result<crate::query::SearchResponse> {
802 let parser = self.query_parser();
803 let query = parser
804 .parse(query_str)
805 .map_err(crate::error::Error::Query)?;
806 self.search_offset(query.as_ref(), limit, offset).await
807 }
808
809 pub async fn search(
811 &self,
812 query: &dyn crate::query::Query,
813 limit: usize,
814 ) -> Result<crate::query::SearchResponse> {
815 self.search_offset(query, limit, 0).await
816 }
817
818 pub async fn search_offset(
820 &self,
821 query: &dyn crate::query::Query,
822 limit: usize,
823 offset: usize,
824 ) -> Result<crate::query::SearchResponse> {
825 let reader = self.reader().await?;
826 let searcher = reader.searcher().await?;
827
828 #[cfg(feature = "sync")]
829 let (results, total_seen) = {
830 let runtime_flavor = tokio::runtime::Handle::current().runtime_flavor();
834 if runtime_flavor == tokio::runtime::RuntimeFlavor::MultiThread {
835 tokio::task::block_in_place(|| {
836 searcher.search_with_offset_and_count_sync(query, limit, offset)
837 })?
838 } else {
839 searcher.search_with_offset_and_count_sync(query, limit, offset)?
840 }
841 };
842
843 #[cfg(not(feature = "sync"))]
844 let (results, total_seen) = {
845 searcher
846 .search_with_offset_and_count(query, limit, offset)
847 .await?
848 };
849
850 let total_hits = total_seen;
851 let hits: Vec<crate::query::SearchHit> = results
852 .into_iter()
853 .map(|result| crate::query::SearchHit {
854 address: crate::query::DocAddress::new(result.segment_id, result.doc_id),
855 score: result.score,
856 matched_fields: result.extract_ordinals(),
857 })
858 .collect();
859
860 Ok(crate::query::SearchResponse { hits, total_hits })
861 }
862
863 pub async fn get_document(
865 &self,
866 address: &crate::query::DocAddress,
867 ) -> Result<Option<crate::dsl::Document>> {
868 let reader = self.reader().await?;
869 let searcher = reader.searcher().await?;
870 searcher.get_document(address).await
871 }
872
873 pub async fn get_postings(
875 &self,
876 field: crate::Field,
877 term: &[u8],
878 ) -> Result<
879 Vec<(
880 Arc<crate::segment::SegmentReader>,
881 crate::structures::BlockPostingList,
882 )>,
883 > {
884 let segments = self.segment_readers().await?;
885 let mut results = Vec::new();
886
887 for segment in segments {
888 if let Some(postings) = segment.get_postings(field, term).await? {
889 results.push((segment, postings));
890 }
891 }
892
893 Ok(results)
894 }
895}
896
897#[cfg(feature = "native")]
899impl<D: crate::directories::DirectoryWriter + 'static> Index<D> {
900 pub fn writer(&self) -> writer::IndexWriter<D> {
902 writer::IndexWriter::from_index(self)
903 }
904}
905
906#[cfg(test)]
907mod tests;
908
909