#[cfg(feature = "native")]
use crate::dsl::Schema;
#[cfg(feature = "native")]
use crate::error::Result;
#[cfg(feature = "sync")]
use std::collections::HashMap;
#[cfg(feature = "native")]
use std::sync::Arc;
#[cfg(feature = "native")]
use std::sync::{OnceLock, Weak};
mod searcher;
pub use searcher::Searcher;
#[cfg(any(feature = "native", feature = "wasm"))]
mod content_hash;
#[cfg(any(feature = "native", feature = "wasm"))]
mod primary_key;
#[cfg(feature = "native")]
mod reader;
#[cfg(any(feature = "native", feature = "wasm"))]
pub(crate) mod staged_row;
#[cfg(feature = "native")]
mod vector_builder;
#[cfg(all(feature = "wasm", not(feature = "native")))]
mod wasm_writer;
#[cfg(feature = "native")]
mod writer;
#[cfg(any(feature = "native", feature = "wasm"))]
pub use primary_key::PrimaryKeyIndex;
#[cfg(feature = "native")]
pub use reader::IndexReader;
#[cfg(feature = "native")]
pub use vector_builder::{AlterVectorIndexOutcome, AlterVectorIndexState};
#[cfg(all(feature = "wasm", not(feature = "native")))]
pub use wasm_writer::IndexWriter as WasmIndexWriter;
#[cfg(feature = "native")]
pub use writer::{IndexWriter, PreparedCommit, WRITER_LOCK_FILENAME};
mod metadata;
pub use metadata::{
FieldVectorMeta, INDEX_META_FILENAME, IndexMetadata, SegmentMetaInfo, VectorIndexState,
};
#[cfg(feature = "native")]
mod helpers;
#[cfg(feature = "native")]
pub use helpers::{
IndexingStats, SchemaConfig, SchemaFieldConfig, create_index_at_path, create_index_from_sdl,
index_documents_from_reader, index_json_document, parse_schema,
};
pub const SLICE_CACHE_FILENAME: &str = "index.slicecache";
#[cfg(feature = "native")]
pub const MAX_CONCURRENT_REORDER_PASSES: usize = 2;
#[cfg(feature = "native")]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ReorderPriority {
Optimizer,
AutomaticMerge,
Foreground,
}
#[cfg(feature = "native")]
#[derive(Debug)]
pub struct ReorderConcurrencyGate {
permits: Arc<tokio::sync::Semaphore>,
optimizer_permits: Arc<tokio::sync::Semaphore>,
automatic_merge_permits: Arc<tokio::sync::Semaphore>,
limit: usize,
foreground_lock: Arc<tokio::sync::Mutex<()>>,
foreground_active: std::sync::atomic::AtomicBool,
foreground_finished: tokio::sync::Notify,
}
#[cfg(feature = "native")]
#[derive(Debug)]
pub(crate) struct SparseIoGate {
limit: usize,
active: parking_lot::Mutex<usize>,
available: parking_lot::Condvar,
async_available: tokio::sync::Notify,
}
#[cfg(feature = "native")]
impl SparseIoGate {
fn new(limit: usize) -> Self {
Self {
limit,
active: parking_lot::Mutex::new(0),
available: parking_lot::Condvar::new(),
async_available: tokio::sync::Notify::new(),
}
}
#[cfg(feature = "sync")]
fn acquire(&self) -> SparseIoPermit<'_> {
let mut active = self.active.lock();
while *active >= self.limit {
self.available.wait(&mut active);
}
*active += 1;
SparseIoPermit { gate: self }
}
async fn acquire_async(&self) -> SparseIoPermit<'_> {
loop {
let notified = self.async_available.notified();
{
let mut active = self.active.lock();
if *active < self.limit {
*active += 1;
return SparseIoPermit { gate: self };
}
}
notified.await;
}
}
}
#[cfg(feature = "native")]
struct SparseIoPermit<'a> {
gate: &'a SparseIoGate,
}
#[cfg(feature = "native")]
impl Drop for SparseIoPermit<'_> {
fn drop(&mut self) {
let mut active = self.gate.active.lock();
*active -= 1;
self.gate.available.notify_one();
self.gate.async_available.notify_one();
}
}
#[cfg(feature = "native")]
impl ReorderConcurrencyGate {
pub fn new(requested_limit: usize) -> Self {
let limit = requested_limit.clamp(1, MAX_CONCURRENT_REORDER_PASSES);
let automatic_merge_limit = limit.saturating_sub(1).max(1);
Self {
permits: Arc::new(tokio::sync::Semaphore::new(limit)),
optimizer_permits: Arc::new(tokio::sync::Semaphore::new(1)),
automatic_merge_permits: Arc::new(tokio::sync::Semaphore::new(automatic_merge_limit)),
limit,
foreground_lock: Arc::new(tokio::sync::Mutex::new(())),
foreground_active: std::sync::atomic::AtomicBool::new(false),
foreground_finished: tokio::sync::Notify::new(),
}
}
pub fn limit(&self) -> usize {
self.limit
}
pub(crate) fn try_acquire_optimizer(
self: &Arc<Self>,
) -> std::result::Result<ReorderPermit, tokio::sync::TryAcquireError> {
use std::sync::atomic::Ordering;
use tokio::sync::TryAcquireError;
if self.foreground_active.load(Ordering::Acquire) {
return Err(TryAcquireError::NoPermits);
}
let optimizer = Arc::clone(&self.optimizer_permits).try_acquire_owned()?;
let permit = Arc::clone(&self.permits).try_acquire_owned()?;
if self.foreground_active.load(Ordering::Acquire) {
return Err(TryAcquireError::NoPermits);
}
Ok(ReorderPermit {
_permit: permit,
_optimizer: Some(optimizer),
_automatic_merge: None,
})
}
pub(crate) async fn acquire(
self: &Arc<Self>,
priority: ReorderPriority,
) -> std::result::Result<ReorderPermit, tokio::sync::AcquireError> {
match priority {
ReorderPriority::Optimizer => {
let optimizer_permit = Arc::clone(&self.optimizer_permits).acquire_owned().await?;
self.acquire_background(Some(optimizer_permit), None).await
}
ReorderPriority::AutomaticMerge => {
let merge_permit = Arc::clone(&self.automatic_merge_permits)
.acquire_owned()
.await?;
self.acquire_background(None, Some(merge_permit)).await
}
ReorderPriority::Foreground => self.acquire_foreground().await,
}
}
async fn acquire_background(
self: &Arc<Self>,
optimizer: Option<tokio::sync::OwnedSemaphorePermit>,
automatic_merge: Option<tokio::sync::OwnedSemaphorePermit>,
) -> std::result::Result<ReorderPermit, tokio::sync::AcquireError> {
loop {
if self
.foreground_active
.load(std::sync::atomic::Ordering::Acquire)
{
let notified = self.foreground_finished.notified();
if self
.foreground_active
.load(std::sync::atomic::Ordering::Acquire)
{
notified.await;
continue;
}
}
let permit = Arc::clone(&self.permits).acquire_owned().await?;
if !self
.foreground_active
.load(std::sync::atomic::Ordering::Acquire)
{
return Ok(ReorderPermit {
_permit: permit,
_optimizer: optimizer,
_automatic_merge: automatic_merge,
});
}
drop(permit);
}
}
async fn acquire_foreground(
self: &Arc<Self>,
) -> std::result::Result<ReorderPermit, tokio::sync::AcquireError> {
let permit = Arc::clone(&self.permits).acquire_owned().await?;
Ok(ReorderPermit {
_permit: permit,
_optimizer: None,
_automatic_merge: None,
})
}
pub(crate) async fn begin_foreground(
self: &Arc<Self>,
) -> std::result::Result<ForegroundReorderGuard, tokio::sync::AcquireError> {
let exclusive = Arc::clone(&self.foreground_lock).lock_owned().await;
self.foreground_active
.store(true, std::sync::atomic::Ordering::Release);
let mut guard = ForegroundReorderGuard {
gate: Arc::clone(self),
reserved: None,
_exclusive: exclusive,
};
if self.limit > 1 {
guard.reserved = Some(
Arc::clone(&self.permits)
.acquire_many_owned((self.limit - 1) as u32)
.await?,
);
}
Ok(guard)
}
}
#[cfg(feature = "native")]
pub(crate) struct ReorderPermit {
_permit: tokio::sync::OwnedSemaphorePermit,
_optimizer: Option<tokio::sync::OwnedSemaphorePermit>,
_automatic_merge: Option<tokio::sync::OwnedSemaphorePermit>,
}
#[cfg(feature = "native")]
pub(crate) struct ForegroundReorderGuard {
gate: Arc<ReorderConcurrencyGate>,
reserved: Option<tokio::sync::OwnedSemaphorePermit>,
_exclusive: tokio::sync::OwnedMutexGuard<()>,
}
#[cfg(feature = "native")]
impl Drop for ForegroundReorderGuard {
fn drop(&mut self) {
drop(self.reserved.take());
self.gate
.foreground_active
.store(false, std::sync::atomic::Ordering::Release);
self.gate.foreground_finished.notify_waiters();
}
}
#[derive(Debug, Clone)]
pub struct IndexConfig {
pub num_threads: usize,
pub sparse_io_concurrency: usize,
pub num_indexing_threads: usize,
pub num_compression_threads: usize,
pub term_cache_blocks: usize,
pub term_cache_budget_bytes: Option<usize>,
pub term_dict_block_size: crate::structures::SSTableBlockSize,
pub store_cache_budget_bytes: usize,
pub max_indexing_memory_bytes: usize,
pub vector_training_max_samples: usize,
pub vector_training_memory_bytes: usize,
pub merge_policy: Box<dyn crate::merge::MergePolicy>,
pub optimization: crate::structures::IndexOptimization,
pub posting_codec: Option<crate::structures::PostingCodec>,
pub quantized_norms: bool,
pub compact_text: bool,
pub posting_ratio_bounds: bool,
pub posting_impact_bounds: bool,
pub reload_interval_ms: u64,
pub max_concurrent_merges: usize,
#[cfg(feature = "native")]
pub background_merge_permits: Arc<tokio::sync::Semaphore>,
pub merge_bp_time_budget: Option<std::time::Duration>,
pub bp_memory_budget_bytes: usize,
pub compaction_memory_budget_bytes: usize,
#[cfg(feature = "native")]
pub background_reorder_permits: Arc<ReorderConcurrencyGate>,
#[cfg(feature = "native")]
pub background_reorder_pool: Option<Arc<rayon::ThreadPool>>,
}
#[cfg(feature = "sync")]
static SEARCH_CPU_POOLS: OnceLock<parking_lot::Mutex<HashMap<usize, Weak<rayon::ThreadPool>>>> =
OnceLock::new();
#[cfg(feature = "native")]
static STORE_CACHE_POOLS: OnceLock<
parking_lot::Mutex<std::collections::HashMap<usize, Weak<crate::segment::SharedStoreCache>>>,
> = OnceLock::new();
#[cfg(feature = "native")]
static SPARSE_IO_GATES: OnceLock<
parking_lot::Mutex<std::collections::HashMap<usize, Weak<SparseIoGate>>>,
> = OnceLock::new();
#[cfg(feature = "native")]
fn shared_resource_log_level(announced: &OnceLock<()>) -> log::Level {
if announced.set(()).is_ok() {
log::Level::Info
} else {
log::Level::Debug
}
}
#[cfg(feature = "native")]
pub(crate) fn shared_sparse_io_gate(limit: usize) -> Arc<SparseIoGate> {
let mut gates = SPARSE_IO_GATES
.get_or_init(|| parking_lot::Mutex::new(std::collections::HashMap::new()))
.lock();
if let Some(gate) = gates.get(&limit).and_then(Weak::upgrade) {
return gate;
}
let gate = Arc::new(SparseIoGate::new(limit));
gates.retain(|_, gate| gate.strong_count() > 0);
gates.insert(limit, Arc::downgrade(&gate));
static ANNOUNCED: OnceLock<()> = OnceLock::new();
log::log!(
shared_resource_log_level(&ANNOUNCED),
"[sparse] process-wide random-I/O concurrency={limit}"
);
gate
}
#[cfg(feature = "native")]
pub(crate) fn shared_store_cache(budget_bytes: usize) -> Arc<crate::segment::SharedStoreCache> {
let mut caches = STORE_CACHE_POOLS
.get_or_init(|| parking_lot::Mutex::new(std::collections::HashMap::new()))
.lock();
if let Some(cache) = caches.get(&budget_bytes).and_then(Weak::upgrade) {
return cache;
}
let cache = Arc::new(crate::segment::SharedStoreCache::new(budget_bytes));
caches.retain(|_, cache| cache.strong_count() > 0);
caches.insert(budget_bytes, Arc::downgrade(&cache));
static ANNOUNCED: OnceLock<()> = OnceLock::new();
log::log!(
shared_resource_log_level(&ANNOUNCED),
"[store_cache] process-wide budget={}",
crate::format_bytes(budget_bytes as u64)
);
cache
}
#[cfg(feature = "sync")]
fn shared_search_pool(num_threads: usize) -> Result<Arc<rayon::ThreadPool>> {
if num_threads == 0 {
return Err(crate::Error::Internal(
"IndexConfig.num_threads must be greater than zero".into(),
));
}
let mut pools = SEARCH_CPU_POOLS
.get_or_init(|| parking_lot::Mutex::new(HashMap::new()))
.lock();
if let Some(pool) = pools.get(&num_threads).and_then(Weak::upgrade) {
return Ok(pool);
}
let pool = Arc::new(
rayon::ThreadPoolBuilder::new()
.num_threads(num_threads)
.thread_name(move |idx| format!("summa-search-{}-{}", num_threads, idx))
.build()
.map_err(|error| {
crate::Error::Internal(format!(
"failed to create {num_threads}-thread search pool: {error}"
))
})?,
);
pools.retain(|_, pool| pool.strong_count() > 0);
pools.insert(num_threads, Arc::downgrade(&pool));
static ANNOUNCED: OnceLock<()> = OnceLock::new();
log::log!(
shared_resource_log_level(&ANNOUNCED),
"[search] process-wide CPU pool: {} thread(s)",
num_threads
);
Ok(pool)
}
impl Default for IndexConfig {
fn default() -> Self {
#[cfg(feature = "native")]
let compression_threads = crate::default_compression_threads();
#[cfg(not(feature = "native"))]
let compression_threads = 1;
#[cfg(feature = "native")]
let search_threads = crate::default_search_threads();
#[cfg(not(feature = "native"))]
let search_threads = 1;
Self {
num_threads: search_threads,
sparse_io_concurrency: 4,
num_indexing_threads: 1, num_compression_threads: compression_threads,
term_cache_blocks: 256,
term_cache_budget_bytes: None,
term_dict_block_size: crate::structures::SSTableBlockSize::default(),
#[cfg(target_pointer_width = "64")]
store_cache_budget_bytes: 2 * 1024 * 1024 * 1024,
#[cfg(not(target_pointer_width = "64"))]
store_cache_budget_bytes: 32 * 1024 * 1024,
max_indexing_memory_bytes: 256 * 1024 * 1024, vector_training_max_samples: 10_000_000,
#[cfg(target_pointer_width = "64")]
vector_training_memory_bytes: 4 * 1024 * 1024 * 1024,
#[cfg(not(target_pointer_width = "64"))]
vector_training_memory_bytes: usize::MAX,
merge_policy: Box::new(crate::merge::TieredMergePolicy::large_scale()),
optimization: crate::structures::IndexOptimization::default(),
posting_codec: None,
quantized_norms: false,
compact_text: false,
posting_ratio_bounds: false,
posting_impact_bounds: false,
reload_interval_ms: 1000, max_concurrent_merges: 4,
#[cfg(feature = "native")]
background_merge_permits: Arc::new(tokio::sync::Semaphore::new(4)),
merge_bp_time_budget: Some(std::time::Duration::from_secs(600)),
#[cfg(target_pointer_width = "64")]
bp_memory_budget_bytes: 24 * 1024 * 1024 * 1024,
#[cfg(not(target_pointer_width = "64"))]
bp_memory_budget_bytes: usize::MAX,
compaction_memory_budget_bytes: 256 * 1024 * 1024,
#[cfg(feature = "native")]
background_reorder_permits: Arc::new(ReorderConcurrencyGate::new(2)),
#[cfg(feature = "native")]
background_reorder_pool: None,
}
}
}
pub const MAX_TERM_CACHE_BLOCKS: usize = 65_536;
#[cfg(feature = "native")]
pub(crate) fn validate_term_cache_blocks(blocks: usize) -> crate::Result<()> {
if blocks > MAX_TERM_CACHE_BLOCKS {
return Err(crate::Error::Internal(format!(
"IndexConfig.term_cache_blocks must be at most {MAX_TERM_CACHE_BLOCKS} (got {blocks})"
)));
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct PostingBounds {
pub ratio: bool,
pub impact: bool,
}
impl PostingBounds {
pub(crate) fn new(ratio: bool, impact: bool) -> Self {
Self {
ratio: ratio || impact,
impact,
}
}
}
impl IndexConfig {
pub fn effective_posting_codec(&self) -> crate::structures::PostingCodec {
self.posting_codec
.unwrap_or_else(|| self.optimization.default_posting_codec())
}
pub fn effective_posting_bounds(&self) -> PostingBounds {
PostingBounds::new(self.posting_ratio_bounds, self.posting_impact_bounds)
}
}
#[cfg(feature = "native")]
const POSTING_BOUNDS_PROBE_SEGMENTS: usize = 32;
#[cfg(feature = "native")]
const POSTING_BOUNDS_PROBE_TERMS: usize = 4096;
#[cfg(feature = "native")]
pub(crate) async fn segments_missing_posting_bounds<D: crate::directories::Directory>(
directory: &D,
metadata: &IndexMetadata,
config: &IndexConfig,
) -> Result<(Vec<String>, usize)> {
use crate::segment::{SegmentFiles, SegmentId};
use crate::structures::{AsyncSSTableReader, BlockPostingList, TermInfo};
let mut missing = Vec::new();
let mut probed = 0usize;
if !config.effective_posting_bounds().ratio {
return Ok((missing, probed));
}
for id in metadata
.segment_ids()
.into_iter()
.take(POSTING_BOUNDS_PROBE_SEGMENTS)
{
let Some(segment_id) = SegmentId::from_hex(&id) else {
continue;
};
let files = SegmentFiles::new(segment_id.0);
if !directory.exists(&files.term_dict).await? {
continue;
}
let term_dict = AsyncSSTableReader::<TermInfo>::open_with_cache_budget(
directory.open_lazy(&files.term_dict).await?,
1,
None,
)
.await?;
let mut terms = term_dict.iter();
let mut external = None;
for _ in 0..POSTING_BOUNDS_PROBE_TERMS {
match terms.next().await? {
Some((_, info)) => {
if let Some(range) = info.external_info() {
external = Some(range);
break;
}
}
None => break,
}
}
let Some((offset, len)) = external else {
continue;
};
let postings = directory.open_lazy(&files.postings).await?;
let end = offset.checked_add(len).ok_or_else(|| {
crate::Error::Corruption("posting range overflow while probing bounds".into())
})?;
let list =
BlockPostingList::deserialize_zero_copy(postings.read_bytes_range(offset..end).await?)?;
probed += 1;
if !list.has_ratio_bounds() {
missing.push(id);
}
}
Ok((missing, probed))
}
#[cfg(feature = "native")]
async fn log_posting_bounds_policy<D: crate::directories::Directory>(
directory: &D,
metadata: &IndexMetadata,
config: &IndexConfig,
) {
match segments_missing_posting_bounds(directory, metadata, config).await {
Ok((missing, probed)) if !missing.is_empty() => log::info!(
"[index] {}: posting_ratio_bounds/posting_impact_bounds are enabled but {} of {} \
probed existing segments carry no block-bound metadata (e.g. {}). Bounds apply \
to new segments only; merges and compaction keep existing block layouts. \
Re-index to add bounds to old data.",
metadata.schema.index_label(),
missing.len(),
probed,
missing[0]
),
Ok(_) => {}
Err(error) => log::warn!(
"[index] {}: could not probe existing segments for posting bounds: {}",
metadata.schema.index_label(),
error
),
}
}
#[cfg(feature = "native")]
fn segment_manager_from_config<D: crate::directories::DirectoryWriter + 'static>(
directory: &Arc<D>,
schema: &Arc<Schema>,
metadata: IndexMetadata,
config: &IndexConfig,
) -> Result<Arc<crate::merge::SegmentManager<D>>> {
validate_term_cache_blocks(config.term_cache_blocks)?;
Ok(Arc::new(
crate::merge::SegmentManager::new(
Arc::clone(directory),
Arc::clone(schema),
metadata,
config.merge_policy.clone_box(),
config.term_cache_blocks,
config.max_concurrent_merges,
Arc::clone(&config.background_merge_permits),
config.merge_bp_time_budget,
config.bp_memory_budget_bytes,
Arc::clone(&config.background_reorder_permits),
config.background_reorder_pool.clone(),
)
.with_posting_config(config.optimization, config.effective_posting_codec())
.with_term_dict_block_size(config.term_dict_block_size)
.with_term_cache_budget(config.term_cache_budget_bytes),
))
}
#[cfg(feature = "native")]
pub struct Index<D: crate::directories::DirectoryWriter + 'static> {
directory: Arc<D>,
config: IndexConfig,
search_resources: searcher::SearcherResources,
segment_manager: Arc<crate::merge::SegmentManager<D>>,
cached_reader: tokio::sync::OnceCell<IndexReader<D>>,
}
#[cfg(feature = "native")]
impl<D: crate::directories::DirectoryWriter + 'static> Index<D> {
pub async fn create(directory: D, schema: Schema, config: IndexConfig) -> Result<Self> {
schema.validate()?;
let search_resources = searcher::SearcherResources::from_config(&config)?;
let directory = Arc::new(directory);
let schema = Arc::new(schema);
directory.set_index_label(schema.index_label());
if directory
.exists(std::path::Path::new(INDEX_META_FILENAME))
.await?
{
return Err(crate::Error::Internal(format!(
"refusing to create index: {} already exists in this directory; \
use Index::open to open the existing index, or delete the \
directory first if you really want to start over",
INDEX_META_FILENAME
)));
}
let metadata = IndexMetadata::new((*schema).clone());
let segment_manager = segment_manager_from_config(&directory, &schema, metadata, &config)?;
segment_manager.update_metadata(|_| {}).await?;
Ok(Self {
directory,
config,
search_resources,
segment_manager,
cached_reader: tokio::sync::OnceCell::new(),
})
}
pub async fn open(directory: D, config: IndexConfig) -> Result<Self> {
let search_resources = searcher::SearcherResources::from_config(&config)?;
let directory = Arc::new(directory);
let metadata = IndexMetadata::load(directory.as_ref()).await?;
let schema = Arc::new(metadata.schema.clone());
directory.set_index_label(schema.index_label());
log_posting_bounds_policy(directory.as_ref(), &metadata, &config).await;
let segment_manager = segment_manager_from_config(&directory, &schema, metadata, &config)?;
segment_manager.try_load_and_publish_trained().await?;
Ok(Self {
directory,
config,
search_resources,
segment_manager,
cached_reader: tokio::sync::OnceCell::new(),
})
}
pub async fn open_with_writer(
directory: D,
config: IndexConfig,
) -> Result<(Self, IndexWriter<D>)> {
let search_resources = searcher::SearcherResources::from_config(&config)?;
let writer = IndexWriter::open(directory, config.clone()).await?;
let index = Self {
directory: Arc::clone(&writer.directory),
config,
search_resources,
segment_manager: Arc::clone(writer.segment_manager()),
cached_reader: tokio::sync::OnceCell::new(),
};
Ok((index, writer))
}
pub fn schema(&self) -> Arc<Schema> {
self.schema_arc()
}
pub fn schema_arc(&self) -> Arc<Schema> {
self.segment_manager.published_generation().schema.clone()
}
pub fn directory(&self) -> &D {
&self.directory
}
pub fn segment_manager(&self) -> &Arc<crate::merge::SegmentManager<D>> {
&self.segment_manager
}
pub async fn reader(&self) -> Result<&IndexReader<D>> {
self.cached_reader
.get_or_try_init(|| async {
IndexReader::from_segment_manager_with_resources(
self.schema_arc(),
Arc::clone(&self.segment_manager),
self.config.reload_interval_ms,
self.search_resources.clone(),
)
.await
})
.await
}
pub fn config(&self) -> &IndexConfig {
&self.config
}
pub async fn segment_readers(&self) -> Result<Vec<Arc<crate::segment::SegmentReader>>> {
let reader = self.reader().await?;
let searcher = reader.searcher().await?;
Ok(searcher.segment_readers().to_vec())
}
pub async fn num_docs(&self) -> Result<u32> {
let reader = self.reader().await?;
let searcher = reader.searcher().await?;
Ok(searcher.num_docs())
}
pub fn default_fields(&self) -> Vec<crate::Field> {
let schema = self.schema_arc();
if !schema.default_fields().is_empty() {
schema.default_fields().to_vec()
} else {
schema
.fields()
.filter(|(_, entry)| {
entry.indexed && entry.field_type == crate::dsl::FieldType::Text
})
.map(|(field, _)| field)
.collect()
}
}
pub fn tokenizers(&self) -> Arc<crate::tokenizer::TokenizerRegistry> {
Arc::new(crate::tokenizer::TokenizerRegistry::default())
}
pub fn query_parser(&self) -> crate::dsl::QueryLanguageParser {
let default_fields = self.default_fields();
let tokenizers = self.tokenizers();
let schema = self.schema_arc();
let query_routers = schema.query_routers();
if !query_routers.is_empty()
&& let Ok(router) = crate::dsl::QueryFieldRouter::from_rules(query_routers)
{
return crate::dsl::QueryLanguageParser::with_router(
Arc::clone(&schema),
default_fields,
tokenizers,
router,
);
}
crate::dsl::QueryLanguageParser::new(schema, default_fields, tokenizers)
}
pub async fn query(
&self,
query_str: &str,
limit: usize,
) -> Result<crate::query::SearchResponse> {
self.query_offset(query_str, limit, 0).await
}
pub async fn query_offset(
&self,
query_str: &str,
limit: usize,
offset: usize,
) -> Result<crate::query::SearchResponse> {
let parser = self.query_parser();
let query = parser
.parse(query_str)
.map_err(crate::error::Error::Query)?;
self.search_offset(query.as_ref(), limit, offset).await
}
pub async fn search(
&self,
query: &dyn crate::query::Query,
limit: usize,
) -> Result<crate::query::SearchResponse> {
self.search_offset(query, limit, 0).await
}
pub async fn search_offset(
&self,
query: &dyn crate::query::Query,
limit: usize,
offset: usize,
) -> Result<crate::query::SearchResponse> {
let reader = self.reader().await?;
let searcher = reader.searcher().await?;
#[cfg(feature = "sync")]
let (results, total_seen) = {
let runtime_flavor = tokio::runtime::Handle::current().runtime_flavor();
if runtime_flavor == tokio::runtime::RuntimeFlavor::MultiThread {
tokio::task::block_in_place(|| {
searcher.search_with_offset_and_count_sync(query, limit, offset)
})?
} else {
searcher.search_with_offset_and_count_sync(query, limit, offset)?
}
};
#[cfg(not(feature = "sync"))]
let (results, total_seen) = {
searcher
.search_with_offset_and_count(query, limit, offset)
.await?
};
let total_hits = total_seen;
let hits: Vec<crate::query::SearchHit> = results
.into_iter()
.map(|result| crate::query::SearchHit {
address: crate::query::DocAddress::new(result.segment_id, result.doc_id),
score: result.score,
matched_fields: result.extract_ordinals(),
})
.collect();
Ok(crate::query::SearchResponse { hits, total_hits })
}
pub async fn get_document(
&self,
address: &crate::query::DocAddress,
) -> Result<Option<crate::dsl::Document>> {
let reader = self.reader().await?;
let searcher = reader.searcher().await?;
searcher.get_document(address).await
}
pub async fn get_postings(
&self,
field: crate::Field,
term: &[u8],
) -> Result<
Vec<(
Arc<crate::segment::SegmentReader>,
crate::structures::BlockPostingList,
)>,
> {
let segments = self.segment_readers().await?;
let mut results = Vec::new();
for segment in segments {
if let Some(postings) = segment.get_postings(field, term).await? {
results.push((segment, postings));
}
}
Ok(results)
}
}
#[cfg(feature = "native")]
impl<D: crate::directories::DirectoryWriter + 'static> Index<D> {
pub fn writer(&self) -> writer::IndexWriter<D> {
writer::IndexWriter::from_index(self)
}
}
#[cfg(test)]
mod tests;