1use std::sync::Arc;
33use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
34
35use rustc_hash::FxHashMap;
36
37use crate::directories::DirectoryWriter;
38use crate::dsl::{Document, Field, Schema};
39use crate::error::{Error, Result};
40use crate::segment::{SegmentBuilder, SegmentBuilderConfig, SegmentId};
41use crate::tokenizer::BoxedTokenizer;
42
43use super::IndexConfig;
44
45const PIPELINE_MAX_SIZE_IN_DOCS: usize = 10_000;
47
48pub struct IndexWriter<D: DirectoryWriter + 'static> {
60 pub(super) directory: Arc<D>,
61 pub(super) schema: Arc<Schema>,
62 pub(super) config: IndexConfig,
63 doc_sender: async_channel::Sender<Document>,
66 workers: Vec<std::thread::JoinHandle<()>>,
68 worker_state: Arc<WorkerState<D>>,
70 pub(super) segment_manager: Arc<crate::merge::SegmentManager<D>>,
72 flushed_segments: Vec<(String, u32)>,
74 primary_key_index: Option<super::primary_key::PrimaryKeyIndex>,
76}
77
78struct WorkerState<D: DirectoryWriter + 'static> {
80 directory: Arc<D>,
81 schema: Arc<Schema>,
82 builder_config: SegmentBuilderConfig,
83 tokenizers: parking_lot::RwLock<FxHashMap<Field, BoxedTokenizer>>,
84 memory_budget_per_worker: usize,
86 segment_manager: Arc<crate::merge::SegmentManager<D>>,
88 built_segments: parking_lot::Mutex<Vec<(String, u32)>>,
90
91 flush_count: AtomicUsize,
98 flush_mutex: parking_lot::Mutex<()>,
100 flush_cvar: parking_lot::Condvar,
101 resume_receiver: parking_lot::Mutex<Option<async_channel::Receiver<Document>>>,
103 resume_epoch: AtomicUsize,
106 resume_cvar: parking_lot::Condvar,
108 shutdown: AtomicBool,
110 num_workers: usize,
112}
113
114impl<D: DirectoryWriter + 'static> IndexWriter<D> {
115 pub async fn create(directory: D, schema: Schema, config: IndexConfig) -> Result<Self> {
117 Self::create_with_config(directory, schema, config, SegmentBuilderConfig::default()).await
118 }
119
120 pub async fn create_with_config(
122 directory: D,
123 schema: Schema,
124 config: IndexConfig,
125 builder_config: SegmentBuilderConfig,
126 ) -> Result<Self> {
127 let directory = Arc::new(directory);
128 let schema = Arc::new(schema);
129 directory.set_index_label(schema.index_label());
131 let metadata = super::IndexMetadata::new((*schema).clone());
132
133 let segment_manager = Arc::new(crate::merge::SegmentManager::new(
134 Arc::clone(&directory),
135 Arc::clone(&schema),
136 metadata,
137 config.merge_policy.clone_box(),
138 config.term_cache_blocks,
139 config.max_concurrent_merges,
140 ));
141 segment_manager.update_metadata(|_| {}).await?;
142
143 Ok(Self::new_with_parts(
144 directory,
145 schema,
146 config,
147 builder_config,
148 segment_manager,
149 ))
150 }
151
152 pub async fn open(directory: D, config: IndexConfig) -> Result<Self> {
154 Self::open_with_config(directory, config, SegmentBuilderConfig::default()).await
155 }
156
157 pub async fn open_with_config(
159 directory: D,
160 config: IndexConfig,
161 builder_config: SegmentBuilderConfig,
162 ) -> Result<Self> {
163 let directory = Arc::new(directory);
164 let metadata = super::IndexMetadata::load(directory.as_ref()).await?;
165 let schema = Arc::new(metadata.schema.clone());
166 directory.set_index_label(schema.index_label());
168
169 let segment_manager = Arc::new(crate::merge::SegmentManager::new(
170 Arc::clone(&directory),
171 Arc::clone(&schema),
172 metadata,
173 config.merge_policy.clone_box(),
174 config.term_cache_blocks,
175 config.max_concurrent_merges,
176 ));
177 segment_manager.load_and_publish_trained().await;
178
179 Ok(Self::new_with_parts(
180 directory,
181 schema,
182 config,
183 builder_config,
184 segment_manager,
185 ))
186 }
187
188 pub fn from_index(index: &super::Index<D>) -> Self {
191 Self::new_with_parts(
192 Arc::clone(&index.directory),
193 Arc::clone(&index.schema),
194 index.config.clone(),
195 SegmentBuilderConfig::default(),
196 Arc::clone(&index.segment_manager),
197 )
198 }
199
200 fn new_with_parts(
206 directory: Arc<D>,
207 schema: Arc<Schema>,
208 config: IndexConfig,
209 builder_config: SegmentBuilderConfig,
210 segment_manager: Arc<crate::merge::SegmentManager<D>>,
211 ) -> Self {
212 let registry = crate::tokenizer::TokenizerRegistry::new();
214 let mut tokenizers = FxHashMap::default();
215 for (field, entry) in schema.fields() {
216 if matches!(entry.field_type, crate::dsl::FieldType::Text)
217 && let Some(ref tok_name) = entry.tokenizer
218 && let Some(tok) = registry.get(tok_name)
219 {
220 tokenizers.insert(field, tok);
221 }
222 }
223
224 let num_workers = config.num_indexing_threads.max(1);
225 let worker_state = Arc::new(WorkerState {
226 directory: Arc::clone(&directory),
227 schema: Arc::clone(&schema),
228 builder_config,
229 tokenizers: parking_lot::RwLock::new(tokenizers),
230 memory_budget_per_worker: config.max_indexing_memory_bytes / num_workers,
231 segment_manager: Arc::clone(&segment_manager),
232 built_segments: parking_lot::Mutex::new(Vec::new()),
233 flush_count: AtomicUsize::new(0),
234 flush_mutex: parking_lot::Mutex::new(()),
235 flush_cvar: parking_lot::Condvar::new(),
236 resume_receiver: parking_lot::Mutex::new(None),
237 resume_epoch: AtomicUsize::new(0),
238 resume_cvar: parking_lot::Condvar::new(),
239 shutdown: AtomicBool::new(false),
240 num_workers,
241 });
242 let (doc_sender, workers) = Self::spawn_workers(&worker_state, num_workers);
243
244 Self {
245 directory,
246 schema,
247 config,
248 doc_sender,
249 workers,
250 worker_state,
251 segment_manager,
252 flushed_segments: Vec::new(),
253 primary_key_index: None,
254 }
255 }
256
257 fn spawn_workers(
258 worker_state: &Arc<WorkerState<D>>,
259 num_workers: usize,
260 ) -> (
261 async_channel::Sender<Document>,
262 Vec<std::thread::JoinHandle<()>>,
263 ) {
264 let (sender, receiver) = async_channel::bounded(PIPELINE_MAX_SIZE_IN_DOCS);
265 let handle = tokio::runtime::Handle::current();
266 let mut workers = Vec::with_capacity(num_workers);
267 for i in 0..num_workers {
268 let state = Arc::clone(worker_state);
269 let rx = receiver.clone();
270 let rt = handle.clone();
271 workers.push(
272 std::thread::Builder::new()
273 .name(format!("index-worker-{}", i))
274 .spawn(move || Self::worker_loop(state, rx, rt))
275 .expect("failed to spawn index worker thread"),
276 );
277 }
278 (sender, workers)
279 }
280
281 pub fn schema(&self) -> &Schema {
283 &self.schema
284 }
285
286 pub fn set_tokenizer<T: crate::tokenizer::Tokenizer>(&mut self, field: Field, tokenizer: T) {
289 self.worker_state
290 .tokenizers
291 .write()
292 .insert(field, Box::new(tokenizer));
293 }
294
295 pub async fn init_primary_key_dedup(&mut self) -> Result<()> {
311 use super::primary_key::{PK_BLOOM_FILE, deserialize_pk_bloom};
312
313 let field = match self.schema.primary_field() {
314 Some(f) => f,
315 None => return Ok(()),
316 };
317
318 let snapshot = self.segment_manager.acquire_snapshot().await;
319 let current_seg_ids: Vec<String> = snapshot.segment_ids().to_vec();
320
321 let cached = match self
323 .directory
324 .open_read(std::path::Path::new(PK_BLOOM_FILE))
325 .await
326 {
327 Ok(handle) => {
328 let data = handle.read_bytes_range(0..handle.len()).await;
329 match data {
330 Ok(bytes) => deserialize_pk_bloom(bytes.as_slice()),
331 Err(_) => None,
332 }
333 }
334 Err(_) => None,
335 };
336
337 let load_futures: Vec<_> = current_seg_ids
339 .iter()
340 .map(|seg_id_str| {
341 let seg_id_str = seg_id_str.clone();
342 let dir = self.directory.as_ref();
343 let schema = Arc::clone(&self.schema);
344 async move { load_pk_segment_data(dir, &seg_id_str, &schema).await }
345 })
346 .collect();
347 let all_data = futures::future::try_join_all(load_futures).await?;
348
349 if let Some((persisted_seg_ids, bloom)) = cached {
350 let mut pk_data = Vec::with_capacity(all_data.len());
352 let mut new_data = Vec::new();
353 for d in all_data {
354 if persisted_seg_ids.contains(&d.segment_id) {
355 pk_data.push(d);
356 } else {
357 new_data.push(d);
358 }
359 }
360 let needs_persist = !new_data.is_empty();
361 let new_start = pk_data.len();
362 pk_data.extend(new_data);
363
364 let pk_index = if new_start == pk_data.len() {
365 super::primary_key::PrimaryKeyIndex::from_persisted(
367 field,
368 bloom,
369 pk_data,
370 &[],
371 snapshot,
372 )
373 } else {
374 tokio::task::spawn_blocking(move || {
376 let mut bloom = bloom;
379 let mut added = 0usize;
380 let num_new = pk_data.len() - new_start;
381 for data in &pk_data[new_start..] {
382 if let Some(ff) = data.fast_fields.get(&field.0)
383 && let Some(dict) = ff.text_dict()
384 {
385 for key in dict.iter() {
386 bloom.insert(key.as_bytes());
387 added += 1;
388 }
389 }
390 }
391 if added > 0 {
392 log::info!(
393 "[primary_key] bloom: added {} keys from {} new segment(s)",
394 added,
395 num_new,
396 );
397 }
398 super::primary_key::PrimaryKeyIndex::from_persisted(
399 field,
400 bloom,
401 pk_data,
402 &[],
403 snapshot,
404 )
405 })
406 .await
407 .map_err(|e| Error::Internal(format!("spawn_blocking failed: {}", e)))?
408 };
409
410 if needs_persist {
411 self.persist_pk_bloom(&pk_index, ¤t_seg_ids).await;
412 }
413
414 self.primary_key_index = Some(pk_index);
415 } else {
416 let pk_index = tokio::task::spawn_blocking(move || {
418 super::primary_key::PrimaryKeyIndex::new(field, all_data, snapshot)
419 })
420 .await
421 .map_err(|e| Error::Internal(format!("spawn_blocking failed: {}", e)))?;
422
423 self.persist_pk_bloom(&pk_index, ¤t_seg_ids).await;
424 self.primary_key_index = Some(pk_index);
425 }
426
427 Ok(())
428 }
429
430 async fn persist_pk_bloom(
433 &self,
434 pk_index: &super::primary_key::PrimaryKeyIndex,
435 segment_ids: &[String],
436 ) {
437 use super::primary_key::{PK_BLOOM_FILE, serialize_pk_bloom};
438
439 let bloom_bytes = pk_index.bloom_to_bytes();
440 let data = serialize_pk_bloom(segment_ids, &bloom_bytes);
441 if let Err(e) = self
442 .directory
443 .write(std::path::Path::new(PK_BLOOM_FILE), &data)
444 .await
445 {
446 log::warn!("[primary_key] failed to persist bloom cache: {}", e);
447 }
448 }
449
450 pub fn add_document(&self, doc: Document) -> Result<()> {
455 if let Some(ref pk_index) = self.primary_key_index {
456 pk_index.check_and_insert(&doc)?;
457 }
458 match self.doc_sender.try_send(doc) {
459 Ok(()) => Ok(()),
460 Err(async_channel::TrySendError::Full(doc)) => {
461 if let Some(ref pk_index) = self.primary_key_index {
463 pk_index.rollback_uncommitted_key(&doc);
464 }
465 Err(Error::QueueFull)
466 }
467 Err(async_channel::TrySendError::Closed(doc)) => {
468 if let Some(ref pk_index) = self.primary_key_index {
470 pk_index.rollback_uncommitted_key(&doc);
471 }
472 Err(Error::Internal("Document channel closed".into()))
473 }
474 }
475 }
476
477 pub fn add_documents(&self, documents: Vec<Document>) -> Result<usize> {
482 let total = documents.len();
483 for (i, doc) in documents.into_iter().enumerate() {
484 match self.add_document(doc) {
485 Ok(()) => {}
486 Err(Error::QueueFull) => return Ok(i),
487 Err(e) => return Err(e),
488 }
489 }
490 Ok(total)
491 }
492
493 fn worker_loop(
506 state: Arc<WorkerState<D>>,
507 initial_receiver: async_channel::Receiver<Document>,
508 handle: tokio::runtime::Handle,
509 ) {
510 let mut receiver = initial_receiver;
511 let mut my_epoch = 0usize;
512
513 loop {
514 let build_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
518 let mut builder: Option<SegmentBuilder> = None;
519
520 while let Ok(doc) = receiver.recv_blocking() {
521 if builder.is_none() {
523 match SegmentBuilder::new(
524 Arc::clone(&state.schema),
525 state.builder_config.clone(),
526 ) {
527 Ok(mut b) => {
528 for (field, tokenizer) in state.tokenizers.read().iter() {
529 b.set_tokenizer(*field, tokenizer.clone_box());
530 }
531 builder = Some(b);
532 }
533 Err(e) => {
534 log::error!("Failed to create segment builder: {:?}", e);
535 continue;
536 }
537 }
538 }
539
540 let b = builder.as_mut().unwrap();
541 if let Err(e) = b.add_document(doc) {
542 log::error!("Failed to index document: {:?}", e);
543 continue;
544 }
545
546 let builder_memory = b.estimated_memory_bytes();
547
548 if b.num_docs() & 0x3FFF == 0 {
549 log::debug!(
550 "[indexing] docs={}, memory={:.2} MB, budget={:.2} MB",
551 b.num_docs(),
552 builder_memory as f64 / (1024.0 * 1024.0),
553 state.memory_budget_per_worker as f64 / (1024.0 * 1024.0)
554 );
555 }
556
557 const MIN_DOCS_BEFORE_FLUSH: u32 = 100;
559
560 let effective_budget = state.memory_budget_per_worker * 4 / 5;
564
565 if builder_memory >= effective_budget && b.num_docs() >= MIN_DOCS_BEFORE_FLUSH {
566 log::info!(
567 "[indexing] memory budget reached, building segment: \
568 docs={}, memory={:.2} MB, budget={:.2} MB",
569 b.num_docs(),
570 builder_memory as f64 / (1024.0 * 1024.0),
571 state.memory_budget_per_worker as f64 / (1024.0 * 1024.0),
572 );
573 let full_builder = builder.take().unwrap();
574 Self::build_segment_inline(&state, full_builder, &handle);
575 }
576 }
577
578 if let Some(b) = builder.take()
580 && b.num_docs() > 0
581 {
582 Self::build_segment_inline(&state, b, &handle);
583 }
584 }));
585
586 if build_result.is_err() {
587 log::error!(
588 "[worker] panic during indexing cycle — documents in this cycle may be lost"
589 );
590 }
591
592 let prev = state.flush_count.fetch_add(1, Ordering::Release);
595 if prev + 1 == state.num_workers {
596 let _lock = state.flush_mutex.lock();
598 state.flush_cvar.notify_one();
599 }
600
601 {
605 let mut lock = state.resume_receiver.lock();
606 loop {
607 if state.shutdown.load(Ordering::Acquire) {
608 return;
609 }
610 let current_epoch = state.resume_epoch.load(Ordering::Acquire);
611 if current_epoch > my_epoch
612 && let Some(rx) = lock.as_ref()
613 {
614 receiver = rx.clone();
615 my_epoch = current_epoch;
616 break;
617 }
618 state.resume_cvar.wait(&mut lock);
619 }
620 }
621 }
622 }
623
624 fn build_segment_inline(
628 state: &WorkerState<D>,
629 builder: SegmentBuilder,
630 handle: &tokio::runtime::Handle,
631 ) {
632 let segment_id = SegmentId::new();
633 let segment_hex = segment_id.to_hex();
634 let trained = state.segment_manager.trained();
635 let doc_count = builder.num_docs();
636 let build_start = std::time::Instant::now();
637
638 log::info!(
639 "[segment_build] segment_id={} doc_count={} ann={}",
640 segment_hex,
641 doc_count,
642 trained.is_some()
643 );
644
645 match handle.block_on(builder.build(
646 state.directory.as_ref(),
647 segment_id,
648 trained.as_deref(),
649 )) {
650 Ok(meta) if meta.num_docs > 0 => {
651 let duration_ms = build_start.elapsed().as_millis() as u64;
652 log::info!(
653 "[segment_build_done] segment_id={} doc_count={} duration_ms={}",
654 segment_hex,
655 meta.num_docs,
656 duration_ms,
657 );
658 state
659 .built_segments
660 .lock()
661 .push((segment_hex, meta.num_docs));
662 }
663 Ok(_) => {}
664 Err(e) => {
665 log::error!(
666 "[segment_build_failed] segment_id={} error={:?}",
667 segment_hex,
668 e
669 );
670 }
671 }
672 }
673
674 pub async fn maybe_merge(&self) {
680 self.segment_manager.maybe_merge().await;
681 }
682
683 pub async fn abort_merges(&self) {
685 self.segment_manager.abort_merges().await;
686 }
687
688 pub async fn wait_for_merging_thread(&self) {
690 self.segment_manager.wait_for_merging_thread().await;
691 }
692
693 pub async fn wait_for_all_merges(&self) {
695 self.segment_manager.wait_for_all_merges().await;
696 }
697
698 pub fn tracker(&self) -> std::sync::Arc<crate::segment::SegmentTracker> {
700 self.segment_manager.tracker()
701 }
702
703 pub async fn acquire_snapshot(&self) -> crate::segment::SegmentSnapshot {
705 self.segment_manager.acquire_snapshot().await
706 }
707
708 pub async fn cleanup_orphan_segments(&self) -> Result<usize> {
710 self.segment_manager.cleanup_orphan_segments().await
711 }
712
713 pub async fn prepare_commit(&mut self) -> Result<PreparedCommit<'_, D>> {
724 self.doc_sender.close();
726
727 self.worker_state.resume_cvar.notify_all();
731
732 let state = Arc::clone(&self.worker_state);
735 let all_flushed = tokio::task::spawn_blocking(move || {
736 let mut lock = state.flush_mutex.lock();
737 let deadline = std::time::Instant::now() + std::time::Duration::from_secs(300);
738 while state.flush_count.load(Ordering::Acquire) < state.num_workers {
739 let remaining = deadline.saturating_duration_since(std::time::Instant::now());
740 if remaining.is_zero() {
741 log::error!(
742 "[prepare_commit] timed out waiting for workers: {}/{} flushed",
743 state.flush_count.load(Ordering::Acquire),
744 state.num_workers
745 );
746 return false;
747 }
748 state.flush_cvar.wait_for(&mut lock, remaining);
749 }
750 true
751 })
752 .await
753 .map_err(|e| Error::Internal(format!("Failed to wait for workers: {}", e)))?;
754
755 if !all_flushed {
756 self.resume_workers();
758 return Err(Error::Internal(format!(
759 "prepare_commit timed out: {}/{} workers flushed",
760 self.worker_state.flush_count.load(Ordering::Acquire),
761 self.worker_state.num_workers
762 )));
763 }
764
765 let built = std::mem::take(&mut *self.worker_state.built_segments.lock());
767 self.flushed_segments.extend(built);
768
769 Ok(PreparedCommit {
770 writer: self,
771 is_resolved: false,
772 })
773 }
774
775 pub async fn commit(&mut self) -> Result<bool> {
780 self.prepare_commit().await?.commit().await
781 }
782
783 pub async fn force_merge(&mut self) -> Result<()> {
785 self.prepare_commit().await?.commit().await?;
786 self.segment_manager.force_merge().await
787 }
788
789 pub async fn reorder(&mut self) -> Result<()> {
794 self.prepare_commit().await?.commit().await?;
795 self.segment_manager.reorder_segments().await
796 }
797
798 pub fn segment_manager(&self) -> &Arc<crate::merge::SegmentManager<D>> {
800 &self.segment_manager
801 }
802
803 fn resume_workers(&mut self) {
808 if tokio::runtime::Handle::try_current().is_err() {
809 self.worker_state.shutdown.store(true, Ordering::Release);
812 self.worker_state.resume_cvar.notify_all();
813 return;
814 }
815
816 self.worker_state.flush_count.store(0, Ordering::Release);
818
819 let (sender, receiver) = async_channel::bounded(PIPELINE_MAX_SIZE_IN_DOCS);
821 self.doc_sender = sender;
822
823 {
825 let mut lock = self.worker_state.resume_receiver.lock();
826 *lock = Some(receiver);
827 }
828 self.worker_state
829 .resume_epoch
830 .fetch_add(1, Ordering::Release);
831 self.worker_state.resume_cvar.notify_all();
832 }
833
834 }
836
837impl<D: DirectoryWriter + 'static> Drop for IndexWriter<D> {
838 fn drop(&mut self) {
839 self.worker_state.shutdown.store(true, Ordering::Release);
841 self.doc_sender.close();
843 self.worker_state.resume_cvar.notify_all();
845 for w in std::mem::take(&mut self.workers) {
847 let _ = w.join();
848 }
849 }
850}
851
852pub struct PreparedCommit<'a, D: DirectoryWriter + 'static> {
859 writer: &'a mut IndexWriter<D>,
860 is_resolved: bool,
861}
862
863impl<'a, D: DirectoryWriter + 'static> PreparedCommit<'a, D> {
864 pub async fn commit(mut self) -> Result<bool> {
868 self.is_resolved = true;
869 let segments = std::mem::take(&mut self.writer.flushed_segments);
870
871 if segments.is_empty() {
873 log::debug!("[commit] no segments to commit, skipping");
874 self.writer.resume_workers();
875 return Ok(false);
876 }
877
878 self.writer.segment_manager.commit(segments).await?;
879
880 if let Some(ref mut pk_index) = self.writer.primary_key_index {
882 let snapshot = self.writer.segment_manager.acquire_snapshot().await;
883 let existing_ids: std::collections::HashSet<&str> =
884 pk_index.committed_segment_ids().collect();
885
886 let load_futures: Vec<_> = snapshot
888 .segment_ids()
889 .iter()
890 .filter(|id| !existing_ids.contains(id.as_str()))
891 .map(|seg_id_str| {
892 let seg_id_str = seg_id_str.clone();
893 let dir = self.writer.directory.as_ref();
894 let schema = Arc::clone(&self.writer.schema);
895 async move { load_pk_segment_data(dir, &seg_id_str, &schema).await }
896 })
897 .collect();
898 let new_data = futures::future::try_join_all(load_futures).await?;
899
900 let seg_ids: Vec<String> = snapshot.segment_ids().to_vec();
901 pk_index.refresh_incremental(new_data, snapshot);
902
903 let bloom_bytes = pk_index.bloom_to_bytes();
905 let data = super::primary_key::serialize_pk_bloom(&seg_ids, &bloom_bytes);
906 if let Err(e) = self
907 .writer
908 .directory
909 .write(
910 std::path::Path::new(super::primary_key::PK_BLOOM_FILE),
911 &data,
912 )
913 .await
914 {
915 log::warn!("[primary_key] failed to persist bloom cache: {}", e);
916 }
917 }
918
919 self.writer.segment_manager.maybe_merge().await;
920 self.writer.resume_workers();
921 Ok(true)
922 }
923
924 pub fn abort(mut self) {
927 self.is_resolved = true;
928 self.writer.flushed_segments.clear();
929 if let Some(ref mut pk_index) = self.writer.primary_key_index {
930 pk_index.clear_uncommitted();
931 }
932 self.writer.resume_workers();
933 }
934}
935
936impl<D: DirectoryWriter + 'static> Drop for PreparedCommit<'_, D> {
937 fn drop(&mut self) {
938 if !self.is_resolved {
939 log::warn!("PreparedCommit dropped without commit/abort — auto-aborting");
940 self.writer.flushed_segments.clear();
941 if let Some(ref mut pk_index) = self.writer.primary_key_index {
942 pk_index.clear_uncommitted();
943 }
944 self.writer.resume_workers();
945 }
946 }
947}
948
949async fn load_pk_segment_data<D: crate::directories::Directory>(
951 dir: &D,
952 seg_id_str: &str,
953 schema: &Arc<crate::dsl::Schema>,
954) -> Result<super::primary_key::PkSegmentData> {
955 let seg_id = crate::segment::SegmentId::from_hex(seg_id_str)
956 .ok_or_else(|| Error::Internal(format!("Invalid segment id: {}", seg_id_str)))?;
957 let files = crate::segment::SegmentFiles::new(seg_id.0);
958 let fast_fields =
959 crate::segment::reader::loader::load_fast_fields_file(dir, &files, schema).await?;
960 Ok(super::primary_key::PkSegmentData {
961 segment_id: seg_id_str.to_string(),
962 fast_fields,
963 })
964}